2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file group_cmd.cpp Handling of the engine groups */
11 #include "cmd_helper.h"
12 #include "command_func.h"
14 #include "vehiclelist.h"
15 #include "vehicle_func.h"
16 #include "autoreplace_base.h"
17 #include "autoreplace_func.h"
18 #include "string_func.h"
19 #include "company_func.h"
20 #include "core/pool_func.hpp"
21 #include "order_backup.h"
23 #include "table/strings.h"
25 #include "safeguards.h"
27 GroupID _new_group_id
;
29 GroupPool
_group_pool("Group");
30 INSTANTIATE_POOL_METHODS(Group
)
32 GroupStatistics::GroupStatistics()
34 this->num_engines
= CallocT
<uint16
>(Engine::GetPoolSize());
37 GroupStatistics::~GroupStatistics()
39 free(this->num_engines
);
45 void GroupStatistics::Clear()
47 this->num_vehicle
= 0;
48 this->num_profit_vehicle
= 0;
49 this->profit_last_year
= 0;
51 /* This is also called when NewGRF change. So the number of engines might have changed. Reallocate. */
52 free(this->num_engines
);
53 this->num_engines
= CallocT
<uint16
>(Engine::GetPoolSize());
57 * Returns the GroupStatistics for a specific group.
58 * @param company Owner of the group.
59 * @param id_g GroupID of the group.
60 * @param type VehicleType of the vehicles in the group.
61 * @return Statistics for the group.
63 /* static */ GroupStatistics
&GroupStatistics::Get(CompanyID company
, GroupID id_g
, VehicleType type
)
65 if (Group::IsValidID(id_g
)) {
66 Group
*g
= Group::Get(id_g
);
67 assert(g
->owner
== company
);
68 assert(g
->vehicle_type
== type
);
72 if (IsDefaultGroupID(id_g
)) return Company::Get(company
)->group_default
[type
];
73 if (IsAllGroupID(id_g
)) return Company::Get(company
)->group_all
[type
];
79 * Returns the GroupStatistic for the group of a vehicle.
81 * @return GroupStatistics for the group of the vehicle.
83 /* static */ GroupStatistics
&GroupStatistics::Get(const Vehicle
*v
)
85 return GroupStatistics::Get(v
->owner
, v
->group_id
, v
->type
);
89 * Returns the GroupStatistic for the ALL_GROUPO of a vehicle type.
91 * @return GroupStatistics for the ALL_GROUP of the vehicle type.
93 /* static */ GroupStatistics
&GroupStatistics::GetAllGroup(const Vehicle
*v
)
95 return GroupStatistics::Get(v
->owner
, ALL_GROUP
, v
->type
);
99 * Update all caches after loading a game, changing NewGRF, etc.
101 /* static */ void GroupStatistics::UpdateAfterLoad()
103 /* Set up the engine count for all companies */
104 for (Company
*c
: Company::Iterate()) {
105 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
106 c
->group_all
[type
].Clear();
107 c
->group_default
[type
].Clear();
112 for (Group
*g
: Group::Iterate()) {
113 g
->statistics
.Clear();
116 for (const Vehicle
*v
: Vehicle::Iterate()) {
117 if (!v
->IsEngineCountable()) continue;
119 GroupStatistics::CountEngine(v
, 1);
120 if (v
->IsPrimaryVehicle()) GroupStatistics::CountVehicle(v
, 1);
123 for (const Company
*c
: Company::Iterate()) {
124 GroupStatistics::UpdateAutoreplace(c
->index
);
129 * Update num_vehicle when adding or removing a vehicle.
130 * @param v Vehicle to count.
131 * @param delta +1 to add, -1 to remove.
133 /* static */ void GroupStatistics::CountVehicle(const Vehicle
*v
, int delta
)
135 assert(delta
== 1 || delta
== -1);
137 GroupStatistics
&stats_all
= GroupStatistics::GetAllGroup(v
);
138 GroupStatistics
&stats
= GroupStatistics::Get(v
);
140 stats_all
.num_vehicle
+= delta
;
141 stats
.num_vehicle
+= delta
;
143 if (v
->age
> VEHICLE_PROFIT_MIN_AGE
) {
144 stats_all
.num_profit_vehicle
+= delta
;
145 stats_all
.profit_last_year
+= v
->GetDisplayProfitLastYear() * delta
;
146 stats
.num_profit_vehicle
+= delta
;
147 stats
.profit_last_year
+= v
->GetDisplayProfitLastYear() * delta
;
152 * Update num_engines when adding/removing an engine.
153 * @param v Engine to count.
154 * @param delta +1 to add, -1 to remove.
156 /* static */ void GroupStatistics::CountEngine(const Vehicle
*v
, int delta
)
158 assert(delta
== 1 || delta
== -1);
159 GroupStatistics::GetAllGroup(v
).num_engines
[v
->engine_type
] += delta
;
160 GroupStatistics::Get(v
).num_engines
[v
->engine_type
] += delta
;
164 * Add a vehicle to the profit sum of its group.
166 /* static */ void GroupStatistics::VehicleReachedProfitAge(const Vehicle
*v
)
168 GroupStatistics
&stats_all
= GroupStatistics::GetAllGroup(v
);
169 GroupStatistics
&stats
= GroupStatistics::Get(v
);
171 stats_all
.num_profit_vehicle
++;
172 stats_all
.profit_last_year
+= v
->GetDisplayProfitLastYear();
173 stats
.num_profit_vehicle
++;
174 stats
.profit_last_year
+= v
->GetDisplayProfitLastYear();
178 * Recompute the profits for all groups.
180 /* static */ void GroupStatistics::UpdateProfits()
182 /* Set up the engine count for all companies */
183 for (Company
*c
: Company::Iterate()) {
184 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
185 c
->group_all
[type
].ClearProfits();
186 c
->group_default
[type
].ClearProfits();
191 for (Group
*g
: Group::Iterate()) {
192 g
->statistics
.ClearProfits();
195 for (const Vehicle
*v
: Vehicle::Iterate()) {
196 if (v
->IsPrimaryVehicle() && v
->age
> VEHICLE_PROFIT_MIN_AGE
) GroupStatistics::VehicleReachedProfitAge(v
);
201 * Update autoreplace_defined and autoreplace_finished of all statistics of a company.
202 * @param company Company to update statistics for.
204 /* static */ void GroupStatistics::UpdateAutoreplace(CompanyID company
)
206 /* Set up the engine count for all companies */
207 Company
*c
= Company::Get(company
);
208 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
209 c
->group_all
[type
].ClearAutoreplace();
210 c
->group_default
[type
].ClearAutoreplace();
214 for (Group
*g
: Group::Iterate()) {
215 if (g
->owner
!= company
) continue;
216 g
->statistics
.ClearAutoreplace();
219 for (EngineRenewList erl
= c
->engine_renew_list
; erl
!= nullptr; erl
= erl
->next
) {
220 const Engine
*e
= Engine::Get(erl
->from
);
221 GroupStatistics
&stats
= GroupStatistics::Get(company
, erl
->group_id
, e
->type
);
222 if (!stats
.autoreplace_defined
) {
223 stats
.autoreplace_defined
= true;
224 stats
.autoreplace_finished
= true;
226 if (GetGroupNumEngines(company
, erl
->group_id
, erl
->from
) > 0) stats
.autoreplace_finished
= false;
231 * Update the num engines of a groupID. Decrease the old one and increase the new one
232 * @note called in SetTrainGroupID and UpdateTrainGroupID
233 * @param v Vehicle we have to update
234 * @param old_g index of the old group
235 * @param new_g index of the new group
237 static inline void UpdateNumEngineGroup(const Vehicle
*v
, GroupID old_g
, GroupID new_g
)
239 if (old_g
!= new_g
) {
240 /* Decrease the num engines in the old group */
241 GroupStatistics::Get(v
->owner
, old_g
, v
->type
).num_engines
[v
->engine_type
]--;
243 /* Increase the num engines in the new group */
244 GroupStatistics::Get(v
->owner
, new_g
, v
->type
).num_engines
[v
->engine_type
]++;
249 const Livery
*GetParentLivery(const Group
*g
)
251 if (g
->parent
== INVALID_GROUP
) {
252 const Company
*c
= Company::Get(g
->owner
);
253 return &c
->livery
[LS_DEFAULT
];
256 const Group
*pg
= Group::Get(g
->parent
);
262 * Propagate a livery change to a group's children.
265 void PropagateChildLivery(const Group
*g
)
267 /* Company colour data is indirectly cached. */
268 for (Vehicle
*v
: Vehicle::Iterate()) {
269 if (v
->group_id
== g
->index
&& (!v
->IsGroundVehicle() || v
->IsFrontEngine())) {
270 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
271 u
->colourmap
= PAL_NONE
;
272 u
->InvalidateNewGRFCache();
277 for (Group
*cg
: Group::Iterate()) {
278 if (cg
->parent
== g
->index
) {
279 if (!HasBit(cg
->livery
.in_use
, 0)) cg
->livery
.colour1
= g
->livery
.colour1
;
280 if (!HasBit(cg
->livery
.in_use
, 1)) cg
->livery
.colour2
= g
->livery
.colour2
;
281 PropagateChildLivery(cg
);
287 Group::Group(Owner owner
)
290 this->folded
= false;
295 * Create a new vehicle group.
297 * @param flags type of operation
298 * @param p1 vehicle type
299 * @param p2 parent groupid
301 * @return the cost of this operation or an error
303 CommandCost
CmdCreateGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
305 VehicleType vt
= Extract
<VehicleType
, 0, 3>(p1
);
306 if (!IsCompanyBuildableVehicleType(vt
)) return CMD_ERROR
;
308 if (!Group::CanAllocateItem()) return CMD_ERROR
;
310 const Group
*pg
= Group::GetIfValid(GB(p2
, 0, 16));
312 if (pg
->owner
!= _current_company
) return CMD_ERROR
;
313 if (pg
->vehicle_type
!= vt
) return CMD_ERROR
;
316 if (flags
& DC_EXEC
) {
317 Group
*g
= new Group(_current_company
);
318 g
->replace_protection
= false;
319 g
->vehicle_type
= vt
;
320 g
->parent
= INVALID_GROUP
;
323 const Company
*c
= Company::Get(_current_company
);
324 g
->livery
.colour1
= c
->livery
[LS_DEFAULT
].colour1
;
325 g
->livery
.colour2
= c
->livery
[LS_DEFAULT
].colour2
;
327 g
->parent
= pg
->index
;
328 g
->livery
.colour1
= pg
->livery
.colour1
;
329 g
->livery
.colour2
= pg
->livery
.colour2
;
332 _new_group_id
= g
->index
;
334 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
335 InvalidateWindowData(WC_COMPANY_COLOUR
, g
->owner
, g
->vehicle_type
);
338 return CommandCost();
343 * Add all vehicles in the given group to the default group and then deletes the group.
345 * @param flags type of operation
346 * @param p1 index of array group
347 * - p1 bit 0-15 : GroupID
350 * @return the cost of this operation or an error
352 CommandCost
CmdDeleteGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
354 Group
*g
= Group::GetIfValid(p1
);
355 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
357 /* Remove all vehicles from the group */
358 DoCommand(0, p1
, 0, flags
, CMD_REMOVE_ALL_VEHICLES_GROUP
);
360 /* Delete sub-groups */
361 for (const Group
*gp
: Group::Iterate()) {
362 if (gp
->parent
== g
->index
) {
363 DoCommand(0, gp
->index
, 0, flags
, CMD_DELETE_GROUP
);
367 if (flags
& DC_EXEC
) {
368 /* Update backupped orders if needed */
369 OrderBackup::ClearGroup(g
->index
);
371 /* If we set an autoreplace for the group we delete, remove it. */
372 if (_current_company
< MAX_COMPANIES
) {
375 c
= Company::Get(_current_company
);
376 for (EngineRenew
*er
: EngineRenew::Iterate()) {
377 if (er
->group_id
== g
->index
) RemoveEngineReplacementForCompany(c
, er
->from
, g
->index
, flags
);
381 VehicleType vt
= g
->vehicle_type
;
383 /* Delete the Replace Vehicle Windows */
384 DeleteWindowById(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
387 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
388 InvalidateWindowData(WC_COMPANY_COLOUR
, _current_company
, vt
);
391 return CommandCost();
397 * @param flags type of operation
398 * @param p1 index of array group
399 * - p1 bit 0-15 : GroupID
400 * - p1 bit 16: 0 - Rename grouop
401 * 1 - Set group parent
402 * @param p2 parent group index
403 * @param text the new name or an empty string when resetting to the default
404 * @return the cost of this operation or an error
406 CommandCost
CmdAlterGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
408 Group
*g
= Group::GetIfValid(GB(p1
, 0, 16));
409 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
411 if (!HasBit(p1
, 16)) {
413 bool reset
= StrEmpty(text
);
416 if (Utf8StringLength(text
) >= MAX_LENGTH_GROUP_NAME_CHARS
) return CMD_ERROR
;
419 if (flags
& DC_EXEC
) {
420 /* Assign the new one */
428 /* Set group parent */
429 const Group
*pg
= Group::GetIfValid(GB(p2
, 0, 16));
432 if (pg
->owner
!= _current_company
) return CMD_ERROR
;
433 if (pg
->vehicle_type
!= g
->vehicle_type
) return CMD_ERROR
;
435 /* Ensure request parent isn't child of group.
436 * This is the only place that infinite loops are prevented. */
437 if (GroupIsInGroup(pg
->index
, g
->index
)) return_cmd_error(STR_ERROR_GROUP_CAN_T_SET_PARENT_RECURSION
);
440 if (flags
& DC_EXEC
) {
441 g
->parent
= (pg
== nullptr) ? INVALID_GROUP
: pg
->index
;
442 GroupStatistics::UpdateAutoreplace(g
->owner
);
444 if (g
->livery
.in_use
== 0) {
445 const Livery
*livery
= GetParentLivery(g
);
446 g
->livery
.colour1
= livery
->colour1
;
447 g
->livery
.colour2
= livery
->colour2
;
449 PropagateChildLivery(g
);
450 MarkWholeScreenDirty();
455 if (flags
& DC_EXEC
) {
456 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
, 1);
457 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
458 InvalidateWindowData(WC_COMPANY_COLOUR
, g
->owner
, g
->vehicle_type
);
459 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
460 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
463 return CommandCost();
468 * Do add a vehicle to a group.
469 * @param v Vehicle to add.
470 * @param new_g Group to add to.
472 static void AddVehicleToGroup(Vehicle
*v
, GroupID new_g
)
474 GroupStatistics::CountVehicle(v
, -1);
477 default: NOT_REACHED();
479 SetTrainGroupID(Train::From(v
), new_g
);
485 if (v
->IsEngineCountable()) UpdateNumEngineGroup(v
, v
->group_id
, new_g
);
487 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
488 u
->colourmap
= PAL_NONE
;
489 u
->InvalidateNewGRFCache();
490 u
->UpdateViewport(true);
495 GroupStatistics::CountVehicle(v
, 1);
499 * Add a vehicle to a group
501 * @param flags type of operation
502 * @param p1 index of array group
503 * - p1 bit 0-15 : GroupID
504 * @param p2 vehicle to add to a group
505 * - p2 bit 0-19 : VehicleID
506 * - p2 bit 31 : Add shared vehicles as well.
508 * @return the cost of this operation or an error
510 CommandCost
CmdAddVehicleGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
512 Vehicle
*v
= Vehicle::GetIfValid(GB(p2
, 0, 20));
515 if (v
== nullptr || (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
) && new_g
!= NEW_GROUP
)) return CMD_ERROR
;
517 if (Group::IsValidID(new_g
)) {
518 Group
*g
= Group::Get(new_g
);
519 if (g
->owner
!= _current_company
|| g
->vehicle_type
!= v
->type
) return CMD_ERROR
;
522 if (v
->owner
!= _current_company
|| !v
->IsPrimaryVehicle()) return CMD_ERROR
;
524 if (new_g
== NEW_GROUP
) {
525 /* Create new group. */
526 CommandCost ret
= CmdCreateGroup(0, flags
, v
->type
, INVALID_GROUP
, nullptr);
527 if (ret
.Failed()) return ret
;
529 new_g
= _new_group_id
;
532 if (flags
& DC_EXEC
) {
533 AddVehicleToGroup(v
, new_g
);
535 if (HasBit(p2
, 31)) {
536 /* Add vehicles in the shared order list as well. */
537 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
538 if (v2
->group_id
!= new_g
) AddVehicleToGroup(v2
, new_g
);
542 GroupStatistics::UpdateAutoreplace(v
->owner
);
544 /* Update the Replace Vehicle Windows */
545 SetWindowDirty(WC_REPLACE_VEHICLE
, v
->type
);
546 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
547 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
548 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
549 InvalidateWindowData(GetWindowClassForVehicleType(v
->type
), VehicleListIdentifier(VL_GROUP_LIST
, v
->type
, _current_company
).Pack());
550 InvalidateWindowData(WC_VEHICLE_VIEW
, v
->index
);
551 InvalidateWindowData(WC_VEHICLE_DETAILS
, v
->index
);
554 return CommandCost();
558 * Add all shared vehicles of all vehicles from a group
560 * @param flags type of operation
561 * @param p1 index of group array
562 * - p1 bit 0-15 : GroupID
563 * @param p2 type of vehicles
565 * @return the cost of this operation or an error
567 CommandCost
CmdAddSharedVehicleGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
569 VehicleType type
= Extract
<VehicleType
, 0, 3>(p2
);
571 if (!Group::IsValidID(id_g
) || !IsCompanyBuildableVehicleType(type
)) return CMD_ERROR
;
573 if (flags
& DC_EXEC
) {
574 /* Find the first front engine which belong to the group id_g
575 * then add all shared vehicles of this front engine to the group id_g */
576 for (const Vehicle
*v
: Vehicle::Iterate()) {
577 if (v
->type
== type
&& v
->IsPrimaryVehicle()) {
578 if (v
->group_id
!= id_g
) continue;
580 /* For each shared vehicles add it to the group */
581 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
582 if (v2
->group_id
!= id_g
) DoCommand(tile
, id_g
, v2
->index
, flags
, CMD_ADD_VEHICLE_GROUP
, text
);
587 InvalidateWindowData(GetWindowClassForVehicleType(type
), VehicleListIdentifier(VL_GROUP_LIST
, type
, _current_company
).Pack());
590 return CommandCost();
595 * Remove all vehicles from a group
597 * @param flags type of operation
598 * @param p1 index of group array
599 * - p1 bit 0-15 : GroupID
602 * @return the cost of this operation or an error
604 CommandCost
CmdRemoveAllVehiclesGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
607 Group
*g
= Group::GetIfValid(old_g
);
609 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
611 if (flags
& DC_EXEC
) {
612 /* Find each Vehicle that belongs to the group old_g and add it to the default group */
613 for (const Vehicle
*v
: Vehicle::Iterate()) {
614 if (v
->IsPrimaryVehicle()) {
615 if (v
->group_id
!= old_g
) continue;
617 /* Add The Vehicle to the default group */
618 DoCommand(tile
, DEFAULT_GROUP
, v
->index
, flags
, CMD_ADD_VEHICLE_GROUP
, text
);
622 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
625 return CommandCost();
629 * Set the livery for a vehicle group.
630 * @param tile Unused.
631 * @param flags Command flags.
633 * - p1 bit 0-15 Group ID.
635 * - p2 bit 8 Set secondary instead of primary colour
636 * - p2 bit 16-23 Colour.
638 CommandCost
CmdSetGroupLivery(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
640 Group
*g
= Group::GetIfValid(p1
);
641 bool primary
= !HasBit(p2
, 8);
642 Colours colour
= Extract
<Colours
, 16, 8>(p2
);
644 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
646 if (colour
>= COLOUR_END
&& colour
!= INVALID_COLOUR
) return CMD_ERROR
;
648 if (flags
& DC_EXEC
) {
650 SB(g
->livery
.in_use
, 0, 1, colour
!= INVALID_COLOUR
);
651 if (colour
== INVALID_COLOUR
) colour
= (Colours
)GetParentLivery(g
)->colour1
;
652 g
->livery
.colour1
= colour
;
654 SB(g
->livery
.in_use
, 1, 1, colour
!= INVALID_COLOUR
);
655 if (colour
== INVALID_COLOUR
) colour
= (Colours
)GetParentLivery(g
)->colour2
;
656 g
->livery
.colour2
= colour
;
659 PropagateChildLivery(g
);
660 MarkWholeScreenDirty();
663 return CommandCost();
667 * Set replace protection for a group and its sub-groups.
668 * @param g initial group.
669 * @param protect 1 to set or 0 to clear protection.
671 static void SetGroupReplaceProtection(Group
*g
, bool protect
)
673 g
->replace_protection
= protect
;
675 for (Group
*pg
: Group::Iterate()) {
676 if (pg
->parent
== g
->index
) SetGroupReplaceProtection(pg
, protect
);
681 * (Un)set global replace protection from a group
683 * @param flags type of operation
684 * @param p1 index of group array
685 * - p1 bit 0-15 : GroupID
687 * - p2 bit 0 : 1 to set or 0 to clear protection.
688 * - p2 bit 1 : 1 to apply to sub-groups.
690 * @return the cost of this operation or an error
692 CommandCost
CmdSetGroupReplaceProtection(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
694 Group
*g
= Group::GetIfValid(p1
);
695 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
697 if (flags
& DC_EXEC
) {
699 SetGroupReplaceProtection(g
, HasBit(p2
, 0));
701 g
->replace_protection
= HasBit(p2
, 0);
704 SetWindowDirty(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
705 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
708 return CommandCost();
712 * Decrease the num_vehicle variable before delete an front engine from a group
713 * @note Called in CmdSellRailWagon and DeleteLasWagon,
714 * @param v FrontEngine of the train we want to remove.
716 void RemoveVehicleFromGroup(const Vehicle
*v
)
718 if (!v
->IsPrimaryVehicle()) return;
720 if (!IsDefaultGroupID(v
->group_id
)) GroupStatistics::CountVehicle(v
, -1);
725 * Affect the groupID of a train to new_g.
726 * @note called in CmdAddVehicleGroup and CmdMoveRailVehicle
727 * @param v First vehicle of the chain.
728 * @param new_g index of array group
730 void SetTrainGroupID(Train
*v
, GroupID new_g
)
732 if (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
)) return;
734 assert(v
->IsFrontEngine() || IsDefaultGroupID(new_g
));
736 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
737 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
740 u
->colourmap
= PAL_NONE
;
741 u
->InvalidateNewGRFCache();
742 u
->UpdateViewport(true);
745 /* Update the Replace Vehicle Windows */
746 GroupStatistics::UpdateAutoreplace(v
->owner
);
747 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
752 * Recalculates the groupID of a train. Should be called each time a vehicle is added
753 * to/removed from the chain,.
754 * @note this needs to be called too for 'wagon chains' (in the depot, without an engine)
755 * @note Called in CmdBuildRailVehicle, CmdBuildRailWagon, CmdMoveRailVehicle, CmdSellRailWagon
756 * @param v First vehicle of the chain.
758 void UpdateTrainGroupID(Train
*v
)
760 assert(v
->IsFrontEngine() || v
->IsFreeWagon());
762 GroupID new_g
= v
->IsFrontEngine() ? v
->group_id
: (GroupID
)DEFAULT_GROUP
;
763 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
764 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
767 u
->colourmap
= PAL_NONE
;
768 u
->InvalidateNewGRFCache();
771 /* Update the Replace Vehicle Windows */
772 GroupStatistics::UpdateAutoreplace(v
->owner
);
773 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
777 * Get the number of engines with EngineID id_e in the group with GroupID
778 * id_g and its sub-groups.
779 * @param company The company the group belongs to
780 * @param id_g The GroupID of the group used
781 * @param id_e The EngineID of the engine to count
782 * @return The number of engines with EngineID id_e in the group
784 uint
GetGroupNumEngines(CompanyID company
, GroupID id_g
, EngineID id_e
)
787 const Engine
*e
= Engine::Get(id_e
);
788 for (const Group
*g
: Group::Iterate()) {
789 if (g
->parent
== id_g
) count
+= GetGroupNumEngines(company
, g
->index
, id_e
);
791 return count
+ GroupStatistics::Get(company
, id_g
, e
->type
).num_engines
[id_e
];
795 * Get the number of vehicles in the group with GroupID
796 * id_g and its sub-groups.
797 * @param company The company the group belongs to
798 * @param id_g The GroupID of the group used
799 * @param type The vehicle type of the group
800 * @return The number of vehicles in the group
802 uint
GetGroupNumVehicle(CompanyID company
, GroupID id_g
, VehicleType type
)
805 for (const Group
*g
: Group::Iterate()) {
806 if (g
->parent
== id_g
) count
+= GetGroupNumVehicle(company
, g
->index
, type
);
808 return count
+ GroupStatistics::Get(company
, id_g
, type
).num_vehicle
;
812 * Get the number of vehicles above profit minimum age in the group with GroupID
813 * id_g and its sub-groups.
814 * @param company The company the group belongs to
815 * @param id_g The GroupID of the group used
816 * @param type The vehicle type of the group
817 * @return The number of vehicles above profit minimum age in the group
819 uint
GetGroupNumProfitVehicle(CompanyID company
, GroupID id_g
, VehicleType type
)
822 for (const Group
*g
: Group::Iterate()) {
823 if (g
->parent
== id_g
) count
+= GetGroupNumProfitVehicle(company
, g
->index
, type
);
825 return count
+ GroupStatistics::Get(company
, id_g
, type
).num_profit_vehicle
;
829 * Get last year's profit for the group with GroupID
830 * id_g and its sub-groups.
831 * @param company The company the group belongs to
832 * @param id_g The GroupID of the group used
833 * @param type The vehicle type of the group
834 * @return Last year's profit for the group
836 Money
GetGroupProfitLastYear(CompanyID company
, GroupID id_g
, VehicleType type
)
839 for (const Group
*g
: Group::Iterate()) {
840 if (g
->parent
== id_g
) sum
+= GetGroupProfitLastYear(company
, g
->index
, type
);
842 return sum
+ GroupStatistics::Get(company
, id_g
, type
).profit_last_year
;
845 void RemoveAllGroupsForCompany(const CompanyID company
)
847 for (Group
*g
: Group::Iterate()) {
848 if (company
== g
->owner
) delete g
;
854 * Test if GroupID group is a descendant of (or is) GroupID search
855 * @param search The GroupID to search in
856 * @param group The GroupID to search for
857 * @return True iff group is search or a descendant of search
859 bool GroupIsInGroup(GroupID search
, GroupID group
)
861 if (!Group::IsValidID(search
)) return search
== group
;
864 if (search
== group
) return true;
865 search
= Group::Get(search
)->parent
;
866 } while (search
!= INVALID_GROUP
);