Rearrange storage of reserved tracks for railway tiles
[openttd/fttd.git] / src / build_vehicle_gui.cpp
blob36619f91c4688819755b693ad8eb146a2925b1b5
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file build_vehicle_gui.cpp GUI for building vehicles. */
12 #include "stdafx.h"
13 #include "engine_base.h"
14 #include "engine_func.h"
15 #include "station_base.h"
16 #include "network/network.h"
17 #include "articulated_vehicles.h"
18 #include "textbuf_gui.h"
19 #include "command_func.h"
20 #include "company_func.h"
21 #include "vehicle_gui.h"
22 #include "newgrf_engine.h"
23 #include "newgrf_text.h"
24 #include "group.h"
25 #include "string_func.h"
26 #include "strings_func.h"
27 #include "window_func.h"
28 #include "date_func.h"
29 #include "vehicle_func.h"
30 #include "widgets/dropdown_func.h"
31 #include "engine_gui.h"
32 #include "cargotype.h"
33 #include "core/geometry_func.hpp"
34 #include "autoreplace_func.h"
35 #include "rail.h"
36 #include "map/rail.h"
37 #include "map/road.h"
39 #include "widgets/build_vehicle_widget.h"
41 #include "table/strings.h"
43 /**
44 * Get the height of a single 'entry' in the engine lists.
45 * @param type the vehicle type to get the height of
46 * @return the height for the entry
48 uint GetEngineListHeight(VehicleType type)
50 return max<uint>(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM, GetVehicleImageCellSize(type, EIT_PURCHASE).height);
53 static const NWidgetPart _nested_build_vehicle_widgets[] = {
54 NWidget(NWID_HORIZONTAL),
55 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
56 NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
57 NWidget(WWT_SHADEBOX, COLOUR_GREY),
58 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
59 NWidget(WWT_STICKYBOX, COLOUR_GREY),
60 EndContainer(),
61 NWidget(WWT_PANEL, COLOUR_GREY),
62 NWidget(NWID_HORIZONTAL),
63 NWidget(NWID_VERTICAL),
64 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASSENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
65 NWidget(NWID_SPACER), SetFill(1, 1),
66 EndContainer(),
67 NWidget(NWID_VERTICAL),
68 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
69 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
70 EndContainer(),
71 EndContainer(),
72 EndContainer(),
73 /* Vehicle list. */
74 NWidget(NWID_HORIZONTAL),
75 NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR),
76 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR),
77 EndContainer(),
78 /* Panel with details. */
79 NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
80 /* Build/rename buttons, resize button. */
81 NWidget(NWID_HORIZONTAL),
82 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL),
83 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0),
84 EndContainer(),
85 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0),
86 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
87 EndContainer(),
90 /** Special cargo filter criteria */
91 static const CargoID CF_ANY = CT_NO_REFIT; ///< Show all vehicles independent of carried cargo (i.e. no filtering)
92 static const CargoID CF_NONE = CT_INVALID; ///< Show only vehicles which do not carry cargo (e.g. train engines)
94 static bool _internal_sort_order; ///< false = descending, true = ascending
95 static byte _last_sort_criteria[] = {0, 0, 0, 0};
96 static bool _last_sort_order[] = {false, false, false, false};
97 static CargoID _last_filter_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_ANY};
99 /**
100 * Determines order of engines by engineID
101 * @param *a first engine to compare
102 * @param *b second engine to compare
103 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
105 static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b)
107 int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position;
109 return _internal_sort_order ? -r : r;
113 * Determines order of engines by introduction date
114 * @param *a first engine to compare
115 * @param *b second engine to compare
116 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
118 static int CDECL EngineIntroDateSorter(const EngineID *a, const EngineID *b)
120 const int va = Engine::Get(*a)->intro_date;
121 const int vb = Engine::Get(*b)->intro_date;
122 const int r = va - vb;
124 /* Use EngineID to sort instead since we want consistent sorting */
125 if (r == 0) return EngineNumberSorter(a, b);
126 return _internal_sort_order ? -r : r;
130 * Determines order of engines by name
131 * @param *a first engine to compare
132 * @param *b second engine to compare
133 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
135 static int CDECL EngineNameSorter(const EngineID *a, const EngineID *b)
137 static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
138 static char last_name[2][64] = { "\0", "\0" };
140 const EngineID va = *a;
141 const EngineID vb = *b;
143 if (va != last_engine[0]) {
144 last_engine[0] = va;
145 SetDParam(0, va);
146 GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
149 if (vb != last_engine[1]) {
150 last_engine[1] = vb;
151 SetDParam(0, vb);
152 GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
155 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
157 /* Use EngineID to sort instead since we want consistent sorting */
158 if (r == 0) return EngineNumberSorter(a, b);
159 return _internal_sort_order ? -r : r;
163 * Determines order of engines by reliability
164 * @param *a first engine to compare
165 * @param *b second engine to compare
166 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
168 static int CDECL EngineReliabilitySorter(const EngineID *a, const EngineID *b)
170 const int va = Engine::Get(*a)->reliability;
171 const int vb = Engine::Get(*b)->reliability;
172 const int r = va - vb;
174 /* Use EngineID to sort instead since we want consistent sorting */
175 if (r == 0) return EngineNumberSorter(a, b);
176 return _internal_sort_order ? -r : r;
180 * Determines order of engines by purchase cost
181 * @param *a first engine to compare
182 * @param *b second engine to compare
183 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
185 static int CDECL EngineCostSorter(const EngineID *a, const EngineID *b)
187 Money va = Engine::Get(*a)->GetCost();
188 Money vb = Engine::Get(*b)->GetCost();
189 int r = ClampToI32(va - vb);
191 /* Use EngineID to sort instead since we want consistent sorting */
192 if (r == 0) return EngineNumberSorter(a, b);
193 return _internal_sort_order ? -r : r;
197 * Determines order of engines by speed
198 * @param *a first engine to compare
199 * @param *b second engine to compare
200 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
202 static int CDECL EngineSpeedSorter(const EngineID *a, const EngineID *b)
204 int va = Engine::Get(*a)->GetDisplayMaxSpeed();
205 int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
206 int r = va - vb;
208 /* Use EngineID to sort instead since we want consistent sorting */
209 if (r == 0) return EngineNumberSorter(a, b);
210 return _internal_sort_order ? -r : r;
214 * Determines order of engines by power
215 * @param *a first engine to compare
216 * @param *b second engine to compare
217 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
219 static int CDECL EnginePowerSorter(const EngineID *a, const EngineID *b)
221 int va = Engine::Get(*a)->GetPower();
222 int vb = Engine::Get(*b)->GetPower();
223 int r = va - vb;
225 /* Use EngineID to sort instead since we want consistent sorting */
226 if (r == 0) return EngineNumberSorter(a, b);
227 return _internal_sort_order ? -r : r;
231 * Determines order of engines by tractive effort
232 * @param *a first engine to compare
233 * @param *b second engine to compare
234 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
236 static int CDECL EngineTractiveEffortSorter(const EngineID *a, const EngineID *b)
238 int va = Engine::Get(*a)->GetDisplayMaxTractiveEffort();
239 int vb = Engine::Get(*b)->GetDisplayMaxTractiveEffort();
240 int r = va - vb;
242 /* Use EngineID to sort instead since we want consistent sorting */
243 if (r == 0) return EngineNumberSorter(a, b);
244 return _internal_sort_order ? -r : r;
248 * Determines order of engines by running costs
249 * @param *a first engine to compare
250 * @param *b second engine to compare
251 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
253 static int CDECL EngineRunningCostSorter(const EngineID *a, const EngineID *b)
255 Money va = Engine::Get(*a)->GetRunningCost();
256 Money vb = Engine::Get(*b)->GetRunningCost();
257 int r = ClampToI32(va - vb);
259 /* Use EngineID to sort instead since we want consistent sorting */
260 if (r == 0) return EngineNumberSorter(a, b);
261 return _internal_sort_order ? -r : r;
265 * Determines order of engines by running costs
266 * @param *a first engine to compare
267 * @param *b second engine to compare
268 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
270 static int CDECL EnginePowerVsRunningCostSorter(const EngineID *a, const EngineID *b)
272 const Engine *e_a = Engine::Get(*a);
273 const Engine *e_b = Engine::Get(*b);
275 /* Here we are using a few tricks to get the right sort.
276 * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
277 * we will actually calculate cunning cost/power (to make it more than 1).
278 * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
279 * Another thing is that both power and running costs should be doubled for multiheaded engines.
280 * Since it would be multiplying with 2 in both numerator and denominator, it will even themselves out and we skip checking for multiheaded. */
281 Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower());
282 Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower());
283 int r = ClampToI32(vb - va);
285 /* Use EngineID to sort instead since we want consistent sorting */
286 if (r == 0) return EngineNumberSorter(a, b);
287 return _internal_sort_order ? -r : r;
290 /* Train sorting functions */
293 * Determines order of train engines by capacity
294 * @param *a first engine to compare
295 * @param *b second engine to compare
296 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
298 static int CDECL TrainEngineCapacitySorter(const EngineID *a, const EngineID *b)
300 const RailVehicleInfo *rvi_a = RailVehInfo(*a);
301 const RailVehicleInfo *rvi_b = RailVehInfo(*b);
303 int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
304 int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
305 int r = va - vb;
307 /* Use EngineID to sort instead since we want consistent sorting */
308 if (r == 0) return EngineNumberSorter(a, b);
309 return _internal_sort_order ? -r : r;
313 * Determines order of train engines by engine / wagon
314 * @param *a first engine to compare
315 * @param *b second engine to compare
316 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
318 static int CDECL TrainEnginesThenWagonsSorter(const EngineID *a, const EngineID *b)
320 int val_a = (RailVehInfo(*a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
321 int val_b = (RailVehInfo(*b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
322 int r = val_a - val_b;
324 /* Use EngineID to sort instead since we want consistent sorting */
325 if (r == 0) return EngineNumberSorter(a, b);
326 return _internal_sort_order ? -r : r;
329 /* Road vehicle sorting functions */
332 * Determines order of road vehicles by capacity
333 * @param *a first engine to compare
334 * @param *b second engine to compare
335 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
337 static int CDECL RoadVehEngineCapacitySorter(const EngineID *a, const EngineID *b)
339 int va = GetTotalCapacityOfArticulatedParts(*a);
340 int vb = GetTotalCapacityOfArticulatedParts(*b);
341 int r = va - vb;
343 /* Use EngineID to sort instead since we want consistent sorting */
344 if (r == 0) return EngineNumberSorter(a, b);
345 return _internal_sort_order ? -r : r;
348 /* Ship vehicle sorting functions */
351 * Determines order of ships by capacity
352 * @param *a first engine to compare
353 * @param *b second engine to compare
354 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
356 static int CDECL ShipEngineCapacitySorter(const EngineID *a, const EngineID *b)
358 const Engine *e_a = Engine::Get(*a);
359 const Engine *e_b = Engine::Get(*b);
361 int va = e_a->GetDisplayDefaultCapacity();
362 int vb = e_b->GetDisplayDefaultCapacity();
363 int r = va - vb;
365 /* Use EngineID to sort instead since we want consistent sorting */
366 if (r == 0) return EngineNumberSorter(a, b);
367 return _internal_sort_order ? -r : r;
370 /* Aircraft sorting functions */
373 * Determines order of aircraft by cargo
374 * @param *a first engine to compare
375 * @param *b second engine to compare
376 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
378 static int CDECL AircraftEngineCargoSorter(const EngineID *a, const EngineID *b)
380 const Engine *e_a = Engine::Get(*a);
381 const Engine *e_b = Engine::Get(*b);
383 uint16 mail_a, mail_b;
384 int va = e_a->GetDisplayDefaultCapacity(&mail_a);
385 int vb = e_b->GetDisplayDefaultCapacity(&mail_b);
386 int r = va - vb;
388 if (r == 0) {
389 /* The planes have the same passenger capacity. Check mail capacity instead */
390 r = mail_a - mail_b;
392 if (r == 0) {
393 /* Use EngineID to sort instead since we want consistent sorting */
394 return EngineNumberSorter(a, b);
397 return _internal_sort_order ? -r : r;
401 * Determines order of aircraft by range.
402 * @param *a first engine to compare.
403 * @param *b second engine to compare.
404 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal.
406 static int CDECL AircraftRangeSorter(const EngineID *a, const EngineID *b)
408 uint16 r_a = Engine::Get(*a)->GetRange();
409 uint16 r_b = Engine::Get(*b)->GetRange();
411 int r = r_a - r_b;
413 /* Use EngineID to sort instead since we want consistent sorting */
414 if (r == 0) return EngineNumberSorter(a, b);
415 return _internal_sort_order ? -r : r;
418 static EngList_SortTypeFunction * const _sorter[][11] = {{
419 /* Trains */
420 &EngineNumberSorter,
421 &EngineCostSorter,
422 &EngineSpeedSorter,
423 &EnginePowerSorter,
424 &EngineTractiveEffortSorter,
425 &EngineIntroDateSorter,
426 &EngineNameSorter,
427 &EngineRunningCostSorter,
428 &EnginePowerVsRunningCostSorter,
429 &EngineReliabilitySorter,
430 &TrainEngineCapacitySorter,
431 }, {
432 /* Road vehicles */
433 &EngineNumberSorter,
434 &EngineCostSorter,
435 &EngineSpeedSorter,
436 &EnginePowerSorter,
437 &EngineTractiveEffortSorter,
438 &EngineIntroDateSorter,
439 &EngineNameSorter,
440 &EngineRunningCostSorter,
441 &EnginePowerVsRunningCostSorter,
442 &EngineReliabilitySorter,
443 &RoadVehEngineCapacitySorter,
444 }, {
445 /* Ships */
446 &EngineNumberSorter,
447 &EngineCostSorter,
448 &EngineSpeedSorter,
449 &EngineIntroDateSorter,
450 &EngineNameSorter,
451 &EngineRunningCostSorter,
452 &EngineReliabilitySorter,
453 &ShipEngineCapacitySorter,
454 }, {
455 /* Aircraft */
456 &EngineNumberSorter,
457 &EngineCostSorter,
458 &EngineSpeedSorter,
459 &EngineIntroDateSorter,
460 &EngineNameSorter,
461 &EngineRunningCostSorter,
462 &EngineReliabilitySorter,
463 &AircraftEngineCargoSorter,
464 &AircraftRangeSorter,
467 static const StringID _sort_listing[][12] = {{
468 /* Trains */
469 STR_SORT_BY_ENGINE_ID,
470 STR_SORT_BY_COST,
471 STR_SORT_BY_MAX_SPEED,
472 STR_SORT_BY_POWER,
473 STR_SORT_BY_TRACTIVE_EFFORT,
474 STR_SORT_BY_INTRO_DATE,
475 STR_SORT_BY_NAME,
476 STR_SORT_BY_RUNNING_COST,
477 STR_SORT_BY_POWER_VS_RUNNING_COST,
478 STR_SORT_BY_RELIABILITY,
479 STR_SORT_BY_CARGO_CAPACITY,
480 INVALID_STRING_ID
481 }, {
482 /* Road vehicles */
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 /* Ships */
497 STR_SORT_BY_ENGINE_ID,
498 STR_SORT_BY_COST,
499 STR_SORT_BY_MAX_SPEED,
500 STR_SORT_BY_INTRO_DATE,
501 STR_SORT_BY_NAME,
502 STR_SORT_BY_RUNNING_COST,
503 STR_SORT_BY_RELIABILITY,
504 STR_SORT_BY_CARGO_CAPACITY,
505 INVALID_STRING_ID
506 }, {
507 /* Aircraft */
508 STR_SORT_BY_ENGINE_ID,
509 STR_SORT_BY_COST,
510 STR_SORT_BY_MAX_SPEED,
511 STR_SORT_BY_INTRO_DATE,
512 STR_SORT_BY_NAME,
513 STR_SORT_BY_RUNNING_COST,
514 STR_SORT_BY_RELIABILITY,
515 STR_SORT_BY_CARGO_CAPACITY,
516 STR_SORT_BY_RANGE,
517 INVALID_STRING_ID
520 /** Cargo filter functions */
521 static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid)
523 if (cid == CF_ANY) return true;
524 uint32 refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true) & _standard_cargo_mask;
525 return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
528 static GUIEngineList::FilterFunction * const _filter_funcs[] = {
529 &CargoFilter,
532 static int DrawCargoCapacityInfo(int left, int right, int y, EngineID engine, bool refittable)
534 CargoArray cap = GetCapacityOfArticulatedParts(engine);
536 for (CargoID c = 0; c < NUM_CARGO; c++) {
537 if (cap[c] == 0) continue;
539 SetDParam(0, c);
540 SetDParam(1, cap[c]);
541 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
542 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
543 y += FONT_HEIGHT_NORMAL;
545 /* Only show as refittable once */
546 refittable = false;
549 return y;
552 /* Draw rail wagon specific details */
553 static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
555 const Engine *e = Engine::Get(engine_number);
557 /* Purchase cost */
558 SetDParam(0, e->GetCost());
559 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
560 y += FONT_HEIGHT_NORMAL;
562 /* Wagon weight - (including cargo) */
563 uint weight = e->GetDisplayWeight();
564 SetDParam(0, weight);
565 uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * GetTotalCapacityOfArticulatedParts(engine_number) / 16 : 0);
566 SetDParam(1, cargo_weight + weight);
567 DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
568 y += FONT_HEIGHT_NORMAL;
570 /* Wagon speed limit, displayed if above zero */
571 if (_settings_game.vehicle.wagon_speed_limits) {
572 uint max_speed = e->GetDisplayMaxSpeed();
573 if (max_speed > 0) {
574 SetDParam(0, max_speed);
575 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED);
576 y += FONT_HEIGHT_NORMAL;
580 /* Running cost */
581 if (rvi->running_cost_class != INVALID_PRICE) {
582 SetDParam(0, e->GetRunningCost());
583 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
584 y += FONT_HEIGHT_NORMAL;
587 return y;
590 /* Draw locomotive specific details */
591 static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
593 const Engine *e = Engine::Get(engine_number);
595 /* Purchase Cost - Engine weight */
596 SetDParam(0, e->GetCost());
597 SetDParam(1, e->GetDisplayWeight());
598 DrawString(left, right, y, STR_PURCHASE_INFO_COST_WEIGHT);
599 y += FONT_HEIGHT_NORMAL;
601 /* Max speed - Engine power */
602 SetDParam(0, e->GetDisplayMaxSpeed());
603 SetDParam(1, e->GetPower());
604 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
605 y += FONT_HEIGHT_NORMAL;
607 /* Max tractive effort - not applicable if old acceleration or maglev */
608 if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != 2) {
609 SetDParam(0, e->GetDisplayMaxTractiveEffort());
610 DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
611 y += FONT_HEIGHT_NORMAL;
614 /* Running cost */
615 if (rvi->running_cost_class != INVALID_PRICE) {
616 SetDParam(0, e->GetRunningCost());
617 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
618 y += FONT_HEIGHT_NORMAL;
621 /* Powered wagons power - Powered wagons extra weight */
622 if (rvi->pow_wag_power != 0) {
623 SetDParam(0, rvi->pow_wag_power);
624 SetDParam(1, rvi->pow_wag_weight);
625 DrawString(left, right, y, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT);
626 y += FONT_HEIGHT_NORMAL;
629 return y;
632 /* Draw road vehicle specific details */
633 static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_number)
635 const Engine *e = Engine::Get(engine_number);
637 if (_settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL) {
638 /* Purchase Cost */
639 SetDParam(0, e->GetCost());
640 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
641 y += FONT_HEIGHT_NORMAL;
643 /* Road vehicle weight - (including cargo) */
644 int16 weight = e->GetDisplayWeight();
645 SetDParam(0, weight);
646 uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * GetTotalCapacityOfArticulatedParts(engine_number) / 16 : 0);
647 SetDParam(1, cargo_weight + weight);
648 DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
649 y += FONT_HEIGHT_NORMAL;
651 /* Max speed - Engine power */
652 SetDParam(0, e->GetDisplayMaxSpeed());
653 SetDParam(1, e->GetPower());
654 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
655 y += FONT_HEIGHT_NORMAL;
657 /* Max tractive effort */
658 SetDParam(0, e->GetDisplayMaxTractiveEffort());
659 DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
660 y += FONT_HEIGHT_NORMAL;
661 } else {
662 /* Purchase cost - Max speed */
663 SetDParam(0, e->GetCost());
664 SetDParam(1, e->GetDisplayMaxSpeed());
665 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
666 y += FONT_HEIGHT_NORMAL;
669 /* Running cost */
670 SetDParam(0, e->GetRunningCost());
671 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
672 y += FONT_HEIGHT_NORMAL;
674 return y;
677 /* Draw ship specific details */
678 static int DrawShipPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
680 const Engine *e = Engine::Get(engine_number);
682 /* Purchase cost - Max speed */
683 uint raw_speed = e->GetDisplayMaxSpeed();
684 uint ocean_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, true);
685 uint canal_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, false);
687 SetDParam(0, e->GetCost());
688 if (ocean_speed == canal_speed) {
689 SetDParam(1, ocean_speed);
690 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
691 y += FONT_HEIGHT_NORMAL;
692 } else {
693 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
694 y += FONT_HEIGHT_NORMAL;
696 SetDParam(0, ocean_speed);
697 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_OCEAN);
698 y += FONT_HEIGHT_NORMAL;
700 SetDParam(0, canal_speed);
701 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_CANAL);
702 y += FONT_HEIGHT_NORMAL;
705 /* Cargo type + capacity */
706 SetDParam(0, e->GetDefaultCargoType());
707 SetDParam(1, e->GetDisplayDefaultCapacity());
708 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
709 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
710 y += FONT_HEIGHT_NORMAL;
712 /* Running cost */
713 SetDParam(0, e->GetRunningCost());
714 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
715 y += FONT_HEIGHT_NORMAL;
717 return y;
720 /* Draw aircraft specific details */
721 static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
723 const Engine *e = Engine::Get(engine_number);
724 CargoID cargo = e->GetDefaultCargoType();
726 /* Purchase cost - Max speed */
727 SetDParam(0, e->GetCost());
728 SetDParam(1, e->GetDisplayMaxSpeed());
729 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
730 y += FONT_HEIGHT_NORMAL;
732 /* Cargo capacity */
733 uint16 mail_capacity;
734 uint capacity = e->GetDisplayDefaultCapacity(&mail_capacity);
735 if (mail_capacity > 0) {
736 SetDParam(0, cargo);
737 SetDParam(1, capacity);
738 SetDParam(2, CT_MAIL);
739 SetDParam(3, mail_capacity);
740 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
741 } else {
742 /* Note, if the default capacity is selected by the refit capacity
743 * callback, then the capacity shown is likely to be incorrect. */
744 SetDParam(0, cargo);
745 SetDParam(1, capacity);
746 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
747 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
749 y += FONT_HEIGHT_NORMAL;
751 /* Running cost */
752 SetDParam(0, e->GetRunningCost());
753 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
754 y += FONT_HEIGHT_NORMAL;
756 uint16 range = e->GetRange();
757 if (range != 0) {
758 SetDParam(0, range);
759 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_RANGE);
760 y += FONT_HEIGHT_NORMAL;
763 return y;
767 * Display additional text from NewGRF in the purchase information window
768 * @param left Left border of text bounding box
769 * @param right Right border of text bounding box
770 * @param y Top border of text bounding box
771 * @param engine Engine to query the additional purchase information for
772 * @return Bottom border of text bounding box
774 static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
776 uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, NULL);
777 if (callback == CALLBACK_FAILED || callback == 0x400) return y;
778 if (callback > 0x400) {
779 ErrorUnknownCallbackResult(Engine::Get(engine)->GetGRFID(), CBID_VEHICLE_ADDITIONAL_TEXT, callback);
780 return y;
783 StartTextRefStackUsage(6);
784 uint result = DrawStringMultiLine(left, right, y, INT32_MAX, GetGRFStringID(Engine::Get(engine)->GetGRFID(), 0xD000 + callback), TC_BLACK);
785 StopTextRefStackUsage();
786 return result;
790 * Draw the purchase info details of a vehicle at a given location.
791 * @param left,right,y location where to draw the info
792 * @param engine_number the engine of which to draw the info of
793 * @return y after drawing all the text
795 int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number)
797 const Engine *e = Engine::Get(engine_number);
798 YearMonthDay ymd;
799 ConvertDateToYMD(e->intro_date, &ymd);
800 bool refittable = IsArticulatedVehicleRefittable(engine_number);
801 bool articulated_cargo = false;
803 switch (e->type) {
804 default: NOT_REACHED();
805 case VEH_TRAIN:
806 if (e->u.rail.railveh_type == RAILVEH_WAGON) {
807 y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail);
808 } else {
809 y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail);
811 articulated_cargo = true;
812 break;
814 case VEH_ROAD:
815 y = DrawRoadVehPurchaseInfo(left, right, y, engine_number);
816 articulated_cargo = true;
817 break;
819 case VEH_SHIP:
820 y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable);
821 break;
823 case VEH_AIRCRAFT:
824 y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable);
825 break;
828 if (articulated_cargo) {
829 /* Cargo type + capacity, or N/A */
830 int new_y = DrawCargoCapacityInfo(left, right, y, engine_number, refittable);
832 if (new_y == y) {
833 SetDParam(0, CT_INVALID);
834 SetDParam(2, STR_EMPTY);
835 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
836 y += FONT_HEIGHT_NORMAL;
837 } else {
838 y = new_y;
842 /* Draw details that apply to all types except rail wagons. */
843 if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
844 /* Design date - Life length */
845 SetDParam(0, ymd.year);
846 SetDParam(1, e->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR);
847 DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
848 y += FONT_HEIGHT_NORMAL;
850 /* Reliability */
851 SetDParam(0, ToPercent16(e->reliability));
852 DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
853 y += FONT_HEIGHT_NORMAL;
856 if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
858 /* Additional text from NewGRF */
859 y = ShowAdditionalText(left, right, y, engine_number);
861 return y;
865 * Engine drawing loop
866 * @param type Type of vehicle (VEH_*)
867 * @param l The left most location of the list
868 * @param r The right most location of the list
869 * @param y The top most location of the list
870 * @param eng_list What engines to draw
871 * @param min where to start in the list
872 * @param max where in the list to end
873 * @param selected_id what engine to highlight as selected, if any
874 * @param show_count Whether to show the amount of engines or not
875 * @param selected_group the group to list the engines of
877 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)
879 static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
881 /* Obligatory sanity checks! */
882 assert(max <= eng_list->Length());
884 bool rtl = _current_text_dir == TD_RTL;
885 int step_size = GetEngineListHeight(type);
886 int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
887 int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
888 int sprite_width = sprite_left + sprite_right;
890 int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
891 int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
893 Dimension replace_icon = {0, 0};
894 int count_width = 0;
895 if (show_count) {
896 replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
897 SetDParamMaxDigits(0, 3, FS_SMALL);
898 count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
901 int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
902 int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
903 int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
904 int count_left = l;
905 int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
907 int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
908 int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
909 int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
911 for (; min < max; min++, y += step_size) {
912 const EngineID engine = (*eng_list)[min];
913 /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
914 const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
916 SetDParam(0, engine);
917 DrawString(text_left, text_right, y + normal_text_y_offset, STR_ENGINE_NAME, engine == selected_id ? TC_WHITE : TC_BLACK);
918 DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
919 if (show_count) {
920 SetDParam(0, num_engines);
921 DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
922 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);
928 struct BuildVehicleWindow : Window {
929 VehicleType vehicle_type;
930 union {
931 RailTypeByte railtype;
932 RoadTypes roadtypes;
933 } filter;
934 bool descending_sort_order;
935 byte sort_criteria;
936 bool listview_mode;
937 EngineID sel_engine;
938 EngineID rename_engine;
939 GUIEngineList eng_list;
940 CargoID cargo_filter[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
941 StringID cargo_filter_texts[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
942 byte cargo_filter_criteria; ///< Selected cargo filter
943 int details_height; ///< Minimal needed height of the details panels (found so far).
944 Scrollbar *vscroll;
946 BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
948 this->vehicle_type = type;
949 this->window_number = tile == INVALID_TILE ? (int)type : tile;
951 this->sel_engine = INVALID_ENGINE;
953 this->sort_criteria = _last_sort_criteria[type];
954 this->descending_sort_order = _last_sort_order[type];
956 switch (type) {
957 default: NOT_REACHED();
958 case VEH_TRAIN:
959 this->filter.railtype = (tile == INVALID_TILE) ? RAILTYPE_END : GetRailType(tile);
960 break;
961 case VEH_ROAD:
962 this->filter.roadtypes = (tile == INVALID_TILE) ? ROADTYPES_ALL : GetRoadTypes(tile);
963 case VEH_SHIP:
964 case VEH_AIRCRAFT:
965 break;
968 this->listview_mode = (this->window_number <= VEH_END);
970 this->CreateNestedTree();
972 this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
974 /* If we are just viewing the list of vehicles, we do not need the Build button.
975 * So we just hide it, and enlarge the Rename button by the now vacant place. */
976 if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE);
978 /* disable renaming engines in network games if you are not the server */
979 this->SetWidgetDisabledState(WID_BV_RENAME, _networking && !_network_server);
981 NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_LIST);
982 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
984 widget = this->GetWidget<NWidgetCore>(WID_BV_BUILD);
985 widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + type;
986 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + type;
988 widget = this->GetWidget<NWidgetCore>(WID_BV_RENAME);
989 widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
990 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
992 this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
994 this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
996 this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
998 this->eng_list.ForceRebuild();
999 this->GenerateBuildList(); // generate the list, since we need it in the next line
1000 /* Select the first engine in the list as default when opening the window */
1001 if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0];
1004 /** Populate the filter list and set the cargo filter criteria. */
1005 void SetCargoFilterArray()
1007 uint filter_items = 0;
1009 /* Add item for disabling filtering. */
1010 this->cargo_filter[filter_items] = CF_ANY;
1011 this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
1012 filter_items++;
1014 /* Add item for vehicles not carrying anything, e.g. train engines.
1015 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1016 if (this->vehicle_type == VEH_TRAIN) {
1017 this->cargo_filter[filter_items] = CF_NONE;
1018 this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
1019 filter_items++;
1022 /* Collect available cargo types for filtering. */
1023 const CargoSpec *cs;
1024 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) {
1025 this->cargo_filter[filter_items] = cs->Index();
1026 this->cargo_filter_texts[filter_items] = cs->name;
1027 filter_items++;
1030 /* Terminate the filter list. */
1031 this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1033 /* If not found, the cargo criteria will be set to all cargoes. */
1034 this->cargo_filter_criteria = 0;
1036 /* Find the last cargo filter criteria. */
1037 for (uint i = 0; i < filter_items; i++) {
1038 if (this->cargo_filter[i] == _last_filter_criteria[this->vehicle_type]) {
1039 this->cargo_filter_criteria = i;
1040 break;
1044 this->eng_list.SetFilterFuncs(_filter_funcs);
1045 this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1048 void OnInit()
1050 this->SetCargoFilterArray();
1053 /** Filter the engine list against the currently selected cargo filter */
1054 void FilterEngineList()
1056 this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
1057 if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine
1058 this->sel_engine = INVALID_ENGINE;
1059 } else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list
1060 this->sel_engine = this->eng_list[0];
1064 /** Filter a single engine */
1065 bool FilterSingleEngine(EngineID eid)
1067 CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
1068 return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
1071 /* Figure out what train EngineIDs to put in the list */
1072 void GenerateBuildTrainList()
1074 EngineID sel_id = INVALID_ENGINE;
1075 int num_engines = 0;
1076 int num_wagons = 0;
1078 this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number);
1080 this->eng_list.Clear();
1082 /* Make list of all available train engines and wagons.
1083 * Also check to see if the previously selected engine is still available,
1084 * and if not, reset selection to INVALID_ENGINE. This could be the case
1085 * when engines become obsolete and are removed */
1086 const Engine *e;
1087 FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) {
1088 EngineID eid = e->index;
1089 const RailVehicleInfo *rvi = &e->u.rail;
1091 if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
1092 if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
1094 /* Filter now! So num_engines and num_wagons is valid */
1095 if (!FilterSingleEngine(eid)) continue;
1097 *this->eng_list.Append() = eid;
1099 if (rvi->railveh_type != RAILVEH_WAGON) {
1100 num_engines++;
1101 } else {
1102 num_wagons++;
1105 if (eid == this->sel_engine) sel_id = eid;
1108 this->sel_engine = sel_id;
1110 /* make engines first, and then wagons, sorted by selected sort_criteria */
1111 _internal_sort_order = false;
1112 EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
1114 /* and then sort engines */
1115 _internal_sort_order = this->descending_sort_order;
1116 EngList_SortPartial(&this->eng_list, _sorter[0][this->sort_criteria], 0, num_engines);
1118 /* and finally sort wagons */
1119 EngList_SortPartial(&this->eng_list, _sorter[0][this->sort_criteria], num_engines, num_wagons);
1122 /* Figure out what road vehicle EngineIDs to put in the list */
1123 void GenerateBuildRoadVehList()
1125 EngineID sel_id = INVALID_ENGINE;
1127 this->eng_list.Clear();
1129 const Engine *e;
1130 FOR_ALL_ENGINES_OF_TYPE(e, VEH_ROAD) {
1131 EngineID eid = e->index;
1132 if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
1133 if (!HasBit(this->filter.roadtypes, HasBit(EngInfo(eid)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD)) continue;
1134 *this->eng_list.Append() = eid;
1136 if (eid == this->sel_engine) sel_id = eid;
1138 this->sel_engine = sel_id;
1141 /* Figure out what ship EngineIDs to put in the list */
1142 void GenerateBuildShipList()
1144 EngineID sel_id = INVALID_ENGINE;
1145 this->eng_list.Clear();
1147 const Engine *e;
1148 FOR_ALL_ENGINES_OF_TYPE(e, VEH_SHIP) {
1149 EngineID eid = e->index;
1150 if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
1151 *this->eng_list.Append() = eid;
1153 if (eid == this->sel_engine) sel_id = eid;
1155 this->sel_engine = sel_id;
1158 /* Figure out what aircraft EngineIDs to put in the list */
1159 void GenerateBuildAircraftList()
1161 EngineID sel_id = INVALID_ENGINE;
1163 this->eng_list.Clear();
1165 const Station *st = this->listview_mode ? NULL : Station::GetByTile(this->window_number);
1167 /* Make list of all available planes.
1168 * Also check to see if the previously selected plane is still available,
1169 * and if not, reset selection to INVALID_ENGINE. This could be the case
1170 * when planes become obsolete and are removed */
1171 const Engine *e;
1172 FOR_ALL_ENGINES_OF_TYPE(e, VEH_AIRCRAFT) {
1173 EngineID eid = e->index;
1174 if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
1175 /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1176 if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
1178 *this->eng_list.Append() = eid;
1179 if (eid == this->sel_engine) sel_id = eid;
1182 this->sel_engine = sel_id;
1185 /* Generate the list of vehicles */
1186 void GenerateBuildList()
1188 if (!this->eng_list.NeedRebuild()) return;
1189 switch (this->vehicle_type) {
1190 default: NOT_REACHED();
1191 case VEH_TRAIN:
1192 this->GenerateBuildTrainList();
1193 this->eng_list.Compact();
1194 this->eng_list.RebuildDone();
1195 return; // trains should not reach the last sorting
1196 case VEH_ROAD:
1197 this->GenerateBuildRoadVehList();
1198 break;
1199 case VEH_SHIP:
1200 this->GenerateBuildShipList();
1201 break;
1202 case VEH_AIRCRAFT:
1203 this->GenerateBuildAircraftList();
1204 break;
1207 this->FilterEngineList();
1209 _internal_sort_order = this->descending_sort_order;
1210 EngList_Sort(&this->eng_list, _sorter[this->vehicle_type][this->sort_criteria]);
1212 this->eng_list.Compact();
1213 this->eng_list.RebuildDone();
1216 void OnClick(Point pt, int widget, int click_count)
1218 switch (widget) {
1219 case WID_BV_SORT_ASSENDING_DESCENDING:
1220 this->descending_sort_order ^= true;
1221 _last_sort_order[this->vehicle_type] = this->descending_sort_order;
1222 this->eng_list.ForceRebuild();
1223 this->SetDirty();
1224 break;
1226 case WID_BV_LIST: {
1227 uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
1228 size_t num_items = this->eng_list.Length();
1229 this->sel_engine = (i < num_items) ? this->eng_list[i] : INVALID_ENGINE;
1230 this->SetDirty();
1231 if (click_count > 1 && !this->listview_mode) this->OnClick(pt, WID_BV_BUILD, 1);
1232 break;
1235 case WID_BV_SORT_DROPDOWN: { // Select sorting criteria dropdown menu
1236 uint32 hidden_mask = 0;
1237 /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
1238 if (this->vehicle_type == VEH_ROAD &&
1239 _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
1240 SetBit(hidden_mask, 3); // power
1241 SetBit(hidden_mask, 4); // tractive effort
1242 SetBit(hidden_mask, 8); // power by running costs
1244 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
1245 if (this->vehicle_type == VEH_TRAIN &&
1246 _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
1247 SetBit(hidden_mask, 4); // tractive effort
1249 ShowDropDownMenu(this, _sort_listing[this->vehicle_type], this->sort_criteria, WID_BV_SORT_DROPDOWN, 0, hidden_mask);
1250 break;
1253 case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
1254 ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_BV_CARGO_FILTER_DROPDOWN, 0, 0);
1255 break;
1257 case WID_BV_BUILD: {
1258 EngineID sel_eng = this->sel_engine;
1259 if (sel_eng != INVALID_ENGINE) {
1260 CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
1261 DoCommandP(this->window_number, sel_eng, 0, GetCmdBuildVeh(this->vehicle_type), callback);
1263 break;
1266 case WID_BV_RENAME: {
1267 EngineID sel_eng = this->sel_engine;
1268 if (sel_eng != INVALID_ENGINE) {
1269 this->rename_engine = sel_eng;
1270 SetDParam(0, sel_eng);
1271 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);
1273 break;
1279 * Some data on this window has become invalid.
1280 * @param data Information about the changed data.
1281 * @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.
1283 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1285 if (!gui_scope) return;
1286 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1287 if (this->vehicle_type == VEH_ROAD &&
1288 _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL &&
1289 this->sort_criteria > 7) {
1290 this->sort_criteria = 0;
1291 _last_sort_criteria[VEH_ROAD] = 0;
1293 this->eng_list.ForceRebuild();
1296 virtual void SetStringParameters(int widget) const
1298 switch (widget) {
1299 case WID_BV_CAPTION:
1300 if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
1301 const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
1302 SetDParam(0, rti->strings.build_caption);
1303 } else {
1304 SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
1306 break;
1308 case WID_BV_SORT_DROPDOWN:
1309 SetDParam(0, _sort_listing[this->vehicle_type][this->sort_criteria]);
1310 break;
1312 case WID_BV_CARGO_FILTER_DROPDOWN:
1313 SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]);
1317 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1319 switch (widget) {
1320 case WID_BV_LIST:
1321 resize->height = GetEngineListHeight(this->vehicle_type);
1322 size->height = 3 * resize->height;
1323 break;
1325 case WID_BV_PANEL:
1326 size->height = this->details_height;
1327 break;
1329 case WID_BV_SORT_ASSENDING_DESCENDING: {
1330 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1331 d.width += padding.width + WD_SORTBUTTON_ARROW_WIDTH * 2; // Doubled since the string is centred and it also looks better.
1332 d.height += padding.height;
1333 *size = maxdim(*size, d);
1334 break;
1339 virtual void DrawWidget(const Rect &r, int widget) const
1341 switch (widget) {
1342 case WID_BV_LIST:
1343 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(), this->eng_list.Length()), this->sel_engine, false, DEFAULT_GROUP);
1344 break;
1346 case WID_BV_SORT_ASSENDING_DESCENDING:
1347 this->DrawSortButtonState(WID_BV_SORT_ASSENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
1348 break;
1352 virtual void OnPaint()
1354 this->GenerateBuildList();
1355 this->vscroll->SetCount(this->eng_list.Length());
1357 this->DrawWidgets();
1359 if (!this->IsShaded()) {
1360 int needed_height = this->details_height;
1361 /* Draw details panels. */
1362 if (this->sel_engine != INVALID_ENGINE) {
1363 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
1364 int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
1365 nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine);
1366 needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
1368 if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
1369 int resize = needed_height - this->details_height;
1370 this->details_height = needed_height;
1371 this->ReInit(0, resize);
1372 return;
1377 virtual void OnQueryTextFinished(char *str)
1379 if (str == NULL) return;
1381 DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), NULL, str);
1384 virtual void OnDropdownSelect(int widget, int index)
1386 switch (widget) {
1387 case WID_BV_SORT_DROPDOWN:
1388 if (this->sort_criteria != index) {
1389 this->sort_criteria = index;
1390 _last_sort_criteria[this->vehicle_type] = this->sort_criteria;
1391 this->eng_list.ForceRebuild();
1393 break;
1395 case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
1396 if (this->cargo_filter_criteria != index) {
1397 this->cargo_filter_criteria = index;
1398 _last_filter_criteria[this->vehicle_type] = this->cargo_filter[this->cargo_filter_criteria];
1399 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1400 this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1401 this->eng_list.ForceRebuild();
1403 break;
1405 this->SetDirty();
1408 virtual void OnResize()
1410 this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
1414 static WindowDesc _build_vehicle_desc(
1415 WDP_AUTO, "build_vehicle", 240, 268,
1416 WC_BUILD_VEHICLE, WC_NONE,
1417 WDF_CONSTRUCTION,
1418 _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets)
1421 void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
1423 /* We want to be able to open both Available Train as Available Ships,
1424 * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1425 * As it always is a low value, it won't collide with any real tile
1426 * number. */
1427 uint num = (tile == INVALID_TILE) ? (int)type : tile;
1429 assert(IsCompanyBuildableVehicleType(type));
1431 DeleteWindowById(WC_BUILD_VEHICLE, num);
1433 new BuildVehicleWindow(&_build_vehicle_desc, tile, type);