4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file group_cmd.cpp Handling of the engine groups */
14 #include "cmd_helper.h"
15 #include "command_func.h"
17 #include "vehiclelist.h"
18 #include "vehicle_func.h"
19 #include "station_base.h"
21 #include "autoreplace_base.h"
22 #include "autoreplace_func.h"
23 #include "string_func.h"
24 #include "strings_func.h"
25 #include "company_func.h"
26 #include "core/pool_func.hpp"
27 #include "order_backup.h"
28 #include "tracerestrict.h"
29 #include "tbtr_template_vehicle.h"
31 #include "table/strings.h"
33 #include "safeguards.h"
35 GroupID _new_group_id
;
37 GroupPool
_group_pool("Group");
38 INSTANTIATE_POOL_METHODS(Group
)
40 GroupStatistics::GroupStatistics()
42 this->num_engines
= CallocT
<uint16
>(Engine::GetPoolSize());
45 GroupStatistics::~GroupStatistics()
47 free(this->num_engines
);
53 void GroupStatistics::Clear()
55 this->num_vehicle
= 0;
56 this->num_profit_vehicle
= 0;
57 this->profit_last_year
= 0;
59 /* This is also called when NewGRF change. So the number of engines might have changed. Reallocate. */
60 free(this->num_engines
);
61 this->num_engines
= CallocT
<uint16
>(Engine::GetPoolSize());
65 * Returns the GroupStatistics for a specific group.
66 * @param company Owner of the group.
67 * @param id_g GroupID of the group.
68 * @param type VehicleType of the vehicles in the group.
69 * @return Statistics for the group.
71 /* static */ GroupStatistics
&GroupStatistics::Get(CompanyID company
, GroupID id_g
, VehicleType type
)
73 if (Group::IsValidID(id_g
)) {
74 Group
*g
= Group::Get(id_g
);
75 assert(g
->owner
== company
);
76 assert(g
->vehicle_type
== type
);
80 if (IsDefaultGroupID(id_g
)) return Company::Get(company
)->group_default
[type
];
81 if (IsAllGroupID(id_g
)) return Company::Get(company
)->group_all
[type
];
87 * Returns the GroupStatistic for the group of a vehicle.
89 * @return GroupStatistics for the group of the vehicle.
91 /* static */ GroupStatistics
&GroupStatistics::Get(const Vehicle
*v
)
93 return GroupStatistics::Get(v
->owner
, v
->group_id
, v
->type
);
97 * Returns the GroupStatistic for the ALL_GROUPO of a vehicle type.
99 * @return GroupStatistics for the ALL_GROUP of the vehicle type.
101 /* static */ GroupStatistics
&GroupStatistics::GetAllGroup(const Vehicle
*v
)
103 return GroupStatistics::Get(v
->owner
, ALL_GROUP
, v
->type
);
107 * Update all caches after loading a game, changing NewGRF etc..
109 /* static */ void GroupStatistics::UpdateAfterLoad()
111 /* Set up the engine count for all companies */
113 FOR_ALL_COMPANIES(c
) {
114 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
115 c
->group_all
[type
].Clear();
116 c
->group_default
[type
].Clear();
123 g
->statistics
.Clear();
127 FOR_ALL_VEHICLES(v
) {
128 if (!v
->IsEngineCountable()) continue;
130 GroupStatistics::CountEngine(v
, 1);
131 if (v
->IsPrimaryVehicle()) GroupStatistics::CountVehicle(v
, 1);
134 FOR_ALL_COMPANIES(c
) {
135 GroupStatistics::UpdateAutoreplace(c
->index
);
140 * Update num_vehicle when adding or removing a vehicle.
141 * @param v Vehicle to count.
142 * @param delta +1 to add, -1 to remove.
144 /* static */ void GroupStatistics::CountVehicle(const Vehicle
*v
, int delta
)
146 /* make virtual trains group-neutral */
147 if (HasBit(v
->subtype
, GVSF_VIRTUAL
)) return;
149 assert(delta
== 1 || delta
== -1);
151 GroupStatistics
&stats_all
= GroupStatistics::GetAllGroup(v
);
152 GroupStatistics
&stats
= GroupStatistics::Get(v
);
154 stats_all
.num_vehicle
+= delta
;
155 stats
.num_vehicle
+= delta
;
157 if (v
->age
> VEHICLE_PROFIT_MIN_AGE
) {
158 stats_all
.num_profit_vehicle
+= delta
;
159 stats_all
.profit_last_year
+= v
->GetDisplayProfitLastYear() * delta
;
160 stats
.num_profit_vehicle
+= delta
;
161 stats
.profit_last_year
+= v
->GetDisplayProfitLastYear() * delta
;
166 * Update num_engines when adding/removing an engine.
167 * @param v Engine to count.
168 * @param delta +1 to add, -1 to remove.
170 /* static */ void GroupStatistics::CountEngine(const Vehicle
*v
, int delta
)
172 /* make virtual trains group-neutral */
173 if (HasBit(v
->subtype
, GVSF_VIRTUAL
)) return;
175 assert(delta
== 1 || delta
== -1);
176 GroupStatistics::GetAllGroup(v
).num_engines
[v
->engine_type
] += delta
;
177 GroupStatistics::Get(v
).num_engines
[v
->engine_type
] += delta
;
181 * Add a vehicle to the profit sum of its group.
183 /* static */ void GroupStatistics::VehicleReachedProfitAge(const Vehicle
*v
)
185 GroupStatistics
&stats_all
= GroupStatistics::GetAllGroup(v
);
186 GroupStatistics
&stats
= GroupStatistics::Get(v
);
188 stats_all
.num_profit_vehicle
++;
189 stats_all
.profit_last_year
+= v
->GetDisplayProfitLastYear();
190 stats
.num_profit_vehicle
++;
191 stats
.profit_last_year
+= v
->GetDisplayProfitLastYear();
195 * Recompute the profits for all groups.
197 /* static */ void GroupStatistics::UpdateProfits()
199 /* Set up the engine count for all companies */
201 FOR_ALL_COMPANIES(c
) {
202 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
203 c
->group_all
[type
].ClearProfits();
204 c
->group_default
[type
].ClearProfits();
211 g
->statistics
.ClearProfits();
215 FOR_ALL_VEHICLES(v
) {
216 if (v
->IsPrimaryVehicle() && v
->age
> VEHICLE_PROFIT_MIN_AGE
) GroupStatistics::VehicleReachedProfitAge(v
);
221 * Update autoreplace_defined and autoreplace_finished of all statistics of a company.
222 * @param company Company to update statistics for.
224 /* static */ void GroupStatistics::UpdateAutoreplace(CompanyID company
)
226 /* Set up the engine count for all companies */
227 Company
*c
= Company::Get(company
);
228 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
229 c
->group_all
[type
].ClearAutoreplace();
230 c
->group_default
[type
].ClearAutoreplace();
236 if (g
->owner
!= company
) continue;
237 g
->statistics
.ClearAutoreplace();
240 for (EngineRenewList erl
= c
->engine_renew_list
; erl
!= NULL
; erl
= erl
->next
) {
241 const Engine
*e
= Engine::Get(erl
->from
);
242 GroupStatistics
&stats
= GroupStatistics::Get(company
, erl
->group_id
, e
->type
);
243 if (!stats
.autoreplace_defined
) {
244 stats
.autoreplace_defined
= true;
245 stats
.autoreplace_finished
= true;
247 if (stats
.num_engines
[erl
->from
] > 0) stats
.autoreplace_finished
= false;
252 * Update the num engines of a groupID. Decrease the old one and increase the new one
253 * @note called in SetTrainGroupID and UpdateTrainGroupID
254 * @param v Vehicle we have to update
255 * @param old_g index of the old group
256 * @param new_g index of the new group
258 static inline void UpdateNumEngineGroup(const Vehicle
*v
, GroupID old_g
, GroupID new_g
)
260 if (old_g
!= new_g
) {
261 /* Decrease the num engines in the old group */
262 GroupStatistics::Get(v
->owner
, old_g
, v
->type
).num_engines
[v
->engine_type
]--;
264 /* Increase the num engines in the new group */
265 GroupStatistics::Get(v
->owner
, new_g
, v
->type
).num_engines
[v
->engine_type
]++;
271 Group::Group(Owner owner
)
283 * Create a new vehicle group.
285 * @param flags type of operation
286 * @param p1 vehicle type
289 * @return the cost of this operation or an error
291 CommandCost
CmdCreateGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
293 VehicleType vt
= Extract
<VehicleType
, 0, 3>(p1
);
294 if (!IsCompanyBuildableVehicleType(vt
)) return CMD_ERROR
;
296 if (!Group::CanAllocateItem()) return CMD_ERROR
;
298 if (flags
& DC_EXEC
) {
299 Group
*g
= new Group(_current_company
);
300 g
->replace_protection
= false;
301 g
->vehicle_type
= vt
;
302 g
->parent
= INVALID_GROUP
;
304 _new_group_id
= g
->index
;
306 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
309 return CommandCost();
314 * Add all vehicles in the given group to the default group and then deletes the group.
316 * @param flags type of operation
317 * @param p1 index of array group
318 * - p1 bit 0-15 : GroupID
321 * @return the cost of this operation or an error
323 CommandCost
CmdDeleteGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
325 Group
*g
= Group::GetIfValid(p1
);
326 if (g
== NULL
|| g
->owner
!= _current_company
) return CMD_ERROR
;
328 /* Remove all vehicles from the group */
329 DoCommand(0, p1
, 0, flags
, CMD_REMOVE_ALL_VEHICLES_GROUP
);
331 /* Delete sub-groups */
334 if (gp
->parent
== g
->index
) {
335 DoCommand(0, gp
->index
, 0, flags
, CMD_DELETE_GROUP
);
339 if (flags
& DC_EXEC
) {
340 /* Update backupped orders if needed */
341 OrderBackup::ClearGroup(g
->index
);
343 /* If we set an autoreplace for the group we delete, remove it. */
344 if (_current_company
< MAX_COMPANIES
) {
348 c
= Company::Get(_current_company
);
349 FOR_ALL_ENGINE_RENEWS(er
) {
350 if (er
->group_id
== g
->index
) RemoveEngineReplacementForCompany(c
, er
->from
, g
->index
, flags
);
354 VehicleType vt
= g
->vehicle_type
;
356 /* Delete all template replacements using the just deleted group */
357 DeleteTemplateReplacementsByGroupID(g
->index
);
359 /* notify tracerestrict that group is about to be deleted */
360 TraceRestrictRemoveGroupID(g
->index
);
362 /* Delete the Replace Vehicle Windows */
363 DeleteWindowById(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
366 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
367 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
368 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
369 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
372 return CommandCost();
375 static bool IsUniqueGroupNameForVehicleType(const char *name
, VehicleType type
)
380 if (g
->name
!= NULL
&& g
->vehicle_type
== type
&& strcmp(g
->name
, name
) == 0) return false;
389 * @param flags type of operation
390 * @param p1 index of array group
391 * - p1 bit 0-15 : GroupID///
392 * - p1 bit 16: 0 - Rename grouop
393 * 1 - Set group parent
394 * @param p2 parent group index
395 * @param text the new name or an empty string when resetting to the default
396 * @return the cost of this operation or an error
398 CommandCost
CmdAlterGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
400 Group
*g
= Group::GetIfValid(GB(p1
, 0, 16));
401 if (g
== NULL
|| g
->owner
!= _current_company
) return CMD_ERROR
;
403 if (!HasBit(p1
, 16)) {
405 bool reset
= StrEmpty(text
);
408 if (Utf8StringLength(text
) >= MAX_LENGTH_GROUP_NAME_CHARS
) return CMD_ERROR
;
409 if (!IsUniqueGroupNameForVehicleType(text
, g
->vehicle_type
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
412 if (flags
& DC_EXEC
) {
413 /* Delete the old name */
415 /* Assign the new one */
416 g
->name
= reset
? NULL
: stredup(text
);
419 /* Set group parent */
420 const Group
*pg
= Group::GetIfValid(GB(p2
, 0, 16));
423 if (pg
->owner
!= _current_company
) return CMD_ERROR
;
424 if (pg
->vehicle_type
!= g
->vehicle_type
) return CMD_ERROR
;
426 /* Ensure request parent isn't child of group.
427 * This is the only place that infinite loops are prevented. */
428 if (GroupIsInGroup(pg
->index
, g
->index
)) return CMD_ERROR
;
431 if (flags
& DC_EXEC
) {
432 g
->parent
= (pg
== NULL
) ? INVALID_GROUP
: pg
->index
;
436 if (flags
& DC_EXEC
) {
437 SetWindowDirty(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
438 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
439 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
440 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
441 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
444 return CommandCost();
449 * Do add a vehicle to a group.///
450 * @param v Vehicle to add.
451 * @param new_g Group to add to.
453 static void AddVehicleToGroup(Vehicle
*v
, GroupID new_g
)
455 GroupStatistics::CountVehicle(v
, -1);
458 default: NOT_REACHED();
460 SetTrainGroupID(Train::From(v
), new_g
);
466 if (v
->IsEngineCountable()) UpdateNumEngineGroup(v
, v
->group_id
, new_g
);
471 GroupStatistics::CountVehicle(v
, 1);
475 * Add a vehicle to a group
477 * @param flags type of operation
478 * @param p1 index of array group
479 * - p1 bit 0-15 : GroupID
480 * @param p2 vehicle to add to a group
481 * - p2 bit 0-19 : VehicleID
482 * - p2 bit 31 : Add shared vehicles as well.
484 * @return the cost of this operation or an error
486 CommandCost
CmdAddVehicleGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
488 Vehicle
*v
= Vehicle::GetIfValid(GB(p2
, 0, 20));
491 if (v
== NULL
|| (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
) && new_g
!= NEW_GROUP
)) return CMD_ERROR
;
493 if (Group::IsValidID(new_g
)) {
494 Group
*g
= Group::Get(new_g
);
495 if (g
->owner
!= _current_company
|| g
->vehicle_type
!= v
->type
) return CMD_ERROR
;
498 if (v
->owner
!= _current_company
|| !v
->IsPrimaryVehicle()) return CMD_ERROR
;
500 if (new_g
== NEW_GROUP
) {
501 /* Create new group. */
502 CommandCost ret
= CmdCreateGroup(0, flags
, v
->type
, 0, NULL
);
503 if (ret
.Failed()) return ret
;
505 new_g
= _new_group_id
;
508 if (flags
& DC_EXEC
) {
509 AddVehicleToGroup(v
, new_g
);
511 if (HasBit(p2
, 31)) {
512 /* Add vehicles in the shared order list as well. */
513 for (Vehicle
*v2
= v
->FirstShared(); v2
!= NULL
; v2
= v2
->NextShared()) {
514 if (v2
->group_id
!= new_g
) AddVehicleToGroup(v2
, new_g
);
518 GroupStatistics::UpdateAutoreplace(v
->owner
);
520 /* Update the Replace Vehicle Windows */
521 SetWindowDirty(WC_REPLACE_VEHICLE
, v
->type
);
522 InvalidateWindowData(GetWindowClassForVehicleType(v
->type
), VehicleListIdentifier(VL_GROUP_LIST
, v
->type
, _current_company
).Pack());
523 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
524 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
525 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
528 return CommandCost();
532 * Create a new group, rename it with specific name and add vehicle to this group
534 * @param flags type of operation
535 * @param p1 vehicle to add to a group
536 * - p1 bit 0-19 : VehicleID
537 * - p1 bit 31 : Add shared vehicles as well.
540 * @return the cost of this operation or an error
542 CommandCost
CmdCreateGroupSpecificName(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
544 Vehicle
*v
= Vehicle::GetIfValid(GB(p1
, 0, 20));
546 if (v
== NULL
) return CMD_ERROR
;
548 if (v
->owner
!= _current_company
|| !v
->IsPrimaryVehicle()) return CMD_ERROR
;
550 /* Get the essential orders */
551 VehicleOrderID start
= 0;
555 Order
*order
= v
->GetOrder(start
);
556 std::vector
<Order
*> unique_orders
;
558 if (order
== NULL
) return_cmd_error(STR_ERROR_GROUP_CAN_T_CREATE_NAME
);
560 VehicleOrderID oid
= start
;
563 if (order
->IsType(OT_GOTO_STATION
)) {
564 if (std::find_if(unique_orders
.begin(), unique_orders
.end(), [order
](Order
* o
) { return o
->GetDestination() == order
->GetDestination(); }) == unique_orders
.end()) {
565 unique_orders
.push_back(order
);
572 order
= v
->GetFirstOrder();
575 } while (oid
!= start
);
577 if (unique_orders
.empty()) return_cmd_error(STR_ERROR_GROUP_CAN_T_CREATE_NAME
);
579 /* Create the name */
581 static char str
[135] = { "" }; // 5 + 63 + 3 + 63 + 1
583 StringID cargo_abbreviation
= INVALID_STRING_ID
;
587 if (temp_v
->cargo_cap
== 0) continue;
589 cargo_abbreviation
= CargoSpec::Get(temp_v
->cargo_type
)->abbrev
;
591 } while ((temp_v
= temp_v
->Next()) != NULL
);
593 if(cargo_abbreviation
== INVALID_STRING_ID
) return_cmd_error(STR_ERROR_GROUP_CAN_T_CREATE_NAME
);
595 if(_settings_client
.gui
.specific_group_name
== 1) { // Use station names
597 static char stationname_first
[64] = { "" };
598 static char stationname_last
[64] = { "" };
600 SetDParam(0, unique_orders
.front()->GetDestination());
601 GetString(stationname_first
, STR_STATION_NAME
, lastof(stationname_first
));
603 SetDParam(0, unique_orders
.back()->GetDestination());
604 GetString(stationname_last
, STR_STATION_NAME
, lastof(stationname_last
));
606 //if(strnatcmp(stationname_first, stationname_last) > 0) { // Sort by name
607 // Order *temp = first;
611 SetDParam(0, cargo_abbreviation
);
612 SetDParam(1, unique_orders
.front()->GetDestination());
613 SetDParam(2, unique_orders
.back()->GetDestination());
614 GetString(str
, STR_GROUP_SPECIFIC_NAME_STATION
, lastof(str
));
617 else { //Use town names
619 Station
*station_first
= Station::GetIfValid(unique_orders
.front()->GetDestination());
620 Station
*station_last
= Station::GetIfValid(unique_orders
.back()->GetDestination());
622 if(station_last
== NULL
|| station_first
== NULL
) return_cmd_error(STR_ERROR_GROUP_CAN_T_CREATE_NAME
);
624 Town
*town_first
= station_first
->town
;
625 Town
*town_last
= station_last
->town
;
627 if(town_first
->index
== town_last
->index
) { // First and last station belong to the same town
628 SetDParam(0, cargo_abbreviation
);
629 SetDParam(1, town_first
->index
);
630 GetString(str
, STR_GROUP_SPECIFIC_NAME_TOWN_LOCAL
, lastof(str
));
633 static char townname_first
[64] = { "" };
634 static char townname_last
[64] = { "" };
636 SetDParam(0, town_first
->index
);
637 GetString(townname_first
, STR_TOWN_NAME
, lastof(townname_first
));
639 SetDParam(0, town_last
->index
);
640 GetString(townname_last
, STR_TOWN_NAME
, lastof(townname_last
));
642 //if(strnatcmp(townname_first, townname_last) > 0) { // Sort by name
643 // Town *town_temp = town_first;
644 // town_first = town_last;
645 // town_last = town_temp;
648 SetDParam(0, cargo_abbreviation
);
649 SetDParam(1, town_first
->index
);
650 SetDParam(2, town_last
->index
);
651 GetString(str
, STR_GROUP_SPECIFIC_NAME_TOWN
, lastof(str
));
655 // Get rid of 'tiny font' formatting
658 strecpy(&str
[0], s
.c_str(), lastof(str
));
660 if (!IsUniqueGroupNameForVehicleType(str
, v
->type
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
662 if (Utf8StringLength(str
) >= MAX_LENGTH_GROUP_NAME_CHARS
) return CMD_ERROR
;
664 CommandCost ret
= CmdCreateGroup(0, flags
, v
->type
, 0, NULL
);
665 if (ret
.Failed()) return ret
;
667 GroupID new_g
= _new_group_id
;
668 CommandCost ret2
= CmdAlterGroup(0, flags
, new_g
, 0, str
);
670 if (flags
& DC_EXEC
) {
671 AddVehicleToGroup(v
, new_g
);
673 if (HasBit(p1
, 31)) {
674 /* Add vehicles in the shared order list as well. */
675 for (Vehicle
*v2
= v
->FirstShared(); v2
!= NULL
; v2
= v2
->NextShared()) {
676 if (v2
->group_id
!= new_g
) {
677 AddVehicleToGroup(v2
, new_g
);
682 GroupStatistics::UpdateAutoreplace(v
->owner
);
684 /* Update the Replace Vehicle Windows */
685 SetWindowDirty(WC_REPLACE_VEHICLE
, v
->type
);
686 InvalidateWindowData(GetWindowClassForVehicleType(v
->type
), VehicleListIdentifier(VL_GROUP_LIST
, v
->type
, _current_company
).Pack());
687 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
690 return CommandCost();
694 * Add all shared vehicles of all vehicles from a group
696 * @param flags type of operation
697 * @param p1 index of group array
698 * - p1 bit 0-15 : GroupID
699 * @param p2 type of vehicles
701 * @return the cost of this operation or an error
703 CommandCost
CmdAddSharedVehicleGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
705 VehicleType type
= Extract
<VehicleType
, 0, 3>(p2
);
707 if (!Group::IsValidID(id_g
) || !IsCompanyBuildableVehicleType(type
)) return CMD_ERROR
;
709 if (flags
& DC_EXEC
) {
712 /* Find the first front engine which belong to the group id_g
713 * then add all shared vehicles of this front engine to the group id_g */
714 FOR_ALL_VEHICLES(v
) {
715 if (v
->type
== type
&& v
->IsPrimaryVehicle()) {
716 if (v
->group_id
!= id_g
) continue;
718 /* For each shared vehicles add it to the group */
719 for (Vehicle
*v2
= v
->FirstShared(); v2
!= NULL
; v2
= v2
->NextShared()) {
720 if (v2
->group_id
!= id_g
) {
721 DoCommand(tile
, id_g
, v2
->index
, flags
, CMD_ADD_VEHICLE_GROUP
, text
);
727 InvalidateWindowData(GetWindowClassForVehicleType(type
), VehicleListIdentifier(VL_GROUP_LIST
, type
, _current_company
).Pack());
728 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
731 return CommandCost();
736 * Remove all vehicles from a group
738 * @param flags type of operation
739 * @param p1 index of group array
740 * - p1 bit 0-15 : GroupID
743 * @return the cost of this operation or an error
745 CommandCost
CmdRemoveAllVehiclesGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
748 Group
*g
= Group::GetIfValid(old_g
);
750 if (g
== NULL
|| g
->owner
!= _current_company
) return CMD_ERROR
;
752 if (flags
& DC_EXEC
) {
755 /* Find each Vehicle that belongs to the group old_g and add it to the default group */
756 FOR_ALL_VEHICLES(v
) {
757 if (v
->IsPrimaryVehicle()) {
758 if (v
->group_id
!= old_g
) continue;
760 /* Add The Vehicle to the default group */
761 DoCommand(tile
, DEFAULT_GROUP
, v
->index
, flags
, CMD_ADD_VEHICLE_GROUP
, text
);
765 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
768 return CommandCost();
773 * Create groups for all vehicles of a certain type that are not yet in any group.
775 * @param flags type of operation
776 * @param p1 The ID of the company whos vehicles should be auto-grouped.
777 * @param p2 The VehicleType of the vehicles that should be auto-grouped.
779 * @return the cost of this operation or an error
781 CommandCost
CmdAutoGroupVehicles(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
783 CompanyID company_id
= (CompanyID
)p1
;
784 VehicleType vehicle_type
= (VehicleType
)p2
;
786 Company
* company
= Company::GetIfValid(company_id
);
788 assert(company
!= nullptr);
790 if (flags
& DC_EXEC
) {
792 FOR_ALL_VEHICLES(v
) {
793 if (!HasBit(v
->subtype
, GVSF_VIRTUAL
) && v
->type
== vehicle_type
&& v
->IsPrimaryVehicle() &&
794 v
->owner
== company_id
&& v
->group_id
== DEFAULT_GROUP
) {
795 DoCommand(0, v
->index
| (1 << 31), 0, flags
, CMD_CREATE_GROUP_SPECIFIC_NAME
);
799 InvalidateWindowData(GetWindowClassForVehicleType(vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, vehicle_type
, _current_company
).Pack());
800 InvalidateWindowClassesData(GetWindowClassForVehicleType(vehicle_type
));
801 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN
);
802 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
803 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
806 return CommandCost();
810 * Set replace protection for a group and its sub-groups.
811 * @param g initial group.
812 * @param protect 1 to set or 0 to clear protection.
814 static void SetGroupReplaceProtection(Group
*g
, bool protect
)
816 g
->replace_protection
= protect
;
820 if (pg
->parent
== g
->index
) SetGroupReplaceProtection(pg
, protect
);
826 * (Un)set global replace protection from a group
828 * @param flags type of operation
829 * @param p1 index of group array
830 * - p1 bit 0-15 : GroupID
832 * - p2 bit 0 : 1 to set or 0 to clear protection.
833 * - p2 bit 1 : 1 to apply to sub-groups.
835 * @return the cost of this operation or an error
837 CommandCost
CmdSetGroupReplaceProtection(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
839 Group
*g
= Group::GetIfValid(p1
);
840 if (g
== NULL
|| g
->owner
!= _current_company
) return CMD_ERROR
;
842 if (flags
& DC_EXEC
) {
844 SetGroupReplaceProtection(g
, HasBit(p2
, 0));
846 g
->replace_protection
= HasBit(p2
, 0);
849 SetWindowDirty(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
850 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
853 return CommandCost();
857 * Decrease the num_vehicle variable before delete an front engine from a group
858 * @note Called in CmdSellRailWagon and DeleteLasWagon,
859 * @param v FrontEngine of the train we want to remove.
861 void RemoveVehicleFromGroup(const Vehicle
*v
)
863 if (!v
->IsPrimaryVehicle()) return;
865 if (!IsDefaultGroupID(v
->group_id
)) GroupStatistics::CountVehicle(v
, -1);
870 * Affect the groupID of a train to new_g.
871 * @note called in CmdAddVehicleGroup and CmdMoveRailVehicle
872 * @param v First vehicle of the chain.
873 * @param new_g index of array group
875 void SetTrainGroupID(Train
*v
, GroupID new_g
)
877 if (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
)) return;
879 assert(v
->IsFrontEngine() || IsDefaultGroupID(new_g
));
881 for (Vehicle
*u
= v
; u
!= NULL
; u
= u
->Next()) {
882 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
887 /* Update the Replace Vehicle Windows */
888 GroupStatistics::UpdateAutoreplace(v
->owner
);
889 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
894 * Recalculates the groupID of a train. Should be called each time a vehicle is added
895 * to/removed from the chain,.
896 * @note this needs to be called too for 'wagon chains' (in the depot, without an engine)
897 * @note Called in CmdBuildRailVehicle, CmdBuildRailWagon, CmdMoveRailVehicle, CmdSellRailWagon
898 * @param v First vehicle of the chain.
900 void UpdateTrainGroupID(Train
*v
)
902 assert(v
->IsFrontEngine() || v
->IsFreeWagon());
904 GroupID new_g
= v
->IsFrontEngine() ? v
->group_id
: (GroupID
)DEFAULT_GROUP
;
905 for (Vehicle
*u
= v
; u
!= NULL
; u
= u
->Next()) {
906 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
911 /* Update the Replace Vehicle Windows */
912 GroupStatistics::UpdateAutoreplace(v
->owner
);
913 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
917 * Get the number of engines with EngineID id_e in the group with GroupID
918 * id_g and its sub-groups.
919 * @param company The company the group belongs to
920 * @param id_g The GroupID of the group used
921 * @param id_e The EngineID of the engine to count
922 * @return The number of engines with EngineID id_e in the group
924 uint
GetGroupNumEngines(CompanyID company
, GroupID id_g
, EngineID id_e
)
927 const Engine
*e
= Engine::Get(id_e
);
930 if (g
->parent
== id_g
) count
+= GetGroupNumEngines(company
, g
->index
, id_e
);
932 return count
+ GroupStatistics::Get(company
, id_g
, e
->type
).num_engines
[id_e
];
935 void RemoveAllGroupsForCompany(const CompanyID company
)
940 if (company
== g
->owner
) {
941 DeleteTemplateReplacementsByGroupID(g
->index
);
949 * Test if GroupID group is a descendant of (or is) GroupID search
950 * @param search The GroupID to search in
951 * @param group The GroupID to search for
952 * @return True iff group is search or a descendant of search
954 bool GroupIsInGroup(GroupID search
, GroupID group
)
956 if (!Group::IsValidID(search
)) return search
== group
;
959 if (search
== group
) return true;
960 search
= Group::Get(search
)->parent
;
961 } while (search
!= INVALID_GROUP
);