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 "command_func.h"
13 #include "vehiclelist.h"
14 #include "vehicle_func.h"
15 #include "autoreplace_base.h"
16 #include "autoreplace_func.h"
17 #include "string_func.h"
18 #include "company_func.h"
19 #include "core/pool_func.hpp"
20 #include "order_backup.h"
21 #include "group_cmd.h"
23 #include "table/strings.h"
25 #include "safeguards.h"
27 GroupPool
_group_pool("Group");
28 INSTANTIATE_POOL_METHODS(Group
)
30 GroupStatistics::GroupStatistics()
32 this->num_engines
= CallocT
<uint16
>(Engine::GetPoolSize());
35 GroupStatistics::~GroupStatistics()
37 free(this->num_engines
);
43 void GroupStatistics::Clear()
45 this->num_vehicle
= 0;
46 this->num_profit_vehicle
= 0;
47 this->profit_last_year
= 0;
49 /* This is also called when NewGRF change. So the number of engines might have changed. Reallocate. */
50 free(this->num_engines
);
51 this->num_engines
= CallocT
<uint16
>(Engine::GetPoolSize());
55 * Returns the GroupStatistics for a specific group.
56 * @param company Owner of the group.
57 * @param id_g GroupID of the group.
58 * @param type VehicleType of the vehicles in the group.
59 * @return Statistics for the group.
61 /* static */ GroupStatistics
&GroupStatistics::Get(CompanyID company
, GroupID id_g
, VehicleType type
)
63 if (Group::IsValidID(id_g
)) {
64 Group
*g
= Group::Get(id_g
);
65 assert(g
->owner
== company
);
66 assert(g
->vehicle_type
== type
);
70 if (IsDefaultGroupID(id_g
)) return Company::Get(company
)->group_default
[type
];
71 if (IsAllGroupID(id_g
)) return Company::Get(company
)->group_all
[type
];
77 * Returns the GroupStatistic for the group of a vehicle.
79 * @return GroupStatistics for the group of the vehicle.
81 /* static */ GroupStatistics
&GroupStatistics::Get(const Vehicle
*v
)
83 return GroupStatistics::Get(v
->owner
, v
->group_id
, v
->type
);
87 * Returns the GroupStatistic for the ALL_GROUPO of a vehicle type.
89 * @return GroupStatistics for the ALL_GROUP of the vehicle type.
91 /* static */ GroupStatistics
&GroupStatistics::GetAllGroup(const Vehicle
*v
)
93 return GroupStatistics::Get(v
->owner
, ALL_GROUP
, v
->type
);
97 * Update all caches after loading a game, changing NewGRF, etc.
99 /* static */ void GroupStatistics::UpdateAfterLoad()
101 /* Set up the engine count for all companies */
102 for (Company
*c
: Company::Iterate()) {
103 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
104 c
->group_all
[type
].Clear();
105 c
->group_default
[type
].Clear();
110 for (Group
*g
: Group::Iterate()) {
111 g
->statistics
.Clear();
114 for (const Vehicle
*v
: Vehicle::Iterate()) {
115 if (!v
->IsEngineCountable()) continue;
117 GroupStatistics::CountEngine(v
, 1);
118 if (v
->IsPrimaryVehicle()) GroupStatistics::CountVehicle(v
, 1);
121 for (const Company
*c
: Company::Iterate()) {
122 GroupStatistics::UpdateAutoreplace(c
->index
);
127 * Update num_vehicle when adding or removing a vehicle.
128 * @param v Vehicle to count.
129 * @param delta +1 to add, -1 to remove.
131 /* static */ void GroupStatistics::CountVehicle(const Vehicle
*v
, int delta
)
133 assert(delta
== 1 || delta
== -1);
135 GroupStatistics
&stats_all
= GroupStatistics::GetAllGroup(v
);
136 GroupStatistics
&stats
= GroupStatistics::Get(v
);
138 stats_all
.num_vehicle
+= delta
;
139 stats
.num_vehicle
+= delta
;
141 if (v
->age
> VEHICLE_PROFIT_MIN_AGE
) {
142 stats_all
.num_profit_vehicle
+= delta
;
143 stats_all
.profit_last_year
+= v
->GetDisplayProfitLastYear() * delta
;
144 stats
.num_profit_vehicle
+= delta
;
145 stats
.profit_last_year
+= v
->GetDisplayProfitLastYear() * delta
;
150 * Update num_engines when adding/removing an engine.
151 * @param v Engine to count.
152 * @param delta +1 to add, -1 to remove.
154 /* static */ void GroupStatistics::CountEngine(const Vehicle
*v
, int delta
)
156 assert(delta
== 1 || delta
== -1);
157 GroupStatistics::GetAllGroup(v
).num_engines
[v
->engine_type
] += delta
;
158 GroupStatistics::Get(v
).num_engines
[v
->engine_type
] += delta
;
162 * Add a vehicle to the profit sum of its group.
164 /* static */ void GroupStatistics::VehicleReachedProfitAge(const Vehicle
*v
)
166 GroupStatistics
&stats_all
= GroupStatistics::GetAllGroup(v
);
167 GroupStatistics
&stats
= GroupStatistics::Get(v
);
169 stats_all
.num_profit_vehicle
++;
170 stats_all
.profit_last_year
+= v
->GetDisplayProfitLastYear();
171 stats
.num_profit_vehicle
++;
172 stats
.profit_last_year
+= v
->GetDisplayProfitLastYear();
176 * Recompute the profits for all groups.
178 /* static */ void GroupStatistics::UpdateProfits()
180 /* Set up the engine count for all companies */
181 for (Company
*c
: Company::Iterate()) {
182 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
183 c
->group_all
[type
].ClearProfits();
184 c
->group_default
[type
].ClearProfits();
189 for (Group
*g
: Group::Iterate()) {
190 g
->statistics
.ClearProfits();
193 for (const Vehicle
*v
: Vehicle::Iterate()) {
194 if (v
->IsPrimaryVehicle() && v
->age
> VEHICLE_PROFIT_MIN_AGE
) GroupStatistics::VehicleReachedProfitAge(v
);
199 * Update autoreplace_defined and autoreplace_finished of all statistics of a company.
200 * @param company Company to update statistics for.
202 /* static */ void GroupStatistics::UpdateAutoreplace(CompanyID company
)
204 /* Set up the engine count for all companies */
205 Company
*c
= Company::Get(company
);
206 for (VehicleType type
= VEH_BEGIN
; type
< VEH_COMPANY_END
; type
++) {
207 c
->group_all
[type
].ClearAutoreplace();
208 c
->group_default
[type
].ClearAutoreplace();
212 for (Group
*g
: Group::Iterate()) {
213 if (g
->owner
!= company
) continue;
214 g
->statistics
.ClearAutoreplace();
217 for (EngineRenewList erl
= c
->engine_renew_list
; erl
!= nullptr; erl
= erl
->next
) {
218 const Engine
*e
= Engine::Get(erl
->from
);
219 GroupStatistics
&stats
= GroupStatistics::Get(company
, erl
->group_id
, e
->type
);
220 if (!stats
.autoreplace_defined
) {
221 stats
.autoreplace_defined
= true;
222 stats
.autoreplace_finished
= true;
224 if (GetGroupNumEngines(company
, erl
->group_id
, erl
->from
) > 0) stats
.autoreplace_finished
= false;
229 * Update the num engines of a groupID. Decrease the old one and increase the new one
230 * @note called in SetTrainGroupID and UpdateTrainGroupID
231 * @param v Vehicle we have to update
232 * @param old_g index of the old group
233 * @param new_g index of the new group
235 static inline void UpdateNumEngineGroup(const Vehicle
*v
, GroupID old_g
, GroupID new_g
)
237 if (old_g
!= new_g
) {
238 /* Decrease the num engines in the old group */
239 GroupStatistics::Get(v
->owner
, old_g
, v
->type
).num_engines
[v
->engine_type
]--;
241 /* Increase the num engines in the new group */
242 GroupStatistics::Get(v
->owner
, new_g
, v
->type
).num_engines
[v
->engine_type
]++;
247 const Livery
*GetParentLivery(const Group
*g
)
249 if (g
->parent
== INVALID_GROUP
) {
250 const Company
*c
= Company::Get(g
->owner
);
251 return &c
->livery
[LS_DEFAULT
];
254 const Group
*pg
= Group::Get(g
->parent
);
260 * Propagate a livery change to a group's children.
263 void PropagateChildLivery(const Group
*g
)
265 /* Company colour data is indirectly cached. */
266 for (Vehicle
*v
: Vehicle::Iterate()) {
267 if (v
->group_id
== g
->index
&& (!v
->IsGroundVehicle() || v
->IsFrontEngine())) {
268 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
269 u
->colourmap
= PAL_NONE
;
270 u
->InvalidateNewGRFCache();
275 for (Group
*cg
: Group::Iterate()) {
276 if (cg
->parent
== g
->index
) {
277 if (!HasBit(cg
->livery
.in_use
, 0)) cg
->livery
.colour1
= g
->livery
.colour1
;
278 if (!HasBit(cg
->livery
.in_use
, 1)) cg
->livery
.colour2
= g
->livery
.colour2
;
279 PropagateChildLivery(cg
);
285 Group::Group(Owner owner
)
288 this->folded
= false;
293 * Create a new vehicle group.
294 * @param flags type of operation
295 * @param vt vehicle type
296 * @param parent_group parent groupid
297 * @return the cost of this operation or an error
299 std::tuple
<CommandCost
, GroupID
> CmdCreateGroup(DoCommandFlag flags
, VehicleType vt
, GroupID parent_group
)
301 if (!IsCompanyBuildableVehicleType(vt
)) return { CMD_ERROR
, INVALID_GROUP
};
303 if (!Group::CanAllocateItem()) return { CMD_ERROR
, INVALID_GROUP
};
305 const Group
*pg
= Group::GetIfValid(parent_group
);
307 if (pg
->owner
!= _current_company
) return { CMD_ERROR
, INVALID_GROUP
};
308 if (pg
->vehicle_type
!= vt
) return { CMD_ERROR
, INVALID_GROUP
};
311 if (flags
& DC_EXEC
) {
312 Group
*g
= new Group(_current_company
);
313 g
->vehicle_type
= vt
;
314 g
->parent
= INVALID_GROUP
;
317 const Company
*c
= Company::Get(_current_company
);
318 g
->livery
.colour1
= c
->livery
[LS_DEFAULT
].colour1
;
319 g
->livery
.colour2
= c
->livery
[LS_DEFAULT
].colour2
;
320 if (c
->settings
.renew_keep_length
) SetBit(g
->flags
, GroupFlags::GF_REPLACE_WAGON_REMOVAL
);
322 g
->parent
= pg
->index
;
323 g
->livery
.colour1
= pg
->livery
.colour1
;
324 g
->livery
.colour2
= pg
->livery
.colour2
;
325 g
->flags
= pg
->flags
;
328 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
329 InvalidateWindowData(WC_COMPANY_COLOUR
, g
->owner
, g
->vehicle_type
);
331 return { CommandCost(), g
->index
};
334 return { CommandCost(), INVALID_GROUP
};
339 * Add all vehicles in the given group to the default group and then deletes the group.
340 * @param flags type of operation
341 * @param group_id index of group
342 * @return the cost of this operation or an error
344 CommandCost
CmdDeleteGroup(DoCommandFlag flags
, GroupID group_id
)
346 Group
*g
= Group::GetIfValid(group_id
);
347 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
349 /* Remove all vehicles from the group */
350 Command
<CMD_REMOVE_ALL_VEHICLES_GROUP
>::Do(flags
, group_id
);
352 /* Delete sub-groups */
353 for (const Group
*gp
: Group::Iterate()) {
354 if (gp
->parent
== g
->index
) {
355 Command
<CMD_DELETE_GROUP
>::Do(flags
, gp
->index
);
359 if (flags
& DC_EXEC
) {
360 /* Update backupped orders if needed */
361 OrderBackup::ClearGroup(g
->index
);
363 /* If we set an autoreplace for the group we delete, remove it. */
364 if (_current_company
< MAX_COMPANIES
) {
367 c
= Company::Get(_current_company
);
368 for (EngineRenew
*er
: EngineRenew::Iterate()) {
369 if (er
->group_id
== g
->index
) RemoveEngineReplacementForCompany(c
, er
->from
, g
->index
, flags
);
373 VehicleType vt
= g
->vehicle_type
;
375 /* Delete the Replace Vehicle Windows */
376 CloseWindowById(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
379 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
380 InvalidateWindowData(WC_COMPANY_COLOUR
, _current_company
, vt
);
383 return CommandCost();
388 * @param flags type of operation
389 * @param mode Operation to perform.
390 * @param group_id GroupID
391 * @param parent_id parent group index
392 * @param text the new name or an empty string when resetting to the default
393 * @return the cost of this operation or an error
395 CommandCost
CmdAlterGroup(DoCommandFlag flags
, AlterGroupMode mode
, GroupID group_id
, GroupID parent_id
, const std::string
&text
)
397 Group
*g
= Group::GetIfValid(group_id
);
398 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
400 if (mode
== AlterGroupMode::Rename
) {
402 bool reset
= text
.empty();
405 if (Utf8StringLength(text
) >= MAX_LENGTH_GROUP_NAME_CHARS
) return CMD_ERROR
;
408 if (flags
& DC_EXEC
) {
409 /* Assign the new one */
416 } else if (mode
== AlterGroupMode::SetParent
) {
417 /* Set group parent */
418 const Group
*pg
= Group::GetIfValid(parent_id
);
421 if (pg
->owner
!= _current_company
) return CMD_ERROR
;
422 if (pg
->vehicle_type
!= g
->vehicle_type
) return CMD_ERROR
;
424 /* Ensure request parent isn't child of group.
425 * This is the only place that infinite loops are prevented. */
426 if (GroupIsInGroup(pg
->index
, g
->index
)) return_cmd_error(STR_ERROR_GROUP_CAN_T_SET_PARENT_RECURSION
);
429 if (flags
& DC_EXEC
) {
430 g
->parent
= (pg
== nullptr) ? INVALID_GROUP
: pg
->index
;
431 GroupStatistics::UpdateAutoreplace(g
->owner
);
433 if (g
->livery
.in_use
== 0) {
434 const Livery
*livery
= GetParentLivery(g
);
435 g
->livery
.colour1
= livery
->colour1
;
436 g
->livery
.colour2
= livery
->colour2
;
438 PropagateChildLivery(g
);
439 MarkWholeScreenDirty();
446 if (flags
& DC_EXEC
) {
447 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
, 1);
448 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
449 InvalidateWindowData(WC_COMPANY_COLOUR
, g
->owner
, g
->vehicle_type
);
450 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
451 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
454 return CommandCost();
459 * Do add a vehicle to a group.
460 * @param v Vehicle to add.
461 * @param new_g Group to add to.
463 static void AddVehicleToGroup(Vehicle
*v
, GroupID new_g
)
465 GroupStatistics::CountVehicle(v
, -1);
468 default: NOT_REACHED();
470 SetTrainGroupID(Train::From(v
), new_g
);
476 if (v
->IsEngineCountable()) UpdateNumEngineGroup(v
, v
->group_id
, new_g
);
478 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
479 u
->colourmap
= PAL_NONE
;
480 u
->InvalidateNewGRFCache();
481 u
->UpdateViewport(true);
486 GroupStatistics::CountVehicle(v
, 1);
490 * Add a vehicle to a group
491 * @param flags type of operation
492 * @param group_id index of group
493 * @param veh_id vehicle to add to a group
494 * @param add_shared Add shared vehicles as well.
495 * @return the cost of this operation or an error
497 std::tuple
<CommandCost
, GroupID
> CmdAddVehicleGroup(DoCommandFlag flags
, GroupID group_id
, VehicleID veh_id
, bool add_shared
)
499 Vehicle
*v
= Vehicle::GetIfValid(veh_id
);
500 GroupID new_g
= group_id
;
502 if (v
== nullptr || (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
) && new_g
!= NEW_GROUP
)) return { CMD_ERROR
, INVALID_GROUP
};
504 if (Group::IsValidID(new_g
)) {
505 Group
*g
= Group::Get(new_g
);
506 if (g
->owner
!= _current_company
|| g
->vehicle_type
!= v
->type
) return { CMD_ERROR
, INVALID_GROUP
};
509 if (v
->owner
!= _current_company
|| !v
->IsPrimaryVehicle()) return { CMD_ERROR
, INVALID_GROUP
};
511 if (new_g
== NEW_GROUP
) {
512 /* Create new group. */
513 auto [ret
, group_id
] = CmdCreateGroup(flags
, v
->type
, INVALID_GROUP
);
514 if (ret
.Failed()) return { ret
, group_id
};
519 if (flags
& DC_EXEC
) {
520 AddVehicleToGroup(v
, new_g
);
523 /* Add vehicles in the shared order list as well. */
524 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
525 if (v2
->group_id
!= new_g
) AddVehicleToGroup(v2
, new_g
);
529 GroupStatistics::UpdateAutoreplace(v
->owner
);
531 /* Update the Replace Vehicle Windows */
532 SetWindowDirty(WC_REPLACE_VEHICLE
, v
->type
);
533 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
534 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
535 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
536 InvalidateWindowData(GetWindowClassForVehicleType(v
->type
), VehicleListIdentifier(VL_GROUP_LIST
, v
->type
, _current_company
).Pack());
537 InvalidateWindowData(WC_VEHICLE_VIEW
, v
->index
);
538 InvalidateWindowData(WC_VEHICLE_DETAILS
, v
->index
);
541 return { CommandCost(), new_g
};
545 * Add all shared vehicles of all vehicles from a group
546 * @param flags type of operation
547 * @param id_g index of group
548 * @param type type of vehicles
549 * @return the cost of this operation or an error
551 CommandCost
CmdAddSharedVehicleGroup(DoCommandFlag flags
, GroupID id_g
, VehicleType type
)
553 if (!Group::IsValidID(id_g
) || !IsCompanyBuildableVehicleType(type
)) return CMD_ERROR
;
555 if (flags
& DC_EXEC
) {
556 /* Find the first front engine which belong to the group id_g
557 * then add all shared vehicles of this front engine to the group id_g */
558 for (const Vehicle
*v
: Vehicle::Iterate()) {
559 if (v
->type
== type
&& v
->IsPrimaryVehicle()) {
560 if (v
->group_id
!= id_g
) continue;
562 /* For each shared vehicles add it to the group */
563 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
564 if (v2
->group_id
!= id_g
) Command
<CMD_ADD_VEHICLE_GROUP
>::Do(flags
, id_g
, v2
->index
, false);
569 InvalidateWindowData(GetWindowClassForVehicleType(type
), VehicleListIdentifier(VL_GROUP_LIST
, type
, _current_company
).Pack());
572 return CommandCost();
577 * Remove all vehicles from a group
578 * @param flags type of operation
579 * @param group_id index of group
580 * @return the cost of this operation or an error
582 CommandCost
CmdRemoveAllVehiclesGroup(DoCommandFlag flags
, GroupID group_id
)
584 Group
*g
= Group::GetIfValid(group_id
);
586 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
588 if (flags
& DC_EXEC
) {
589 /* Find each Vehicle that belongs to the group old_g and add it to the default group */
590 for (const Vehicle
*v
: Vehicle::Iterate()) {
591 if (v
->IsPrimaryVehicle()) {
592 if (v
->group_id
!= group_id
) continue;
594 /* Add The Vehicle to the default group */
595 Command
<CMD_ADD_VEHICLE_GROUP
>::Do(flags
, DEFAULT_GROUP
, v
->index
, false);
599 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
602 return CommandCost();
606 * Set the livery for a vehicle group.
607 * @param flags Command flags.
608 * @param group_id Group ID.
609 * @param primary Set primary instead of secondary colour
610 * @param colour Colour.
612 CommandCost
CmdSetGroupLivery(DoCommandFlag flags
, GroupID group_id
, bool primary
, Colours colour
)
614 Group
*g
= Group::GetIfValid(group_id
);
616 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
618 if (colour
>= COLOUR_END
&& colour
!= INVALID_COLOUR
) return CMD_ERROR
;
620 if (flags
& DC_EXEC
) {
622 SB(g
->livery
.in_use
, 0, 1, colour
!= INVALID_COLOUR
);
623 if (colour
== INVALID_COLOUR
) colour
= (Colours
)GetParentLivery(g
)->colour1
;
624 g
->livery
.colour1
= colour
;
626 SB(g
->livery
.in_use
, 1, 1, colour
!= INVALID_COLOUR
);
627 if (colour
== INVALID_COLOUR
) colour
= (Colours
)GetParentLivery(g
)->colour2
;
628 g
->livery
.colour2
= colour
;
631 PropagateChildLivery(g
);
632 MarkWholeScreenDirty();
635 return CommandCost();
639 * Set group flag for a group and its sub-groups.
640 * @param g initial group.
641 * @param set 1 to set or 0 to clear protection.
643 static void SetGroupFlag(Group
*g
, GroupFlags flag
, bool set
, bool children
)
646 SetBit(g
->flags
, flag
);
648 ClrBit(g
->flags
, flag
);
651 if (!children
) return;
653 for (Group
*pg
: Group::Iterate()) {
654 if (pg
->parent
== g
->index
) SetGroupFlag(pg
, flag
, set
, true);
659 * (Un)set group flag from a group
660 * @param flags type of operation
661 * @param group_id index of group array
662 * @param flag flag to set, by value not bit.
663 * @param value value to set the flag to.
664 * @param recursive to apply to sub-groups.
665 * @return the cost of this operation or an error
667 CommandCost
CmdSetGroupFlag(DoCommandFlag flags
, GroupID group_id
, GroupFlags flag
, bool value
, bool recursive
)
669 Group
*g
= Group::GetIfValid(group_id
);
670 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
672 if (flag
>= GroupFlags::GF_END
) return CMD_ERROR
;
674 if (flags
& DC_EXEC
) {
675 SetGroupFlag(g
, flag
, value
, recursive
);
677 SetWindowDirty(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
678 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
681 return CommandCost();
685 * Decrease the num_vehicle variable before delete an front engine from a group
686 * @note Called in CmdSellRailWagon and DeleteLasWagon,
687 * @param v FrontEngine of the train we want to remove.
689 void RemoveVehicleFromGroup(const Vehicle
*v
)
691 if (!v
->IsPrimaryVehicle()) return;
693 if (!IsDefaultGroupID(v
->group_id
)) GroupStatistics::CountVehicle(v
, -1);
698 * Affect the groupID of a train to new_g.
699 * @note called in CmdAddVehicleGroup and CmdMoveRailVehicle
700 * @param v First vehicle of the chain.
701 * @param new_g index of array group
703 void SetTrainGroupID(Train
*v
, GroupID new_g
)
705 if (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
)) return;
707 assert(v
->IsFrontEngine() || IsDefaultGroupID(new_g
));
709 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
710 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
713 u
->colourmap
= PAL_NONE
;
714 u
->InvalidateNewGRFCache();
715 u
->UpdateViewport(true);
718 /* Update the Replace Vehicle Windows */
719 GroupStatistics::UpdateAutoreplace(v
->owner
);
720 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
725 * Recalculates the groupID of a train. Should be called each time a vehicle is added
726 * to/removed from the chain,.
727 * @note this needs to be called too for 'wagon chains' (in the depot, without an engine)
728 * @note Called in CmdBuildRailVehicle, CmdBuildRailWagon, CmdMoveRailVehicle, CmdSellRailWagon
729 * @param v First vehicle of the chain.
731 void UpdateTrainGroupID(Train
*v
)
733 assert(v
->IsFrontEngine() || v
->IsFreeWagon());
735 GroupID new_g
= v
->IsFrontEngine() ? v
->group_id
: (GroupID
)DEFAULT_GROUP
;
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();
744 /* Update the Replace Vehicle Windows */
745 GroupStatistics::UpdateAutoreplace(v
->owner
);
746 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
750 * Get the number of engines with EngineID id_e in the group with GroupID
751 * id_g and its sub-groups.
752 * @param company The company the group belongs to
753 * @param id_g The GroupID of the group used
754 * @param id_e The EngineID of the engine to count
755 * @return The number of engines with EngineID id_e in the group
757 uint
GetGroupNumEngines(CompanyID company
, GroupID id_g
, EngineID id_e
)
760 const Engine
*e
= Engine::Get(id_e
);
761 for (const Group
*g
: Group::Iterate()) {
762 if (g
->parent
== id_g
) count
+= GetGroupNumEngines(company
, g
->index
, id_e
);
764 return count
+ GroupStatistics::Get(company
, id_g
, e
->type
).num_engines
[id_e
];
768 * Get the number of vehicles in the group with GroupID
769 * id_g and its sub-groups.
770 * @param company The company the group belongs to
771 * @param id_g The GroupID of the group used
772 * @param type The vehicle type of the group
773 * @return The number of vehicles in the group
775 uint
GetGroupNumVehicle(CompanyID company
, GroupID id_g
, VehicleType type
)
778 for (const Group
*g
: Group::Iterate()) {
779 if (g
->parent
== id_g
) count
+= GetGroupNumVehicle(company
, g
->index
, type
);
781 return count
+ GroupStatistics::Get(company
, id_g
, type
).num_vehicle
;
785 * Get the number of vehicles above profit minimum age in the group with GroupID
786 * id_g and its sub-groups.
787 * @param company The company the group belongs to
788 * @param id_g The GroupID of the group used
789 * @param type The vehicle type of the group
790 * @return The number of vehicles above profit minimum age in the group
792 uint
GetGroupNumProfitVehicle(CompanyID company
, GroupID id_g
, VehicleType type
)
795 for (const Group
*g
: Group::Iterate()) {
796 if (g
->parent
== id_g
) count
+= GetGroupNumProfitVehicle(company
, g
->index
, type
);
798 return count
+ GroupStatistics::Get(company
, id_g
, type
).num_profit_vehicle
;
802 * Get last year's profit for the group with GroupID
803 * id_g and its sub-groups.
804 * @param company The company the group belongs to
805 * @param id_g The GroupID of the group used
806 * @param type The vehicle type of the group
807 * @return Last year's profit for the group
809 Money
GetGroupProfitLastYear(CompanyID company
, GroupID id_g
, VehicleType type
)
812 for (const Group
*g
: Group::Iterate()) {
813 if (g
->parent
== id_g
) sum
+= GetGroupProfitLastYear(company
, g
->index
, type
);
815 return sum
+ GroupStatistics::Get(company
, id_g
, type
).profit_last_year
;
818 void RemoveAllGroupsForCompany(const CompanyID company
)
820 for (Group
*g
: Group::Iterate()) {
821 if (company
== g
->owner
) delete g
;
827 * Test if GroupID group is a descendant of (or is) GroupID search
828 * @param search The GroupID to search in
829 * @param group The GroupID to search for
830 * @return True iff group is search or a descendant of search
832 bool GroupIsInGroup(GroupID search
, GroupID group
)
834 if (!Group::IsValidID(search
)) return search
== group
;
837 if (search
== group
) return true;
838 search
= Group::Get(search
)->parent
;
839 } while (search
!= INVALID_GROUP
);