1 /*
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/>.
6  */
7 
8 /** @file group_cmd.cpp Handling of the engine groups */
9 
10 #include "stdafx.h"
11 #include "cmd_helper.h"
12 #include "command_func.h"
13 #include "train.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"
22 
23 #include "table/strings.h"
24 
25 #include "safeguards.h"
26 
27 GroupID _new_group_id;
28 
29 GroupPool _group_pool("Group");
INSTANTIATE_POOL_METHODS(Group)30 INSTANTIATE_POOL_METHODS(Group)
31 
32 GroupStatistics::GroupStatistics()
33 {
34 	this->num_engines = CallocT<uint16>(Engine::GetPoolSize());
35 }
36 
~GroupStatistics()37 GroupStatistics::~GroupStatistics()
38 {
39 	free(this->num_engines);
40 }
41 
42 /**
43  * Clear all caches.
44  */
Clear()45 void GroupStatistics::Clear()
46 {
47 	this->num_vehicle = 0;
48 	this->num_profit_vehicle = 0;
49 	this->profit_last_year = 0;
50 
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());
54 }
55 
56 /**
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.
62  */
Get(CompanyID company,GroupID id_g,VehicleType type)63 /* static */ GroupStatistics &GroupStatistics::Get(CompanyID company, GroupID id_g, VehicleType type)
64 {
65 	if (Group::IsValidID(id_g)) {
66 		Group *g = Group::Get(id_g);
67 		assert(g->owner == company);
68 		assert(g->vehicle_type == type);
69 		return g->statistics;
70 	}
71 
72 	if (IsDefaultGroupID(id_g)) return Company::Get(company)->group_default[type];
73 	if (IsAllGroupID(id_g)) return Company::Get(company)->group_all[type];
74 
75 	NOT_REACHED();
76 }
77 
78 /**
79  * Returns the GroupStatistic for the group of a vehicle.
80  * @param v Vehicle.
81  * @return GroupStatistics for the group of the vehicle.
82  */
Get(const Vehicle * v)83 /* static */ GroupStatistics &GroupStatistics::Get(const Vehicle *v)
84 {
85 	return GroupStatistics::Get(v->owner, v->group_id, v->type);
86 }
87 
88 /**
89  * Returns the GroupStatistic for the ALL_GROUPO of a vehicle type.
90  * @param v Vehicle.
91  * @return GroupStatistics for the ALL_GROUP of the vehicle type.
92  */
GetAllGroup(const Vehicle * v)93 /* static */ GroupStatistics &GroupStatistics::GetAllGroup(const Vehicle *v)
94 {
95 	return GroupStatistics::Get(v->owner, ALL_GROUP, v->type);
96 }
97 
98 /**
99  * Update all caches after loading a game, changing NewGRF, etc.
100  */
UpdateAfterLoad()101 /* static */ void GroupStatistics::UpdateAfterLoad()
102 {
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();
108 		}
109 	}
110 
111 	/* Recalculate */
112 	for (Group *g : Group::Iterate()) {
113 		g->statistics.Clear();
114 	}
115 
116 	for (const Vehicle *v : Vehicle::Iterate()) {
117 		if (!v->IsEngineCountable()) continue;
118 
119 		GroupStatistics::CountEngine(v, 1);
120 		if (v->IsPrimaryVehicle()) GroupStatistics::CountVehicle(v, 1);
121 	}
122 
123 	for (const Company *c : Company::Iterate()) {
124 		GroupStatistics::UpdateAutoreplace(c->index);
125 	}
126 }
127 
128 /**
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.
132  */
CountVehicle(const Vehicle * v,int delta)133 /* static */ void GroupStatistics::CountVehicle(const Vehicle *v, int delta)
134 {
135 	assert(delta == 1 || delta == -1);
136 
137 	GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
138 	GroupStatistics &stats = GroupStatistics::Get(v);
139 
140 	stats_all.num_vehicle += delta;
141 	stats.num_vehicle += delta;
142 
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;
148 	}
149 }
150 
151 /**
152  * Update num_engines when adding/removing an engine.
153  * @param v Engine to count.
154  * @param delta +1 to add, -1 to remove.
155  */
CountEngine(const Vehicle * v,int delta)156 /* static */ void GroupStatistics::CountEngine(const Vehicle *v, int delta)
157 {
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;
161 }
162 
163 /**
164  * Add a vehicle to the profit sum of its group.
165  */
VehicleReachedProfitAge(const Vehicle * v)166 /* static */ void GroupStatistics::VehicleReachedProfitAge(const Vehicle *v)
167 {
168 	GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
169 	GroupStatistics &stats = GroupStatistics::Get(v);
170 
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();
175 }
176 
177 /**
178  * Recompute the profits for all groups.
179  */
UpdateProfits()180 /* static */ void GroupStatistics::UpdateProfits()
181 {
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();
187 		}
188 	}
189 
190 	/* Recalculate */
191 	for (Group *g : Group::Iterate()) {
192 		g->statistics.ClearProfits();
193 	}
194 
195 	for (const Vehicle *v : Vehicle::Iterate()) {
196 		if (v->IsPrimaryVehicle() && v->age > VEHICLE_PROFIT_MIN_AGE) GroupStatistics::VehicleReachedProfitAge(v);
197 	}
198 }
199 
200 /**
201  * Update autoreplace_defined and autoreplace_finished of all statistics of a company.
202  * @param company Company to update statistics for.
203  */
UpdateAutoreplace(CompanyID company)204 /* static */ void GroupStatistics::UpdateAutoreplace(CompanyID company)
205 {
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();
211 	}
212 
213 	/* Recalculate */
214 	for (Group *g : Group::Iterate()) {
215 		if (g->owner != company) continue;
216 		g->statistics.ClearAutoreplace();
217 	}
218 
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;
225 		}
226 		if (GetGroupNumEngines(company, erl->group_id, erl->from) > 0) stats.autoreplace_finished = false;
227 	}
228 }
229 
230 /**
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
236  */
UpdateNumEngineGroup(const Vehicle * v,GroupID old_g,GroupID new_g)237 static inline void UpdateNumEngineGroup(const Vehicle *v, GroupID old_g, GroupID new_g)
238 {
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]--;
242 
243 		/* Increase the num engines in the new group */
244 		GroupStatistics::Get(v->owner, new_g, v->type).num_engines[v->engine_type]++;
245 	}
246 }
247 
248 
GetParentLivery(const Group * g)249 const Livery *GetParentLivery(const Group *g)
250 {
251 	if (g->parent == INVALID_GROUP) {
252 		const Company *c = Company::Get(g->owner);
253 		return &c->livery[LS_DEFAULT];
254 	}
255 
256 	const Group *pg = Group::Get(g->parent);
257 	return &pg->livery;
258 }
259 
260 
261 /**
262  * Propagate a livery change to a group's children.
263  * @param g Group.
264  */
PropagateChildLivery(const Group * g)265 void PropagateChildLivery(const Group *g)
266 {
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();
273 			}
274 		}
275 	}
276 
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);
282 		}
283 	}
284 }
285 
286 
Group(Owner owner)287 Group::Group(Owner owner)
288 {
289 	this->owner = owner;
290 	this->folded = false;
291 }
292 
293 
294 /**
295  * Create a new vehicle group.
296  * @param tile unused
297  * @param flags type of operation
298  * @param p1   vehicle type
299  * @param p2   parent groupid
300  * @param text unused
301  * @return the cost of this operation or an error
302  */
CmdCreateGroup(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)303 CommandCost CmdCreateGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
304 {
305 	VehicleType vt = Extract<VehicleType, 0, 3>(p1);
306 	if (!IsCompanyBuildableVehicleType(vt)) return CMD_ERROR;
307 
308 	if (!Group::CanAllocateItem()) return CMD_ERROR;
309 
310 	const Group *pg = Group::GetIfValid(GB(p2, 0, 16));
311 	if (pg != nullptr) {
312 		if (pg->owner != _current_company) return CMD_ERROR;
313 		if (pg->vehicle_type != vt) return CMD_ERROR;
314 	}
315 
316 	if (flags & DC_EXEC) {
317 		Group *g = new Group(_current_company);
318 		g->vehicle_type = vt;
319 		g->parent = INVALID_GROUP;
320 
321 		if (pg == nullptr) {
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);
326 		} else {
327 			g->parent = pg->index;
328 			g->livery.colour1 = pg->livery.colour1;
329 			g->livery.colour2 = pg->livery.colour2;
330 			g->flags = pg->flags;
331 		}
332 
333 		_new_group_id = g->index;
334 
335 		InvalidateWindowData(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, _current_company).Pack());
336 		InvalidateWindowData(WC_COMPANY_COLOUR, g->owner, g->vehicle_type);
337 	}
338 
339 	return CommandCost();
340 }
341 
342 
343 /**
344  * Add all vehicles in the given group to the default group and then deletes the group.
345  * @param tile unused
346  * @param flags type of operation
347  * @param p1   index of array group
348  *      - p1 bit 0-15 : GroupID
349  * @param p2   unused
350  * @param text unused
351  * @return the cost of this operation or an error
352  */
CmdDeleteGroup(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)353 CommandCost CmdDeleteGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
354 {
355 	Group *g = Group::GetIfValid(p1);
356 	if (g == nullptr || g->owner != _current_company) return CMD_ERROR;
357 
358 	/* Remove all vehicles from the group */
359 	DoCommand(0, p1, 0, flags, CMD_REMOVE_ALL_VEHICLES_GROUP);
360 
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);
365 		}
366 	}
367 
368 	if (flags & DC_EXEC) {
369 		/* Update backupped orders if needed */
370 		OrderBackup::ClearGroup(g->index);
371 
372 		/* If we set an autoreplace for the group we delete, remove it. */
373 		if (_current_company < MAX_COMPANIES) {
374 			Company *c;
375 
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);
379 			}
380 		}
381 
382 		VehicleType vt = g->vehicle_type;
383 
384 		/* Delete the Replace Vehicle Windows */
385 		CloseWindowById(WC_REPLACE_VEHICLE, g->vehicle_type);
386 		delete g;
387 
388 		InvalidateWindowData(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, _current_company).Pack());
389 		InvalidateWindowData(WC_COMPANY_COLOUR, _current_company, vt);
390 	}
391 
392 	return CommandCost();
393 }
394 
395 /**
396  * Alter a group
397  * @param tile unused
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
406  */
CmdAlterGroup(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)407 CommandCost CmdAlterGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
408 {
409 	Group *g = Group::GetIfValid(GB(p1, 0, 16));
410 	if (g == nullptr || g->owner != _current_company) return CMD_ERROR;
411 
412 	if (!HasBit(p1, 16)) {
413 		/* Rename group */
414 		bool reset = text.empty();
415 
416 		if (!reset) {
417 			if (Utf8StringLength(text) >= MAX_LENGTH_GROUP_NAME_CHARS) return CMD_ERROR;
418 		}
419 
420 		if (flags & DC_EXEC) {
421 			/* Assign the new one */
422 			if (reset) {
423 				g->name.clear();
424 			} else {
425 				g->name = text;
426 			}
427 		}
428 	} else {
429 		/* Set group parent */
430 		const Group *pg = Group::GetIfValid(GB(p2, 0, 16));
431 
432 		if (pg != nullptr) {
433 			if (pg->owner != _current_company) return CMD_ERROR;
434 			if (pg->vehicle_type != g->vehicle_type) return CMD_ERROR;
435 
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);
439 		}
440 
441 		if (flags & DC_EXEC) {
442 			g->parent = (pg == nullptr) ? INVALID_GROUP : pg->index;
443 			GroupStatistics::UpdateAutoreplace(g->owner);
444 
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;
449 
450 				PropagateChildLivery(g);
451 				MarkWholeScreenDirty();
452 			}
453 		}
454 	}
455 
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);
462 	}
463 
464 	return CommandCost();
465 }
466 
467 
468 /**
469  * Do add a vehicle to a group.
470  * @param v Vehicle to add.
471  * @param new_g Group to add to.
472  */
AddVehicleToGroup(Vehicle * v,GroupID new_g)473 static void AddVehicleToGroup(Vehicle *v, GroupID new_g)
474 {
475 	GroupStatistics::CountVehicle(v, -1);
476 
477 	switch (v->type) {
478 		default: NOT_REACHED();
479 		case VEH_TRAIN:
480 			SetTrainGroupID(Train::From(v), new_g);
481 			break;
482 
483 		case VEH_ROAD:
484 		case VEH_SHIP:
485 		case VEH_AIRCRAFT:
486 			if (v->IsEngineCountable()) UpdateNumEngineGroup(v, v->group_id, new_g);
487 			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);
492 			}
493 			break;
494 	}
495 
496 	GroupStatistics::CountVehicle(v, 1);
497 }
498 
499 /**
500  * Add a vehicle to a group
501  * @param tile unused
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.
508  * @param text unused
509  * @return the cost of this operation or an error
510  */
CmdAddVehicleGroup(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)511 CommandCost CmdAddVehicleGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
512 {
513 	Vehicle *v = Vehicle::GetIfValid(GB(p2, 0, 20));
514 	GroupID new_g = p1;
515 
516 	if (v == nullptr || (!Group::IsValidID(new_g) && !IsDefaultGroupID(new_g) && new_g != NEW_GROUP)) return CMD_ERROR;
517 
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;
521 	}
522 
523 	if (v->owner != _current_company || !v->IsPrimaryVehicle()) return CMD_ERROR;
524 
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;
529 
530 		new_g = _new_group_id;
531 	}
532 
533 	if (flags & DC_EXEC) {
534 		AddVehicleToGroup(v, new_g);
535 
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);
540 			}
541 		}
542 
543 		GroupStatistics::UpdateAutoreplace(v->owner);
544 
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);
553 	}
554 
555 	return CommandCost();
556 }
557 
558 /**
559  * Add all shared vehicles of all vehicles from a group
560  * @param tile unused
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
565  * @param text unused
566  * @return the cost of this operation or an error
567  */
CmdAddSharedVehicleGroup(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)568 CommandCost CmdAddSharedVehicleGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
569 {
570 	VehicleType type = Extract<VehicleType, 0, 3>(p2);
571 	GroupID id_g = p1;
572 	if (!Group::IsValidID(id_g) || !IsCompanyBuildableVehicleType(type)) return CMD_ERROR;
573 
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;
580 
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);
584 				}
585 			}
586 		}
587 
588 		InvalidateWindowData(GetWindowClassForVehicleType(type), VehicleListIdentifier(VL_GROUP_LIST, type, _current_company).Pack());
589 	}
590 
591 	return CommandCost();
592 }
593 
594 
595 /**
596  * Remove all vehicles from a group
597  * @param tile unused
598  * @param flags type of operation
599  * @param p1   index of group array
600  * - p1 bit 0-15 : GroupID
601  * @param p2   unused
602  * @param text unused
603  * @return the cost of this operation or an error
604  */
CmdRemoveAllVehiclesGroup(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)605 CommandCost CmdRemoveAllVehiclesGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
606 {
607 	GroupID old_g = p1;
608 	Group *g = Group::GetIfValid(old_g);
609 
610 	if (g == nullptr || g->owner != _current_company) return CMD_ERROR;
611 
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;
617 
618 				/* Add The Vehicle to the default group */
619 				DoCommand(tile, DEFAULT_GROUP, v->index, flags, CMD_ADD_VEHICLE_GROUP, text);
620 			}
621 		}
622 
623 		InvalidateWindowData(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack());
624 	}
625 
626 	return CommandCost();
627 }
628 
629 /**
630  * Set the livery for a vehicle group.
631  * @param tile      Unused.
632  * @param flags     Command flags.
633  * @param p1
634  * - p1 bit  0-15   Group ID.
635  * @param p2
636  * - p2 bit  8      Set secondary instead of primary colour
637  * - p2 bit 16-23   Colour.
638  */
CmdSetGroupLivery(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)639 CommandCost CmdSetGroupLivery(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
640 {
641 	Group *g = Group::GetIfValid(p1);
642 	bool primary = !HasBit(p2, 8);
643 	Colours colour = Extract<Colours, 16, 8>(p2);
644 
645 	if (g == nullptr || g->owner != _current_company) return CMD_ERROR;
646 
647 	if (colour >= COLOUR_END && colour != INVALID_COLOUR) return CMD_ERROR;
648 
649 	if (flags & DC_EXEC) {
650 		if (primary) {
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;
654 		} else {
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;
658 		}
659 
660 		PropagateChildLivery(g);
661 		MarkWholeScreenDirty();
662 	}
663 
664 	return CommandCost();
665 }
666 
667 /**
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.
671  */
SetGroupFlag(Group * g,GroupFlags flag,bool set,bool children)672 static void SetGroupFlag(Group *g, GroupFlags flag, bool set, bool children)
673 {
674 	if (set) {
675 		SetBit(g->flags, flag);
676 	} else {
677 		ClrBit(g->flags, flag);
678 	}
679 
680 	if (!children) return;
681 
682 	for (Group *pg : Group::Iterate()) {
683 		if (pg->parent == g->index) SetGroupFlag(pg, flag, set, true);
684 	}
685 }
686 
687 /**
688  * (Un)set group flag from a group
689  * @param tile unused
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.
694  * @param p2
695  * - p2 bit 0    : 1 to set or 0 to clear protection.
696  * - p2 bit 1    : 1 to apply to sub-groups.
697  * @param text unused
698  * @return the cost of this operation or an error
699  */
CmdSetGroupFlag(TileIndex tile,DoCommandFlag flags,uint32 p1,uint32 p2,const std::string & text)700 CommandCost CmdSetGroupFlag(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text)
701 {
702 	Group *g = Group::GetIfValid(GB(p1, 0, 16));
703 	if (g == nullptr || g->owner != _current_company) return CMD_ERROR;
704 
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;
709 
710 	if (flags & DC_EXEC) {
711 		SetGroupFlag(g, flag, HasBit(p2, 0), HasBit(p2, 1));
712 
713 		SetWindowDirty(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack());
714 		InvalidateWindowData(WC_REPLACE_VEHICLE, g->vehicle_type);
715 	}
716 
717 	return CommandCost();
718 }
719 
720 /**
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.
724  */
RemoveVehicleFromGroup(const Vehicle * v)725 void RemoveVehicleFromGroup(const Vehicle *v)
726 {
727 	if (!v->IsPrimaryVehicle()) return;
728 
729 	if (!IsDefaultGroupID(v->group_id)) GroupStatistics::CountVehicle(v, -1);
730 }
731 
732 
733 /**
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
738  */
SetTrainGroupID(Train * v,GroupID new_g)739 void SetTrainGroupID(Train *v, GroupID new_g)
740 {
741 	if (!Group::IsValidID(new_g) && !IsDefaultGroupID(new_g)) return;
742 
743 	assert(v->IsFrontEngine() || IsDefaultGroupID(new_g));
744 
745 	for (Vehicle *u = v; u != nullptr; u = u->Next()) {
746 		if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g);
747 
748 		u->group_id = new_g;
749 		u->colourmap = PAL_NONE;
750 		u->InvalidateNewGRFCache();
751 		u->UpdateViewport(true);
752 	}
753 
754 	/* Update the Replace Vehicle Windows */
755 	GroupStatistics::UpdateAutoreplace(v->owner);
756 	SetWindowDirty(WC_REPLACE_VEHICLE, VEH_TRAIN);
757 }
758 
759 
760 /**
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.
766  */
UpdateTrainGroupID(Train * v)767 void UpdateTrainGroupID(Train *v)
768 {
769 	assert(v->IsFrontEngine() || v->IsFreeWagon());
770 
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);
774 
775 		u->group_id = new_g;
776 		u->colourmap = PAL_NONE;
777 		u->InvalidateNewGRFCache();
778 	}
779 
780 	/* Update the Replace Vehicle Windows */
781 	GroupStatistics::UpdateAutoreplace(v->owner);
782 	SetWindowDirty(WC_REPLACE_VEHICLE, VEH_TRAIN);
783 }
784 
785 /**
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
792  */
GetGroupNumEngines(CompanyID company,GroupID id_g,EngineID id_e)793 uint GetGroupNumEngines(CompanyID company, GroupID id_g, EngineID id_e)
794 {
795 	uint count = 0;
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);
799 	}
800 	return count + GroupStatistics::Get(company, id_g, e->type).num_engines[id_e];
801 }
802 
803 /**
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
810  */
GetGroupNumVehicle(CompanyID company,GroupID id_g,VehicleType type)811 uint GetGroupNumVehicle(CompanyID company, GroupID id_g, VehicleType type)
812 {
813 	uint count = 0;
814 	for (const Group *g : Group::Iterate()) {
815 		if (g->parent == id_g) count += GetGroupNumVehicle(company, g->index, type);
816 	}
817 	return count + GroupStatistics::Get(company, id_g, type).num_vehicle;
818 }
819 
820 /**
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
827  */
GetGroupNumProfitVehicle(CompanyID company,GroupID id_g,VehicleType type)828 uint GetGroupNumProfitVehicle(CompanyID company, GroupID id_g, VehicleType type)
829 {
830 	uint count = 0;
831 	for (const Group *g : Group::Iterate()) {
832 		if (g->parent == id_g) count += GetGroupNumProfitVehicle(company, g->index, type);
833 	}
834 	return count + GroupStatistics::Get(company, id_g, type).num_profit_vehicle;
835 }
836 
837 /**
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
844  */
GetGroupProfitLastYear(CompanyID company,GroupID id_g,VehicleType type)845 Money GetGroupProfitLastYear(CompanyID company, GroupID id_g, VehicleType type)
846 {
847 	Money sum = 0;
848 	for (const Group *g : Group::Iterate()) {
849 		if (g->parent == id_g) sum += GetGroupProfitLastYear(company, g->index, type);
850 	}
851 	return sum + GroupStatistics::Get(company, id_g, type).profit_last_year;
852 }
853 
RemoveAllGroupsForCompany(const CompanyID company)854 void RemoveAllGroupsForCompany(const CompanyID company)
855 {
856 	for (Group *g : Group::Iterate()) {
857 		if (company == g->owner) delete g;
858 	}
859 }
860 
861 
862 /**
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
867  */
GroupIsInGroup(GroupID search,GroupID group)868 bool GroupIsInGroup(GroupID search, GroupID group)
869 {
870 	if (!Group::IsValidID(search)) return search == group;
871 
872 	do {
873 		if (search == group) return true;
874 		search = Group::Get(search)->parent;
875 	} while (search != INVALID_GROUP);
876 
877 	return false;
878 }
879