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/>.
8 /** @file build_vehicle_gui.cpp GUI for building vehicles. */
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"
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"
33 #include "engine_cmd.h"
34 #include "train_cmd.h"
35 #include "vehicle_cmd.h"
37 #include "widgets/build_vehicle_widget.h"
39 #include "table/strings.h"
41 #include "safeguards.h"
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 std::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
),
61 NWidget(WWT_PANEL
, COLOUR_GREY
),
62 NWidget(NWID_VERTICAL
),
63 NWidget(NWID_HORIZONTAL
),
64 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_SORT_ASCENDING_DESCENDING
), SetDataTip(STR_BUTTON_SORT_BY
, STR_TOOLTIP_SORT_ORDER
),
65 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_BV_SORT_DROPDOWN
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_SORT_CRITERIA
),
67 NWidget(NWID_HORIZONTAL
),
68 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_BV_SHOW_HIDDEN_ENGINES
),
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
),
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
),
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),
85 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_SHOW_HIDE
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_NULL
),
86 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_RENAME
), SetResize(1, 0), SetFill(1, 0),
87 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
91 /** Special cargo filter criteria */
92 static const CargoID CF_ANY
= CT_NO_REFIT
; ///< Show all vehicles independent of carried cargo (i.e. no filtering)
93 static const CargoID CF_NONE
= CT_INVALID
; ///< Show only vehicles which do not carry cargo (e.g. train engines)
94 static const CargoID CF_ENGINES
= CT_AUTO_REFIT
; ///< Show only engines (for rail vehicles only)
96 bool _engine_sort_direction
; ///< \c false = descending, \c true = ascending.
97 byte _engine_sort_last_criteria
[] = {0, 0, 0, 0}; ///< Last set sort criteria, for each vehicle type.
98 bool _engine_sort_last_order
[] = {false, false, false, false}; ///< Last set direction of the sort order, for each vehicle type.
99 bool _engine_sort_show_hidden_engines
[] = {false, false, false, false}; ///< Last set 'show hidden engines' setting for each vehicle type.
100 static CargoID _engine_sort_last_cargo_criteria
[] = {CF_ANY
, CF_ANY
, CF_ANY
, CF_ANY
}; ///< Last set filter criteria, for each vehicle type.
103 * Determines order of engines by engineID
104 * @param a first engine to compare
105 * @param b second engine to compare
106 * @return for descending order: returns true if a < b. Vice versa for ascending order
108 static bool EngineNumberSorter(const EngineID
&a
, const EngineID
&b
)
110 int r
= Engine::Get(a
)->list_position
- Engine::Get(b
)->list_position
;
112 return _engine_sort_direction
? r
> 0 : r
< 0;
116 * Determines order of engines by introduction date
117 * @param a first engine to compare
118 * @param b second engine to compare
119 * @return for descending order: returns true if a < b. Vice versa for ascending order
121 static bool EngineIntroDateSorter(const EngineID
&a
, const EngineID
&b
)
123 const int va
= Engine::Get(a
)->intro_date
;
124 const int vb
= Engine::Get(b
)->intro_date
;
125 const int r
= va
- vb
;
127 /* Use EngineID to sort instead since we want consistent sorting */
128 if (r
== 0) return EngineNumberSorter(a
, b
);
129 return _engine_sort_direction
? r
> 0 : r
< 0;
132 /* cached values for EngineNameSorter to spare many GetString() calls */
133 static EngineID _last_engine
[2] = { INVALID_ENGINE
, INVALID_ENGINE
};
136 * Determines order of engines by name
137 * @param a first engine to compare
138 * @param b second engine to compare
139 * @return for descending order: returns true if a < b. Vice versa for ascending order
141 static bool EngineNameSorter(const EngineID
&a
, const EngineID
&b
)
143 static char last_name
[2][64] = { "", "" };
145 if (a
!= _last_engine
[0]) {
148 GetString(last_name
[0], STR_ENGINE_NAME
, lastof(last_name
[0]));
151 if (b
!= _last_engine
[1]) {
154 GetString(last_name
[1], STR_ENGINE_NAME
, lastof(last_name
[1]));
157 int r
= strnatcmp(last_name
[0], last_name
[1]); // Sort by name (natural sorting).
159 /* Use EngineID to sort instead since we want consistent sorting */
160 if (r
== 0) return EngineNumberSorter(a
, b
);
161 return _engine_sort_direction
? r
> 0 : r
< 0;
165 * Determines order of engines by reliability
166 * @param a first engine to compare
167 * @param b second engine to compare
168 * @return for descending order: returns true if a < b. Vice versa for ascending order
170 static bool EngineReliabilitySorter(const EngineID
&a
, const EngineID
&b
)
172 const int va
= Engine::Get(a
)->reliability
;
173 const int vb
= Engine::Get(b
)->reliability
;
174 const int r
= va
- vb
;
176 /* Use EngineID to sort instead since we want consistent sorting */
177 if (r
== 0) return EngineNumberSorter(a
, b
);
178 return _engine_sort_direction
? r
> 0 : r
< 0;
182 * Determines order of engines by purchase cost
183 * @param a first engine to compare
184 * @param b second engine to compare
185 * @return for descending order: returns true if a < b. Vice versa for ascending order
187 static bool EngineCostSorter(const EngineID
&a
, const EngineID
&b
)
189 Money va
= Engine::Get(a
)->GetCost();
190 Money vb
= Engine::Get(b
)->GetCost();
191 int r
= ClampToI32(va
- vb
);
193 /* Use EngineID to sort instead since we want consistent sorting */
194 if (r
== 0) return EngineNumberSorter(a
, b
);
195 return _engine_sort_direction
? r
> 0 : r
< 0;
199 * Determines order of engines by speed
200 * @param a first engine to compare
201 * @param b second engine to compare
202 * @return for descending order: returns true if a < b. Vice versa for ascending order
204 static bool EngineSpeedSorter(const EngineID
&a
, const EngineID
&b
)
206 int va
= Engine::Get(a
)->GetDisplayMaxSpeed();
207 int vb
= Engine::Get(b
)->GetDisplayMaxSpeed();
210 /* Use EngineID to sort instead since we want consistent sorting */
211 if (r
== 0) return EngineNumberSorter(a
, b
);
212 return _engine_sort_direction
? r
> 0 : r
< 0;
216 * Determines order of engines by power
217 * @param a first engine to compare
218 * @param b second engine to compare
219 * @return for descending order: returns true if a < b. Vice versa for ascending order
221 static bool EnginePowerSorter(const EngineID
&a
, const EngineID
&b
)
223 int va
= Engine::Get(a
)->GetPower();
224 int vb
= Engine::Get(b
)->GetPower();
227 /* Use EngineID to sort instead since we want consistent sorting */
228 if (r
== 0) return EngineNumberSorter(a
, b
);
229 return _engine_sort_direction
? r
> 0 : r
< 0;
233 * Determines order of engines by tractive effort
234 * @param a first engine to compare
235 * @param b second engine to compare
236 * @return for descending order: returns true if a < b. Vice versa for ascending order
238 static bool EngineTractiveEffortSorter(const EngineID
&a
, const EngineID
&b
)
240 int va
= Engine::Get(a
)->GetDisplayMaxTractiveEffort();
241 int vb
= Engine::Get(b
)->GetDisplayMaxTractiveEffort();
244 /* Use EngineID to sort instead since we want consistent sorting */
245 if (r
== 0) return EngineNumberSorter(a
, b
);
246 return _engine_sort_direction
? r
> 0 : r
< 0;
250 * Determines order of engines by running costs
251 * @param a first engine to compare
252 * @param b second engine to compare
253 * @return for descending order: returns true if a < b. Vice versa for ascending order
255 static bool EngineRunningCostSorter(const EngineID
&a
, const EngineID
&b
)
257 Money va
= Engine::Get(a
)->GetRunningCost();
258 Money vb
= Engine::Get(b
)->GetRunningCost();
259 int r
= ClampToI32(va
- vb
);
261 /* Use EngineID to sort instead since we want consistent sorting */
262 if (r
== 0) return EngineNumberSorter(a
, b
);
263 return _engine_sort_direction
? r
> 0 : r
< 0;
267 * Determines order of engines by running costs
268 * @param a first engine to compare
269 * @param b second engine to compare
270 * @return for descending order: returns true if a < b. Vice versa for ascending order
272 static bool EnginePowerVsRunningCostSorter(const EngineID
&a
, const EngineID
&b
)
274 const Engine
*e_a
= Engine::Get(a
);
275 const Engine
*e_b
= Engine::Get(b
);
276 uint p_a
= e_a
->GetPower();
277 uint p_b
= e_b
->GetPower();
278 Money r_a
= e_a
->GetRunningCost();
279 Money r_b
= e_b
->GetRunningCost();
280 /* Check if running cost is zero in one or both engines.
281 * If only one of them is zero then that one has higher value,
282 * else if both have zero cost then compare powers. */
285 /* If it is ambiguous which to return go with their ID */
286 if (p_a
== p_b
) return EngineNumberSorter(a
, b
);
287 return _engine_sort_direction
!= (p_a
< p_b
);
289 return !_engine_sort_direction
;
291 if (r_b
== 0) return _engine_sort_direction
;
292 /* Using double for more precision when comparing close values.
293 * This shouldn't have any major effects in performance nor in keeping
294 * the game in sync between players since it's used in GUI only in client side */
295 double v_a
= (double)p_a
/ (double)r_a
;
296 double v_b
= (double)p_b
/ (double)r_b
;
297 /* Use EngineID to sort if both have same power/running cost,
298 * since we want consistent sorting.
299 * Also if both have no power then sort with reverse of running cost to simulate
300 * previous sorting behaviour for wagons. */
301 if (v_a
== 0 && v_b
== 0) return !EngineRunningCostSorter(a
, b
);
302 if (v_a
== v_b
) return EngineNumberSorter(a
, b
);
303 return _engine_sort_direction
!= (v_a
< v_b
);
306 /* Train sorting functions */
309 * Determines order of train engines by capacity
310 * @param a first engine to compare
311 * @param b second engine to compare
312 * @return for descending order: returns true if a < b. Vice versa for ascending order
314 static bool TrainEngineCapacitySorter(const EngineID
&a
, const EngineID
&b
)
316 const RailVehicleInfo
*rvi_a
= RailVehInfo(a
);
317 const RailVehicleInfo
*rvi_b
= RailVehInfo(b
);
319 int va
= GetTotalCapacityOfArticulatedParts(a
) * (rvi_a
->railveh_type
== RAILVEH_MULTIHEAD
? 2 : 1);
320 int vb
= GetTotalCapacityOfArticulatedParts(b
) * (rvi_b
->railveh_type
== RAILVEH_MULTIHEAD
? 2 : 1);
323 /* Use EngineID to sort instead since we want consistent sorting */
324 if (r
== 0) return EngineNumberSorter(a
, b
);
325 return _engine_sort_direction
? r
> 0 : r
< 0;
329 * Determines order of train engines by engine / wagon
330 * @param a first engine to compare
331 * @param b second engine to compare
332 * @return for descending order: returns true if a < b. Vice versa for ascending order
334 static bool TrainEnginesThenWagonsSorter(const EngineID
&a
, const EngineID
&b
)
336 int val_a
= (RailVehInfo(a
)->railveh_type
== RAILVEH_WAGON
? 1 : 0);
337 int val_b
= (RailVehInfo(b
)->railveh_type
== RAILVEH_WAGON
? 1 : 0);
338 int r
= val_a
- val_b
;
340 /* Use EngineID to sort instead since we want consistent sorting */
341 if (r
== 0) return EngineNumberSorter(a
, b
);
342 return _engine_sort_direction
? r
> 0 : r
< 0;
345 /* Road vehicle sorting functions */
348 * Determines order of road vehicles by capacity
349 * @param a first engine to compare
350 * @param b second engine to compare
351 * @return for descending order: returns true if a < b. Vice versa for ascending order
353 static bool RoadVehEngineCapacitySorter(const EngineID
&a
, const EngineID
&b
)
355 int va
= GetTotalCapacityOfArticulatedParts(a
);
356 int vb
= GetTotalCapacityOfArticulatedParts(b
);
359 /* Use EngineID to sort instead since we want consistent sorting */
360 if (r
== 0) return EngineNumberSorter(a
, b
);
361 return _engine_sort_direction
? r
> 0 : r
< 0;
364 /* Ship vehicle sorting functions */
367 * Determines order of ships by capacity
368 * @param a first engine to compare
369 * @param b second engine to compare
370 * @return for descending order: returns true if a < b. Vice versa for ascending order
372 static bool ShipEngineCapacitySorter(const EngineID
&a
, const EngineID
&b
)
374 const Engine
*e_a
= Engine::Get(a
);
375 const Engine
*e_b
= Engine::Get(b
);
377 int va
= e_a
->GetDisplayDefaultCapacity();
378 int vb
= e_b
->GetDisplayDefaultCapacity();
381 /* Use EngineID to sort instead since we want consistent sorting */
382 if (r
== 0) return EngineNumberSorter(a
, b
);
383 return _engine_sort_direction
? r
> 0 : r
< 0;
386 /* Aircraft sorting functions */
389 * Determines order of aircraft by cargo
390 * @param a first engine to compare
391 * @param b second engine to compare
392 * @return for descending order: returns true if a < b. Vice versa for ascending order
394 static bool AircraftEngineCargoSorter(const EngineID
&a
, const EngineID
&b
)
396 const Engine
*e_a
= Engine::Get(a
);
397 const Engine
*e_b
= Engine::Get(b
);
399 uint16 mail_a
, mail_b
;
400 int va
= e_a
->GetDisplayDefaultCapacity(&mail_a
);
401 int vb
= e_b
->GetDisplayDefaultCapacity(&mail_b
);
405 /* The planes have the same passenger capacity. Check mail capacity instead */
409 /* Use EngineID to sort instead since we want consistent sorting */
410 return EngineNumberSorter(a
, b
);
413 return _engine_sort_direction
? r
> 0 : r
< 0;
417 * Determines order of aircraft by range.
418 * @param a first engine to compare
419 * @param b second engine to compare
420 * @return for descending order: returns true if a < b. Vice versa for ascending order
422 static bool AircraftRangeSorter(const EngineID
&a
, const EngineID
&b
)
424 uint16 r_a
= Engine::Get(a
)->GetRange();
425 uint16 r_b
= Engine::Get(b
)->GetRange();
429 /* Use EngineID to sort instead since we want consistent sorting */
430 if (r
== 0) return EngineNumberSorter(a
, b
);
431 return _engine_sort_direction
? r
> 0 : r
< 0;
434 /** Sort functions for the vehicle sort criteria, for each vehicle type. */
435 EngList_SortTypeFunction
* const _engine_sort_functions
[][11] = {{
441 &EngineTractiveEffortSorter
,
442 &EngineIntroDateSorter
,
444 &EngineRunningCostSorter
,
445 &EnginePowerVsRunningCostSorter
,
446 &EngineReliabilitySorter
,
447 &TrainEngineCapacitySorter
,
454 &EngineTractiveEffortSorter
,
455 &EngineIntroDateSorter
,
457 &EngineRunningCostSorter
,
458 &EnginePowerVsRunningCostSorter
,
459 &EngineReliabilitySorter
,
460 &RoadVehEngineCapacitySorter
,
466 &EngineIntroDateSorter
,
468 &EngineRunningCostSorter
,
469 &EngineReliabilitySorter
,
470 &ShipEngineCapacitySorter
,
476 &EngineIntroDateSorter
,
478 &EngineRunningCostSorter
,
479 &EngineReliabilitySorter
,
480 &AircraftEngineCargoSorter
,
481 &AircraftRangeSorter
,
484 /** Dropdown menu strings for the vehicle sort criteria. */
485 const StringID _engine_sort_listing
[][12] = {{
487 STR_SORT_BY_ENGINE_ID
,
489 STR_SORT_BY_MAX_SPEED
,
491 STR_SORT_BY_TRACTIVE_EFFORT
,
492 STR_SORT_BY_INTRO_DATE
,
494 STR_SORT_BY_RUNNING_COST
,
495 STR_SORT_BY_POWER_VS_RUNNING_COST
,
496 STR_SORT_BY_RELIABILITY
,
497 STR_SORT_BY_CARGO_CAPACITY
,
501 STR_SORT_BY_ENGINE_ID
,
503 STR_SORT_BY_MAX_SPEED
,
505 STR_SORT_BY_TRACTIVE_EFFORT
,
506 STR_SORT_BY_INTRO_DATE
,
508 STR_SORT_BY_RUNNING_COST
,
509 STR_SORT_BY_POWER_VS_RUNNING_COST
,
510 STR_SORT_BY_RELIABILITY
,
511 STR_SORT_BY_CARGO_CAPACITY
,
515 STR_SORT_BY_ENGINE_ID
,
517 STR_SORT_BY_MAX_SPEED
,
518 STR_SORT_BY_INTRO_DATE
,
520 STR_SORT_BY_RUNNING_COST
,
521 STR_SORT_BY_RELIABILITY
,
522 STR_SORT_BY_CARGO_CAPACITY
,
526 STR_SORT_BY_ENGINE_ID
,
528 STR_SORT_BY_MAX_SPEED
,
529 STR_SORT_BY_INTRO_DATE
,
531 STR_SORT_BY_RUNNING_COST
,
532 STR_SORT_BY_RELIABILITY
,
533 STR_SORT_BY_CARGO_CAPACITY
,
538 /** Filters vehicles by cargo and engine (in case of rail vehicle). */
539 static bool CDECL
CargoAndEngineFilter(const EngineID
*eid
, const CargoID cid
)
543 } else if (cid
== CF_ENGINES
) {
544 return Engine::Get(*eid
)->GetPower() != 0;
546 CargoTypes refit_mask
= GetUnionOfArticulatedRefitMasks(*eid
, true) & _standard_cargo_mask
;
547 return (cid
== CF_NONE
? refit_mask
== 0 : HasBit(refit_mask
, cid
));
551 static GUIEngineList::FilterFunction
* const _filter_funcs
[] = {
552 &CargoAndEngineFilter
,
555 static int DrawCargoCapacityInfo(int left
, int right
, int y
, EngineID engine
, TestedEngineDetails
&te
)
559 GetArticulatedVehicleCargoesAndRefits(engine
, &cap
, &refits
, te
.cargo
, te
.capacity
);
561 for (CargoID c
= 0; c
< NUM_CARGO
; c
++) {
562 if (cap
[c
] == 0) continue;
565 SetDParam(1, cap
[c
]);
566 SetDParam(2, HasBit(refits
, c
) ? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
567 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
568 y
+= FONT_HEIGHT_NORMAL
;
574 /* Draw rail wagon specific details */
575 static int DrawRailWagonPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, const RailVehicleInfo
*rvi
, TestedEngineDetails
&te
)
577 const Engine
*e
= Engine::Get(engine_number
);
581 SetDParam(0, e
->GetCost() + te
.cost
);
582 SetDParam(1, te
.cost
);
583 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT
);
585 SetDParam(0, e
->GetCost());
586 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
588 y
+= FONT_HEIGHT_NORMAL
;
590 /* Wagon weight - (including cargo) */
591 uint weight
= e
->GetDisplayWeight();
592 SetDParam(0, weight
);
593 uint cargo_weight
= (e
->CanCarryCargo() ? CargoSpec::Get(te
.cargo
)->weight
* te
.capacity
/ 16 : 0);
594 SetDParam(1, cargo_weight
+ weight
);
595 DrawString(left
, right
, y
, STR_PURCHASE_INFO_WEIGHT_CWEIGHT
);
596 y
+= FONT_HEIGHT_NORMAL
;
598 /* Wagon speed limit, displayed if above zero */
599 if (_settings_game
.vehicle
.wagon_speed_limits
) {
600 uint max_speed
= e
->GetDisplayMaxSpeed();
602 SetDParam(0, max_speed
);
603 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED
);
604 y
+= FONT_HEIGHT_NORMAL
;
609 if (rvi
->running_cost_class
!= INVALID_PRICE
) {
610 SetDParam(0, e
->GetRunningCost());
611 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
612 y
+= FONT_HEIGHT_NORMAL
;
618 /* Draw locomotive specific details */
619 static int DrawRailEnginePurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, const RailVehicleInfo
*rvi
, TestedEngineDetails
&te
)
621 const Engine
*e
= Engine::Get(engine_number
);
623 /* Purchase Cost - Engine weight */
625 SetDParam(0, e
->GetCost() + te
.cost
);
626 SetDParam(1, te
.cost
);
627 SetDParam(2, e
->GetDisplayWeight());
628 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_WEIGHT
);
630 SetDParam(0, e
->GetCost());
631 SetDParam(1, e
->GetDisplayWeight());
632 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_WEIGHT
);
634 y
+= FONT_HEIGHT_NORMAL
;
636 /* Max speed - Engine power */
637 SetDParam(0, e
->GetDisplayMaxSpeed());
638 SetDParam(1, e
->GetPower());
639 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_POWER
);
640 y
+= FONT_HEIGHT_NORMAL
;
642 /* Max tractive effort - not applicable if old acceleration or maglev */
643 if (_settings_game
.vehicle
.train_acceleration_model
!= AM_ORIGINAL
&& GetRailTypeInfo(rvi
->railtype
)->acceleration_type
!= 2) {
644 SetDParam(0, e
->GetDisplayMaxTractiveEffort());
645 DrawString(left
, right
, y
, STR_PURCHASE_INFO_MAX_TE
);
646 y
+= FONT_HEIGHT_NORMAL
;
650 if (rvi
->running_cost_class
!= INVALID_PRICE
) {
651 SetDParam(0, e
->GetRunningCost());
652 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
653 y
+= FONT_HEIGHT_NORMAL
;
656 /* Powered wagons power - Powered wagons extra weight */
657 if (rvi
->pow_wag_power
!= 0) {
658 SetDParam(0, rvi
->pow_wag_power
);
659 SetDParam(1, rvi
->pow_wag_weight
);
660 DrawString(left
, right
, y
, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT
);
661 y
+= FONT_HEIGHT_NORMAL
;
667 /* Draw road vehicle specific details */
668 static int DrawRoadVehPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, TestedEngineDetails
&te
)
670 const Engine
*e
= Engine::Get(engine_number
);
672 if (_settings_game
.vehicle
.roadveh_acceleration_model
!= AM_ORIGINAL
) {
675 SetDParam(0, e
->GetCost() + te
.cost
);
676 SetDParam(1, te
.cost
);
677 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT
);
679 SetDParam(0, e
->GetCost());
680 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
682 y
+= FONT_HEIGHT_NORMAL
;
684 /* Road vehicle weight - (including cargo) */
685 int16 weight
= e
->GetDisplayWeight();
686 SetDParam(0, weight
);
687 uint cargo_weight
= (e
->CanCarryCargo() ? CargoSpec::Get(te
.cargo
)->weight
* te
.capacity
/ 16 : 0);
688 SetDParam(1, cargo_weight
+ weight
);
689 DrawString(left
, right
, y
, STR_PURCHASE_INFO_WEIGHT_CWEIGHT
);
690 y
+= FONT_HEIGHT_NORMAL
;
692 /* Max speed - Engine power */
693 SetDParam(0, e
->GetDisplayMaxSpeed());
694 SetDParam(1, e
->GetPower());
695 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_POWER
);
696 y
+= FONT_HEIGHT_NORMAL
;
698 /* Max tractive effort */
699 SetDParam(0, e
->GetDisplayMaxTractiveEffort());
700 DrawString(left
, right
, y
, STR_PURCHASE_INFO_MAX_TE
);
701 y
+= FONT_HEIGHT_NORMAL
;
703 /* Purchase cost - Max speed */
705 SetDParam(0, e
->GetCost() + te
.cost
);
706 SetDParam(1, te
.cost
);
707 SetDParam(2, e
->GetDisplayMaxSpeed());
708 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_SPEED
);
710 SetDParam(0, e
->GetCost());
711 SetDParam(1, e
->GetDisplayMaxSpeed());
712 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
714 y
+= FONT_HEIGHT_NORMAL
;
718 SetDParam(0, e
->GetRunningCost());
719 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
720 y
+= FONT_HEIGHT_NORMAL
;
725 /* Draw ship specific details */
726 static int DrawShipPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, bool refittable
, TestedEngineDetails
&te
)
728 const Engine
*e
= Engine::Get(engine_number
);
730 /* Purchase cost - Max speed */
731 uint raw_speed
= e
->GetDisplayMaxSpeed();
732 uint ocean_speed
= e
->u
.ship
.ApplyWaterClassSpeedFrac(raw_speed
, true);
733 uint canal_speed
= e
->u
.ship
.ApplyWaterClassSpeedFrac(raw_speed
, false);
735 if (ocean_speed
== canal_speed
) {
737 SetDParam(0, e
->GetCost() + te
.cost
);
738 SetDParam(1, te
.cost
);
739 SetDParam(2, ocean_speed
);
740 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_SPEED
);
742 SetDParam(0, e
->GetCost());
743 SetDParam(1, ocean_speed
);
744 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
746 y
+= FONT_HEIGHT_NORMAL
;
749 SetDParam(0, e
->GetCost() + te
.cost
);
750 SetDParam(1, te
.cost
);
751 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT
);
753 SetDParam(0, e
->GetCost());
754 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
756 y
+= FONT_HEIGHT_NORMAL
;
758 SetDParam(0, ocean_speed
);
759 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_OCEAN
);
760 y
+= FONT_HEIGHT_NORMAL
;
762 SetDParam(0, canal_speed
);
763 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_CANAL
);
764 y
+= FONT_HEIGHT_NORMAL
;
767 /* Cargo type + capacity */
768 SetDParam(0, te
.cargo
);
769 SetDParam(1, te
.capacity
);
770 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
771 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
772 y
+= FONT_HEIGHT_NORMAL
;
775 SetDParam(0, e
->GetRunningCost());
776 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
777 y
+= FONT_HEIGHT_NORMAL
;
783 * Draw aircraft specific details in the buy window.
784 * @param left Left edge of the window to draw in.
785 * @param right Right edge of the window to draw in.
786 * @param y Top of the area to draw in.
787 * @param engine_number Engine to display.
788 * @param refittable If set, the aircraft can be refitted.
789 * @return Bottom of the used area.
791 static int DrawAircraftPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, bool refittable
, TestedEngineDetails
&te
)
793 const Engine
*e
= Engine::Get(engine_number
);
795 /* Purchase cost - Max speed */
797 SetDParam(0, e
->GetCost() + te
.cost
);
798 SetDParam(1, te
.cost
);
799 SetDParam(2, e
->GetDisplayMaxSpeed());
800 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_SPEED
);
802 SetDParam(0, e
->GetCost());
803 SetDParam(1, e
->GetDisplayMaxSpeed());
804 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
806 y
+= FONT_HEIGHT_NORMAL
;
809 if (te
.mail_capacity
> 0) {
810 SetDParam(0, te
.cargo
);
811 SetDParam(1, te
.capacity
);
812 SetDParam(2, CT_MAIL
);
813 SetDParam(3, te
.mail_capacity
);
814 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY
);
816 /* Note, if the default capacity is selected by the refit capacity
817 * callback, then the capacity shown is likely to be incorrect. */
818 SetDParam(0, te
.cargo
);
819 SetDParam(1, te
.capacity
);
820 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
821 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
823 y
+= FONT_HEIGHT_NORMAL
;
826 SetDParam(0, e
->GetRunningCost());
827 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
828 y
+= FONT_HEIGHT_NORMAL
;
831 SetDParam(0, e
->GetAircraftTypeText());
832 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_TYPE
);
833 y
+= FONT_HEIGHT_NORMAL
;
835 /* Aircraft range, if available. */
836 uint16 range
= e
->GetRange();
839 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_RANGE
);
840 y
+= FONT_HEIGHT_NORMAL
;
847 * Display additional text from NewGRF in the purchase information window
848 * @param left Left border of text bounding box
849 * @param right Right border of text bounding box
850 * @param y Top border of text bounding box
851 * @param engine Engine to query the additional purchase information for
852 * @return Bottom border of text bounding box
854 static uint
ShowAdditionalText(int left
, int right
, int y
, EngineID engine
)
856 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT
, 0, 0, engine
, nullptr);
857 if (callback
== CALLBACK_FAILED
|| callback
== 0x400) return y
;
858 const GRFFile
*grffile
= Engine::Get(engine
)->GetGRF();
859 if (callback
> 0x400) {
860 ErrorUnknownCallbackResult(grffile
->grfid
, CBID_VEHICLE_ADDITIONAL_TEXT
, callback
);
864 StartTextRefStackUsage(grffile
, 6);
865 uint result
= DrawStringMultiLine(left
, right
, y
, INT32_MAX
, GetGRFStringID(grffile
->grfid
, 0xD000 + callback
), TC_BLACK
);
866 StopTextRefStackUsage();
871 * Draw the purchase info details of a vehicle at a given location.
872 * @param left,right,y location where to draw the info
873 * @param engine_number the engine of which to draw the info of
874 * @return y after drawing all the text
876 int DrawVehiclePurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, TestedEngineDetails
&te
)
878 const Engine
*e
= Engine::Get(engine_number
);
880 ConvertDateToYMD(e
->intro_date
, &ymd
);
881 bool refittable
= IsArticulatedVehicleRefittable(engine_number
);
882 bool articulated_cargo
= false;
885 default: NOT_REACHED();
887 if (e
->u
.rail
.railveh_type
== RAILVEH_WAGON
) {
888 y
= DrawRailWagonPurchaseInfo(left
, right
, y
, engine_number
, &e
->u
.rail
, te
);
890 y
= DrawRailEnginePurchaseInfo(left
, right
, y
, engine_number
, &e
->u
.rail
, te
);
892 articulated_cargo
= true;
896 y
= DrawRoadVehPurchaseInfo(left
, right
, y
, engine_number
, te
);
897 articulated_cargo
= true;
901 y
= DrawShipPurchaseInfo(left
, right
, y
, engine_number
, refittable
, te
);
905 y
= DrawAircraftPurchaseInfo(left
, right
, y
, engine_number
, refittable
, te
);
909 if (articulated_cargo
) {
910 /* Cargo type + capacity, or N/A */
911 int new_y
= DrawCargoCapacityInfo(left
, right
, y
, engine_number
, te
);
914 SetDParam(0, CT_INVALID
);
915 SetDParam(2, STR_EMPTY
);
916 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
917 y
+= FONT_HEIGHT_NORMAL
;
923 /* Draw details that apply to all types except rail wagons. */
924 if (e
->type
!= VEH_TRAIN
|| e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
) {
925 /* Design date - Life length */
926 SetDParam(0, ymd
.year
);
927 SetDParam(1, e
->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR
);
928 DrawString(left
, right
, y
, STR_PURCHASE_INFO_DESIGNED_LIFE
);
929 y
+= FONT_HEIGHT_NORMAL
;
932 SetDParam(0, ToPercent16(e
->reliability
));
933 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RELIABILITY
);
934 y
+= FONT_HEIGHT_NORMAL
;
937 if (refittable
) y
= ShowRefitOptionsList(left
, right
, y
, engine_number
);
939 /* Additional text from NewGRF */
940 y
= ShowAdditionalText(left
, right
, y
, engine_number
);
942 /* The NewGRF's name which the vehicle comes from */
943 const GRFConfig
*config
= GetGRFConfig(e
->GetGRFID());
944 if (_settings_client
.gui
.show_newgrf_name
&& config
!= nullptr)
946 DrawString(left
, right
, y
, config
->GetName(), TC_BLACK
);
947 y
+= FONT_HEIGHT_NORMAL
;
954 * Engine drawing loop
955 * @param type Type of vehicle (VEH_*)
956 * @param l The left most location of the list
957 * @param r The right most location of the list
958 * @param y The top most location of the list
959 * @param eng_list What engines to draw
960 * @param min where to start in the list
961 * @param max where in the list to end
962 * @param selected_id what engine to highlight as selected, if any
963 * @param show_count Whether to show the amount of engines or not
964 * @param selected_group the group to list the engines of
966 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
)
968 static const int sprite_y_offsets
[] = { -1, -1, -2, -2 };
970 /* Obligatory sanity checks! */
971 assert(max
<= eng_list
->size());
973 bool rtl
= _current_text_dir
== TD_RTL
;
974 int step_size
= GetEngineListHeight(type
);
975 int sprite_left
= GetVehicleImageCellSize(type
, EIT_PURCHASE
).extend_left
;
976 int sprite_right
= GetVehicleImageCellSize(type
, EIT_PURCHASE
).extend_right
;
977 int sprite_width
= sprite_left
+ sprite_right
;
979 int sprite_x
= rtl
? r
- sprite_right
- 1 : l
+ sprite_left
+ 1;
980 int sprite_y_offset
= sprite_y_offsets
[type
] + step_size
/ 2;
982 Dimension replace_icon
= {0, 0};
985 replace_icon
= GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE
);
986 SetDParamMaxDigits(0, 3, FS_SMALL
);
987 count_width
= GetStringBoundingBox(STR_TINY_BLACK_COMA
).width
;
990 int text_left
= l
+ (rtl
? WD_FRAMERECT_LEFT
+ replace_icon
.width
+ 8 + count_width
: sprite_width
+ WD_FRAMETEXT_LEFT
);
991 int text_right
= r
- (rtl
? sprite_width
+ WD_FRAMETEXT_RIGHT
: WD_FRAMERECT_RIGHT
+ replace_icon
.width
+ 8 + count_width
);
992 int replace_icon_left
= rtl
? l
+ WD_FRAMERECT_LEFT
: r
- WD_FRAMERECT_RIGHT
- replace_icon
.width
;
994 int count_right
= rtl
? text_left
: r
- WD_FRAMERECT_RIGHT
- replace_icon
.width
- 8;
996 int normal_text_y_offset
= (step_size
- FONT_HEIGHT_NORMAL
) / 2;
997 int small_text_y_offset
= step_size
- FONT_HEIGHT_SMALL
- WD_FRAMERECT_BOTTOM
- 1;
998 int replace_icon_y_offset
= (step_size
- replace_icon
.height
) / 2 - 1;
1000 for (; min
< max
; min
++, y
+= step_size
) {
1001 const EngineID engine
= (*eng_list
)[min
];
1002 /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
1003 const uint num_engines
= GetGroupNumEngines(_local_company
, selected_group
, engine
);
1005 const Engine
*e
= Engine::Get(engine
);
1006 bool hidden
= HasBit(e
->company_hidden
, _local_company
);
1007 StringID str
= hidden
? STR_HIDDEN_ENGINE_NAME
: STR_ENGINE_NAME
;
1008 TextColour tc
= (engine
== selected_id
) ? TC_WHITE
: (TC_NO_SHADE
| (hidden
? TC_GREY
: TC_BLACK
));
1010 SetDParam(0, engine
);
1011 DrawString(text_left
, text_right
, y
+ normal_text_y_offset
, str
, tc
);
1012 DrawVehicleEngine(l
, r
, sprite_x
, y
+ sprite_y_offset
, engine
, (show_count
&& num_engines
== 0) ? PALETTE_CRASH
: GetEnginePalette(engine
, _local_company
), EIT_PURCHASE
);
1014 SetDParam(0, num_engines
);
1015 DrawString(count_left
, count_right
, y
+ small_text_y_offset
, STR_TINY_BLACK_COMA
, TC_FROMSTRING
, SA_RIGHT
| SA_FORCE
);
1016 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
);
1022 * Display the dropdown for the vehicle sort criteria.
1023 * @param w Parent window (holds the dropdown button).
1024 * @param vehicle_type %Vehicle type being sorted.
1025 * @param selected Currently selected sort criterium.
1026 * @param button Widget button.
1028 void DisplayVehicleSortDropDown(Window
*w
, VehicleType vehicle_type
, int selected
, int button
)
1030 uint32 hidden_mask
= 0;
1031 /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
1032 if (vehicle_type
== VEH_ROAD
&& _settings_game
.vehicle
.roadveh_acceleration_model
== AM_ORIGINAL
) {
1033 SetBit(hidden_mask
, 3); // power
1034 SetBit(hidden_mask
, 4); // tractive effort
1035 SetBit(hidden_mask
, 8); // power by running costs
1037 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
1038 if (vehicle_type
== VEH_TRAIN
&& _settings_game
.vehicle
.train_acceleration_model
== AM_ORIGINAL
) {
1039 SetBit(hidden_mask
, 4); // tractive effort
1041 ShowDropDownMenu(w
, _engine_sort_listing
[vehicle_type
], selected
, button
, 0, hidden_mask
);
1044 /** GUI for building vehicles. */
1045 struct BuildVehicleWindow
: Window
{
1046 VehicleType vehicle_type
; ///< Type of vehicles shown in the window.
1048 RailType railtype
; ///< Rail type to show, or #INVALID_RAILTYPE.
1049 RoadType roadtype
; ///< Road type to show, or #INVALID_ROADTYPE.
1050 } filter
; ///< Filter to apply.
1051 bool descending_sort_order
; ///< Sort direction, @see _engine_sort_direction
1052 byte sort_criteria
; ///< Current sort criterium.
1053 bool show_hidden_engines
; ///< State of the 'show hidden engines' button.
1054 bool listview_mode
; ///< If set, only display the available vehicles and do not show a 'build' button.
1055 EngineID sel_engine
; ///< Currently selected engine, or #INVALID_ENGINE
1056 EngineID rename_engine
; ///< Engine being renamed.
1057 GUIEngineList eng_list
;
1058 CargoID cargo_filter
[NUM_CARGO
+ 3]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE or CF_ENGINES
1059 StringID cargo_filter_texts
[NUM_CARGO
+ 4]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
1060 byte cargo_filter_criteria
; ///< Selected cargo filter
1061 int details_height
; ///< Minimal needed height of the details panels, in text lines (found so far).
1063 TestedEngineDetails te
; ///< Tested cost and capacity after refit.
1065 void SetBuyVehicleText()
1067 NWidgetCore
*widget
= this->GetWidget
<NWidgetCore
>(WID_BV_BUILD
);
1069 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
;
1070 if (refit
) refit
= Engine::Get(this->sel_engine
)->GetDefaultCargoType() != this->cargo_filter
[this->cargo_filter_criteria
];
1073 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON
+ this->vehicle_type
;
1074 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP
+ this->vehicle_type
;
1076 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON
+ this->vehicle_type
;
1077 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP
+ this->vehicle_type
;
1081 BuildVehicleWindow(WindowDesc
*desc
, TileIndex tile
, VehicleType type
) : Window(desc
)
1083 this->vehicle_type
= type
;
1084 this->listview_mode
= tile
== INVALID_TILE
;
1085 this->window_number
= this->listview_mode
? (int)type
: (int)tile
;
1087 this->sel_engine
= INVALID_ENGINE
;
1089 this->sort_criteria
= _engine_sort_last_criteria
[type
];
1090 this->descending_sort_order
= _engine_sort_last_order
[type
];
1091 this->show_hidden_engines
= _engine_sort_show_hidden_engines
[type
];
1093 this->UpdateFilterByTile();
1095 this->CreateNestedTree();
1097 this->vscroll
= this->GetScrollbar(WID_BV_SCROLLBAR
);
1099 /* If we are just viewing the list of vehicles, we do not need the Build button.
1100 * So we just hide it, and enlarge the Rename button by the now vacant place. */
1101 if (this->listview_mode
) this->GetWidget
<NWidgetStacked
>(WID_BV_BUILD_SEL
)->SetDisplayedPlane(SZSP_NONE
);
1103 NWidgetCore
*widget
= this->GetWidget
<NWidgetCore
>(WID_BV_LIST
);
1104 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP
+ type
;
1106 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_SHOW_HIDE
);
1107 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP
+ type
;
1109 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_RENAME
);
1110 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON
+ type
;
1111 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP
+ type
;
1113 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_SHOW_HIDDEN_ENGINES
);
1114 widget
->widget_data
= STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN
+ type
;
1115 widget
->tool_tip
= STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP
+ type
;
1116 widget
->SetLowered(this->show_hidden_engines
);
1118 this->details_height
= ((this->vehicle_type
== VEH_TRAIN
) ? 10 : 9);
1120 this->FinishInitNested(tile
== INVALID_TILE
? (int)type
: (int)tile
);
1122 this->owner
= (tile
!= INVALID_TILE
) ? GetTileOwner(tile
) : _local_company
;
1124 this->eng_list
.ForceRebuild();
1125 this->GenerateBuildList(); // generate the list, since we need it in the next line
1126 /* Select the first engine in the list as default when opening the window */
1127 if (this->eng_list
.size() > 0) {
1128 this->SelectEngine(this->eng_list
[0]);
1130 this->SelectEngine(INVALID_ENGINE
);
1134 /** Set the filter type according to the depot type */
1135 void UpdateFilterByTile()
1137 switch (this->vehicle_type
) {
1138 default: NOT_REACHED();
1140 if (this->listview_mode
) {
1141 this->filter
.railtype
= INVALID_RAILTYPE
;
1143 this->filter
.railtype
= GetRailType(this->window_number
);
1148 if (this->listview_mode
) {
1149 this->filter
.roadtype
= INVALID_ROADTYPE
;
1151 this->filter
.roadtype
= GetRoadTypeRoad(this->window_number
);
1152 if (this->filter
.roadtype
== INVALID_ROADTYPE
) {
1153 this->filter
.roadtype
= GetRoadTypeTram(this->window_number
);
1164 /** Populate the filter list and set the cargo filter criteria. */
1165 void SetCargoFilterArray()
1167 uint filter_items
= 0;
1169 /* Add item for disabling filtering. */
1170 this->cargo_filter
[filter_items
] = CF_ANY
;
1171 this->cargo_filter_texts
[filter_items
] = STR_PURCHASE_INFO_ALL_TYPES
;
1174 /* Specific filters for trains. */
1175 if (this->vehicle_type
== VEH_TRAIN
) {
1176 /* Add item for locomotives only in case of trains. */
1177 this->cargo_filter
[filter_items
] = CF_ENGINES
;
1178 this->cargo_filter_texts
[filter_items
] = STR_PURCHASE_INFO_ENGINES_ONLY
;
1181 /* Add item for vehicles not carrying anything, e.g. train engines.
1182 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1183 this->cargo_filter
[filter_items
] = CF_NONE
;
1184 this->cargo_filter_texts
[filter_items
] = STR_PURCHASE_INFO_NONE
;
1188 /* Collect available cargo types for filtering. */
1189 for (const CargoSpec
*cs
: _sorted_standard_cargo_specs
) {
1190 this->cargo_filter
[filter_items
] = cs
->Index();
1191 this->cargo_filter_texts
[filter_items
] = cs
->name
;
1195 /* Terminate the filter list. */
1196 this->cargo_filter_texts
[filter_items
] = INVALID_STRING_ID
;
1198 /* If not found, the cargo criteria will be set to all cargoes. */
1199 this->cargo_filter_criteria
= 0;
1201 /* Find the last cargo filter criteria. */
1202 for (uint i
= 0; i
< filter_items
; i
++) {
1203 if (this->cargo_filter
[i
] == _engine_sort_last_cargo_criteria
[this->vehicle_type
]) {
1204 this->cargo_filter_criteria
= i
;
1209 this->eng_list
.SetFilterFuncs(_filter_funcs
);
1210 this->eng_list
.SetFilterState(this->cargo_filter
[this->cargo_filter_criteria
] != CF_ANY
);
1213 void SelectEngine(EngineID engine
)
1215 CargoID cargo
= this->cargo_filter
[this->cargo_filter_criteria
];
1216 if (cargo
== CF_ANY
) cargo
= CF_NONE
;
1218 this->sel_engine
= engine
;
1219 this->SetBuyVehicleText();
1221 if (this->sel_engine
== INVALID_ENGINE
) return;
1223 const Engine
*e
= Engine::Get(this->sel_engine
);
1224 if (!e
->CanCarryCargo()) {
1226 this->te
.cargo
= CT_INVALID
;
1230 if (!this->listview_mode
) {
1231 /* Query for cost and refitted capacity */
1232 auto [ret
, veh_id
, refit_capacity
, refit_mail
] = Command
<CMD_BUILD_VEHICLE
>::Do(DC_QUERY_COST
, this->window_number
, this->sel_engine
, true, cargo
, INVALID_CLIENT_ID
);
1233 if (ret
.Succeeded()) {
1234 this->te
.cost
= ret
.GetCost() - e
->GetCost();
1235 this->te
.capacity
= refit_capacity
;
1236 this->te
.mail_capacity
= refit_mail
;
1237 this->te
.cargo
= (cargo
== CT_INVALID
) ? e
->GetDefaultCargoType() : cargo
;
1242 /* Purchase test was not possible or failed, fill in the defaults instead. */
1244 this->te
.capacity
= e
->GetDisplayDefaultCapacity(&this->te
.mail_capacity
);
1245 this->te
.cargo
= e
->GetDefaultCargoType();
1248 void OnInit() override
1250 this->SetCargoFilterArray();
1253 /** Filter the engine list against the currently selected cargo filter */
1254 void FilterEngineList()
1256 this->eng_list
.Filter(this->cargo_filter
[this->cargo_filter_criteria
]);
1257 if (0 == this->eng_list
.size()) { // no engine passed through the filter, invalidate the previously selected engine
1258 this->SelectEngine(INVALID_ENGINE
);
1259 } 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
1260 this->SelectEngine(this->eng_list
[0]);
1264 /** Filter a single engine */
1265 bool FilterSingleEngine(EngineID eid
)
1267 CargoID filter_type
= this->cargo_filter
[this->cargo_filter_criteria
];
1268 return CargoAndEngineFilter(&eid
, filter_type
);
1271 /* Figure out what train EngineIDs to put in the list */
1272 void GenerateBuildTrainList()
1274 EngineID sel_id
= INVALID_ENGINE
;
1275 int num_engines
= 0;
1278 this->eng_list
.clear();
1280 /* Make list of all available train engines and wagons.
1281 * Also check to see if the previously selected engine is still available,
1282 * and if not, reset selection to INVALID_ENGINE. This could be the case
1283 * when engines become obsolete and are removed */
1284 for (const Engine
*e
: Engine::IterateType(VEH_TRAIN
)) {
1285 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1286 EngineID eid
= e
->index
;
1287 const RailVehicleInfo
*rvi
= &e
->u
.rail
;
1289 if (this->filter
.railtype
!= INVALID_RAILTYPE
&& !HasPowerOnRail(rvi
->railtype
, this->filter
.railtype
)) continue;
1290 if (!IsEngineBuildable(eid
, VEH_TRAIN
, _local_company
)) continue;
1292 /* Filter now! So num_engines and num_wagons is valid */
1293 if (!FilterSingleEngine(eid
)) continue;
1295 this->eng_list
.push_back(eid
);
1297 if (rvi
->railveh_type
!= RAILVEH_WAGON
) {
1303 if (eid
== this->sel_engine
) sel_id
= eid
;
1306 this->SelectEngine(sel_id
);
1308 /* invalidate cached values for name sorter - engine names could change */
1309 _last_engine
[0] = _last_engine
[1] = INVALID_ENGINE
;
1311 /* make engines first, and then wagons, sorted by selected sort_criteria */
1312 _engine_sort_direction
= false;
1313 EngList_Sort(&this->eng_list
, TrainEnginesThenWagonsSorter
);
1315 /* and then sort engines */
1316 _engine_sort_direction
= this->descending_sort_order
;
1317 EngList_SortPartial(&this->eng_list
, _engine_sort_functions
[0][this->sort_criteria
], 0, num_engines
);
1319 /* and finally sort wagons */
1320 EngList_SortPartial(&this->eng_list
, _engine_sort_functions
[0][this->sort_criteria
], num_engines
, num_wagons
);
1323 /* Figure out what road vehicle EngineIDs to put in the list */
1324 void GenerateBuildRoadVehList()
1326 EngineID sel_id
= INVALID_ENGINE
;
1328 this->eng_list
.clear();
1330 for (const Engine
*e
: Engine::IterateType(VEH_ROAD
)) {
1331 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1332 EngineID eid
= e
->index
;
1333 if (!IsEngineBuildable(eid
, VEH_ROAD
, _local_company
)) continue;
1334 if (this->filter
.roadtype
!= INVALID_ROADTYPE
&& !HasPowerOnRoad(e
->u
.road
.roadtype
, this->filter
.roadtype
)) continue;
1336 this->eng_list
.push_back(eid
);
1338 if (eid
== this->sel_engine
) sel_id
= eid
;
1340 this->SelectEngine(sel_id
);
1343 /* Figure out what ship EngineIDs to put in the list */
1344 void GenerateBuildShipList()
1346 EngineID sel_id
= INVALID_ENGINE
;
1347 this->eng_list
.clear();
1349 for (const Engine
*e
: Engine::IterateType(VEH_SHIP
)) {
1350 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1351 EngineID eid
= e
->index
;
1352 if (!IsEngineBuildable(eid
, VEH_SHIP
, _local_company
)) continue;
1353 this->eng_list
.push_back(eid
);
1355 if (eid
== this->sel_engine
) sel_id
= eid
;
1357 this->SelectEngine(sel_id
);
1360 /* Figure out what aircraft EngineIDs to put in the list */
1361 void GenerateBuildAircraftList()
1363 EngineID sel_id
= INVALID_ENGINE
;
1365 this->eng_list
.clear();
1367 const Station
*st
= this->listview_mode
? nullptr : Station::GetByTile(this->window_number
);
1369 /* Make list of all available planes.
1370 * Also check to see if the previously selected plane is still available,
1371 * and if not, reset selection to INVALID_ENGINE. This could be the case
1372 * when planes become obsolete and are removed */
1373 for (const Engine
*e
: Engine::IterateType(VEH_AIRCRAFT
)) {
1374 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1375 EngineID eid
= e
->index
;
1376 if (!IsEngineBuildable(eid
, VEH_AIRCRAFT
, _local_company
)) continue;
1377 /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1378 if (!this->listview_mode
&& !CanVehicleUseStation(eid
, st
)) continue;
1380 this->eng_list
.push_back(eid
);
1381 if (eid
== this->sel_engine
) sel_id
= eid
;
1384 this->SelectEngine(sel_id
);
1387 /* Generate the list of vehicles */
1388 void GenerateBuildList()
1390 if (!this->eng_list
.NeedRebuild()) return;
1392 /* Update filter type in case the road/railtype of the depot got converted */
1393 this->UpdateFilterByTile();
1395 switch (this->vehicle_type
) {
1396 default: NOT_REACHED();
1398 this->GenerateBuildTrainList();
1399 this->eng_list
.shrink_to_fit();
1400 this->eng_list
.RebuildDone();
1401 return; // trains should not reach the last sorting
1403 this->GenerateBuildRoadVehList();
1406 this->GenerateBuildShipList();
1409 this->GenerateBuildAircraftList();
1413 this->FilterEngineList();
1415 _engine_sort_direction
= this->descending_sort_order
;
1416 EngList_Sort(&this->eng_list
, _engine_sort_functions
[this->vehicle_type
][this->sort_criteria
]);
1418 this->eng_list
.shrink_to_fit();
1419 this->eng_list
.RebuildDone();
1422 void OnClick(Point pt
, int widget
, int click_count
) override
1425 case WID_BV_SORT_ASCENDING_DESCENDING
:
1426 this->descending_sort_order
^= true;
1427 _engine_sort_last_order
[this->vehicle_type
] = this->descending_sort_order
;
1428 this->eng_list
.ForceRebuild();
1432 case WID_BV_SHOW_HIDDEN_ENGINES
:
1433 this->show_hidden_engines
^= true;
1434 _engine_sort_show_hidden_engines
[this->vehicle_type
] = this->show_hidden_engines
;
1435 this->eng_list
.ForceRebuild();
1436 this->SetWidgetLoweredState(widget
, this->show_hidden_engines
);
1441 uint i
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_BV_LIST
);
1442 size_t num_items
= this->eng_list
.size();
1443 this->SelectEngine((i
< num_items
) ? this->eng_list
[i
] : INVALID_ENGINE
);
1445 if (_ctrl_pressed
) {
1446 this->OnClick(pt
, WID_BV_SHOW_HIDE
, 1);
1447 } else if (click_count
> 1 && !this->listview_mode
) {
1448 this->OnClick(pt
, WID_BV_BUILD
, 1);
1453 case WID_BV_SORT_DROPDOWN
: // Select sorting criteria dropdown menu
1454 DisplayVehicleSortDropDown(this, this->vehicle_type
, this->sort_criteria
, WID_BV_SORT_DROPDOWN
);
1457 case WID_BV_CARGO_FILTER_DROPDOWN
: // Select cargo filtering criteria dropdown menu
1458 ShowDropDownMenu(this, this->cargo_filter_texts
, this->cargo_filter_criteria
, WID_BV_CARGO_FILTER_DROPDOWN
, 0, 0);
1461 case WID_BV_SHOW_HIDE
: {
1462 const Engine
*e
= (this->sel_engine
== INVALID_ENGINE
) ? nullptr : Engine::Get(this->sel_engine
);
1464 Command
<CMD_SET_VEHICLE_VISIBILITY
>::Post(this->sel_engine
, !e
->IsHidden(_current_company
));
1469 case WID_BV_BUILD
: {
1470 EngineID sel_eng
= this->sel_engine
;
1471 if (sel_eng
!= INVALID_ENGINE
) {
1472 CargoID cargo
= this->cargo_filter
[this->cargo_filter_criteria
];
1473 if (cargo
== CF_ANY
|| cargo
== CF_ENGINES
) cargo
= CF_NONE
;
1474 if (this->vehicle_type
== VEH_TRAIN
&& RailVehInfo(sel_eng
)->railveh_type
== RAILVEH_WAGON
) {
1475 Command
<CMD_BUILD_VEHICLE
>::Post(GetCmdBuildVehMsg(this->vehicle_type
), CcBuildWagon
, this->window_number
, sel_eng
, true, cargo
, INVALID_CLIENT_ID
);
1477 Command
<CMD_BUILD_VEHICLE
>::Post(GetCmdBuildVehMsg(this->vehicle_type
), CcBuildPrimaryVehicle
, this->window_number
, sel_eng
, true, cargo
, INVALID_CLIENT_ID
);
1483 case WID_BV_RENAME
: {
1484 EngineID sel_eng
= this->sel_engine
;
1485 if (sel_eng
!= INVALID_ENGINE
) {
1486 this->rename_engine
= sel_eng
;
1487 SetDParam(0, sel_eng
);
1488 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
);
1496 * Some data on this window has become invalid.
1497 * @param data Information about the changed data.
1498 * @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.
1500 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
1502 if (!gui_scope
) return;
1503 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1504 if (this->vehicle_type
== VEH_ROAD
&&
1505 _settings_game
.vehicle
.roadveh_acceleration_model
== AM_ORIGINAL
&&
1506 this->sort_criteria
> 7) {
1507 this->sort_criteria
= 0;
1508 _engine_sort_last_criteria
[VEH_ROAD
] = 0;
1510 this->eng_list
.ForceRebuild();
1513 void SetStringParameters(int widget
) const override
1516 case WID_BV_CAPTION
:
1517 if (this->vehicle_type
== VEH_TRAIN
&& !this->listview_mode
) {
1518 const RailtypeInfo
*rti
= GetRailTypeInfo(this->filter
.railtype
);
1519 SetDParam(0, rti
->strings
.build_caption
);
1520 } else if (this->vehicle_type
== VEH_ROAD
&& !this->listview_mode
) {
1521 const RoadTypeInfo
*rti
= GetRoadTypeInfo(this->filter
.roadtype
);
1522 SetDParam(0, rti
->strings
.build_caption
);
1524 SetDParam(0, (this->listview_mode
? STR_VEHICLE_LIST_AVAILABLE_TRAINS
: STR_BUY_VEHICLE_TRAIN_ALL_CAPTION
) + this->vehicle_type
);
1528 case WID_BV_SORT_DROPDOWN
:
1529 SetDParam(0, _engine_sort_listing
[this->vehicle_type
][this->sort_criteria
]);
1532 case WID_BV_CARGO_FILTER_DROPDOWN
:
1533 SetDParam(0, this->cargo_filter_texts
[this->cargo_filter_criteria
]);
1536 case WID_BV_SHOW_HIDE
: {
1537 const Engine
*e
= (this->sel_engine
== INVALID_ENGINE
) ? nullptr : Engine::Get(this->sel_engine
);
1538 if (e
!= nullptr && e
->IsHidden(_local_company
)) {
1539 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON
+ this->vehicle_type
);
1541 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON
+ this->vehicle_type
);
1548 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
1552 resize
->height
= GetEngineListHeight(this->vehicle_type
);
1553 size
->height
= 3 * resize
->height
;
1554 size
->width
= std::max(size
->width
, GetVehicleImageCellSize(this->vehicle_type
, EIT_PURCHASE
).extend_left
+ GetVehicleImageCellSize(this->vehicle_type
, EIT_PURCHASE
).extend_right
+ 165);
1558 size
->height
= FONT_HEIGHT_NORMAL
* this->details_height
+ padding
.height
;
1561 case WID_BV_SORT_ASCENDING_DESCENDING
: {
1562 Dimension d
= GetStringBoundingBox(this->GetWidget
<NWidgetCore
>(widget
)->widget_data
);
1563 d
.width
+= padding
.width
+ Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1564 d
.height
+= padding
.height
;
1565 *size
= maxdim(*size
, d
);
1570 *size
= GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON
+ this->vehicle_type
);
1571 *size
= maxdim(*size
, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON
+ this->vehicle_type
));
1572 size
->width
+= padding
.width
;
1573 size
->height
+= padding
.height
;
1576 case WID_BV_SHOW_HIDE
:
1577 *size
= GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON
+ this->vehicle_type
);
1578 *size
= maxdim(*size
, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON
+ this->vehicle_type
));
1579 size
->width
+= padding
.width
;
1580 size
->height
+= padding
.height
;
1585 void DrawWidget(const Rect
&r
, int widget
) const override
1591 r
.left
+ WD_FRAMERECT_LEFT
,
1592 r
.right
- WD_FRAMERECT_RIGHT
,
1593 r
.top
+ WD_FRAMERECT_TOP
,
1595 this->vscroll
->GetPosition(),
1596 static_cast<uint16
>(std::min
<size_t>(this->vscroll
->GetPosition() + this->vscroll
->GetCapacity(), this->eng_list
.size())),
1603 case WID_BV_SORT_ASCENDING_DESCENDING
:
1604 this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING
, this->descending_sort_order
? SBS_DOWN
: SBS_UP
);
1609 void OnPaint() override
1611 this->GenerateBuildList();
1612 this->vscroll
->SetCount((uint
)this->eng_list
.size());
1614 this->SetWidgetsDisabledState(this->sel_engine
== INVALID_ENGINE
, WID_BV_SHOW_HIDE
, WID_BV_BUILD
, WIDGET_LIST_END
);
1616 /* Disable renaming engines in network games if you are not the server. */
1617 this->SetWidgetDisabledState(WID_BV_RENAME
, this->sel_engine
== INVALID_ENGINE
|| (_networking
&& !_network_server
));
1619 this->DrawWidgets();
1621 if (!this->IsShaded()) {
1622 int needed_height
= this->details_height
;
1623 /* Draw details panels. */
1624 if (this->sel_engine
!= INVALID_ENGINE
) {
1625 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(WID_BV_PANEL
);
1626 int text_end
= DrawVehiclePurchaseInfo(nwi
->pos_x
+ WD_FRAMETEXT_LEFT
, nwi
->pos_x
+ nwi
->current_x
- WD_FRAMETEXT_RIGHT
,
1627 nwi
->pos_y
+ WD_FRAMERECT_TOP
, this->sel_engine
, this->te
);
1628 needed_height
= std::max(needed_height
, (text_end
- (int)nwi
->pos_y
- WD_FRAMERECT_TOP
) / FONT_HEIGHT_NORMAL
);
1630 if (needed_height
!= this->details_height
) { // Details window are not high enough, enlarge them.
1631 int resize
= needed_height
- this->details_height
;
1632 this->details_height
= needed_height
;
1633 this->ReInit(0, resize
* FONT_HEIGHT_NORMAL
);
1639 void OnQueryTextFinished(char *str
) override
1641 if (str
== nullptr) return;
1643 Command
<CMD_RENAME_ENGINE
>::Post(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE
+ this->vehicle_type
, this->rename_engine
, str
);
1646 void OnDropdownSelect(int widget
, int index
) override
1649 case WID_BV_SORT_DROPDOWN
:
1650 if (this->sort_criteria
!= index
) {
1651 this->sort_criteria
= index
;
1652 _engine_sort_last_criteria
[this->vehicle_type
] = this->sort_criteria
;
1653 this->eng_list
.ForceRebuild();
1657 case WID_BV_CARGO_FILTER_DROPDOWN
: // Select a cargo filter criteria
1658 if (this->cargo_filter_criteria
!= index
) {
1659 this->cargo_filter_criteria
= index
;
1660 _engine_sort_last_cargo_criteria
[this->vehicle_type
] = this->cargo_filter
[this->cargo_filter_criteria
];
1661 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1662 this->eng_list
.SetFilterState(this->cargo_filter
[this->cargo_filter_criteria
] != CF_ANY
);
1663 this->eng_list
.ForceRebuild();
1664 this->SelectEngine(this->sel_engine
);
1671 void OnResize() override
1673 this->vscroll
->SetCapacityFromWidget(this, WID_BV_LIST
);
1677 static WindowDesc
_build_vehicle_desc(
1678 WDP_AUTO
, "build_vehicle", 240, 268,
1679 WC_BUILD_VEHICLE
, WC_NONE
,
1681 _nested_build_vehicle_widgets
, lengthof(_nested_build_vehicle_widgets
)
1684 void ShowBuildVehicleWindow(TileIndex tile
, VehicleType type
)
1686 /* We want to be able to open both Available Train as Available Ships,
1687 * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1688 * As it always is a low value, it won't collide with any real tile
1690 uint num
= (tile
== INVALID_TILE
) ? (int)type
: (int)tile
;
1692 assert(IsCompanyBuildableVehicleType(type
));
1694 CloseWindowById(WC_BUILD_VEHICLE
, num
);
1696 new BuildVehicleWindow(&_build_vehicle_desc
, tile
, type
);