Fix crash when setting separation mode for vehicles with no orders list.
[openttd-joker.git] / src / group_cmd.cpp
blob6ee3fbf74fa526a6ecd2542d6c5dbba8a2d95f76
1 /* $Id$ */
3 /*
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/>.
8 */
10 /** @file group_cmd.cpp Handling of the engine groups */
12 #include "stdafx.h"
13 #include <algorithm>
14 #include "cmd_helper.h"
15 #include "command_func.h"
16 #include "train.h"
17 #include "vehiclelist.h"
18 #include "vehicle_func.h"
19 #include "station_base.h"
20 #include "town.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);
50 /**
51 * Clear all caches.
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());
64 /**
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);
77 return g->statistics;
80 if (IsDefaultGroupID(id_g)) return Company::Get(company)->group_default[type];
81 if (IsAllGroupID(id_g)) return Company::Get(company)->group_all[type];
83 NOT_REACHED();
86 /**
87 * Returns the GroupStatistic for the group of a vehicle.
88 * @param v 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);
96 /**
97 * Returns the GroupStatistic for the ALL_GROUPO of a vehicle type.
98 * @param v Vehicle.
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 */
112 Company *c;
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();
120 /* Recalculate */
121 Group *g;
122 FOR_ALL_GROUPS(g) {
123 g->statistics.Clear();
126 const Vehicle *v;
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 */
200 Company *c;
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();
208 /* Recalculate */
209 Group *g;
210 FOR_ALL_GROUPS(g) {
211 g->statistics.ClearProfits();
214 const Vehicle *v;
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();
233 /* Recalculate */
234 Group *g;
235 FOR_ALL_GROUPS(g) {
236 if (g->owner != company) continue;
237 g->statistics.ClearAutoreplace();
240 for (EngineRenewList erl = c->engine_renew_list; erl != nullptr; 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)
273 this->owner = owner;
276 Group::~Group()
278 free(this->name);
283 * Create a new vehicle group.
284 * @param tile unused
285 * @param flags type of operation
286 * @param p1 vehicle type
287 * @param p2 unused
288 * @param text unused
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 CommandError();
296 if (!Group::CanAllocateItem()) return CommandError();
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.
315 * @param tile unused
316 * @param flags type of operation
317 * @param p1 index of array group
318 * - p1 bit 0-15 : GroupID
319 * @param p2 unused
320 * @param text unused
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 == nullptr || g->owner != _current_company) return CommandError();
328 /* Remove all vehicles from the group */
329 DoCommand(0, p1, 0, flags, CMD_REMOVE_ALL_VEHICLES_GROUP);
331 /* Delete sub-groups */
332 Group *gp;
333 FOR_ALL_GROUPS(gp) {
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) {
345 Company *c;
346 EngineRenew *er;
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);
364 delete g;
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)
377 const Group *g;
379 FOR_ALL_GROUPS(g) {
380 if (g->name != nullptr && g->vehicle_type == type && strcmp(g->name, name) == 0) return false;
383 return true;
387 * Alter a group
388 * @param tile unused
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 == nullptr || g->owner != _current_company) return CommandError();
403 if (!HasBit(p1, 16)) {
404 /* Rename group */
405 bool reset = StrEmpty(text);
407 if (!reset) {
408 if (Utf8StringLength(text) >= MAX_LENGTH_GROUP_NAME_CHARS) return CommandError();
409 if (!IsUniqueGroupNameForVehicleType(text, g->vehicle_type)) return CommandError(STR_ERROR_NAME_MUST_BE_UNIQUE);
412 if (flags & DC_EXEC) {
413 /* Delete the old name */
414 free(g->name);
415 /* Assign the new one */
416 g->name = reset ? nullptr : stredup(text);
418 } else {
419 /* Set group parent */
420 const Group *pg = Group::GetIfValid(GB(p2, 0, 16));
422 if (pg != nullptr) {
423 if (pg->owner != _current_company) return CommandError();
424 if (pg->vehicle_type != g->vehicle_type) return CommandError();
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 CommandError();
431 if (flags & DC_EXEC) {
432 g->parent = (pg == nullptr) ? 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);
457 switch (v->type) {
458 default: NOT_REACHED();
459 case VEH_TRAIN:
460 SetTrainGroupID(Train::From(v), new_g);
461 break;
463 case VEH_ROAD:
464 case VEH_SHIP:
465 case VEH_AIRCRAFT:
466 if (v->IsEngineCountable()) UpdateNumEngineGroup(v, v->group_id, new_g);
467 v->group_id = new_g;
468 break;
471 GroupStatistics::CountVehicle(v, 1);
475 * Add a vehicle to a group
476 * @param tile unused
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.
483 * @param text unused
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));
489 GroupID new_g = p1;
491 if (v == nullptr || (!Group::IsValidID(new_g) && !IsDefaultGroupID(new_g) && new_g != NEW_GROUP)) return CommandError();
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 CommandError();
498 if (v->owner != _current_company || !v->IsPrimaryVehicle()) return CommandError();
500 if (new_g == NEW_GROUP) {
501 /* Create new group. */
502 CommandCost ret = CmdCreateGroup(0, flags, v->type, 0, nullptr);
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 != nullptr; 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
533 * @param tile unused
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.
538 * @param p2 unused
539 * @param text unused
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 == nullptr) return CommandError();
548 if (v->owner != _current_company || !v->IsPrimaryVehicle()) return CommandError();
550 /* Get the essential orders */
551 VehicleOrderID start = 0;
553 Order *first = nullptr;
554 Order *last = nullptr;
555 Order *order = v->GetOrder(start);
556 std::vector<Order*> unique_orders;
558 if (order == nullptr) return CommandError(STR_ERROR_GROUP_CAN_T_CREATE_NAME);
560 VehicleOrderID oid = start;
562 do {
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);
569 oid++;
570 order = order->next;
571 if (order == nullptr) {
572 order = v->GetFirstOrder();
573 oid = 0;
575 } while (oid != start);
577 if (unique_orders.empty()) return CommandError(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;
584 auto temp_v = v;
586 do {
587 if (temp_v->cargo_cap == 0) continue;
589 cargo_abbreviation = CargoSpec::Get(temp_v->cargo_type)->abbrev;
590 break;
591 } while ((temp_v = temp_v->Next()) != nullptr);
593 if(cargo_abbreviation == INVALID_STRING_ID) return CommandError(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;
608 // first = last;
609 // last = temp;
610 //}
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 == nullptr || station_first == nullptr) return CommandError(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));
632 else {
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
656 std::string s(str);
657 s = s.erase(1, 3);
658 strecpy(&str[0], s.c_str(), lastof(str));
660 if (!IsUniqueGroupNameForVehicleType(str, v->type)) return CommandError(STR_ERROR_NAME_MUST_BE_UNIQUE);
662 if (Utf8StringLength(str) >= MAX_LENGTH_GROUP_NAME_CHARS) return CommandError();
664 CommandCost ret = CmdCreateGroup(0, flags, v->type, 0, nullptr);
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 != nullptr; 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
695 * @param tile unused
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
700 * @param text unused
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);
706 GroupID id_g = p1;
707 if (!Group::IsValidID(id_g) || !IsCompanyBuildableVehicleType(type)) return CommandError();
709 if (flags & DC_EXEC) {
710 Vehicle *v;
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 != nullptr; 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
737 * @param tile unused
738 * @param flags type of operation
739 * @param p1 index of group array
740 * - p1 bit 0-15 : GroupID
741 * @param p2 unused
742 * @param text unused
743 * @return the cost of this operation or an error
745 CommandCost CmdRemoveAllVehiclesGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
747 GroupID old_g = p1;
748 Group *g = Group::GetIfValid(old_g);
750 if (g == nullptr || g->owner != _current_company) return CommandError();
752 if (flags & DC_EXEC) {
753 Vehicle *v;
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.
774 * @param tile unused
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.
778 * @param text unused
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) {
791 const Vehicle* v;
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;
818 Group *pg;
819 FOR_ALL_GROUPS(pg) {
820 if (pg->parent == g->index) SetGroupReplaceProtection(pg, protect);
826 * (Un)set global replace protection from a group
827 * @param tile unused
828 * @param flags type of operation
829 * @param p1 index of group array
830 * - p1 bit 0-15 : GroupID
831 * @param p2
832 * - p2 bit 0 : 1 to set or 0 to clear protection.
833 * - p2 bit 1 : 1 to apply to sub-groups.
834 * @param text unused
835 * @return the cost of this operation or an error
836 *////
837 CommandCost CmdSetGroupReplaceProtection(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
839 Group *g = Group::GetIfValid(p1);
840 if (g == nullptr || g->owner != _current_company) return CommandError();
842 if (flags & DC_EXEC) {
843 if (HasBit(p2, 1)) {
844 SetGroupReplaceProtection(g, HasBit(p2, 0));
845 } else {
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 != nullptr; u = u->Next()) {
882 if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g);
884 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 != nullptr; u = u->Next()) {
906 if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g);
908 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
923 *////
924 uint GetGroupNumEngines(CompanyID company, GroupID id_g, EngineID id_e)
926 uint count = 0;
927 const Engine *e = Engine::Get(id_e);
928 const Group *g;
929 FOR_ALL_GROUPS(g) {
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)
937 Group *g;
939 FOR_ALL_GROUPS(g) {
940 if (company == g->owner) {
941 DeleteTemplateReplacementsByGroupID(g->index);
942 delete g;
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;
958 do {
959 if (search == group) return true;
960 search = Group::Get(search)->parent;
961 } while (search != INVALID_GROUP);
963 return false;