OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

group_cmd.cpp
Go to the documentation of this file.
1 /* $Id: group_cmd.cpp 27090 2014-12-24 16:49:57Z frosch $ */
2
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 */
9
12 #include "stdafx.h"
13 #include "cmd_helper.h"
14 #include "command_func.h"
15 #include "train.h"
16 #include "vehiclelist.h"
17 #include "vehicle_func.h"
18 #include "autoreplace_base.h"
19 #include "autoreplace_func.h"
20 #include "string_func.h"
21 #include "company_func.h"
22 #include "core/pool_func.hpp"
23 #include "order_backup.h"
24
25 #include "table/strings.h"
26
27 #include "safeguards.h"
28
29 GroupID _new_group_id;
30
31 GroupPool _group_pool("Group");
32 INSTANTIATE_POOL_METHODS(Group)
33
34 GroupStatistics::GroupStatistics()
35 {
36 this->num_engines = CallocT<uint16>(Engine::GetPoolSize());
37 }
38
39 GroupStatistics::~GroupStatistics()
40 {
41 free(this->num_engines);
42 }
43
47 void GroupStatistics::Clear()
48 {
49 this->num_vehicle = 0;
50 this->num_profit_vehicle = 0;
51 this->profit_last_year = 0;
52
53 /* This is also called when NewGRF change. So the number of engines might have changed. Reallocate. */
54 free(this->num_engines);
55 this->num_engines = CallocT<uint16>(Engine::GetPoolSize());
56 }
57
65 /* static */ GroupStatistics &GroupStatistics::Get(CompanyID company, GroupID id_g, VehicleType type)
66 {
67 if (Group::IsValidID(id_g)) {
68 Group *g = Group::Get(id_g);
69 assert(g->owner == company);
70 assert(g->vehicle_type == type);
71 return g->statistics;
72 }
73
74 if (IsDefaultGroupID(id_g)) return Company::Get(company)->group_default[type];
75 if (IsAllGroupID(id_g)) return Company::Get(company)->group_all[type];
76
77 NOT_REACHED();
78 }
79
85 /* static */ GroupStatistics &GroupStatistics::Get(const Vehicle *v)
86 {
87 return GroupStatistics::Get(v->owner, v->group_id, v->type);
88 }
89
95 /* static */ GroupStatistics &GroupStatistics::GetAllGroup(const Vehicle *v)
96 {
97 return GroupStatistics::Get(v->owner, ALL_GROUP, v->type);
98 }
99
103 /* static */ void GroupStatistics::UpdateAfterLoad()
104 {
105 /* Set up the engine count for all companies */
106 Company *c;
107 FOR_ALL_COMPANIES(c) {
108 for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
109 c->group_all[type].Clear();
110 c->group_default[type].Clear();
111 }
112 }
113
114 /* Recalculate */
115 Group *g;
116 FOR_ALL_GROUPS(g) {
117 g->statistics.Clear();
118 }
119
120 const Vehicle *v;
121 FOR_ALL_VEHICLES(v) {
122 if (!v->IsEngineCountable()) continue;
123
124 GroupStatistics::CountEngine(v, 1);
125 if (v->IsPrimaryVehicle()) GroupStatistics::CountVehicle(v, 1);
126 }
127
128 FOR_ALL_COMPANIES(c) {
129 GroupStatistics::UpdateAutoreplace(c->index);
130 }
131 }
132
138 /* static */ void GroupStatistics::CountVehicle(const Vehicle *v, int delta)
139 {
140 assert(delta == 1 || delta == -1);
141
142 GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
143 GroupStatistics &stats = GroupStatistics::Get(v);
144
145 stats_all.num_vehicle += delta;
146 stats.num_vehicle += delta;
147
148 if (v->age > VEHICLE_PROFIT_MIN_AGE) {
149 stats_all.num_profit_vehicle += delta;
150 stats_all.profit_last_year += v->GetDisplayProfitLastYear() * delta;
151 stats.num_profit_vehicle += delta;
152 stats.profit_last_year += v->GetDisplayProfitLastYear() * delta;
153 }
154 }
155
161 /* static */ void GroupStatistics::CountEngine(const Vehicle *v, int delta)
162 {
163 assert(delta == 1 || delta == -1);
164 GroupStatistics::GetAllGroup(v).num_engines[v->engine_type] += delta;
165 GroupStatistics::Get(v).num_engines[v->engine_type] += delta;
166 }
167
171 /* static */ void GroupStatistics::VehicleReachedProfitAge(const Vehicle *v)
172 {
173 GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
174 GroupStatistics &stats = GroupStatistics::Get(v);
175
176 stats_all.num_profit_vehicle++;
177 stats_all.profit_last_year += v->GetDisplayProfitLastYear();
178 stats.num_profit_vehicle++;
179 stats.profit_last_year += v->GetDisplayProfitLastYear();
180 }
181
185 /* static */ void GroupStatistics::UpdateProfits()
186 {
187 /* Set up the engine count for all companies */
188 Company *c;
189 FOR_ALL_COMPANIES(c) {
190 for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
191 c->group_all[type].ClearProfits();
192 c->group_default[type].ClearProfits();
193 }
194 }
195
196 /* Recalculate */
197 Group *g;
198 FOR_ALL_GROUPS(g) {
199 g->statistics.ClearProfits();
200 }
201
202 const Vehicle *v;
203 FOR_ALL_VEHICLES(v) {
204 if (v->IsPrimaryVehicle() && v->age > VEHICLE_PROFIT_MIN_AGE) GroupStatistics::VehicleReachedProfitAge(v);
205 }
206 }
207
212 /* static */ void GroupStatistics::UpdateAutoreplace(CompanyID company)
213 {
214 /* Set up the engine count for all companies */
215 Company *c = Company::Get(company);
216 for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
217 c->group_all[type].ClearAutoreplace();
218 c->group_default[type].ClearAutoreplace();
219 }
220
221 /* Recalculate */
222 Group *g;
223 FOR_ALL_GROUPS(g) {
224 if (g->owner != company) continue;
225 g->statistics.ClearAutoreplace();
226 }
227
228 for (EngineRenewList erl = c->engine_renew_list; erl != NULL; erl = erl->next) {
229 const Engine *e = Engine::Get(erl->from);
230 GroupStatistics &stats = GroupStatistics::Get(company, erl->group_id, e->type);
231 if (!stats.autoreplace_defined) {
232 stats.autoreplace_defined = true;
233 stats.autoreplace_finished = true;
234 }
235 if (stats.num_engines[erl->from] > 0) stats.autoreplace_finished = false;
236 }
237 }
238
246 static inline void UpdateNumEngineGroup(const Vehicle *v, GroupID old_g, GroupID new_g)
247 {
248 if (old_g != new_g) {
249 /* Decrease the num engines in the old group */
250 GroupStatistics::Get(v->owner, old_g, v->type).num_engines[v->engine_type]--;
251
252 /* Increase the num engines in the new group */
253 GroupStatistics::Get(v->owner, new_g, v->type).num_engines[v->engine_type]++;
254 }
255 }
256
257
258
259 Group::Group(Owner owner)
260 {
261 this->owner = owner;
262 }
263
264 Group::~Group()
265 {
266 free(this->name);
267 }
268
269
279 CommandCost CmdCreateGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
280 {
281 VehicleType vt = Extract<VehicleType, 0, 3>(p1);
282 if (!IsCompanyBuildableVehicleType(vt)) return CMD_ERROR;
283
284 if (!Group::CanAllocateItem()) return CMD_ERROR;
285
286 if (flags & DC_EXEC) {
287 Group *g = new Group(_current_company);
288 g->replace_protection = false;
289 g->vehicle_type = vt;
290 g->parent = INVALID_GROUP;
291
292 _new_group_id = g->index;
293
294 InvalidateWindowData(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, _current_company).Pack());
295 }
296
297 return CommandCost();
298 }
299
300
311 CommandCost CmdDeleteGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
312 {
313 Group *g = Group::GetIfValid(p1);
314 if (g == NULL || g->owner != _current_company) return CMD_ERROR;
315
316 /* Remove all vehicles from the group */
317 DoCommand(0, p1, 0, flags, CMD_REMOVE_ALL_VEHICLES_GROUP);
318
319 /* Delete sub-groups */
320 Group *gp;
321 FOR_ALL_GROUPS(gp) {
322 if (gp->parent == g->index) {
323 DoCommand(0, gp->index, 0, flags, CMD_DELETE_GROUP);
324 }
325 }
326
327 if (flags & DC_EXEC) {
328 /* Update backupped orders if needed */
329 OrderBackup::ClearGroup(g->index);
330
331 /* If we set an autoreplace for the group we delete, remove it. */
332 if (_current_company < MAX_COMPANIES) {
333 Company *c;
334 EngineRenew *er;
335
336 c = Company::Get(_current_company);
337 FOR_ALL_ENGINE_RENEWS(er) {
338 if (er->group_id == g->index) RemoveEngineReplacementForCompany(c, er->from, g->index, flags);
339 }
340 }
341
342 VehicleType vt = g->vehicle_type;
343
344 /* Delete the Replace Vehicle Windows */
345 DeleteWindowById(WC_REPLACE_VEHICLE, g->vehicle_type);
346 delete g;
347
348 InvalidateWindowData(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_GROUP_LIST, vt, _current_company).Pack());
349 }
350
351 return CommandCost();
352 }
353
354 static bool IsUniqueGroupNameForVehicleType(const char *name, VehicleType type)
355 {
356 const Group *g;
357
358 FOR_ALL_GROUPS(g) {
359 if (g->name != NULL && g->vehicle_type == type && strcmp(g->name, name) == 0) return false;
360 }
361
362 return true;
363 }
364
377 CommandCost CmdAlterGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
378 {
379 Group *g = Group::GetIfValid(GB(p1, 0, 16));
380 if (g == NULL || g->owner != _current_company) return CMD_ERROR;
381
382 if (!HasBit(p1, 16)) {
383 /* Rename group */
384 bool reset = StrEmpty(text);
385
386 if (!reset) {
387 if (Utf8StringLength(text) >= MAX_LENGTH_GROUP_NAME_CHARS) return CMD_ERROR;
388 if (!IsUniqueGroupNameForVehicleType(text, g->vehicle_type)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE);
389 }
390
391 if (flags & DC_EXEC) {
392 /* Delete the old name */
393 free(g->name);
394 /* Assign the new one */
395 g->name = reset ? NULL : stredup(text);
396 }
397 } else {
398 /* Set group parent */
399 const Group *pg = Group::GetIfValid(GB(p2, 0, 16));
400
401 if (pg != NULL) {
402 if (pg->owner != _current_company) return CMD_ERROR;
403 if (pg->vehicle_type != g->vehicle_type) return CMD_ERROR;
404
405 /* Ensure request parent isn't child of group.
406 * This is the only place that infinite loops are prevented. */
407 if (GroupIsInGroup(pg->index, g->index)) return CMD_ERROR;
408 }
409
410 if (flags & DC_EXEC) {
411 g->parent = (pg == NULL) ? INVALID_GROUP : pg->index;
412 }
413 }
414
415 if (flags & DC_EXEC) {
416 SetWindowDirty(WC_REPLACE_VEHICLE, g->vehicle_type);
417 InvalidateWindowData(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack());
418 }
419
420 return CommandCost();
421 }
422
423
429 static void AddVehicleToGroup(Vehicle *v, GroupID new_g)
430 {
431 GroupStatistics::CountVehicle(v, -1);
432
433 switch (v->type) {
434 default: NOT_REACHED();
435 case VEH_TRAIN:
436 SetTrainGroupID(Train::From(v), new_g);
437 break;
438
439 case VEH_ROAD:
440 case VEH_SHIP:
441 case VEH_AIRCRAFT:
442 if (v->IsEngineCountable()) UpdateNumEngineGroup(v, v->group_id, new_g);
443 v->group_id = new_g;
444 break;
445 }
446
447 GroupStatistics::CountVehicle(v, 1);
448 }
449
462 CommandCost CmdAddVehicleGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
463 {
464 Vehicle *v = Vehicle::GetIfValid(GB(p2, 0, 20));
465 GroupID new_g = p1;
466
467 if (v == NULL || (!Group::IsValidID(new_g) && !IsDefaultGroupID(new_g) && new_g != NEW_GROUP)) return CMD_ERROR;
468
469 if (Group::IsValidID(new_g)) {
470 Group *g = Group::Get(new_g);
471 if (g->owner != _current_company || g->vehicle_type != v->type) return CMD_ERROR;
472 }
473
474 if (v->owner != _current_company || !v->IsPrimaryVehicle()) return CMD_ERROR;
475
476 if (new_g == NEW_GROUP) {
477 /* Create new group. */
478 CommandCost ret = CmdCreateGroup(0, flags, v->type, 0, NULL);
479 if (ret.Failed()) return ret;
480
481 new_g = _new_group_id;
482 }
483
484 if (flags & DC_EXEC) {
485 AddVehicleToGroup(v, new_g);
486
487 if (HasBit(p2, 31)) {
488 /* Add vehicles in the shared order list as well. */
489 for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
490 if (v2->group_id != new_g) AddVehicleToGroup(v2, new_g);
491 }
492 }
493
494 GroupStatistics::UpdateAutoreplace(v->owner);
495
496 /* Update the Replace Vehicle Windows */
497 SetWindowDirty(WC_REPLACE_VEHICLE, v->type);
498 InvalidateWindowData(GetWindowClassForVehicleType(v->type), VehicleListIdentifier(VL_GROUP_LIST, v->type, _current_company).Pack());
499 }
500
501 return CommandCost();
502 }
503
514 CommandCost CmdAddSharedVehicleGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
515 {
516 VehicleType type = Extract<VehicleType, 0, 3>(p2);
517 GroupID id_g = p1;
518 if (!Group::IsValidID(id_g) || !IsCompanyBuildableVehicleType(type)) return CMD_ERROR;
519
520 if (flags & DC_EXEC) {
521 Vehicle *v;
522
523 /* Find the first front engine which belong to the group id_g
524 * then add all shared vehicles of this front engine to the group id_g */
525 FOR_ALL_VEHICLES(v) {
526 if (v->type == type && v->IsPrimaryVehicle()) {
527 if (v->group_id != id_g) continue;
528
529 /* For each shared vehicles add it to the group */
530 for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
531 if (v2->group_id != id_g) DoCommand(tile, id_g, v2->index, flags, CMD_ADD_VEHICLE_GROUP, text);
532 }
533 }
534 }
535
536 InvalidateWindowData(GetWindowClassForVehicleType(type), VehicleListIdentifier(VL_GROUP_LIST, type, _current_company).Pack());
537 }
538
539 return CommandCost();
540 }
541
542
553 CommandCost CmdRemoveAllVehiclesGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
554 {
555 GroupID old_g = p1;
556 Group *g = Group::GetIfValid(old_g);
557
558 if (g == NULL || g->owner != _current_company) return CMD_ERROR;
559
560 if (flags & DC_EXEC) {
561 Vehicle *v;
562
563 /* Find each Vehicle that belongs to the group old_g and add it to the default group */
564 FOR_ALL_VEHICLES(v) {
565 if (v->IsPrimaryVehicle()) {
566 if (v->group_id != old_g) continue;
567
568 /* Add The Vehicle to the default group */
569 DoCommand(tile, DEFAULT_GROUP, v->index, flags, CMD_ADD_VEHICLE_GROUP, text);
570 }
571 }
572
573 InvalidateWindowData(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack());
574 }
575
576 return CommandCost();
577 }
578
584 static void SetGroupReplaceProtection(Group *g, bool protect)
585 {
586 g->replace_protection = protect;
587
588 Group *pg;
589 FOR_ALL_GROUPS(pg) {
590 if (pg->parent == g->index) SetGroupReplaceProtection(pg, protect);
591 }
592 }
593
606 CommandCost CmdSetGroupReplaceProtection(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
607 {
608 Group *g = Group::GetIfValid(p1);
609 if (g == NULL || g->owner != _current_company) return CMD_ERROR;
610
611 if (flags & DC_EXEC) {
612 if (HasBit(p2, 1)) {
613 SetGroupReplaceProtection(g, HasBit(p2, 0));
614 } else {
615 g->replace_protection = HasBit(p2, 0);
616 }
617
618 SetWindowDirty(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack());
619 InvalidateWindowData(WC_REPLACE_VEHICLE, g->vehicle_type);
620 }
621
622 return CommandCost();
623 }
624
630 void RemoveVehicleFromGroup(const Vehicle *v)
631 {
632 if (!v->IsPrimaryVehicle()) return;
633
634 if (!IsDefaultGroupID(v->group_id)) GroupStatistics::CountVehicle(v, -1);
635 }
636
637
644 void SetTrainGroupID(Train *v, GroupID new_g)
645 {
646 if (!Group::IsValidID(new_g) && !IsDefaultGroupID(new_g)) return;
647
648 assert(v->IsFrontEngine() || IsDefaultGroupID(new_g));
649
650 for (Vehicle *u = v; u != NULL; u = u->Next()) {
651 if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g);
652
653 u->group_id = new_g;
654 }
655
656 /* Update the Replace Vehicle Windows */
657 GroupStatistics::UpdateAutoreplace(v->owner);
658 SetWindowDirty(WC_REPLACE_VEHICLE, VEH_TRAIN);
659 }
660
661
669 void UpdateTrainGroupID(Train *v)
670 {
671 assert(v->IsFrontEngine() || v->IsFreeWagon());
672
673 GroupID new_g = v->IsFrontEngine() ? v->group_id : (GroupID)DEFAULT_GROUP;
674 for (Vehicle *u = v; u != NULL; u = u->Next()) {
675 if (u->IsEngineCountable()) UpdateNumEngineGroup(u, u->group_id, new_g);
676
677 u->group_id = new_g;
678 }
679
680 /* Update the Replace Vehicle Windows */
681 GroupStatistics::UpdateAutoreplace(v->owner);
682 SetWindowDirty(WC_REPLACE_VEHICLE, VEH_TRAIN);
683 }
684
693 uint GetGroupNumEngines(CompanyID company, GroupID id_g, EngineID id_e)
694 {
695 uint count = 0;
696 const Engine *e = Engine::Get(id_e);
697 const Group *g;
698 FOR_ALL_GROUPS(g) {
699 if (g->parent == id_g) count += GetGroupNumEngines(company, g->index, id_e);
700 }
701 return count + GroupStatistics::Get(company, id_g, e->type).num_engines[id_e];
702 }
703
704 void RemoveAllGroupsForCompany(const CompanyID company)
705 {
706 Group *g;
707
708 FOR_ALL_GROUPS(g) {
709 if (company == g->owner) delete g;
710 }
711 }
712
713
720 bool GroupIsInGroup(GroupID search, GroupID group)
721 {
722 if (!Group::IsValidID(search)) return search == group;
723
724 do {
725 if (search == group) return true;
726 search = Group::Get(search)->parent;
727 } while (search != INVALID_GROUP);
728
729 return false;
730 }