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 std::string
&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
->vehicle_type
= vt
;
319 g
->parent
= INVALID_GROUP
;
322 const Company
*c
= Company::Get(_current_company
);
323 g
->livery
.colour1
= c
->livery
[LS_DEFAULT
].colour1
;
324 g
->livery
.colour2
= c
->livery
[LS_DEFAULT
].colour2
;
325 if (c
->settings
.renew_keep_length
) SetBit(g
->flags
, GroupFlags::GF_REPLACE_WAGON_REMOVAL
);
327 g
->parent
= pg
->index
;
328 g
->livery
.colour1
= pg
->livery
.colour1
;
329 g
->livery
.colour2
= pg
->livery
.colour2
;
330 g
->flags
= pg
->flags
;
333 _new_group_id
= g
->index
;
335 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
336 InvalidateWindowData(WC_COMPANY_COLOUR
, g
->owner
, g
->vehicle_type
);
339 return CommandCost();
344 * Add all vehicles in the given group to the default group and then deletes the group.
346 * @param flags type of operation
347 * @param p1 index of array group
348 * - p1 bit 0-15 : GroupID
351 * @return the cost of this operation or an error
353 CommandCost
CmdDeleteGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
355 Group
*g
= Group::GetIfValid(p1
);
356 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
358 /* Remove all vehicles from the group */
359 DoCommand(0, p1
, 0, flags
, CMD_REMOVE_ALL_VEHICLES_GROUP
);
361 /* Delete sub-groups */
362 for (const Group
*gp
: Group::Iterate()) {
363 if (gp
->parent
== g
->index
) {
364 DoCommand(0, gp
->index
, 0, flags
, CMD_DELETE_GROUP
);
368 if (flags
& DC_EXEC
) {
369 /* Update backupped orders if needed */
370 OrderBackup::ClearGroup(g
->index
);
372 /* If we set an autoreplace for the group we delete, remove it. */
373 if (_current_company
< MAX_COMPANIES
) {
376 c
= Company::Get(_current_company
);
377 for (EngineRenew
*er
: EngineRenew::Iterate()) {
378 if (er
->group_id
== g
->index
) RemoveEngineReplacementForCompany(c
, er
->from
, g
->index
, flags
);
382 VehicleType vt
= g
->vehicle_type
;
384 /* Delete the Replace Vehicle Windows */
385 CloseWindowById(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
388 InvalidateWindowData(GetWindowClassForVehicleType(vt
), VehicleListIdentifier(VL_GROUP_LIST
, vt
, _current_company
).Pack());
389 InvalidateWindowData(WC_COMPANY_COLOUR
, _current_company
, vt
);
392 return CommandCost();
398 * @param flags type of operation
399 * @param p1 index of array group
400 * - p1 bit 0-15 : GroupID
401 * - p1 bit 16: 0 - Rename grouop
402 * 1 - Set group parent
403 * @param p2 parent group index
404 * @param text the new name or an empty string when resetting to the default
405 * @return the cost of this operation or an error
407 CommandCost
CmdAlterGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
409 Group
*g
= Group::GetIfValid(GB(p1
, 0, 16));
410 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
412 if (!HasBit(p1
, 16)) {
414 bool reset
= text
.empty();
417 if (Utf8StringLength(text
) >= MAX_LENGTH_GROUP_NAME_CHARS
) return CMD_ERROR
;
420 if (flags
& DC_EXEC
) {
421 /* Assign the new one */
429 /* Set group parent */
430 const Group
*pg
= Group::GetIfValid(GB(p2
, 0, 16));
433 if (pg
->owner
!= _current_company
) return CMD_ERROR
;
434 if (pg
->vehicle_type
!= g
->vehicle_type
) return CMD_ERROR
;
436 /* Ensure request parent isn't child of group.
437 * This is the only place that infinite loops are prevented. */
438 if (GroupIsInGroup(pg
->index
, g
->index
)) return_cmd_error(STR_ERROR_GROUP_CAN_T_SET_PARENT_RECURSION
);
441 if (flags
& DC_EXEC
) {
442 g
->parent
= (pg
== nullptr) ? INVALID_GROUP
: pg
->index
;
443 GroupStatistics::UpdateAutoreplace(g
->owner
);
445 if (g
->livery
.in_use
== 0) {
446 const Livery
*livery
= GetParentLivery(g
);
447 g
->livery
.colour1
= livery
->colour1
;
448 g
->livery
.colour2
= livery
->colour2
;
450 PropagateChildLivery(g
);
451 MarkWholeScreenDirty();
456 if (flags
& DC_EXEC
) {
457 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
, 1);
458 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
459 InvalidateWindowData(WC_COMPANY_COLOUR
, g
->owner
, g
->vehicle_type
);
460 InvalidateWindowClassesData(WC_VEHICLE_VIEW
);
461 InvalidateWindowClassesData(WC_VEHICLE_DETAILS
);
464 return CommandCost();
469 * Do add a vehicle to a group.
470 * @param v Vehicle to add.
471 * @param new_g Group to add to.
473 static void AddVehicleToGroup(Vehicle
*v
, GroupID new_g
)
475 GroupStatistics::CountVehicle(v
, -1);
478 default: NOT_REACHED();
480 SetTrainGroupID(Train::From(v
), new_g
);
486 if (v
->IsEngineCountable()) UpdateNumEngineGroup(v
, v
->group_id
, new_g
);
488 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
489 u
->colourmap
= PAL_NONE
;
490 u
->InvalidateNewGRFCache();
491 u
->UpdateViewport(true);
496 GroupStatistics::CountVehicle(v
, 1);
500 * Add a vehicle to a group
502 * @param flags type of operation
503 * @param p1 index of array group
504 * - p1 bit 0-15 : GroupID
505 * @param p2 vehicle to add to a group
506 * - p2 bit 0-19 : VehicleID
507 * - p2 bit 31 : Add shared vehicles as well.
509 * @return the cost of this operation or an error
511 CommandCost
CmdAddVehicleGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
513 Vehicle
*v
= Vehicle::GetIfValid(GB(p2
, 0, 20));
516 if (v
== nullptr || (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
) && new_g
!= NEW_GROUP
)) return CMD_ERROR
;
518 if (Group::IsValidID(new_g
)) {
519 Group
*g
= Group::Get(new_g
);
520 if (g
->owner
!= _current_company
|| g
->vehicle_type
!= v
->type
) return CMD_ERROR
;
523 if (v
->owner
!= _current_company
|| !v
->IsPrimaryVehicle()) return CMD_ERROR
;
525 if (new_g
== NEW_GROUP
) {
526 /* Create new group. */
527 CommandCost ret
= CmdCreateGroup(0, flags
, v
->type
, INVALID_GROUP
, {});
528 if (ret
.Failed()) return ret
;
530 new_g
= _new_group_id
;
533 if (flags
& DC_EXEC
) {
534 AddVehicleToGroup(v
, new_g
);
536 if (HasBit(p2
, 31)) {
537 /* Add vehicles in the shared order list as well. */
538 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
539 if (v2
->group_id
!= new_g
) AddVehicleToGroup(v2
, new_g
);
543 GroupStatistics::UpdateAutoreplace(v
->owner
);
545 /* Update the Replace Vehicle Windows */
546 SetWindowDirty(WC_REPLACE_VEHICLE
, v
->type
);
547 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
548 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
549 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
550 InvalidateWindowData(GetWindowClassForVehicleType(v
->type
), VehicleListIdentifier(VL_GROUP_LIST
, v
->type
, _current_company
).Pack());
551 InvalidateWindowData(WC_VEHICLE_VIEW
, v
->index
);
552 InvalidateWindowData(WC_VEHICLE_DETAILS
, v
->index
);
555 return CommandCost();
559 * Add all shared vehicles of all vehicles from a group
561 * @param flags type of operation
562 * @param p1 index of group array
563 * - p1 bit 0-15 : GroupID
564 * @param p2 type of vehicles
566 * @return the cost of this operation or an error
568 CommandCost
CmdAddSharedVehicleGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
570 VehicleType type
= Extract
<VehicleType
, 0, 3>(p2
);
572 if (!Group::IsValidID(id_g
) || !IsCompanyBuildableVehicleType(type
)) return CMD_ERROR
;
574 if (flags
& DC_EXEC
) {
575 /* Find the first front engine which belong to the group id_g
576 * then add all shared vehicles of this front engine to the group id_g */
577 for (const Vehicle
*v
: Vehicle::Iterate()) {
578 if (v
->type
== type
&& v
->IsPrimaryVehicle()) {
579 if (v
->group_id
!= id_g
) continue;
581 /* For each shared vehicles add it to the group */
582 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
583 if (v2
->group_id
!= id_g
) DoCommand(tile
, id_g
, v2
->index
, flags
, CMD_ADD_VEHICLE_GROUP
, text
);
588 InvalidateWindowData(GetWindowClassForVehicleType(type
), VehicleListIdentifier(VL_GROUP_LIST
, type
, _current_company
).Pack());
591 return CommandCost();
596 * Remove all vehicles from a group
598 * @param flags type of operation
599 * @param p1 index of group array
600 * - p1 bit 0-15 : GroupID
603 * @return the cost of this operation or an error
605 CommandCost
CmdRemoveAllVehiclesGroup(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
608 Group
*g
= Group::GetIfValid(old_g
);
610 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
612 if (flags
& DC_EXEC
) {
613 /* Find each Vehicle that belongs to the group old_g and add it to the default group */
614 for (const Vehicle
*v
: Vehicle::Iterate()) {
615 if (v
->IsPrimaryVehicle()) {
616 if (v
->group_id
!= old_g
) continue;
618 /* Add The Vehicle to the default group */
619 DoCommand(tile
, DEFAULT_GROUP
, v
->index
, flags
, CMD_ADD_VEHICLE_GROUP
, text
);
623 InvalidateWindowData(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
626 return CommandCost();
630 * Set the livery for a vehicle group.
631 * @param tile Unused.
632 * @param flags Command flags.
634 * - p1 bit 0-15 Group ID.
636 * - p2 bit 8 Set secondary instead of primary colour
637 * - p2 bit 16-23 Colour.
639 CommandCost
CmdSetGroupLivery(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
641 Group
*g
= Group::GetIfValid(p1
);
642 bool primary
= !HasBit(p2
, 8);
643 Colours colour
= Extract
<Colours
, 16, 8>(p2
);
645 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
647 if (colour
>= COLOUR_END
&& colour
!= INVALID_COLOUR
) return CMD_ERROR
;
649 if (flags
& DC_EXEC
) {
651 SB(g
->livery
.in_use
, 0, 1, colour
!= INVALID_COLOUR
);
652 if (colour
== INVALID_COLOUR
) colour
= (Colours
)GetParentLivery(g
)->colour1
;
653 g
->livery
.colour1
= colour
;
655 SB(g
->livery
.in_use
, 1, 1, colour
!= INVALID_COLOUR
);
656 if (colour
== INVALID_COLOUR
) colour
= (Colours
)GetParentLivery(g
)->colour2
;
657 g
->livery
.colour2
= colour
;
660 PropagateChildLivery(g
);
661 MarkWholeScreenDirty();
664 return CommandCost();
668 * Set group flag for a group and its sub-groups.
669 * @param g initial group.
670 * @param set 1 to set or 0 to clear protection.
672 static void SetGroupFlag(Group
*g
, GroupFlags flag
, bool set
, bool children
)
675 SetBit(g
->flags
, flag
);
677 ClrBit(g
->flags
, flag
);
680 if (!children
) return;
682 for (Group
*pg
: Group::Iterate()) {
683 if (pg
->parent
== g
->index
) SetGroupFlag(pg
, flag
, set
, true);
688 * (Un)set group flag from a group
690 * @param flags type of operation
691 * @param p1 index of group array
692 * - p1 bit 0-15 : GroupID
693 * - p1 bit 16-18 : Flag to set, by value not bit.
695 * - p2 bit 0 : 1 to set or 0 to clear protection.
696 * - p2 bit 1 : 1 to apply to sub-groups.
698 * @return the cost of this operation or an error
700 CommandCost
CmdSetGroupFlag(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const std::string
&text
)
702 Group
*g
= Group::GetIfValid(GB(p1
, 0, 16));
703 if (g
== nullptr || g
->owner
!= _current_company
) return CMD_ERROR
;
705 /* GroupFlags are stored in as an 8 bit bitfield but passed here by value,
706 * so 3 bits is sufficient to cover each possible value. */
707 GroupFlags flag
= (GroupFlags
)GB(p1
, 16, 3);
708 if (flag
>= GroupFlags::GF_END
) return CMD_ERROR
;
710 if (flags
& DC_EXEC
) {
711 SetGroupFlag(g
, flag
, HasBit(p2
, 0), HasBit(p2
, 1));
713 SetWindowDirty(GetWindowClassForVehicleType(g
->vehicle_type
), VehicleListIdentifier(VL_GROUP_LIST
, g
->vehicle_type
, _current_company
).Pack());
714 InvalidateWindowData(WC_REPLACE_VEHICLE
, g
->vehicle_type
);
717 return CommandCost();
721 * Decrease the num_vehicle variable before delete an front engine from a group
722 * @note Called in CmdSellRailWagon and DeleteLasWagon,
723 * @param v FrontEngine of the train we want to remove.
725 void RemoveVehicleFromGroup(const Vehicle
*v
)
727 if (!v
->IsPrimaryVehicle()) return;
729 if (!IsDefaultGroupID(v
->group_id
)) GroupStatistics::CountVehicle(v
, -1);
734 * Affect the groupID of a train to new_g.
735 * @note called in CmdAddVehicleGroup and CmdMoveRailVehicle
736 * @param v First vehicle of the chain.
737 * @param new_g index of array group
739 void SetTrainGroupID(Train
*v
, GroupID new_g
)
741 if (!Group::IsValidID(new_g
) && !IsDefaultGroupID(new_g
)) return;
743 assert(v
->IsFrontEngine() || IsDefaultGroupID(new_g
));
745 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
746 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
749 u
->colourmap
= PAL_NONE
;
750 u
->InvalidateNewGRFCache();
751 u
->UpdateViewport(true);
754 /* Update the Replace Vehicle Windows */
755 GroupStatistics::UpdateAutoreplace(v
->owner
);
756 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
761 * Recalculates the groupID of a train. Should be called each time a vehicle is added
762 * to/removed from the chain,.
763 * @note this needs to be called too for 'wagon chains' (in the depot, without an engine)
764 * @note Called in CmdBuildRailVehicle, CmdBuildRailWagon, CmdMoveRailVehicle, CmdSellRailWagon
765 * @param v First vehicle of the chain.
767 void UpdateTrainGroupID(Train
*v
)
769 assert(v
->IsFrontEngine() || v
->IsFreeWagon());
771 GroupID new_g
= v
->IsFrontEngine() ? v
->group_id
: (GroupID
)DEFAULT_GROUP
;
772 for (Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
773 if (u
->IsEngineCountable()) UpdateNumEngineGroup(u
, u
->group_id
, new_g
);
776 u
->colourmap
= PAL_NONE
;
777 u
->InvalidateNewGRFCache();
780 /* Update the Replace Vehicle Windows */
781 GroupStatistics::UpdateAutoreplace(v
->owner
);
782 SetWindowDirty(WC_REPLACE_VEHICLE
, VEH_TRAIN
);
786 * Get the number of engines with EngineID id_e in the group with GroupID
787 * id_g and its sub-groups.
788 * @param company The company the group belongs to
789 * @param id_g The GroupID of the group used
790 * @param id_e The EngineID of the engine to count
791 * @return The number of engines with EngineID id_e in the group
793 uint
GetGroupNumEngines(CompanyID company
, GroupID id_g
, EngineID id_e
)
796 const Engine
*e
= Engine::Get(id_e
);
797 for (const Group
*g
: Group::Iterate()) {
798 if (g
->parent
== id_g
) count
+= GetGroupNumEngines(company
, g
->index
, id_e
);
800 return count
+ GroupStatistics::Get(company
, id_g
, e
->type
).num_engines
[id_e
];
804 * Get the number of vehicles in the group with GroupID
805 * id_g and its sub-groups.
806 * @param company The company the group belongs to
807 * @param id_g The GroupID of the group used
808 * @param type The vehicle type of the group
809 * @return The number of vehicles in the group
811 uint
GetGroupNumVehicle(CompanyID company
, GroupID id_g
, VehicleType type
)
814 for (const Group
*g
: Group::Iterate()) {
815 if (g
->parent
== id_g
) count
+= GetGroupNumVehicle(company
, g
->index
, type
);
817 return count
+ GroupStatistics::Get(company
, id_g
, type
).num_vehicle
;
821 * Get the number of vehicles above profit minimum age in the group with GroupID
822 * id_g and its sub-groups.
823 * @param company The company the group belongs to
824 * @param id_g The GroupID of the group used
825 * @param type The vehicle type of the group
826 * @return The number of vehicles above profit minimum age in the group
828 uint
GetGroupNumProfitVehicle(CompanyID company
, GroupID id_g
, VehicleType type
)
831 for (const Group
*g
: Group::Iterate()) {
832 if (g
->parent
== id_g
) count
+= GetGroupNumProfitVehicle(company
, g
->index
, type
);
834 return count
+ GroupStatistics::Get(company
, id_g
, type
).num_profit_vehicle
;
838 * Get last year's profit for the group with GroupID
839 * id_g and its sub-groups.
840 * @param company The company the group belongs to
841 * @param id_g The GroupID of the group used
842 * @param type The vehicle type of the group
843 * @return Last year's profit for the group
845 Money
GetGroupProfitLastYear(CompanyID company
, GroupID id_g
, VehicleType type
)
848 for (const Group
*g
: Group::Iterate()) {
849 if (g
->parent
== id_g
) sum
+= GetGroupProfitLastYear(company
, g
->index
, type
);
851 return sum
+ GroupStatistics::Get(company
, id_g
, type
).profit_last_year
;
854 void RemoveAllGroupsForCompany(const CompanyID company
)
856 for (Group
*g
: Group::Iterate()) {
857 if (company
== g
->owner
) delete g
;
863 * Test if GroupID group is a descendant of (or is) GroupID search
864 * @param search The GroupID to search in
865 * @param group The GroupID to search for
866 * @return True iff group is search or a descendant of search
868 bool GroupIsInGroup(GroupID search
, GroupID group
)
870 if (!Group::IsValidID(search
)) return search
== group
;
873 if (search
== group
) return true;
874 search
= Group::Get(search
)->parent
;
875 } while (search
!= INVALID_GROUP
);