Fix: Violation of strict weak ordering in engine name sorter
[openttd-github.git] / src / build_vehicle_gui.cpp
bloba761e9b1368a744b02ac5a5ae5ee701edd047518
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 */
8 /** @file build_vehicle_gui.cpp GUI for building vehicles. */
10 #include "stdafx.h"
11 #include "engine_base.h"
12 #include "engine_func.h"
13 #include "station_base.h"
14 #include "network/network.h"
15 #include "articulated_vehicles.h"
16 #include "textbuf_gui.h"
17 #include "command_func.h"
18 #include "company_func.h"
19 #include "vehicle_gui.h"
20 #include "newgrf_engine.h"
21 #include "newgrf_text.h"
22 #include "group.h"
23 #include "string_func.h"
24 #include "strings_func.h"
25 #include "window_func.h"
26 #include "date_func.h"
27 #include "vehicle_func.h"
28 #include "widgets/dropdown_func.h"
29 #include "engine_gui.h"
30 #include "cargotype.h"
31 #include "core/geometry_func.hpp"
32 #include "autoreplace_func.h"
34 #include "widgets/build_vehicle_widget.h"
36 #include "table/strings.h"
38 #include "safeguards.h"
40 /**
41 * Get the height of a single 'entry' in the engine lists.
42 * @param type the vehicle type to get the height of
43 * @return the height for the entry
45 uint GetEngineListHeight(VehicleType type)
47 return max<uint>(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM, GetVehicleImageCellSize(type, EIT_PURCHASE).height);
50 static const NWidgetPart _nested_build_vehicle_widgets[] = {
51 NWidget(NWID_HORIZONTAL),
52 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
53 NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
54 NWidget(WWT_SHADEBOX, COLOUR_GREY),
55 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
56 NWidget(WWT_STICKYBOX, COLOUR_GREY),
57 EndContainer(),
58 NWidget(WWT_PANEL, COLOUR_GREY),
59 NWidget(NWID_VERTICAL),
60 NWidget(NWID_HORIZONTAL),
61 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
62 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
63 EndContainer(),
64 NWidget(NWID_HORIZONTAL),
65 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_ENGINES),
66 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
67 EndContainer(),
68 EndContainer(),
69 EndContainer(),
70 /* Vehicle list. */
71 NWidget(NWID_HORIZONTAL),
72 NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR),
73 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR),
74 EndContainer(),
75 /* Panel with details. */
76 NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
77 /* Build/rename buttons, resize button. */
78 NWidget(NWID_HORIZONTAL),
79 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL),
80 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0),
81 EndContainer(),
82 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
83 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0),
84 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
85 EndContainer(),
88 /** Special cargo filter criteria */
89 static const CargoID CF_ANY = CT_NO_REFIT; ///< Show all vehicles independent of carried cargo (i.e. no filtering)
90 static const CargoID CF_NONE = CT_INVALID; ///< Show only vehicles which do not carry cargo (e.g. train engines)
92 bool _engine_sort_direction; ///< \c false = descending, \c true = ascending.
93 byte _engine_sort_last_criteria[] = {0, 0, 0, 0}; ///< Last set sort criteria, for each vehicle type.
94 bool _engine_sort_last_order[] = {false, false, false, false}; ///< Last set direction of the sort order, for each vehicle type.
95 bool _engine_sort_show_hidden_engines[] = {false, false, false, false}; ///< Last set 'show hidden engines' setting for each vehicle type.
96 static CargoID _engine_sort_last_cargo_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_ANY}; ///< Last set filter criteria, for each vehicle type.
98 /**
99 * Determines order of engines by engineID
100 * @param a first engine to compare
101 * @param b second engine to compare
102 * @return for descending order: returns true if a < b. Vice versa for ascending order
104 static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
106 int r = Engine::Get(a)->list_position - Engine::Get(b)->list_position;
108 return _engine_sort_direction ? r > 0 : r < 0;
112 * Determines order of engines by introduction date
113 * @param a first engine to compare
114 * @param b second engine to compare
115 * @return for descending order: returns true if a < b. Vice versa for ascending order
117 static bool EngineIntroDateSorter(const EngineID &a, const EngineID &b)
119 const int va = Engine::Get(a)->intro_date;
120 const int vb = Engine::Get(b)->intro_date;
121 const int r = va - vb;
123 /* Use EngineID to sort instead since we want consistent sorting */
124 if (r == 0) return EngineNumberSorter(a, b);
125 return _engine_sort_direction ? r > 0 : r < 0;
128 /* cached values for EngineNameSorter to spare many GetString() calls */
129 static EngineID _last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
132 * Determines order of engines by name
133 * @param a first engine to compare
134 * @param b second engine to compare
135 * @return for descending order: returns true if a < b. Vice versa for ascending order
137 static bool EngineNameSorter(const EngineID &a, const EngineID &b)
139 static char last_name[2][64] = { "", "" };
141 if (a != _last_engine[0]) {
142 _last_engine[0] = a;
143 SetDParam(0, a);
144 GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
147 if (b != _last_engine[1]) {
148 _last_engine[1] = b;
149 SetDParam(0, b);
150 GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
153 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
155 /* Use EngineID to sort instead since we want consistent sorting */
156 if (r == 0) return EngineNumberSorter(a, b);
157 return _engine_sort_direction ? r > 0 : r < 0;
161 * Determines order of engines by reliability
162 * @param a first engine to compare
163 * @param b second engine to compare
164 * @return for descending order: returns true if a < b. Vice versa for ascending order
166 static bool EngineReliabilitySorter(const EngineID &a, const EngineID &b)
168 const int va = Engine::Get(a)->reliability;
169 const int vb = Engine::Get(b)->reliability;
170 const int r = va - vb;
172 /* Use EngineID to sort instead since we want consistent sorting */
173 if (r == 0) return EngineNumberSorter(a, b);
174 return _engine_sort_direction ? r > 0 : r < 0;
178 * Determines order of engines by purchase cost
179 * @param a first engine to compare
180 * @param b second engine to compare
181 * @return for descending order: returns true if a < b. Vice versa for ascending order
183 static bool EngineCostSorter(const EngineID &a, const EngineID &b)
185 Money va = Engine::Get(a)->GetCost();
186 Money vb = Engine::Get(b)->GetCost();
187 int r = ClampToI32(va - vb);
189 /* Use EngineID to sort instead since we want consistent sorting */
190 if (r == 0) return EngineNumberSorter(a, b);
191 return _engine_sort_direction ? r > 0 : r < 0;
195 * Determines order of engines by speed
196 * @param a first engine to compare
197 * @param b second engine to compare
198 * @return for descending order: returns true if a < b. Vice versa for ascending order
200 static bool EngineSpeedSorter(const EngineID &a, const EngineID &b)
202 int va = Engine::Get(a)->GetDisplayMaxSpeed();
203 int vb = Engine::Get(b)->GetDisplayMaxSpeed();
204 int r = va - vb;
206 /* Use EngineID to sort instead since we want consistent sorting */
207 if (r == 0) return EngineNumberSorter(a, b);
208 return _engine_sort_direction ? r > 0 : r < 0;
212 * Determines order of engines by power
213 * @param a first engine to compare
214 * @param b second engine to compare
215 * @return for descending order: returns true if a < b. Vice versa for ascending order
217 static bool EnginePowerSorter(const EngineID &a, const EngineID &b)
219 int va = Engine::Get(a)->GetPower();
220 int vb = Engine::Get(b)->GetPower();
221 int r = va - vb;
223 /* Use EngineID to sort instead since we want consistent sorting */
224 if (r == 0) return EngineNumberSorter(a, b);
225 return _engine_sort_direction ? r > 0 : r < 0;
229 * Determines order of engines by tractive effort
230 * @param a first engine to compare
231 * @param b second engine to compare
232 * @return for descending order: returns true if a < b. Vice versa for ascending order
234 static bool EngineTractiveEffortSorter(const EngineID &a, const EngineID &b)
236 int va = Engine::Get(a)->GetDisplayMaxTractiveEffort();
237 int vb = Engine::Get(b)->GetDisplayMaxTractiveEffort();
238 int r = va - vb;
240 /* Use EngineID to sort instead since we want consistent sorting */
241 if (r == 0) return EngineNumberSorter(a, b);
242 return _engine_sort_direction ? r > 0 : r < 0;
246 * Determines order of engines by running costs
247 * @param a first engine to compare
248 * @param b second engine to compare
249 * @return for descending order: returns true if a < b. Vice versa for ascending order
251 static bool EngineRunningCostSorter(const EngineID &a, const EngineID &b)
253 Money va = Engine::Get(a)->GetRunningCost();
254 Money vb = Engine::Get(b)->GetRunningCost();
255 int r = ClampToI32(va - vb);
257 /* Use EngineID to sort instead since we want consistent sorting */
258 if (r == 0) return EngineNumberSorter(a, b);
259 return _engine_sort_direction ? r > 0 : r < 0;
263 * Determines order of engines by running costs
264 * @param a first engine to compare
265 * @param b second engine to compare
266 * @return for descending order: returns true if a < b. Vice versa for ascending order
268 static bool EnginePowerVsRunningCostSorter(const EngineID &a, const EngineID &b)
270 const Engine *e_a = Engine::Get(a);
271 const Engine *e_b = Engine::Get(b);
272 uint p_a = e_a->GetPower();
273 uint p_b = e_b->GetPower();
274 Money r_a = e_a->GetRunningCost();
275 Money r_b = e_b->GetRunningCost();
276 /* Check if running cost is zero in one or both engines.
277 * If only one of them is zero then that one has higher value,
278 * else if both have zero cost then compare powers. */
279 if (r_a == 0) {
280 if (r_b == 0) {
281 /* If it is ambiguous which to return go with their ID */
282 if (p_a == p_b) return EngineNumberSorter(a, b);
283 return _engine_sort_direction != (p_a < p_b);
285 return !_engine_sort_direction;
287 if (r_b == 0) return _engine_sort_direction;
288 /* Using double for more precision when comparing close values.
289 * This shouldn't have any major effects in performance nor in keeping
290 * the game in sync between players since it's used in GUI only in client side */
291 double v_a = (double)p_a / (double)r_a;
292 double v_b = (double)p_b / (double)r_b;
293 /* Use EngineID to sort if both have same power/running cost,
294 * since we want consistent sorting.
295 * Also if both have no power then sort with reverse of running cost to simulate
296 * previous sorting behaviour for wagons. */
297 if (v_a == 0 && v_b == 0) return !EngineRunningCostSorter(a, b);
298 if (v_a == v_b) return EngineNumberSorter(a, b);
299 return _engine_sort_direction != (v_a < v_b);
302 /* Train sorting functions */
305 * Determines order of train engines by capacity
306 * @param a first engine to compare
307 * @param b second engine to compare
308 * @return for descending order: returns true if a < b. Vice versa for ascending order
310 static bool TrainEngineCapacitySorter(const EngineID &a, const EngineID &b)
312 const RailVehicleInfo *rvi_a = RailVehInfo(a);
313 const RailVehicleInfo *rvi_b = RailVehInfo(b);
315 int va = GetTotalCapacityOfArticulatedParts(a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
316 int vb = GetTotalCapacityOfArticulatedParts(b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
317 int r = va - vb;
319 /* Use EngineID to sort instead since we want consistent sorting */
320 if (r == 0) return EngineNumberSorter(a, b);
321 return _engine_sort_direction ? r > 0 : r < 0;
325 * Determines order of train engines by engine / wagon
326 * @param a first engine to compare
327 * @param b second engine to compare
328 * @return for descending order: returns true if a < b. Vice versa for ascending order
330 static bool TrainEnginesThenWagonsSorter(const EngineID &a, const EngineID &b)
332 int val_a = (RailVehInfo(a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
333 int val_b = (RailVehInfo(b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
334 int r = val_a - val_b;
336 /* Use EngineID to sort instead since we want consistent sorting */
337 if (r == 0) return EngineNumberSorter(a, b);
338 return _engine_sort_direction ? r > 0 : r < 0;
341 /* Road vehicle sorting functions */
344 * Determines order of road vehicles by capacity
345 * @param a first engine to compare
346 * @param b second engine to compare
347 * @return for descending order: returns true if a < b. Vice versa for ascending order
349 static bool RoadVehEngineCapacitySorter(const EngineID &a, const EngineID &b)
351 int va = GetTotalCapacityOfArticulatedParts(a);
352 int vb = GetTotalCapacityOfArticulatedParts(b);
353 int r = va - vb;
355 /* Use EngineID to sort instead since we want consistent sorting */
356 if (r == 0) return EngineNumberSorter(a, b);
357 return _engine_sort_direction ? r > 0 : r < 0;
360 /* Ship vehicle sorting functions */
363 * Determines order of ships by capacity
364 * @param a first engine to compare
365 * @param b second engine to compare
366 * @return for descending order: returns true if a < b. Vice versa for ascending order
368 static bool ShipEngineCapacitySorter(const EngineID &a, const EngineID &b)
370 const Engine *e_a = Engine::Get(a);
371 const Engine *e_b = Engine::Get(b);
373 int va = e_a->GetDisplayDefaultCapacity();
374 int vb = e_b->GetDisplayDefaultCapacity();
375 int r = va - vb;
377 /* Use EngineID to sort instead since we want consistent sorting */
378 if (r == 0) return EngineNumberSorter(a, b);
379 return _engine_sort_direction ? r > 0 : r < 0;
382 /* Aircraft sorting functions */
385 * Determines order of aircraft by cargo
386 * @param a first engine to compare
387 * @param b second engine to compare
388 * @return for descending order: returns true if a < b. Vice versa for ascending order
390 static bool AircraftEngineCargoSorter(const EngineID &a, const EngineID &b)
392 const Engine *e_a = Engine::Get(a);
393 const Engine *e_b = Engine::Get(b);
395 uint16 mail_a, mail_b;
396 int va = e_a->GetDisplayDefaultCapacity(&mail_a);
397 int vb = e_b->GetDisplayDefaultCapacity(&mail_b);
398 int r = va - vb;
400 if (r == 0) {
401 /* The planes have the same passenger capacity. Check mail capacity instead */
402 r = mail_a - mail_b;
404 if (r == 0) {
405 /* Use EngineID to sort instead since we want consistent sorting */
406 return EngineNumberSorter(a, b);
409 return _engine_sort_direction ? r > 0 : r < 0;
413 * Determines order of aircraft by range.
414 * @param a first engine to compare
415 * @param b second engine to compare
416 * @return for descending order: returns true if a < b. Vice versa for ascending order
418 static bool AircraftRangeSorter(const EngineID &a, const EngineID &b)
420 uint16 r_a = Engine::Get(a)->GetRange();
421 uint16 r_b = Engine::Get(b)->GetRange();
423 int r = r_a - r_b;
425 /* Use EngineID to sort instead since we want consistent sorting */
426 if (r == 0) return EngineNumberSorter(a, b);
427 return _engine_sort_direction ? r > 0 : r < 0;
430 /** Sort functions for the vehicle sort criteria, for each vehicle type. */
431 EngList_SortTypeFunction * const _engine_sort_functions[][11] = {{
432 /* Trains */
433 &EngineNumberSorter,
434 &EngineCostSorter,
435 &EngineSpeedSorter,
436 &EnginePowerSorter,
437 &EngineTractiveEffortSorter,
438 &EngineIntroDateSorter,
439 &EngineNameSorter,
440 &EngineRunningCostSorter,
441 &EnginePowerVsRunningCostSorter,
442 &EngineReliabilitySorter,
443 &TrainEngineCapacitySorter,
444 }, {
445 /* Road vehicles */
446 &EngineNumberSorter,
447 &EngineCostSorter,
448 &EngineSpeedSorter,
449 &EnginePowerSorter,
450 &EngineTractiveEffortSorter,
451 &EngineIntroDateSorter,
452 &EngineNameSorter,
453 &EngineRunningCostSorter,
454 &EnginePowerVsRunningCostSorter,
455 &EngineReliabilitySorter,
456 &RoadVehEngineCapacitySorter,
457 }, {
458 /* Ships */
459 &EngineNumberSorter,
460 &EngineCostSorter,
461 &EngineSpeedSorter,
462 &EngineIntroDateSorter,
463 &EngineNameSorter,
464 &EngineRunningCostSorter,
465 &EngineReliabilitySorter,
466 &ShipEngineCapacitySorter,
467 }, {
468 /* Aircraft */
469 &EngineNumberSorter,
470 &EngineCostSorter,
471 &EngineSpeedSorter,
472 &EngineIntroDateSorter,
473 &EngineNameSorter,
474 &EngineRunningCostSorter,
475 &EngineReliabilitySorter,
476 &AircraftEngineCargoSorter,
477 &AircraftRangeSorter,
480 /** Dropdown menu strings for the vehicle sort criteria. */
481 const StringID _engine_sort_listing[][12] = {{
482 /* Trains */
483 STR_SORT_BY_ENGINE_ID,
484 STR_SORT_BY_COST,
485 STR_SORT_BY_MAX_SPEED,
486 STR_SORT_BY_POWER,
487 STR_SORT_BY_TRACTIVE_EFFORT,
488 STR_SORT_BY_INTRO_DATE,
489 STR_SORT_BY_NAME,
490 STR_SORT_BY_RUNNING_COST,
491 STR_SORT_BY_POWER_VS_RUNNING_COST,
492 STR_SORT_BY_RELIABILITY,
493 STR_SORT_BY_CARGO_CAPACITY,
494 INVALID_STRING_ID
495 }, {
496 /* Road vehicles */
497 STR_SORT_BY_ENGINE_ID,
498 STR_SORT_BY_COST,
499 STR_SORT_BY_MAX_SPEED,
500 STR_SORT_BY_POWER,
501 STR_SORT_BY_TRACTIVE_EFFORT,
502 STR_SORT_BY_INTRO_DATE,
503 STR_SORT_BY_NAME,
504 STR_SORT_BY_RUNNING_COST,
505 STR_SORT_BY_POWER_VS_RUNNING_COST,
506 STR_SORT_BY_RELIABILITY,
507 STR_SORT_BY_CARGO_CAPACITY,
508 INVALID_STRING_ID
509 }, {
510 /* Ships */
511 STR_SORT_BY_ENGINE_ID,
512 STR_SORT_BY_COST,
513 STR_SORT_BY_MAX_SPEED,
514 STR_SORT_BY_INTRO_DATE,
515 STR_SORT_BY_NAME,
516 STR_SORT_BY_RUNNING_COST,
517 STR_SORT_BY_RELIABILITY,
518 STR_SORT_BY_CARGO_CAPACITY,
519 INVALID_STRING_ID
520 }, {
521 /* Aircraft */
522 STR_SORT_BY_ENGINE_ID,
523 STR_SORT_BY_COST,
524 STR_SORT_BY_MAX_SPEED,
525 STR_SORT_BY_INTRO_DATE,
526 STR_SORT_BY_NAME,
527 STR_SORT_BY_RUNNING_COST,
528 STR_SORT_BY_RELIABILITY,
529 STR_SORT_BY_CARGO_CAPACITY,
530 STR_SORT_BY_RANGE,
531 INVALID_STRING_ID
534 /** Cargo filter functions */
535 static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid)
537 if (cid == CF_ANY) return true;
538 CargoTypes refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true) & _standard_cargo_mask;
539 return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
542 static GUIEngineList::FilterFunction * const _filter_funcs[] = {
543 &CargoFilter,
546 static int DrawCargoCapacityInfo(int left, int right, int y, EngineID engine, TestedEngineDetails &te)
548 CargoArray cap;
549 CargoTypes refits;
550 GetArticulatedVehicleCargoesAndRefits(engine, &cap, &refits, te.cargo, te.capacity);
552 for (CargoID c = 0; c < NUM_CARGO; c++) {
553 if (cap[c] == 0) continue;
555 SetDParam(0, c);
556 SetDParam(1, cap[c]);
557 SetDParam(2, HasBit(refits, c) ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
558 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
559 y += FONT_HEIGHT_NORMAL;
562 return y;
565 /* Draw rail wagon specific details */
566 static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi, TestedEngineDetails &te)
568 const Engine *e = Engine::Get(engine_number);
570 /* Purchase cost */
571 if (te.cost != 0) {
572 SetDParam(0, e->GetCost() + te.cost);
573 SetDParam(1, te.cost);
574 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT);
575 } else {
576 SetDParam(0, e->GetCost());
577 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
579 y += FONT_HEIGHT_NORMAL;
581 /* Wagon weight - (including cargo) */
582 uint weight = e->GetDisplayWeight();
583 SetDParam(0, weight);
584 uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->weight * te.capacity / 16 : 0);
585 SetDParam(1, cargo_weight + weight);
586 DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
587 y += FONT_HEIGHT_NORMAL;
589 /* Wagon speed limit, displayed if above zero */
590 if (_settings_game.vehicle.wagon_speed_limits) {
591 uint max_speed = e->GetDisplayMaxSpeed();
592 if (max_speed > 0) {
593 SetDParam(0, max_speed);
594 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED);
595 y += FONT_HEIGHT_NORMAL;
599 /* Running cost */
600 if (rvi->running_cost_class != INVALID_PRICE) {
601 SetDParam(0, e->GetRunningCost());
602 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
603 y += FONT_HEIGHT_NORMAL;
606 return y;
609 /* Draw locomotive specific details */
610 static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi, TestedEngineDetails &te)
612 const Engine *e = Engine::Get(engine_number);
614 /* Purchase Cost - Engine weight */
615 if (te.cost != 0) {
616 SetDParam(0, e->GetCost() + te.cost);
617 SetDParam(1, te.cost);
618 SetDParam(2, e->GetDisplayWeight());
619 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_WEIGHT);
620 } else {
621 SetDParam(0, e->GetCost());
622 SetDParam(1, e->GetDisplayWeight());
623 DrawString(left, right, y, STR_PURCHASE_INFO_COST_WEIGHT);
625 y += FONT_HEIGHT_NORMAL;
627 /* Max speed - Engine power */
628 SetDParam(0, e->GetDisplayMaxSpeed());
629 SetDParam(1, e->GetPower());
630 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
631 y += FONT_HEIGHT_NORMAL;
633 /* Max tractive effort - not applicable if old acceleration or maglev */
634 if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != 2) {
635 SetDParam(0, e->GetDisplayMaxTractiveEffort());
636 DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
637 y += FONT_HEIGHT_NORMAL;
640 /* Running cost */
641 if (rvi->running_cost_class != INVALID_PRICE) {
642 SetDParam(0, e->GetRunningCost());
643 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
644 y += FONT_HEIGHT_NORMAL;
647 /* Powered wagons power - Powered wagons extra weight */
648 if (rvi->pow_wag_power != 0) {
649 SetDParam(0, rvi->pow_wag_power);
650 SetDParam(1, rvi->pow_wag_weight);
651 DrawString(left, right, y, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT);
652 y += FONT_HEIGHT_NORMAL;
655 return y;
658 /* Draw road vehicle specific details */
659 static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_number, TestedEngineDetails &te)
661 const Engine *e = Engine::Get(engine_number);
663 if (_settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL) {
664 /* Purchase Cost */
665 if (te.cost != 0) {
666 SetDParam(0, e->GetCost() + te.cost);
667 SetDParam(1, te.cost);
668 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT);
669 } else {
670 SetDParam(0, e->GetCost());
671 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
673 y += FONT_HEIGHT_NORMAL;
675 /* Road vehicle weight - (including cargo) */
676 int16 weight = e->GetDisplayWeight();
677 SetDParam(0, weight);
678 uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->weight * te.capacity / 16 : 0);
679 SetDParam(1, cargo_weight + weight);
680 DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
681 y += FONT_HEIGHT_NORMAL;
683 /* Max speed - Engine power */
684 SetDParam(0, e->GetDisplayMaxSpeed());
685 SetDParam(1, e->GetPower());
686 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
687 y += FONT_HEIGHT_NORMAL;
689 /* Max tractive effort */
690 SetDParam(0, e->GetDisplayMaxTractiveEffort());
691 DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
692 y += FONT_HEIGHT_NORMAL;
693 } else {
694 /* Purchase cost - Max speed */
695 if (te.cost != 0) {
696 SetDParam(0, e->GetCost() + te.cost);
697 SetDParam(1, te.cost);
698 SetDParam(2, e->GetDisplayMaxSpeed());
699 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_SPEED);
700 } else {
701 SetDParam(0, e->GetCost());
702 SetDParam(1, e->GetDisplayMaxSpeed());
703 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
705 y += FONT_HEIGHT_NORMAL;
708 /* Running cost */
709 SetDParam(0, e->GetRunningCost());
710 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
711 y += FONT_HEIGHT_NORMAL;
713 return y;
716 /* Draw ship specific details */
717 static int DrawShipPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable, TestedEngineDetails &te)
719 const Engine *e = Engine::Get(engine_number);
721 /* Purchase cost - Max speed */
722 uint raw_speed = e->GetDisplayMaxSpeed();
723 uint ocean_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, true);
724 uint canal_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, false);
726 if (ocean_speed == canal_speed) {
727 if (te.cost != 0) {
728 SetDParam(0, e->GetCost() + te.cost);
729 SetDParam(1, te.cost);
730 SetDParam(2, ocean_speed);
731 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_SPEED);
732 } else {
733 SetDParam(0, e->GetCost());
734 SetDParam(1, ocean_speed);
735 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
737 y += FONT_HEIGHT_NORMAL;
738 } else {
739 if (te.cost != 0) {
740 SetDParam(0, e->GetCost() + te.cost);
741 SetDParam(1, te.cost);
742 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT);
743 } else {
744 SetDParam(0, e->GetCost());
745 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
747 y += FONT_HEIGHT_NORMAL;
749 SetDParam(0, ocean_speed);
750 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_OCEAN);
751 y += FONT_HEIGHT_NORMAL;
753 SetDParam(0, canal_speed);
754 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_CANAL);
755 y += FONT_HEIGHT_NORMAL;
758 /* Cargo type + capacity */
759 SetDParam(0, te.cargo);
760 SetDParam(1, te.capacity);
761 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
762 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
763 y += FONT_HEIGHT_NORMAL;
765 /* Running cost */
766 SetDParam(0, e->GetRunningCost());
767 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
768 y += FONT_HEIGHT_NORMAL;
770 return y;
774 * Draw aircraft specific details in the buy window.
775 * @param left Left edge of the window to draw in.
776 * @param right Right edge of the window to draw in.
777 * @param y Top of the area to draw in.
778 * @param engine_number Engine to display.
779 * @param refittable If set, the aircraft can be refitted.
780 * @return Bottom of the used area.
782 static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable, TestedEngineDetails &te)
784 const Engine *e = Engine::Get(engine_number);
786 /* Purchase cost - Max speed */
787 if (te.cost != 0) {
788 SetDParam(0, e->GetCost() + te.cost);
789 SetDParam(1, te.cost);
790 SetDParam(2, e->GetDisplayMaxSpeed());
791 DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_SPEED);
792 } else {
793 SetDParam(0, e->GetCost());
794 SetDParam(1, e->GetDisplayMaxSpeed());
795 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
797 y += FONT_HEIGHT_NORMAL;
799 /* Cargo capacity */
800 if (te.mail_capacity > 0) {
801 SetDParam(0, te.cargo);
802 SetDParam(1, te.capacity);
803 SetDParam(2, CT_MAIL);
804 SetDParam(3, te.mail_capacity);
805 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
806 } else {
807 /* Note, if the default capacity is selected by the refit capacity
808 * callback, then the capacity shown is likely to be incorrect. */
809 SetDParam(0, te.cargo);
810 SetDParam(1, te.capacity);
811 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
812 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
814 y += FONT_HEIGHT_NORMAL;
816 /* Running cost */
817 SetDParam(0, e->GetRunningCost());
818 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
819 y += FONT_HEIGHT_NORMAL;
821 /* Aircraft type */
822 SetDParam(0, e->GetAircraftTypeText());
823 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_TYPE);
824 y += FONT_HEIGHT_NORMAL;
826 /* Aircraft range, if available. */
827 uint16 range = e->GetRange();
828 if (range != 0) {
829 SetDParam(0, range);
830 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_RANGE);
831 y += FONT_HEIGHT_NORMAL;
834 return y;
838 * Display additional text from NewGRF in the purchase information window
839 * @param left Left border of text bounding box
840 * @param right Right border of text bounding box
841 * @param y Top border of text bounding box
842 * @param engine Engine to query the additional purchase information for
843 * @return Bottom border of text bounding box
845 static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
847 uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr);
848 if (callback == CALLBACK_FAILED || callback == 0x400) return y;
849 const GRFFile *grffile = Engine::Get(engine)->GetGRF();
850 if (callback > 0x400) {
851 ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback);
852 return y;
855 StartTextRefStackUsage(grffile, 6);
856 uint result = DrawStringMultiLine(left, right, y, INT32_MAX, GetGRFStringID(grffile->grfid, 0xD000 + callback), TC_BLACK);
857 StopTextRefStackUsage();
858 return result;
862 * Draw the purchase info details of a vehicle at a given location.
863 * @param left,right,y location where to draw the info
864 * @param engine_number the engine of which to draw the info of
865 * @return y after drawing all the text
867 int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number, TestedEngineDetails &te)
869 const Engine *e = Engine::Get(engine_number);
870 YearMonthDay ymd;
871 ConvertDateToYMD(e->intro_date, &ymd);
872 bool refittable = IsArticulatedVehicleRefittable(engine_number);
873 bool articulated_cargo = false;
875 switch (e->type) {
876 default: NOT_REACHED();
877 case VEH_TRAIN:
878 if (e->u.rail.railveh_type == RAILVEH_WAGON) {
879 y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail, te);
880 } else {
881 y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail, te);
883 articulated_cargo = true;
884 break;
886 case VEH_ROAD:
887 y = DrawRoadVehPurchaseInfo(left, right, y, engine_number, te);
888 articulated_cargo = true;
889 break;
891 case VEH_SHIP:
892 y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable, te);
893 break;
895 case VEH_AIRCRAFT:
896 y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable, te);
897 break;
900 if (articulated_cargo) {
901 /* Cargo type + capacity, or N/A */
902 int new_y = DrawCargoCapacityInfo(left, right, y, engine_number, te);
904 if (new_y == y) {
905 SetDParam(0, CT_INVALID);
906 SetDParam(2, STR_EMPTY);
907 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
908 y += FONT_HEIGHT_NORMAL;
909 } else {
910 y = new_y;
914 /* Draw details that apply to all types except rail wagons. */
915 if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
916 /* Design date - Life length */
917 SetDParam(0, ymd.year);
918 SetDParam(1, e->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR);
919 DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
920 y += FONT_HEIGHT_NORMAL;
922 /* Reliability */
923 SetDParam(0, ToPercent16(e->reliability));
924 DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
925 y += FONT_HEIGHT_NORMAL;
928 if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
930 /* Additional text from NewGRF */
931 y = ShowAdditionalText(left, right, y, engine_number);
933 /* The NewGRF's name which the vehicle comes from */
934 const GRFConfig *config = GetGRFConfig(e->GetGRFID());
935 if (_settings_client.gui.show_newgrf_name && config != nullptr)
937 DrawString(left, right, y, config->GetName(), TC_BLACK);
938 y += FONT_HEIGHT_NORMAL;
941 return y;
945 * Engine drawing loop
946 * @param type Type of vehicle (VEH_*)
947 * @param l The left most location of the list
948 * @param r The right most location of the list
949 * @param y The top most location of the list
950 * @param eng_list What engines to draw
951 * @param min where to start in the list
952 * @param max where in the list to end
953 * @param selected_id what engine to highlight as selected, if any
954 * @param show_count Whether to show the amount of engines or not
955 * @param selected_group the group to list the engines of
957 void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
959 static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
961 /* Obligatory sanity checks! */
962 assert(max <= eng_list->size());
964 bool rtl = _current_text_dir == TD_RTL;
965 int step_size = GetEngineListHeight(type);
966 int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
967 int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
968 int sprite_width = sprite_left + sprite_right;
970 int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
971 int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
973 Dimension replace_icon = {0, 0};
974 int count_width = 0;
975 if (show_count) {
976 replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
977 SetDParamMaxDigits(0, 3, FS_SMALL);
978 count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
981 int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
982 int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
983 int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
984 int count_left = l;
985 int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
987 int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
988 int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
989 int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
991 for (; min < max; min++, y += step_size) {
992 const EngineID engine = (*eng_list)[min];
993 /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
994 const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
996 const Engine *e = Engine::Get(engine);
997 bool hidden = HasBit(e->company_hidden, _local_company);
998 StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
999 TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK));
1001 SetDParam(0, engine);
1002 DrawString(text_left, text_right, y + normal_text_y_offset, str, tc);
1003 DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
1004 if (show_count) {
1005 SetDParam(0, num_engines);
1006 DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
1007 if (EngineHasReplacementForCompany(Company::Get(_local_company), engine, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, replace_icon_left, y + replace_icon_y_offset);
1013 * Display the dropdown for the vehicle sort criteria.
1014 * @param w Parent window (holds the dropdown button).
1015 * @param vehicle_type %Vehicle type being sorted.
1016 * @param selected Currently selected sort criterium.
1017 * @param button Widget button.
1019 void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, int button)
1021 uint32 hidden_mask = 0;
1022 /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
1023 if (vehicle_type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
1024 SetBit(hidden_mask, 3); // power
1025 SetBit(hidden_mask, 4); // tractive effort
1026 SetBit(hidden_mask, 8); // power by running costs
1028 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
1029 if (vehicle_type == VEH_TRAIN && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
1030 SetBit(hidden_mask, 4); // tractive effort
1032 ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask);
1035 /** GUI for building vehicles. */
1036 struct BuildVehicleWindow : Window {
1037 VehicleType vehicle_type; ///< Type of vehicles shown in the window.
1038 union {
1039 RailType railtype; ///< Rail type to show, or #INVALID_RAILTYPE.
1040 RoadType roadtype; ///< Road type to show, or #INVALID_ROADTYPE.
1041 } filter; ///< Filter to apply.
1042 bool descending_sort_order; ///< Sort direction, @see _engine_sort_direction
1043 byte sort_criteria; ///< Current sort criterium.
1044 bool show_hidden_engines; ///< State of the 'show hidden engines' button.
1045 bool listview_mode; ///< If set, only display the available vehicles and do not show a 'build' button.
1046 EngineID sel_engine; ///< Currently selected engine, or #INVALID_ENGINE
1047 EngineID rename_engine; ///< Engine being renamed.
1048 GUIEngineList eng_list;
1049 CargoID cargo_filter[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
1050 StringID cargo_filter_texts[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
1051 byte cargo_filter_criteria; ///< Selected cargo filter
1052 int details_height; ///< Minimal needed height of the details panels (found so far).
1053 Scrollbar *vscroll;
1054 TestedEngineDetails te; ///< Tested cost and capacity after refit.
1056 void SetBuyVehicleText()
1058 NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_BUILD);
1060 bool refit = this->sel_engine != INVALID_ENGINE && this->cargo_filter[this->cargo_filter_criteria] != CF_ANY && this->cargo_filter[this->cargo_filter_criteria] != CF_NONE;
1061 if (refit) refit = Engine::Get(this->sel_engine)->GetDefaultCargoType() != this->cargo_filter[this->cargo_filter_criteria];
1063 if (refit) {
1064 widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type;
1065 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP + this->vehicle_type;
1066 } else {
1067 widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type;
1068 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + this->vehicle_type;
1072 BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
1074 this->vehicle_type = type;
1075 this->listview_mode = tile == INVALID_TILE;
1076 this->window_number = this->listview_mode ? (int)type : tile;
1078 this->sel_engine = INVALID_ENGINE;
1080 this->sort_criteria = _engine_sort_last_criteria[type];
1081 this->descending_sort_order = _engine_sort_last_order[type];
1082 this->show_hidden_engines = _engine_sort_show_hidden_engines[type];
1084 this->UpdateFilterByTile();
1086 this->CreateNestedTree();
1088 this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
1090 /* If we are just viewing the list of vehicles, we do not need the Build button.
1091 * So we just hide it, and enlarge the Rename button by the now vacant place. */
1092 if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE);
1094 /* disable renaming engines in network games if you are not the server */
1095 this->SetWidgetDisabledState(WID_BV_RENAME, _networking && !_network_server);
1097 NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_LIST);
1098 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
1100 widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE);
1101 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
1103 widget = this->GetWidget<NWidgetCore>(WID_BV_RENAME);
1104 widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
1105 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
1107 widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_ENGINES);
1108 widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
1109 widget->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
1110 widget->SetLowered(this->show_hidden_engines);
1112 this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1114 this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
1116 this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
1118 this->eng_list.ForceRebuild();
1119 this->GenerateBuildList(); // generate the list, since we need it in the next line
1120 /* Select the first engine in the list as default when opening the window */
1121 if (this->eng_list.size() > 0) {
1122 this->SelectEngine(this->eng_list[0]);
1123 } else {
1124 this->SelectEngine(INVALID_ENGINE);
1128 /** Set the filter type according to the depot type */
1129 void UpdateFilterByTile()
1131 switch (this->vehicle_type) {
1132 default: NOT_REACHED();
1133 case VEH_TRAIN:
1134 if (this->listview_mode) {
1135 this->filter.railtype = INVALID_RAILTYPE;
1136 } else {
1137 this->filter.railtype = GetRailType(this->window_number);
1139 break;
1141 case VEH_ROAD:
1142 if (this->listview_mode) {
1143 this->filter.roadtype = INVALID_ROADTYPE;
1144 } else {
1145 this->filter.roadtype = GetRoadTypeRoad(this->window_number);
1146 if (this->filter.roadtype == INVALID_ROADTYPE) {
1147 this->filter.roadtype = GetRoadTypeTram(this->window_number);
1150 break;
1152 case VEH_SHIP:
1153 case VEH_AIRCRAFT:
1154 break;
1158 /** Populate the filter list and set the cargo filter criteria. */
1159 void SetCargoFilterArray()
1161 uint filter_items = 0;
1163 /* Add item for disabling filtering. */
1164 this->cargo_filter[filter_items] = CF_ANY;
1165 this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
1166 filter_items++;
1168 /* Add item for vehicles not carrying anything, e.g. train engines.
1169 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1170 if (this->vehicle_type == VEH_TRAIN) {
1171 this->cargo_filter[filter_items] = CF_NONE;
1172 this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_NONE;
1173 filter_items++;
1176 /* Collect available cargo types for filtering. */
1177 const CargoSpec *cs;
1178 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) {
1179 this->cargo_filter[filter_items] = cs->Index();
1180 this->cargo_filter_texts[filter_items] = cs->name;
1181 filter_items++;
1184 /* Terminate the filter list. */
1185 this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1187 /* If not found, the cargo criteria will be set to all cargoes. */
1188 this->cargo_filter_criteria = 0;
1190 /* Find the last cargo filter criteria. */
1191 for (uint i = 0; i < filter_items; i++) {
1192 if (this->cargo_filter[i] == _engine_sort_last_cargo_criteria[this->vehicle_type]) {
1193 this->cargo_filter_criteria = i;
1194 break;
1198 this->eng_list.SetFilterFuncs(_filter_funcs);
1199 this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1202 void SelectEngine(EngineID engine)
1204 CargoID cargo = this->cargo_filter[this->cargo_filter_criteria];
1205 if (cargo == CF_ANY) cargo = CF_NONE;
1207 this->sel_engine = engine;
1208 this->SetBuyVehicleText();
1210 if (this->sel_engine == INVALID_ENGINE) return;
1212 const Engine *e = Engine::Get(this->sel_engine);
1213 if (!e->CanCarryCargo()) {
1214 this->te.cost = 0;
1215 this->te.cargo = CT_INVALID;
1216 return;
1219 if (!this->listview_mode) {
1220 /* Query for cost and refitted capacity */
1221 CommandCost ret = DoCommand(this->window_number, this->sel_engine | (cargo << 24), 0, DC_QUERY_COST, GetCmdBuildVeh(this->vehicle_type), nullptr);
1222 if (ret.Succeeded()) {
1223 this->te.cost = ret.GetCost() - e->GetCost();
1224 this->te.capacity = _returned_refit_capacity;
1225 this->te.mail_capacity = _returned_mail_refit_capacity;
1226 this->te.cargo = (cargo == CT_INVALID) ? e->GetDefaultCargoType() : cargo;
1227 return;
1231 /* Purchase test was not possible or failed, fill in the defaults instead. */
1232 this->te.cost = 0;
1233 this->te.capacity = e->GetDisplayDefaultCapacity(&this->te.mail_capacity);
1234 this->te.cargo = e->GetDefaultCargoType();
1237 void OnInit() override
1239 this->SetCargoFilterArray();
1242 /** Filter the engine list against the currently selected cargo filter */
1243 void FilterEngineList()
1245 this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
1246 if (0 == this->eng_list.size()) { // no engine passed through the filter, invalidate the previously selected engine
1247 this->SelectEngine(INVALID_ENGINE);
1248 } else if (std::find(this->eng_list.begin(), this->eng_list.end(), this->sel_engine) == this->eng_list.end()) { // previously selected engine didn't pass the filter, select the first engine of the list
1249 this->SelectEngine(this->eng_list[0]);
1253 /** Filter a single engine */
1254 bool FilterSingleEngine(EngineID eid)
1256 CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
1257 return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
1260 /* Figure out what train EngineIDs to put in the list */
1261 void GenerateBuildTrainList()
1263 EngineID sel_id = INVALID_ENGINE;
1264 int num_engines = 0;
1265 int num_wagons = 0;
1267 this->eng_list.clear();
1269 /* Make list of all available train engines and wagons.
1270 * Also check to see if the previously selected engine is still available,
1271 * and if not, reset selection to INVALID_ENGINE. This could be the case
1272 * when engines become obsolete and are removed */
1273 for (const Engine *e : Engine::IterateType(VEH_TRAIN)) {
1274 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1275 EngineID eid = e->index;
1276 const RailVehicleInfo *rvi = &e->u.rail;
1278 if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
1279 if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
1281 /* Filter now! So num_engines and num_wagons is valid */
1282 if (!FilterSingleEngine(eid)) continue;
1284 this->eng_list.push_back(eid);
1286 if (rvi->railveh_type != RAILVEH_WAGON) {
1287 num_engines++;
1288 } else {
1289 num_wagons++;
1292 if (eid == this->sel_engine) sel_id = eid;
1295 this->SelectEngine(sel_id);
1297 /* invalidate cached values for name sorter - engine names could change */
1298 _last_engine[0] = _last_engine[1] = INVALID_ENGINE;
1300 /* make engines first, and then wagons, sorted by selected sort_criteria */
1301 _engine_sort_direction = false;
1302 EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
1304 /* and then sort engines */
1305 _engine_sort_direction = this->descending_sort_order;
1306 EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
1308 /* and finally sort wagons */
1309 EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
1312 /* Figure out what road vehicle EngineIDs to put in the list */
1313 void GenerateBuildRoadVehList()
1315 EngineID sel_id = INVALID_ENGINE;
1317 this->eng_list.clear();
1319 for (const Engine *e : Engine::IterateType(VEH_ROAD)) {
1320 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1321 EngineID eid = e->index;
1322 if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
1323 if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue;
1325 this->eng_list.push_back(eid);
1327 if (eid == this->sel_engine) sel_id = eid;
1329 this->SelectEngine(sel_id);
1332 /* Figure out what ship EngineIDs to put in the list */
1333 void GenerateBuildShipList()
1335 EngineID sel_id = INVALID_ENGINE;
1336 this->eng_list.clear();
1338 for (const Engine *e : Engine::IterateType(VEH_SHIP)) {
1339 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1340 EngineID eid = e->index;
1341 if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
1342 this->eng_list.push_back(eid);
1344 if (eid == this->sel_engine) sel_id = eid;
1346 this->SelectEngine(sel_id);
1349 /* Figure out what aircraft EngineIDs to put in the list */
1350 void GenerateBuildAircraftList()
1352 EngineID sel_id = INVALID_ENGINE;
1354 this->eng_list.clear();
1356 const Station *st = this->listview_mode ? nullptr : Station::GetByTile(this->window_number);
1358 /* Make list of all available planes.
1359 * Also check to see if the previously selected plane is still available,
1360 * and if not, reset selection to INVALID_ENGINE. This could be the case
1361 * when planes become obsolete and are removed */
1362 for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
1363 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1364 EngineID eid = e->index;
1365 if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
1366 /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1367 if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
1369 this->eng_list.push_back(eid);
1370 if (eid == this->sel_engine) sel_id = eid;
1373 this->SelectEngine(sel_id);
1376 /* Generate the list of vehicles */
1377 void GenerateBuildList()
1379 if (!this->eng_list.NeedRebuild()) return;
1381 /* Update filter type in case the road/railtype of the depot got converted */
1382 this->UpdateFilterByTile();
1384 switch (this->vehicle_type) {
1385 default: NOT_REACHED();
1386 case VEH_TRAIN:
1387 this->GenerateBuildTrainList();
1388 this->eng_list.shrink_to_fit();
1389 this->eng_list.RebuildDone();
1390 return; // trains should not reach the last sorting
1391 case VEH_ROAD:
1392 this->GenerateBuildRoadVehList();
1393 break;
1394 case VEH_SHIP:
1395 this->GenerateBuildShipList();
1396 break;
1397 case VEH_AIRCRAFT:
1398 this->GenerateBuildAircraftList();
1399 break;
1402 this->FilterEngineList();
1404 _engine_sort_direction = this->descending_sort_order;
1405 EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]);
1407 this->eng_list.shrink_to_fit();
1408 this->eng_list.RebuildDone();
1411 void OnClick(Point pt, int widget, int click_count) override
1413 switch (widget) {
1414 case WID_BV_SORT_ASCENDING_DESCENDING:
1415 this->descending_sort_order ^= true;
1416 _engine_sort_last_order[this->vehicle_type] = this->descending_sort_order;
1417 this->eng_list.ForceRebuild();
1418 this->SetDirty();
1419 break;
1421 case WID_BV_SHOW_HIDDEN_ENGINES:
1422 this->show_hidden_engines ^= true;
1423 _engine_sort_show_hidden_engines[this->vehicle_type] = this->show_hidden_engines;
1424 this->eng_list.ForceRebuild();
1425 this->SetWidgetLoweredState(widget, this->show_hidden_engines);
1426 this->SetDirty();
1427 break;
1429 case WID_BV_LIST: {
1430 uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
1431 size_t num_items = this->eng_list.size();
1432 this->SelectEngine((i < num_items) ? this->eng_list[i] : INVALID_ENGINE);
1433 this->SetDirty();
1434 if (_ctrl_pressed) {
1435 this->OnClick(pt, WID_BV_SHOW_HIDE, 1);
1436 } else if (click_count > 1 && !this->listview_mode) {
1437 this->OnClick(pt, WID_BV_BUILD, 1);
1439 break;
1442 case WID_BV_SORT_DROPDOWN: // Select sorting criteria dropdown menu
1443 DisplayVehicleSortDropDown(this, this->vehicle_type, this->sort_criteria, WID_BV_SORT_DROPDOWN);
1444 break;
1446 case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
1447 ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_BV_CARGO_FILTER_DROPDOWN, 0, 0);
1448 break;
1450 case WID_BV_SHOW_HIDE: {
1451 const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine);
1452 if (e != nullptr) {
1453 DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
1455 break;
1458 case WID_BV_BUILD: {
1459 EngineID sel_eng = this->sel_engine;
1460 if (sel_eng != INVALID_ENGINE) {
1461 CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
1462 CargoID cargo = this->cargo_filter[this->cargo_filter_criteria];
1463 if (cargo == CF_ANY) cargo = CF_NONE;
1464 DoCommandP(this->window_number, sel_eng | (cargo << 24), 0, GetCmdBuildVeh(this->vehicle_type), callback);
1466 break;
1469 case WID_BV_RENAME: {
1470 EngineID sel_eng = this->sel_engine;
1471 if (sel_eng != INVALID_ENGINE) {
1472 this->rename_engine = sel_eng;
1473 SetDParam(0, sel_eng);
1474 ShowQueryString(STR_ENGINE_NAME, STR_QUERY_RENAME_TRAIN_TYPE_CAPTION + this->vehicle_type, MAX_LENGTH_ENGINE_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
1476 break;
1482 * Some data on this window has become invalid.
1483 * @param data Information about the changed data.
1484 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1486 void OnInvalidateData(int data = 0, bool gui_scope = true) override
1488 if (!gui_scope) return;
1489 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1490 if (this->vehicle_type == VEH_ROAD &&
1491 _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL &&
1492 this->sort_criteria > 7) {
1493 this->sort_criteria = 0;
1494 _engine_sort_last_criteria[VEH_ROAD] = 0;
1496 this->eng_list.ForceRebuild();
1499 void SetStringParameters(int widget) const override
1501 switch (widget) {
1502 case WID_BV_CAPTION:
1503 if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
1504 const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
1505 SetDParam(0, rti->strings.build_caption);
1506 } else if (this->vehicle_type == VEH_ROAD && !this->listview_mode) {
1507 const RoadTypeInfo *rti = GetRoadTypeInfo(this->filter.roadtype);
1508 SetDParam(0, rti->strings.build_caption);
1509 } else {
1510 SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
1512 break;
1514 case WID_BV_SORT_DROPDOWN:
1515 SetDParam(0, _engine_sort_listing[this->vehicle_type][this->sort_criteria]);
1516 break;
1518 case WID_BV_CARGO_FILTER_DROPDOWN:
1519 SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]);
1520 break;
1522 case WID_BV_SHOW_HIDE: {
1523 const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine);
1524 if (e != nullptr && e->IsHidden(_local_company)) {
1525 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
1526 } else {
1527 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1529 break;
1534 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1536 switch (widget) {
1537 case WID_BV_LIST:
1538 resize->height = GetEngineListHeight(this->vehicle_type);
1539 size->height = 3 * resize->height;
1540 size->width = max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165);
1541 break;
1543 case WID_BV_PANEL:
1544 size->height = this->details_height;
1545 break;
1547 case WID_BV_SORT_ASCENDING_DESCENDING: {
1548 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1549 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1550 d.height += padding.height;
1551 *size = maxdim(*size, d);
1552 break;
1555 case WID_BV_BUILD:
1556 *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type);
1557 *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type));
1558 size->width += padding.width;
1559 size->height += padding.height;
1560 break;
1562 case WID_BV_SHOW_HIDE:
1563 *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1564 *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
1565 size->width += padding.width;
1566 size->height += padding.height;
1567 break;
1571 void DrawWidget(const Rect &r, int widget) const override
1573 switch (widget) {
1574 case WID_BV_LIST:
1575 DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, &this->eng_list, this->vscroll->GetPosition(), min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (uint)this->eng_list.size()), this->sel_engine, false, DEFAULT_GROUP);
1576 break;
1578 case WID_BV_SORT_ASCENDING_DESCENDING:
1579 this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
1580 break;
1584 void OnPaint() override
1586 this->GenerateBuildList();
1587 this->vscroll->SetCount((uint)this->eng_list.size());
1589 this->SetWidgetsDisabledState(this->sel_engine == INVALID_ENGINE, WID_BV_SHOW_HIDE, WID_BV_BUILD, WID_BV_RENAME, WIDGET_LIST_END);
1591 this->DrawWidgets();
1593 if (!this->IsShaded()) {
1594 int needed_height = this->details_height;
1595 /* Draw details panels. */
1596 if (this->sel_engine != INVALID_ENGINE) {
1597 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
1598 int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
1599 nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine, this->te);
1600 needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
1602 if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
1603 int resize = needed_height - this->details_height;
1604 this->details_height = needed_height;
1605 this->ReInit(0, resize);
1606 return;
1611 void OnQueryTextFinished(char *str) override
1613 if (str == nullptr) return;
1615 DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str);
1618 void OnDropdownSelect(int widget, int index) override
1620 switch (widget) {
1621 case WID_BV_SORT_DROPDOWN:
1622 if (this->sort_criteria != index) {
1623 this->sort_criteria = index;
1624 _engine_sort_last_criteria[this->vehicle_type] = this->sort_criteria;
1625 this->eng_list.ForceRebuild();
1627 break;
1629 case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
1630 if (this->cargo_filter_criteria != index) {
1631 this->cargo_filter_criteria = index;
1632 _engine_sort_last_cargo_criteria[this->vehicle_type] = this->cargo_filter[this->cargo_filter_criteria];
1633 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1634 this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1635 this->eng_list.ForceRebuild();
1636 this->SelectEngine(this->sel_engine);
1638 break;
1640 this->SetDirty();
1643 void OnResize() override
1645 this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
1649 static WindowDesc _build_vehicle_desc(
1650 WDP_AUTO, "build_vehicle", 240, 268,
1651 WC_BUILD_VEHICLE, WC_NONE,
1652 WDF_CONSTRUCTION,
1653 _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets)
1656 void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
1658 /* We want to be able to open both Available Train as Available Ships,
1659 * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1660 * As it always is a low value, it won't collide with any real tile
1661 * number. */
1662 uint num = (tile == INVALID_TILE) ? (int)type : tile;
1664 assert(IsCompanyBuildableVehicleType(type));
1666 DeleteWindowById(WC_BUILD_VEHICLE, num);
1668 new BuildVehicleWindow(&_build_vehicle_desc, tile, type);