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 "timer/timer_game_calendar.h"
27 #include "vehicle_func.h"
28 #include "dropdown_type.h"
29 #include "dropdown_func.h"
30 #include "engine_gui.h"
31 #include "cargotype.h"
32 #include "core/geometry_func.hpp"
33 #include "autoreplace_func.h"
34 #include "engine_cmd.h"
35 #include "train_cmd.h"
36 #include "vehicle_cmd.h"
37 #include "zoom_func.h"
38 #include "querystring_gui.h"
39 #include "stringfilter_type.h"
42 #include "widgets/build_vehicle_widget.h"
44 #include "table/strings.h"
46 #include "safeguards.h"
49 * Get the height of a single 'entry' in the engine lists.
50 * @param type the vehicle type to get the height of
51 * @return the height for the entry
53 uint
GetEngineListHeight(VehicleType type
)
55 return std::max
<uint
>(GetCharacterHeight(FS_NORMAL
) + WidgetDimensions::scaled
.matrix
.Vertical(), GetVehicleImageCellSize(type
, EIT_PURCHASE
).height
);
58 static constexpr NWidgetPart _nested_build_vehicle_widgets
[] = {
59 NWidget(NWID_HORIZONTAL
),
60 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
61 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_BV_CAPTION
), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
), SetTextStyle(TC_WHITE
),
62 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
63 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
64 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
66 NWidget(NWID_VERTICAL
),
67 NWidget(NWID_HORIZONTAL
),
68 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_SORT_ASCENDING_DESCENDING
), SetDataTip(STR_BUTTON_SORT_BY
, STR_TOOLTIP_SORT_ORDER
),
69 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_BV_SORT_DROPDOWN
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_SORT_CRITERIA
),
71 NWidget(NWID_HORIZONTAL
),
72 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_BV_SHOW_HIDDEN_ENGINES
),
73 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_BV_CARGO_FILTER_DROPDOWN
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_FILTER_CRITERIA
),
75 NWidget(WWT_PANEL
, COLOUR_GREY
),
76 NWidget(WWT_EDITBOX
, COLOUR_GREY
, WID_BV_FILTER
), SetResize(1, 0), SetFill(1, 0), SetPadding(2), SetDataTip(STR_LIST_FILTER_OSKTITLE
, STR_LIST_FILTER_TOOLTIP
),
80 NWidget(NWID_HORIZONTAL
),
81 NWidget(WWT_MATRIX
, COLOUR_GREY
, WID_BV_LIST
), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL
), SetScrollbar(WID_BV_SCROLLBAR
),
82 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_BV_SCROLLBAR
),
84 /* Panel with details. */
85 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_BV_PANEL
), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
86 /* Build/rename buttons, resize button. */
87 NWidget(NWID_HORIZONTAL
),
88 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_BV_BUILD_SEL
),
89 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_BUILD
), SetResize(1, 0), SetFill(1, 0),
91 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_SHOW_HIDE
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_NULL
),
92 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_RENAME
), SetResize(1, 0), SetFill(1, 0),
93 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
98 bool _engine_sort_direction
; ///< \c false = descending, \c true = ascending.
99 uint8_t _engine_sort_last_criteria
[] = {0, 0, 0, 0}; ///< Last set sort criteria, for each vehicle type.
100 bool _engine_sort_last_order
[] = {false, false, false, false}; ///< Last set direction of the sort order, for each vehicle type.
101 bool _engine_sort_show_hidden_engines
[] = {false, false, false, false}; ///< Last set 'show hidden engines' setting for each vehicle type.
102 static CargoID _engine_sort_last_cargo_criteria
[] = {CargoFilterCriteria::CF_ANY
, CargoFilterCriteria::CF_ANY
, CargoFilterCriteria::CF_ANY
, CargoFilterCriteria::CF_ANY
}; ///< Last set filter criteria, for each vehicle type.
105 * Determines order of engines by engineID
106 * @param a first engine to compare
107 * @param b second engine to compare
108 * @return for descending order: returns true if a < b. Vice versa for ascending order
110 static bool EngineNumberSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
112 int r
= Engine::Get(a
.engine_id
)->list_position
- Engine::Get(b
.engine_id
)->list_position
;
114 return _engine_sort_direction
? r
> 0 : r
< 0;
118 * Determines order of engines by introduction date
119 * @param a first engine to compare
120 * @param b second engine to compare
121 * @return for descending order: returns true if a < b. Vice versa for ascending order
123 static bool EngineIntroDateSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
125 const auto va
= Engine::Get(a
.engine_id
)->intro_date
;
126 const auto vb
= Engine::Get(b
.engine_id
)->intro_date
;
127 const auto r
= va
- vb
;
129 /* Use EngineID to sort instead since we want consistent sorting */
130 if (r
== 0) return EngineNumberSorter(a
, b
);
131 return _engine_sort_direction
? r
> 0 : r
< 0;
134 /* cached values for EngineNameSorter to spare many GetString() calls */
135 static EngineID _last_engine
[2] = { INVALID_ENGINE
, INVALID_ENGINE
};
138 * Determines order of engines by name
139 * @param a first engine to compare
140 * @param b second engine to compare
141 * @return for descending order: returns true if a < b. Vice versa for ascending order
143 static bool EngineNameSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
145 static std::string last_name
[2] = { {}, {} };
147 if (a
.engine_id
!= _last_engine
[0]) {
148 _last_engine
[0] = a
.engine_id
;
149 SetDParam(0, PackEngineNameDParam(a
.engine_id
, EngineNameContext::PurchaseList
));
150 last_name
[0] = GetString(STR_ENGINE_NAME
);
153 if (b
.engine_id
!= _last_engine
[1]) {
154 _last_engine
[1] = b
.engine_id
;
155 SetDParam(0, PackEngineNameDParam(b
.engine_id
, EngineNameContext::PurchaseList
));
156 last_name
[1] = GetString(STR_ENGINE_NAME
);
159 int r
= StrNaturalCompare(last_name
[0], last_name
[1]); // Sort by name (natural sorting).
161 /* Use EngineID to sort instead since we want consistent sorting */
162 if (r
== 0) return EngineNumberSorter(a
, b
);
163 return _engine_sort_direction
? r
> 0 : r
< 0;
167 * Determines order of engines by reliability
168 * @param a first engine to compare
169 * @param b second engine to compare
170 * @return for descending order: returns true if a < b. Vice versa for ascending order
172 static bool EngineReliabilitySorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
174 const int va
= Engine::Get(a
.engine_id
)->reliability
;
175 const int vb
= Engine::Get(b
.engine_id
)->reliability
;
176 const int r
= va
- vb
;
178 /* Use EngineID to sort instead since we want consistent sorting */
179 if (r
== 0) return EngineNumberSorter(a
, b
);
180 return _engine_sort_direction
? r
> 0 : r
< 0;
184 * Determines order of engines by purchase cost
185 * @param a first engine to compare
186 * @param b second engine to compare
187 * @return for descending order: returns true if a < b. Vice versa for ascending order
189 static bool EngineCostSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
191 Money va
= Engine::Get(a
.engine_id
)->GetCost();
192 Money vb
= Engine::Get(b
.engine_id
)->GetCost();
193 int r
= ClampTo
<int32_t>(va
- vb
);
195 /* Use EngineID to sort instead since we want consistent sorting */
196 if (r
== 0) return EngineNumberSorter(a
, b
);
197 return _engine_sort_direction
? r
> 0 : r
< 0;
201 * Determines order of engines by speed
202 * @param a first engine to compare
203 * @param b second engine to compare
204 * @return for descending order: returns true if a < b. Vice versa for ascending order
206 static bool EngineSpeedSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
208 int va
= Engine::Get(a
.engine_id
)->GetDisplayMaxSpeed();
209 int vb
= Engine::Get(b
.engine_id
)->GetDisplayMaxSpeed();
212 /* Use EngineID to sort instead since we want consistent sorting */
213 if (r
== 0) return EngineNumberSorter(a
, b
);
214 return _engine_sort_direction
? r
> 0 : r
< 0;
218 * Determines order of engines by power
219 * @param a first engine to compare
220 * @param b second engine to compare
221 * @return for descending order: returns true if a < b. Vice versa for ascending order
223 static bool EnginePowerSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
225 int va
= Engine::Get(a
.engine_id
)->GetPower();
226 int vb
= Engine::Get(b
.engine_id
)->GetPower();
229 /* Use EngineID to sort instead since we want consistent sorting */
230 if (r
== 0) return EngineNumberSorter(a
, b
);
231 return _engine_sort_direction
? r
> 0 : r
< 0;
235 * Determines order of engines by tractive effort
236 * @param a first engine to compare
237 * @param b second engine to compare
238 * @return for descending order: returns true if a < b. Vice versa for ascending order
240 static bool EngineTractiveEffortSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
242 int va
= Engine::Get(a
.engine_id
)->GetDisplayMaxTractiveEffort();
243 int vb
= Engine::Get(b
.engine_id
)->GetDisplayMaxTractiveEffort();
246 /* Use EngineID to sort instead since we want consistent sorting */
247 if (r
== 0) return EngineNumberSorter(a
, b
);
248 return _engine_sort_direction
? r
> 0 : r
< 0;
252 * Determines order of engines by running costs
253 * @param a first engine to compare
254 * @param b second engine to compare
255 * @return for descending order: returns true if a < b. Vice versa for ascending order
257 static bool EngineRunningCostSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
259 Money va
= Engine::Get(a
.engine_id
)->GetRunningCost();
260 Money vb
= Engine::Get(b
.engine_id
)->GetRunningCost();
261 int r
= ClampTo
<int32_t>(va
- vb
);
263 /* Use EngineID to sort instead since we want consistent sorting */
264 if (r
== 0) return EngineNumberSorter(a
, b
);
265 return _engine_sort_direction
? r
> 0 : r
< 0;
269 * Determines order of engines by running costs
270 * @param a first engine to compare
271 * @param b second engine to compare
272 * @return for descending order: returns true if a < b. Vice versa for ascending order
274 static bool EnginePowerVsRunningCostSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
276 const Engine
*e_a
= Engine::Get(a
.engine_id
);
277 const Engine
*e_b
= Engine::Get(b
.engine_id
);
278 uint p_a
= e_a
->GetPower();
279 uint p_b
= e_b
->GetPower();
280 Money r_a
= e_a
->GetRunningCost();
281 Money r_b
= e_b
->GetRunningCost();
282 /* Check if running cost is zero in one or both engines.
283 * If only one of them is zero then that one has higher value,
284 * else if both have zero cost then compare powers. */
287 /* If it is ambiguous which to return go with their ID */
288 if (p_a
== p_b
) return EngineNumberSorter(a
, b
);
289 return _engine_sort_direction
!= (p_a
< p_b
);
291 return !_engine_sort_direction
;
293 if (r_b
== 0) return _engine_sort_direction
;
294 /* Using double for more precision when comparing close values.
295 * This shouldn't have any major effects in performance nor in keeping
296 * the game in sync between players since it's used in GUI only in client side */
297 double v_a
= (double)p_a
/ (double)r_a
;
298 double v_b
= (double)p_b
/ (double)r_b
;
299 /* Use EngineID to sort if both have same power/running cost,
300 * since we want consistent sorting.
301 * Also if both have no power then sort with reverse of running cost to simulate
302 * previous sorting behaviour for wagons. */
303 if (v_a
== 0 && v_b
== 0) return EngineRunningCostSorter(b
, a
);
304 if (v_a
== v_b
) return EngineNumberSorter(a
, b
);
305 return _engine_sort_direction
!= (v_a
< v_b
);
308 /* Train sorting functions */
311 * Determines order of train engines by capacity
312 * @param a first engine to compare
313 * @param b second engine to compare
314 * @return for descending order: returns true if a < b. Vice versa for ascending order
316 static bool TrainEngineCapacitySorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
318 const RailVehicleInfo
*rvi_a
= RailVehInfo(a
.engine_id
);
319 const RailVehicleInfo
*rvi_b
= RailVehInfo(b
.engine_id
);
321 int va
= GetTotalCapacityOfArticulatedParts(a
.engine_id
) * (rvi_a
->railveh_type
== RAILVEH_MULTIHEAD
? 2 : 1);
322 int vb
= GetTotalCapacityOfArticulatedParts(b
.engine_id
) * (rvi_b
->railveh_type
== RAILVEH_MULTIHEAD
? 2 : 1);
325 /* Use EngineID to sort instead since we want consistent sorting */
326 if (r
== 0) return EngineNumberSorter(a
, b
);
327 return _engine_sort_direction
? r
> 0 : r
< 0;
331 * Determines order of train engines by engine / wagon
332 * @param a first engine to compare
333 * @param b second engine to compare
334 * @return for descending order: returns true if a < b. Vice versa for ascending order
336 static bool TrainEnginesThenWagonsSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
338 int val_a
= (RailVehInfo(a
.engine_id
)->railveh_type
== RAILVEH_WAGON
? 1 : 0);
339 int val_b
= (RailVehInfo(b
.engine_id
)->railveh_type
== RAILVEH_WAGON
? 1 : 0);
340 int r
= val_a
- val_b
;
342 /* Use EngineID to sort instead since we want consistent sorting */
343 if (r
== 0) return EngineNumberSorter(a
, b
);
344 return _engine_sort_direction
? r
> 0 : r
< 0;
347 /* Road vehicle sorting functions */
350 * Determines order of road vehicles by capacity
351 * @param a first engine to compare
352 * @param b second engine to compare
353 * @return for descending order: returns true if a < b. Vice versa for ascending order
355 static bool RoadVehEngineCapacitySorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
357 int va
= GetTotalCapacityOfArticulatedParts(a
.engine_id
);
358 int vb
= GetTotalCapacityOfArticulatedParts(b
.engine_id
);
361 /* Use EngineID to sort instead since we want consistent sorting */
362 if (r
== 0) return EngineNumberSorter(a
, b
);
363 return _engine_sort_direction
? r
> 0 : r
< 0;
366 /* Ship vehicle sorting functions */
369 * Determines order of ships by capacity
370 * @param a first engine to compare
371 * @param b second engine to compare
372 * @return for descending order: returns true if a < b. Vice versa for ascending order
374 static bool ShipEngineCapacitySorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
376 const Engine
*e_a
= Engine::Get(a
.engine_id
);
377 const Engine
*e_b
= Engine::Get(b
.engine_id
);
379 int va
= e_a
->GetDisplayDefaultCapacity();
380 int vb
= e_b
->GetDisplayDefaultCapacity();
383 /* Use EngineID to sort instead since we want consistent sorting */
384 if (r
== 0) return EngineNumberSorter(a
, b
);
385 return _engine_sort_direction
? r
> 0 : r
< 0;
388 /* Aircraft sorting functions */
391 * Determines order of aircraft by cargo
392 * @param a first engine to compare
393 * @param b second engine to compare
394 * @return for descending order: returns true if a < b. Vice versa for ascending order
396 static bool AircraftEngineCargoSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
398 const Engine
*e_a
= Engine::Get(a
.engine_id
);
399 const Engine
*e_b
= Engine::Get(b
.engine_id
);
401 uint16_t mail_a
, mail_b
;
402 int va
= e_a
->GetDisplayDefaultCapacity(&mail_a
);
403 int vb
= e_b
->GetDisplayDefaultCapacity(&mail_b
);
407 /* The planes have the same passenger capacity. Check mail capacity instead */
411 /* Use EngineID to sort instead since we want consistent sorting */
412 return EngineNumberSorter(a
, b
);
415 return _engine_sort_direction
? r
> 0 : r
< 0;
419 * Determines order of aircraft by range.
420 * @param a first engine to compare
421 * @param b second engine to compare
422 * @return for descending order: returns true if a < b. Vice versa for ascending order
424 static bool AircraftRangeSorter(const GUIEngineListItem
&a
, const GUIEngineListItem
&b
)
426 uint16_t r_a
= Engine::Get(a
.engine_id
)->GetRange();
427 uint16_t r_b
= Engine::Get(b
.engine_id
)->GetRange();
431 /* Use EngineID to sort instead since we want consistent sorting */
432 if (r
== 0) return EngineNumberSorter(a
, b
);
433 return _engine_sort_direction
? r
> 0 : r
< 0;
436 /** Sort functions for the vehicle sort criteria, for each vehicle type. */
437 EngList_SortTypeFunction
* const _engine_sort_functions
[][11] = {{
443 &EngineTractiveEffortSorter
,
444 &EngineIntroDateSorter
,
446 &EngineRunningCostSorter
,
447 &EnginePowerVsRunningCostSorter
,
448 &EngineReliabilitySorter
,
449 &TrainEngineCapacitySorter
,
456 &EngineTractiveEffortSorter
,
457 &EngineIntroDateSorter
,
459 &EngineRunningCostSorter
,
460 &EnginePowerVsRunningCostSorter
,
461 &EngineReliabilitySorter
,
462 &RoadVehEngineCapacitySorter
,
468 &EngineIntroDateSorter
,
470 &EngineRunningCostSorter
,
471 &EngineReliabilitySorter
,
472 &ShipEngineCapacitySorter
,
478 &EngineIntroDateSorter
,
480 &EngineRunningCostSorter
,
481 &EngineReliabilitySorter
,
482 &AircraftEngineCargoSorter
,
483 &AircraftRangeSorter
,
486 /** Dropdown menu strings for the vehicle sort criteria. */
487 const std::initializer_list
<const StringID
> _engine_sort_listing
[] = {{
489 STR_SORT_BY_ENGINE_ID
,
491 STR_SORT_BY_MAX_SPEED
,
493 STR_SORT_BY_TRACTIVE_EFFORT
,
494 STR_SORT_BY_INTRO_DATE
,
496 STR_SORT_BY_RUNNING_COST
,
497 STR_SORT_BY_POWER_VS_RUNNING_COST
,
498 STR_SORT_BY_RELIABILITY
,
499 STR_SORT_BY_CARGO_CAPACITY
,
502 STR_SORT_BY_ENGINE_ID
,
504 STR_SORT_BY_MAX_SPEED
,
506 STR_SORT_BY_TRACTIVE_EFFORT
,
507 STR_SORT_BY_INTRO_DATE
,
509 STR_SORT_BY_RUNNING_COST
,
510 STR_SORT_BY_POWER_VS_RUNNING_COST
,
511 STR_SORT_BY_RELIABILITY
,
512 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
,
525 STR_SORT_BY_ENGINE_ID
,
527 STR_SORT_BY_MAX_SPEED
,
528 STR_SORT_BY_INTRO_DATE
,
530 STR_SORT_BY_RUNNING_COST
,
531 STR_SORT_BY_RELIABILITY
,
532 STR_SORT_BY_CARGO_CAPACITY
,
536 /** Filters vehicles by cargo and engine (in case of rail vehicle). */
537 static bool CargoAndEngineFilter(const GUIEngineListItem
*item
, const CargoID cid
)
539 if (cid
== CargoFilterCriteria::CF_ANY
) {
541 } else if (cid
== CargoFilterCriteria::CF_ENGINES
) {
542 return Engine::Get(item
->engine_id
)->GetPower() != 0;
544 CargoTypes refit_mask
= GetUnionOfArticulatedRefitMasks(item
->engine_id
, true) & _standard_cargo_mask
;
545 return (cid
== CargoFilterCriteria::CF_NONE
? refit_mask
== 0 : HasBit(refit_mask
, cid
));
549 static GUIEngineList::FilterFunction
* const _engine_filter_funcs
[] = {
550 &CargoAndEngineFilter
,
553 static uint
GetCargoWeight(const CargoArray
&cap
, VehicleType vtype
)
556 for (CargoID c
= 0; c
< NUM_CARGO
; c
++) {
558 if (vtype
== VEH_TRAIN
) {
559 weight
+= CargoSpec::Get(c
)->WeightOfNUnitsInTrain(cap
[c
]);
561 weight
+= CargoSpec::Get(c
)->WeightOfNUnits(cap
[c
]);
568 static int DrawCargoCapacityInfo(int left
, int right
, int y
, TestedEngineDetails
&te
, bool refittable
)
570 for (const CargoSpec
*cs
: _sorted_cargo_specs
) {
571 CargoID cid
= cs
->Index();
572 if (te
.all_capacities
[cid
] == 0) continue;
575 SetDParam(1, te
.all_capacities
[cid
]);
576 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
577 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
578 y
+= GetCharacterHeight(FS_NORMAL
);
584 /* Draw rail wagon specific details */
585 static int DrawRailWagonPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, const RailVehicleInfo
*rvi
, TestedEngineDetails
&te
)
587 const Engine
*e
= Engine::Get(engine_number
);
591 SetDParam(0, e
->GetCost() + te
.cost
);
592 SetDParam(1, te
.cost
);
593 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT
);
595 SetDParam(0, e
->GetCost());
596 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
598 y
+= GetCharacterHeight(FS_NORMAL
);
600 /* Wagon weight - (including cargo) */
601 uint weight
= e
->GetDisplayWeight();
602 SetDParam(0, weight
);
603 SetDParam(1, GetCargoWeight(te
.all_capacities
, VEH_TRAIN
) + weight
);
604 DrawString(left
, right
, y
, STR_PURCHASE_INFO_WEIGHT_CWEIGHT
);
605 y
+= GetCharacterHeight(FS_NORMAL
);
607 /* Wagon speed limit, displayed if above zero */
608 if (_settings_game
.vehicle
.wagon_speed_limits
) {
609 uint max_speed
= e
->GetDisplayMaxSpeed();
611 SetDParam(0, PackVelocity(max_speed
, e
->type
));
612 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED
);
613 y
+= GetCharacterHeight(FS_NORMAL
);
618 if (rvi
->running_cost_class
!= INVALID_PRICE
) {
619 SetDParam(0, e
->GetRunningCost());
620 DrawString(left
, right
, y
, TimerGameEconomy::UsingWallclockUnits() ? STR_PURCHASE_INFO_RUNNINGCOST_PERIOD
: STR_PURCHASE_INFO_RUNNINGCOST_YEAR
);
621 y
+= GetCharacterHeight(FS_NORMAL
);
627 /* Draw locomotive specific details */
628 static int DrawRailEnginePurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, const RailVehicleInfo
*rvi
, TestedEngineDetails
&te
)
630 const Engine
*e
= Engine::Get(engine_number
);
632 /* Purchase Cost - Engine weight */
634 SetDParam(0, e
->GetCost() + te
.cost
);
635 SetDParam(1, te
.cost
);
636 SetDParam(2, e
->GetDisplayWeight());
637 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_WEIGHT
);
639 SetDParam(0, e
->GetCost());
640 SetDParam(1, e
->GetDisplayWeight());
641 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_WEIGHT
);
643 y
+= GetCharacterHeight(FS_NORMAL
);
645 /* Max speed - Engine power */
646 SetDParam(0, PackVelocity(e
->GetDisplayMaxSpeed(), e
->type
));
647 SetDParam(1, e
->GetPower());
648 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_POWER
);
649 y
+= GetCharacterHeight(FS_NORMAL
);
651 /* Max tractive effort - not applicable if old acceleration or maglev */
652 if (_settings_game
.vehicle
.train_acceleration_model
!= AM_ORIGINAL
&& GetRailTypeInfo(rvi
->railtype
)->acceleration_type
!= 2) {
653 SetDParam(0, e
->GetDisplayMaxTractiveEffort());
654 DrawString(left
, right
, y
, STR_PURCHASE_INFO_MAX_TE
);
655 y
+= GetCharacterHeight(FS_NORMAL
);
659 if (rvi
->running_cost_class
!= INVALID_PRICE
) {
660 SetDParam(0, e
->GetRunningCost());
661 DrawString(left
, right
, y
, TimerGameEconomy::UsingWallclockUnits() ? STR_PURCHASE_INFO_RUNNINGCOST_PERIOD
: STR_PURCHASE_INFO_RUNNINGCOST_YEAR
);
662 y
+= GetCharacterHeight(FS_NORMAL
);
665 /* Powered wagons power - Powered wagons extra weight */
666 if (rvi
->pow_wag_power
!= 0) {
667 SetDParam(0, rvi
->pow_wag_power
);
668 SetDParam(1, rvi
->pow_wag_weight
);
669 DrawString(left
, right
, y
, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT
);
670 y
+= GetCharacterHeight(FS_NORMAL
);
676 /* Draw road vehicle specific details */
677 static int DrawRoadVehPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, TestedEngineDetails
&te
)
679 const Engine
*e
= Engine::Get(engine_number
);
681 if (_settings_game
.vehicle
.roadveh_acceleration_model
!= AM_ORIGINAL
) {
684 SetDParam(0, e
->GetCost() + te
.cost
);
685 SetDParam(1, te
.cost
);
686 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT
);
688 SetDParam(0, e
->GetCost());
689 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
691 y
+= GetCharacterHeight(FS_NORMAL
);
693 /* Road vehicle weight - (including cargo) */
694 int16_t weight
= e
->GetDisplayWeight();
695 SetDParam(0, weight
);
696 SetDParam(1, GetCargoWeight(te
.all_capacities
, VEH_ROAD
) + weight
);
697 DrawString(left
, right
, y
, STR_PURCHASE_INFO_WEIGHT_CWEIGHT
);
698 y
+= GetCharacterHeight(FS_NORMAL
);
700 /* Max speed - Engine power */
701 SetDParam(0, PackVelocity(e
->GetDisplayMaxSpeed(), e
->type
));
702 SetDParam(1, e
->GetPower());
703 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_POWER
);
704 y
+= GetCharacterHeight(FS_NORMAL
);
706 /* Max tractive effort */
707 SetDParam(0, e
->GetDisplayMaxTractiveEffort());
708 DrawString(left
, right
, y
, STR_PURCHASE_INFO_MAX_TE
);
709 y
+= GetCharacterHeight(FS_NORMAL
);
711 /* Purchase cost - Max speed */
713 SetDParam(0, e
->GetCost() + te
.cost
);
714 SetDParam(1, te
.cost
);
715 SetDParam(2, PackVelocity(e
->GetDisplayMaxSpeed(), e
->type
));
716 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_SPEED
);
718 SetDParam(0, e
->GetCost());
719 SetDParam(1, PackVelocity(e
->GetDisplayMaxSpeed(), e
->type
));
720 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
722 y
+= GetCharacterHeight(FS_NORMAL
);
726 SetDParam(0, e
->GetRunningCost());
727 DrawString(left
, right
, y
, TimerGameEconomy::UsingWallclockUnits() ? STR_PURCHASE_INFO_RUNNINGCOST_PERIOD
: STR_PURCHASE_INFO_RUNNINGCOST_YEAR
);
728 y
+= GetCharacterHeight(FS_NORMAL
);
733 /* Draw ship specific details */
734 static int DrawShipPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, bool refittable
, TestedEngineDetails
&te
)
736 const Engine
*e
= Engine::Get(engine_number
);
738 /* Purchase cost - Max speed */
739 uint raw_speed
= e
->GetDisplayMaxSpeed();
740 uint ocean_speed
= e
->u
.ship
.ApplyWaterClassSpeedFrac(raw_speed
, true);
741 uint canal_speed
= e
->u
.ship
.ApplyWaterClassSpeedFrac(raw_speed
, false);
743 if (ocean_speed
== canal_speed
) {
745 SetDParam(0, e
->GetCost() + te
.cost
);
746 SetDParam(1, te
.cost
);
747 SetDParam(2, PackVelocity(ocean_speed
, e
->type
));
748 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_SPEED
);
750 SetDParam(0, e
->GetCost());
751 SetDParam(1, PackVelocity(ocean_speed
, e
->type
));
752 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
754 y
+= GetCharacterHeight(FS_NORMAL
);
757 SetDParam(0, e
->GetCost() + te
.cost
);
758 SetDParam(1, te
.cost
);
759 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT
);
761 SetDParam(0, e
->GetCost());
762 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
764 y
+= GetCharacterHeight(FS_NORMAL
);
766 SetDParam(0, PackVelocity(ocean_speed
, e
->type
));
767 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_OCEAN
);
768 y
+= GetCharacterHeight(FS_NORMAL
);
770 SetDParam(0, PackVelocity(canal_speed
, e
->type
));
771 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_CANAL
);
772 y
+= GetCharacterHeight(FS_NORMAL
);
775 /* Cargo type + capacity */
776 SetDParam(0, te
.cargo
);
777 SetDParam(1, te
.capacity
);
778 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
779 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
780 y
+= GetCharacterHeight(FS_NORMAL
);
783 SetDParam(0, e
->GetRunningCost());
784 DrawString(left
, right
, y
, TimerGameEconomy::UsingWallclockUnits() ? STR_PURCHASE_INFO_RUNNINGCOST_PERIOD
: STR_PURCHASE_INFO_RUNNINGCOST_YEAR
);
785 y
+= GetCharacterHeight(FS_NORMAL
);
791 * Draw aircraft specific details in the buy window.
792 * @param left Left edge of the window to draw in.
793 * @param right Right edge of the window to draw in.
794 * @param y Top of the area to draw in.
795 * @param engine_number Engine to display.
796 * @param refittable If set, the aircraft can be refitted.
797 * @return Bottom of the used area.
799 static int DrawAircraftPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, bool refittable
, TestedEngineDetails
&te
)
801 const Engine
*e
= Engine::Get(engine_number
);
803 /* Purchase cost - Max speed */
805 SetDParam(0, e
->GetCost() + te
.cost
);
806 SetDParam(1, te
.cost
);
807 SetDParam(2, PackVelocity(e
->GetDisplayMaxSpeed(), e
->type
));
808 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_REFIT_SPEED
);
810 SetDParam(0, e
->GetCost());
811 SetDParam(1, PackVelocity(e
->GetDisplayMaxSpeed(), e
->type
));
812 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
814 y
+= GetCharacterHeight(FS_NORMAL
);
817 if (te
.mail_capacity
> 0) {
818 SetDParam(0, te
.cargo
);
819 SetDParam(1, te
.capacity
);
820 SetDParam(2, GetCargoIDByLabel(CT_MAIL
));
821 SetDParam(3, te
.mail_capacity
);
822 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY
);
824 /* Note, if the default capacity is selected by the refit capacity
825 * callback, then the capacity shown is likely to be incorrect. */
826 SetDParam(0, te
.cargo
);
827 SetDParam(1, te
.capacity
);
828 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
829 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
831 y
+= GetCharacterHeight(FS_NORMAL
);
834 SetDParam(0, e
->GetRunningCost());
835 DrawString(left
, right
, y
, TimerGameEconomy::UsingWallclockUnits() ? STR_PURCHASE_INFO_RUNNINGCOST_PERIOD
: STR_PURCHASE_INFO_RUNNINGCOST_YEAR
);
836 y
+= GetCharacterHeight(FS_NORMAL
);
839 SetDParam(0, e
->GetAircraftTypeText());
840 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_TYPE
);
841 y
+= GetCharacterHeight(FS_NORMAL
);
843 /* Aircraft range, if available. */
844 uint16_t range
= e
->GetRange();
847 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_RANGE
);
848 y
+= GetCharacterHeight(FS_NORMAL
);
856 * Try to get the NewGRF engine additional text callback as an optional std::string.
857 * @param engine The engine whose additional text to get.
858 * @return The std::string if present, otherwise std::nullopt.
860 static std::optional
<std::string
> GetNewGRFAdditionalText(EngineID engine
)
862 uint16_t callback
= GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT
, 0, 0, engine
, nullptr);
863 if (callback
== CALLBACK_FAILED
|| callback
== 0x400) return std::nullopt
;
864 const GRFFile
*grffile
= Engine::Get(engine
)->GetGRF();
865 assert(grffile
!= nullptr);
866 if (callback
> 0x400) {
867 ErrorUnknownCallbackResult(grffile
->grfid
, CBID_VEHICLE_ADDITIONAL_TEXT
, callback
);
871 StartTextRefStackUsage(grffile
, 6);
872 std::string result
= GetString(GetGRFStringID(grffile
->grfid
, 0xD000 + callback
));
873 StopTextRefStackUsage();
878 * Display additional text from NewGRF in the purchase information window
879 * @param left Left border of text bounding box
880 * @param right Right border of text bounding box
881 * @param y Top border of text bounding box
882 * @param engine Engine to query the additional purchase information for
883 * @return Bottom border of text bounding box
885 static uint
ShowAdditionalText(int left
, int right
, int y
, EngineID engine
)
887 auto text
= GetNewGRFAdditionalText(engine
);
889 return DrawStringMultiLine(left
, right
, y
, INT32_MAX
, *text
, TC_BLACK
);
892 void TestedEngineDetails::FillDefaultCapacities(const Engine
*e
)
894 this->cargo
= e
->GetDefaultCargoType();
895 if (e
->type
== VEH_TRAIN
|| e
->type
== VEH_ROAD
) {
896 this->all_capacities
= GetCapacityOfArticulatedParts(e
->index
);
897 this->capacity
= this->all_capacities
[this->cargo
];
898 this->mail_capacity
= 0;
900 this->capacity
= e
->GetDisplayDefaultCapacity(&this->mail_capacity
);
901 this->all_capacities
[this->cargo
] = this->capacity
;
902 if (IsValidCargoID(GetCargoIDByLabel(CT_MAIL
))) {
903 this->all_capacities
[GetCargoIDByLabel(CT_MAIL
)] = this->mail_capacity
;
905 this->mail_capacity
= 0;
908 if (this->all_capacities
.GetCount() == 0) this->cargo
= INVALID_CARGO
;
912 * Draw the purchase info details of a vehicle at a given location.
913 * @param left,right,y location where to draw the info
914 * @param engine_number the engine of which to draw the info of
915 * @return y after drawing all the text
917 int DrawVehiclePurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, TestedEngineDetails
&te
)
919 const Engine
*e
= Engine::Get(engine_number
);
920 TimerGameCalendar::YearMonthDay ymd
= TimerGameCalendar::ConvertDateToYMD(e
->intro_date
);
921 bool refittable
= IsArticulatedVehicleRefittable(engine_number
);
922 bool articulated_cargo
= false;
925 default: NOT_REACHED();
927 if (e
->u
.rail
.railveh_type
== RAILVEH_WAGON
) {
928 y
= DrawRailWagonPurchaseInfo(left
, right
, y
, engine_number
, &e
->u
.rail
, te
);
930 y
= DrawRailEnginePurchaseInfo(left
, right
, y
, engine_number
, &e
->u
.rail
, te
);
932 articulated_cargo
= true;
936 y
= DrawRoadVehPurchaseInfo(left
, right
, y
, engine_number
, te
);
937 articulated_cargo
= true;
941 y
= DrawShipPurchaseInfo(left
, right
, y
, engine_number
, refittable
, te
);
945 y
= DrawAircraftPurchaseInfo(left
, right
, y
, engine_number
, refittable
, te
);
949 if (articulated_cargo
) {
950 /* Cargo type + capacity, or N/A */
951 int new_y
= DrawCargoCapacityInfo(left
, right
, y
, te
, refittable
);
954 SetDParam(0, INVALID_CARGO
);
955 SetDParam(2, STR_EMPTY
);
956 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
957 y
+= GetCharacterHeight(FS_NORMAL
);
963 /* Draw details that apply to all types except rail wagons. */
964 if (e
->type
!= VEH_TRAIN
|| e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
) {
965 /* Design date - Life length */
966 SetDParam(0, ymd
.year
);
967 SetDParam(1, TimerGameCalendar::DateToYear(e
->GetLifeLengthInDays()));
968 DrawString(left
, right
, y
, STR_PURCHASE_INFO_DESIGNED_LIFE
);
969 y
+= GetCharacterHeight(FS_NORMAL
);
972 SetDParam(0, ToPercent16(e
->reliability
));
973 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RELIABILITY
);
974 y
+= GetCharacterHeight(FS_NORMAL
);
977 if (refittable
) y
= ShowRefitOptionsList(left
, right
, y
, engine_number
);
979 /* Additional text from NewGRF */
980 y
= ShowAdditionalText(left
, right
, y
, engine_number
);
982 /* The NewGRF's name which the vehicle comes from */
983 const GRFConfig
*config
= GetGRFConfig(e
->GetGRFID());
984 if (_settings_client
.gui
.show_newgrf_name
&& config
!= nullptr)
986 DrawString(left
, right
, y
, config
->GetName(), TC_BLACK
);
987 y
+= GetCharacterHeight(FS_NORMAL
);
994 * Engine drawing loop
995 * @param type Type of vehicle (VEH_*)
996 * @param r The Rect of the list
997 * @param eng_list What engines to draw
998 * @param sb Scrollbar of list.
999 * @param selected_id what engine to highlight as selected, if any
1000 * @param show_count Whether to show the amount of engines or not
1001 * @param selected_group the group to list the engines of
1003 void DrawEngineList(VehicleType type
, const Rect
&r
, const GUIEngineList
&eng_list
, const Scrollbar
&sb
, EngineID selected_id
, bool show_count
, GroupID selected_group
)
1005 static const int sprite_y_offsets
[] = { -1, -1, -2, -2 };
1007 auto [first
, last
] = sb
.GetVisibleRangeIterators(eng_list
);
1009 bool rtl
= _current_text_dir
== TD_RTL
;
1010 int step_size
= GetEngineListHeight(type
);
1011 int sprite_left
= GetVehicleImageCellSize(type
, EIT_PURCHASE
).extend_left
;
1012 int sprite_right
= GetVehicleImageCellSize(type
, EIT_PURCHASE
).extend_right
;
1013 int sprite_width
= sprite_left
+ sprite_right
;
1014 int circle_width
= std::max(GetScaledSpriteSize(SPR_CIRCLE_FOLDED
).width
, GetScaledSpriteSize(SPR_CIRCLE_UNFOLDED
).width
);
1015 int linecolour
= GetColourGradient(COLOUR_ORANGE
, SHADE_NORMAL
);
1017 Rect ir
= r
.WithHeight(step_size
).Shrink(WidgetDimensions::scaled
.matrix
);
1018 int sprite_y_offset
= ScaleSpriteTrad(sprite_y_offsets
[type
]) + ir
.Height() / 2;
1020 Dimension replace_icon
= {0, 0};
1021 int count_width
= 0;
1023 replace_icon
= GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE
);
1025 uint biggest_num_engines
= 0;
1026 for (auto it
= first
; it
!= last
; ++it
) {
1027 const uint num_engines
= GetGroupNumEngines(_local_company
, selected_group
, it
->engine_id
);
1028 biggest_num_engines
= std::max(biggest_num_engines
, num_engines
);
1031 SetDParam(0, biggest_num_engines
);
1032 count_width
= GetStringBoundingBox(STR_JUST_COMMA
, FS_SMALL
).width
;
1035 Rect tr
= ir
.Indent(circle_width
+ WidgetDimensions::scaled
.hsep_normal
+ sprite_width
+ WidgetDimensions::scaled
.hsep_wide
, rtl
); // Name position
1036 Rect cr
= tr
.Indent(replace_icon
.width
+ WidgetDimensions::scaled
.hsep_wide
, !rtl
).WithWidth(count_width
, !rtl
); // Count position
1037 Rect rr
= tr
.WithWidth(replace_icon
.width
, !rtl
); // Replace icon position
1038 if (show_count
) tr
= tr
.Indent(count_width
+ WidgetDimensions::scaled
.hsep_normal
+ replace_icon
.width
+ WidgetDimensions::scaled
.hsep_wide
, !rtl
);
1040 int normal_text_y_offset
= (ir
.Height() - GetCharacterHeight(FS_NORMAL
)) / 2;
1041 int small_text_y_offset
= ir
.Height() - GetCharacterHeight(FS_SMALL
);
1042 int replace_icon_y_offset
= (ir
.Height() - replace_icon
.height
) / 2;
1044 const int offset
= (rtl
? -circle_width
: circle_width
) / 2;
1045 const int level_width
= rtl
? -WidgetDimensions::scaled
.hsep_indent
: WidgetDimensions::scaled
.hsep_indent
;
1048 for (auto it
= first
; it
!= last
; ++it
) {
1049 const auto &item
= *it
;
1050 uint indent
= item
.indent
* WidgetDimensions::scaled
.hsep_indent
;
1051 bool has_variants
= HasFlag(item
.flags
, EngineDisplayFlags::HasVariants
);
1052 bool is_folded
= HasFlag(item
.flags
, EngineDisplayFlags::IsFolded
);
1053 bool shaded
= HasFlag(item
.flags
, EngineDisplayFlags::Shaded
);
1055 if (item
.indent
> 0) {
1056 /* Draw tree continuation lines. */
1057 int tx
= (rtl
? ir
.right
: ir
.left
) + offset
;
1058 int ty
= y
- WidgetDimensions::scaled
.matrix
.top
;
1059 for (uint lvl
= 1; lvl
<= item
.indent
; ++lvl
) {
1060 if (HasBit(item
.level_mask
, lvl
)) GfxDrawLine(tx
, ty
, tx
, ty
+ step_size
- 1, linecolour
, WidgetDimensions::scaled
.fullbevel
.top
);
1061 if (lvl
< item
.indent
) tx
+= level_width
;
1063 /* Draw our node in the tree. */
1064 int ycentre
= y
+ normal_text_y_offset
+ GetCharacterHeight(FS_NORMAL
) / 2 - 1;
1065 if (!HasBit(item
.level_mask
, item
.indent
)) GfxDrawLine(tx
, ty
, tx
, ycentre
, linecolour
, WidgetDimensions::scaled
.fullbevel
.top
);
1066 GfxDrawLine(tx
, ycentre
, tx
+ offset
- (rtl
? -1 : 1), ycentre
, linecolour
, WidgetDimensions::scaled
.fullbevel
.top
);
1069 /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
1070 const uint num_engines
= GetGroupNumEngines(_local_company
, selected_group
, item
.engine_id
);
1072 const Engine
*e
= Engine::Get(item
.engine_id
);
1073 bool hidden
= HasBit(e
->company_hidden
, _local_company
);
1074 StringID str
= hidden
? STR_HIDDEN_ENGINE_NAME
: STR_ENGINE_NAME
;
1075 TextColour tc
= (item
.engine_id
== selected_id
) ? TC_WHITE
: ((hidden
| shaded
) ? (TC_GREY
| TC_FORCED
| TC_NO_SHADE
) : TC_BLACK
);
1078 /* relies on show_count to find 'Vehicle in use' panel of autoreplace window */
1079 SetDParam(0, PackEngineNameDParam(item
.engine_id
, EngineNameContext::AutoreplaceVehicleInUse
, item
.indent
));
1081 SetDParam(0, PackEngineNameDParam(item
.engine_id
, EngineNameContext::PurchaseList
, item
.indent
));
1083 Rect itr
= tr
.Indent(indent
, rtl
);
1084 DrawString(itr
.left
, itr
.right
, y
+ normal_text_y_offset
, str
, tc
);
1085 int sprite_x
= ir
.Indent(indent
+ circle_width
+ WidgetDimensions::scaled
.hsep_normal
, rtl
).WithWidth(sprite_width
, rtl
).left
+ sprite_left
;
1086 DrawVehicleEngine(r
.left
, r
.right
, sprite_x
, y
+ sprite_y_offset
, item
.engine_id
, (show_count
&& num_engines
== 0) ? PALETTE_CRASH
: GetEnginePalette(item
.engine_id
, _local_company
), EIT_PURCHASE
);
1088 SetDParam(0, num_engines
);
1089 DrawString(cr
.left
, cr
.right
, y
+ small_text_y_offset
, STR_JUST_COMMA
, TC_BLACK
, SA_RIGHT
| SA_FORCE
, false, FS_SMALL
);
1090 if (EngineHasReplacementForCompany(Company::Get(_local_company
), item
.engine_id
, selected_group
)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE
, num_engines
== 0 ? PALETTE_CRASH
: PAL_NONE
, rr
.left
, y
+ replace_icon_y_offset
);
1093 Rect fr
= ir
.Indent(indent
, rtl
).WithWidth(circle_width
, rtl
);
1094 DrawSpriteIgnorePadding(is_folded
? SPR_CIRCLE_FOLDED
: SPR_CIRCLE_UNFOLDED
, PAL_NONE
, {fr
.left
, y
, fr
.right
, y
+ ir
.Height() - 1}, SA_CENTER
);
1101 * Display the dropdown for the vehicle sort criteria.
1102 * @param w Parent window (holds the dropdown button).
1103 * @param vehicle_type %Vehicle type being sorted.
1104 * @param selected Currently selected sort criterium.
1105 * @param button Widget button.
1107 void DisplayVehicleSortDropDown(Window
*w
, VehicleType vehicle_type
, int selected
, WidgetID button
)
1109 uint32_t hidden_mask
= 0;
1110 /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
1111 if (vehicle_type
== VEH_ROAD
&& _settings_game
.vehicle
.roadveh_acceleration_model
== AM_ORIGINAL
) {
1112 SetBit(hidden_mask
, 3); // power
1113 SetBit(hidden_mask
, 4); // tractive effort
1114 SetBit(hidden_mask
, 8); // power by running costs
1116 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
1117 if (vehicle_type
== VEH_TRAIN
&& _settings_game
.vehicle
.train_acceleration_model
== AM_ORIGINAL
) {
1118 SetBit(hidden_mask
, 4); // tractive effort
1120 ShowDropDownMenu(w
, _engine_sort_listing
[vehicle_type
], selected
, button
, 0, hidden_mask
);
1124 * Add children to GUI engine list to build a hierarchical tree.
1125 * @param dst Destination list.
1126 * @param src Source list.
1127 * @param parent Current tree parent (set by self with recursion).
1128 * @param indent Current tree indentation level (set by self with recursion).
1130 void GUIEngineListAddChildren(GUIEngineList
&dst
, const GUIEngineList
&src
, EngineID parent
, uint8_t indent
)
1132 for (const auto &item
: src
) {
1133 if (item
.variant_id
!= parent
|| item
.engine_id
== parent
) continue;
1135 const Engine
*e
= Engine::Get(item
.engine_id
);
1136 EngineDisplayFlags flags
= item
.flags
;
1137 if (e
->display_last_variant
!= INVALID_ENGINE
) flags
&= ~EngineDisplayFlags::Shaded
;
1138 dst
.emplace_back(e
->display_last_variant
== INVALID_ENGINE
? item
.engine_id
: e
->display_last_variant
, item
.engine_id
, flags
, indent
);
1140 /* Add variants if not folded */
1141 if (HasFlag(item
.flags
, EngineDisplayFlags::HasVariants
) && !HasFlag(item
.flags
, EngineDisplayFlags::IsFolded
)) {
1142 /* Add this engine again as a child */
1143 if (!HasFlag(item
.flags
, EngineDisplayFlags::Shaded
)) {
1144 dst
.emplace_back(item
.engine_id
, item
.engine_id
, EngineDisplayFlags::None
, indent
+ 1);
1146 GUIEngineListAddChildren(dst
, src
, item
.engine_id
, indent
+ 1);
1150 if (indent
> 0 || dst
.empty()) return;
1152 /* Hierarchy is complete, traverse in reverse to find where indentation levels continue. */
1153 uint16_t level_mask
= 0;
1154 for (auto it
= std::rbegin(dst
); std::next(it
) != std::rend(dst
); ++it
) {
1155 auto next_it
= std::next(it
);
1156 SB(level_mask
, it
->indent
, 1, it
->indent
<= next_it
->indent
);
1157 next_it
->level_mask
= level_mask
;
1161 /** Enum referring to the Hotkeys in the build vehicle window */
1162 enum BuildVehicleHotkeys
{
1163 BVHK_FOCUS_FILTER_BOX
, ///< Focus the edit box for editing the filter string
1166 /** GUI for building vehicles. */
1167 struct BuildVehicleWindow
: Window
{
1168 VehicleType vehicle_type
; ///< Type of vehicles shown in the window.
1170 RailType railtype
; ///< Rail type to show, or #INVALID_RAILTYPE.
1171 RoadType roadtype
; ///< Road type to show, or #INVALID_ROADTYPE.
1172 } filter
; ///< Filter to apply.
1173 bool descending_sort_order
; ///< Sort direction, @see _engine_sort_direction
1174 uint8_t sort_criteria
; ///< Current sort criterium.
1175 bool show_hidden_engines
; ///< State of the 'show hidden engines' button.
1176 bool listview_mode
; ///< If set, only display the available vehicles and do not show a 'build' button.
1177 EngineID sel_engine
; ///< Currently selected engine, or #INVALID_ENGINE
1178 EngineID rename_engine
; ///< Engine being renamed.
1179 GUIEngineList eng_list
;
1180 CargoID cargo_filter_criteria
; ///< Selected cargo filter
1181 int details_height
; ///< Minimal needed height of the details panels, in text lines (found so far).
1183 TestedEngineDetails te
; ///< Tested cost and capacity after refit.
1185 StringFilter string_filter
; ///< Filter for vehicle name
1186 QueryString vehicle_editbox
; ///< Filter editbox
1188 void SetBuyVehicleText()
1190 NWidgetCore
*widget
= this->GetWidget
<NWidgetCore
>(WID_BV_BUILD
);
1192 bool refit
= this->sel_engine
!= INVALID_ENGINE
&& this->cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
&& this->cargo_filter_criteria
!= CargoFilterCriteria::CF_NONE
&& this->cargo_filter_criteria
!= CargoFilterCriteria::CF_ENGINES
;
1193 if (refit
) refit
= Engine::Get(this->sel_engine
)->GetDefaultCargoType() != this->cargo_filter_criteria
;
1196 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON
+ this->vehicle_type
;
1197 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP
+ this->vehicle_type
;
1199 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON
+ this->vehicle_type
;
1200 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP
+ this->vehicle_type
;
1204 BuildVehicleWindow(WindowDesc
&desc
, TileIndex tile
, VehicleType type
) : Window(desc
), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS
* MAX_CHAR_LENGTH
, MAX_LENGTH_VEHICLE_NAME_CHARS
)
1206 this->vehicle_type
= type
;
1207 this->listview_mode
= tile
== INVALID_TILE
;
1208 this->window_number
= this->listview_mode
? (int)type
: tile
.base();
1210 this->sel_engine
= INVALID_ENGINE
;
1212 this->sort_criteria
= _engine_sort_last_criteria
[type
];
1213 this->descending_sort_order
= _engine_sort_last_order
[type
];
1214 this->show_hidden_engines
= _engine_sort_show_hidden_engines
[type
];
1216 this->UpdateFilterByTile();
1218 this->CreateNestedTree();
1220 this->vscroll
= this->GetScrollbar(WID_BV_SCROLLBAR
);
1222 /* If we are just viewing the list of vehicles, we do not need the Build button.
1223 * So we just hide it, and enlarge the Rename button by the now vacant place. */
1224 if (this->listview_mode
) this->GetWidget
<NWidgetStacked
>(WID_BV_BUILD_SEL
)->SetDisplayedPlane(SZSP_NONE
);
1226 NWidgetCore
*widget
= this->GetWidget
<NWidgetCore
>(WID_BV_LIST
);
1227 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP
+ type
;
1229 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_SHOW_HIDE
);
1230 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP
+ type
;
1232 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_RENAME
);
1233 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON
+ type
;
1234 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP
+ type
;
1236 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_SHOW_HIDDEN_ENGINES
);
1237 widget
->widget_data
= STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN
+ type
;
1238 widget
->tool_tip
= STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP
+ type
;
1239 widget
->SetLowered(this->show_hidden_engines
);
1241 this->details_height
= ((this->vehicle_type
== VEH_TRAIN
) ? 10 : 9);
1243 if (tile
== INVALID_TILE
) {
1244 this->FinishInitNested(type
);
1246 this->FinishInitNested(tile
);
1249 this->querystrings
[WID_BV_FILTER
] = &this->vehicle_editbox
;
1250 this->vehicle_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
1252 this->owner
= (tile
!= INVALID_TILE
) ? GetTileOwner(tile
) : _local_company
;
1254 this->eng_list
.ForceRebuild();
1255 this->GenerateBuildList(); // generate the list, since we need it in the next line
1257 /* Select the first unshaded engine in the list as default when opening the window */
1258 EngineID engine
= INVALID_ENGINE
;
1259 auto it
= std::find_if(this->eng_list
.begin(), this->eng_list
.end(), [&](GUIEngineListItem
&item
) { return !HasFlag(item
.flags
, EngineDisplayFlags::Shaded
); });
1260 if (it
!= this->eng_list
.end()) engine
= it
->engine_id
;
1261 this->SelectEngine(engine
);
1264 /** Set the filter type according to the depot type */
1265 void UpdateFilterByTile()
1267 switch (this->vehicle_type
) {
1268 default: NOT_REACHED();
1270 if (this->listview_mode
) {
1271 this->filter
.railtype
= INVALID_RAILTYPE
;
1273 this->filter
.railtype
= GetRailType(this->window_number
);
1278 if (this->listview_mode
) {
1279 this->filter
.roadtype
= INVALID_ROADTYPE
;
1281 this->filter
.roadtype
= GetRoadTypeRoad(this->window_number
);
1282 if (this->filter
.roadtype
== INVALID_ROADTYPE
) {
1283 this->filter
.roadtype
= GetRoadTypeTram(this->window_number
);
1294 StringID
GetCargoFilterLabel(CargoID cid
) const
1297 case CargoFilterCriteria::CF_ANY
: return STR_PURCHASE_INFO_ALL_TYPES
;
1298 case CargoFilterCriteria::CF_ENGINES
: return STR_PURCHASE_INFO_ENGINES_ONLY
;
1299 case CargoFilterCriteria::CF_NONE
: return STR_PURCHASE_INFO_NONE
;
1300 default: return CargoSpec::Get(cid
)->name
;
1304 /** Populate the filter list and set the cargo filter criteria. */
1305 void SetCargoFilterArray()
1307 /* Set the last cargo filter criteria. */
1308 this->cargo_filter_criteria
= _engine_sort_last_cargo_criteria
[this->vehicle_type
];
1309 if (this->cargo_filter_criteria
< NUM_CARGO
&& !HasBit(_standard_cargo_mask
, this->cargo_filter_criteria
)) this->cargo_filter_criteria
= CargoFilterCriteria::CF_ANY
;
1311 this->eng_list
.SetFilterFuncs(_engine_filter_funcs
);
1312 this->eng_list
.SetFilterState(this->cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
);
1315 void SelectEngine(EngineID engine
)
1317 CargoID cargo
= this->cargo_filter_criteria
;
1318 if (cargo
== CargoFilterCriteria::CF_ANY
|| cargo
== CargoFilterCriteria::CF_ENGINES
|| cargo
== CargoFilterCriteria::CF_NONE
) cargo
= INVALID_CARGO
;
1320 this->sel_engine
= engine
;
1321 this->SetBuyVehicleText();
1323 if (this->sel_engine
== INVALID_ENGINE
) return;
1325 const Engine
*e
= Engine::Get(this->sel_engine
);
1327 if (!this->listview_mode
) {
1328 /* Query for cost and refitted capacity */
1329 auto [ret
, veh_id
, refit_capacity
, refit_mail
, cargo_capacities
] = Command
<CMD_BUILD_VEHICLE
>::Do(DC_QUERY_COST
, this->window_number
, this->sel_engine
, true, cargo
, INVALID_CLIENT_ID
);
1330 if (ret
.Succeeded()) {
1331 this->te
.cost
= ret
.GetCost() - e
->GetCost();
1332 this->te
.capacity
= refit_capacity
;
1333 this->te
.mail_capacity
= refit_mail
;
1334 this->te
.cargo
= !IsValidCargoID(cargo
) ? e
->GetDefaultCargoType() : cargo
;
1335 this->te
.all_capacities
= cargo_capacities
;
1340 /* Purchase test was not possible or failed, fill in the defaults instead. */
1342 this->te
.FillDefaultCapacities(e
);
1345 void OnInit() override
1347 this->SetCargoFilterArray();
1350 /** Filter the engine list against the currently selected cargo filter */
1351 void FilterEngineList()
1353 this->eng_list
.Filter(this->cargo_filter_criteria
);
1354 if (0 == this->eng_list
.size()) { // no engine passed through the filter, invalidate the previously selected engine
1355 this->SelectEngine(INVALID_ENGINE
);
1356 } 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
1357 this->SelectEngine(this->eng_list
[0].engine_id
);
1361 /** Filter a single engine */
1362 bool FilterSingleEngine(EngineID eid
)
1364 GUIEngineListItem item
= {eid
, eid
, EngineDisplayFlags::None
, 0};
1365 return CargoAndEngineFilter(&item
, this->cargo_filter_criteria
);
1368 /** Filter by name and NewGRF extra text */
1369 bool FilterByText(const Engine
*e
)
1371 /* Do not filter if the filter text box is empty */
1372 if (this->string_filter
.IsEmpty()) return true;
1374 /* Filter engine name */
1375 this->string_filter
.ResetState();
1376 SetDParam(0, PackEngineNameDParam(e
->index
, EngineNameContext::PurchaseList
));
1377 this->string_filter
.AddLine(GetString(STR_ENGINE_NAME
));
1379 /* Filter NewGRF extra text */
1380 auto text
= GetNewGRFAdditionalText(e
->index
);
1381 if (text
) this->string_filter
.AddLine(*text
);
1383 return this->string_filter
.GetState();
1386 /* Figure out what train EngineIDs to put in the list */
1387 void GenerateBuildTrainList(GUIEngineList
&list
)
1389 std::vector
<EngineID
> variants
;
1390 EngineID sel_id
= INVALID_ENGINE
;
1391 size_t num_engines
= 0;
1395 /* Make list of all available train engines and wagons.
1396 * Also check to see if the previously selected engine is still available,
1397 * and if not, reset selection to INVALID_ENGINE. This could be the case
1398 * when engines become obsolete and are removed */
1399 for (const Engine
*e
: Engine::IterateType(VEH_TRAIN
)) {
1400 if (!this->show_hidden_engines
&& e
->IsVariantHidden(_local_company
)) continue;
1401 EngineID eid
= e
->index
;
1402 const RailVehicleInfo
*rvi
= &e
->u
.rail
;
1404 if (this->filter
.railtype
!= INVALID_RAILTYPE
&& !HasPowerOnRail(rvi
->railtype
, this->filter
.railtype
)) continue;
1405 if (!IsEngineBuildable(eid
, VEH_TRAIN
, _local_company
)) continue;
1407 /* Filter now! So num_engines and num_wagons is valid */
1408 if (!FilterSingleEngine(eid
)) continue;
1410 /* Filter by name or NewGRF extra text */
1411 if (!FilterByText(e
)) continue;
1413 list
.emplace_back(eid
, e
->info
.variant_id
, e
->display_flags
, 0);
1415 if (rvi
->railveh_type
!= RAILVEH_WAGON
) num_engines
++;
1417 /* Add all parent variants of this engine to the variant list */
1418 EngineID parent
= e
->info
.variant_id
;
1419 while (parent
!= INVALID_ENGINE
) {
1420 variants
.push_back(parent
);
1421 parent
= Engine::Get(parent
)->info
.variant_id
;
1424 if (eid
== this->sel_engine
) sel_id
= eid
;
1427 /* ensure primary engine of variant group is in list */
1428 for (const auto &variant
: variants
) {
1429 if (std::find(list
.begin(), list
.end(), variant
) == list
.end()) {
1430 const Engine
*e
= Engine::Get(variant
);
1431 list
.emplace_back(variant
, e
->info
.variant_id
, e
->display_flags
| EngineDisplayFlags::Shaded
, 0);
1432 if (e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
) num_engines
++;
1436 this->SelectEngine(sel_id
);
1438 /* invalidate cached values for name sorter - engine names could change */
1439 _last_engine
[0] = _last_engine
[1] = INVALID_ENGINE
;
1441 /* make engines first, and then wagons, sorted by selected sort_criteria */
1442 _engine_sort_direction
= false;
1443 EngList_Sort(list
, TrainEnginesThenWagonsSorter
);
1445 /* and then sort engines */
1446 _engine_sort_direction
= this->descending_sort_order
;
1447 EngList_SortPartial(list
, _engine_sort_functions
[0][this->sort_criteria
], 0, num_engines
);
1449 /* and finally sort wagons */
1450 EngList_SortPartial(list
, _engine_sort_functions
[0][this->sort_criteria
], num_engines
, list
.size() - num_engines
);
1453 /* Figure out what road vehicle EngineIDs to put in the list */
1454 void GenerateBuildRoadVehList()
1456 EngineID sel_id
= INVALID_ENGINE
;
1458 this->eng_list
.clear();
1460 for (const Engine
*e
: Engine::IterateType(VEH_ROAD
)) {
1461 if (!this->show_hidden_engines
&& e
->IsVariantHidden(_local_company
)) continue;
1462 EngineID eid
= e
->index
;
1463 if (!IsEngineBuildable(eid
, VEH_ROAD
, _local_company
)) continue;
1464 if (this->filter
.roadtype
!= INVALID_ROADTYPE
&& !HasPowerOnRoad(e
->u
.road
.roadtype
, this->filter
.roadtype
)) continue;
1466 /* Filter by name or NewGRF extra text */
1467 if (!FilterByText(e
)) continue;
1469 this->eng_list
.emplace_back(eid
, e
->info
.variant_id
, e
->display_flags
, 0);
1471 if (eid
== this->sel_engine
) sel_id
= eid
;
1473 this->SelectEngine(sel_id
);
1476 /* Figure out what ship EngineIDs to put in the list */
1477 void GenerateBuildShipList()
1479 EngineID sel_id
= INVALID_ENGINE
;
1480 this->eng_list
.clear();
1482 for (const Engine
*e
: Engine::IterateType(VEH_SHIP
)) {
1483 if (!this->show_hidden_engines
&& e
->IsVariantHidden(_local_company
)) continue;
1484 EngineID eid
= e
->index
;
1485 if (!IsEngineBuildable(eid
, VEH_SHIP
, _local_company
)) continue;
1487 /* Filter by name or NewGRF extra text */
1488 if (!FilterByText(e
)) continue;
1490 this->eng_list
.emplace_back(eid
, e
->info
.variant_id
, e
->display_flags
, 0);
1492 if (eid
== this->sel_engine
) sel_id
= eid
;
1494 this->SelectEngine(sel_id
);
1497 /* Figure out what aircraft EngineIDs to put in the list */
1498 void GenerateBuildAircraftList()
1500 EngineID sel_id
= INVALID_ENGINE
;
1502 this->eng_list
.clear();
1504 const Station
*st
= this->listview_mode
? nullptr : Station::GetByTile(this->window_number
);
1506 /* Make list of all available planes.
1507 * Also check to see if the previously selected plane is still available,
1508 * and if not, reset selection to INVALID_ENGINE. This could be the case
1509 * when planes become obsolete and are removed */
1510 for (const Engine
*e
: Engine::IterateType(VEH_AIRCRAFT
)) {
1511 if (!this->show_hidden_engines
&& e
->IsVariantHidden(_local_company
)) continue;
1512 EngineID eid
= e
->index
;
1513 if (!IsEngineBuildable(eid
, VEH_AIRCRAFT
, _local_company
)) continue;
1514 /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1515 if (!this->listview_mode
&& !CanVehicleUseStation(eid
, st
)) continue;
1517 /* Filter by name or NewGRF extra text */
1518 if (!FilterByText(e
)) continue;
1520 this->eng_list
.emplace_back(eid
, e
->info
.variant_id
, e
->display_flags
, 0);
1522 if (eid
== this->sel_engine
) sel_id
= eid
;
1525 this->SelectEngine(sel_id
);
1528 /* Generate the list of vehicles */
1529 void GenerateBuildList()
1531 if (!this->eng_list
.NeedRebuild()) return;
1533 /* Update filter type in case the road/railtype of the depot got converted */
1534 this->UpdateFilterByTile();
1536 this->eng_list
.clear();
1540 switch (this->vehicle_type
) {
1541 default: NOT_REACHED();
1543 this->GenerateBuildTrainList(list
);
1544 GUIEngineListAddChildren(this->eng_list
, list
);
1545 this->eng_list
.RebuildDone();
1548 this->GenerateBuildRoadVehList();
1551 this->GenerateBuildShipList();
1554 this->GenerateBuildAircraftList();
1558 this->FilterEngineList();
1560 /* ensure primary engine of variant group is in list after filtering */
1561 std::vector
<EngineID
> variants
;
1562 for (const auto &item
: this->eng_list
) {
1563 EngineID parent
= item
.variant_id
;
1564 while (parent
!= INVALID_ENGINE
) {
1565 variants
.push_back(parent
);
1566 parent
= Engine::Get(parent
)->info
.variant_id
;
1570 for (const auto &variant
: variants
) {
1571 if (std::find(this->eng_list
.begin(), this->eng_list
.end(), variant
) == this->eng_list
.end()) {
1572 const Engine
*e
= Engine::Get(variant
);
1573 this->eng_list
.emplace_back(variant
, e
->info
.variant_id
, e
->display_flags
| EngineDisplayFlags::Shaded
, 0);
1577 _engine_sort_direction
= this->descending_sort_order
;
1578 EngList_Sort(this->eng_list
, _engine_sort_functions
[this->vehicle_type
][this->sort_criteria
]);
1580 this->eng_list
.swap(list
);
1581 GUIEngineListAddChildren(this->eng_list
, list
, INVALID_ENGINE
, 0);
1582 this->eng_list
.RebuildDone();
1585 DropDownList
BuildCargoDropDownList() const
1589 /* Add item for disabling filtering. */
1590 list
.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ANY
), CargoFilterCriteria::CF_ANY
));
1591 /* Specific filters for trains. */
1592 if (this->vehicle_type
== VEH_TRAIN
) {
1593 /* Add item for locomotives only in case of trains. */
1594 list
.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_ENGINES
), CargoFilterCriteria::CF_ENGINES
));
1595 /* Add item for vehicles not carrying anything, e.g. train engines.
1596 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1597 list
.push_back(MakeDropDownListStringItem(this->GetCargoFilterLabel(CargoFilterCriteria::CF_NONE
), CargoFilterCriteria::CF_NONE
));
1601 Dimension d
= GetLargestCargoIconSize();
1602 for (const CargoSpec
*cs
: _sorted_standard_cargo_specs
) {
1603 list
.push_back(MakeDropDownListIconItem(d
, cs
->GetCargoIcon(), PAL_NONE
, cs
->name
, cs
->Index()));
1611 EngineID sel_eng
= this->sel_engine
;
1612 if (sel_eng
== INVALID_ENGINE
) return;
1614 CargoID cargo
= this->cargo_filter_criteria
;
1615 if (cargo
== CargoFilterCriteria::CF_ANY
|| cargo
== CargoFilterCriteria::CF_ENGINES
|| cargo
== CargoFilterCriteria::CF_NONE
) cargo
= INVALID_CARGO
;
1616 if (this->vehicle_type
== VEH_TRAIN
&& RailVehInfo(sel_eng
)->railveh_type
== RAILVEH_WAGON
) {
1617 Command
<CMD_BUILD_VEHICLE
>::Post(GetCmdBuildVehMsg(this->vehicle_type
), CcBuildWagon
, this->window_number
, sel_eng
, true, cargo
, INVALID_CLIENT_ID
);
1619 Command
<CMD_BUILD_VEHICLE
>::Post(GetCmdBuildVehMsg(this->vehicle_type
), CcBuildPrimaryVehicle
, this->window_number
, sel_eng
, true, cargo
, INVALID_CLIENT_ID
);
1622 /* Update last used variant in hierarchy and refresh if necessary. */
1623 bool refresh
= false;
1624 EngineID parent
= sel_eng
;
1625 while (parent
!= INVALID_ENGINE
) {
1626 Engine
*e
= Engine::Get(parent
);
1627 refresh
|= (e
->display_last_variant
!= sel_eng
);
1628 e
->display_last_variant
= sel_eng
;
1629 parent
= e
->info
.variant_id
;
1633 InvalidateWindowData(WC_REPLACE_VEHICLE
, this->vehicle_type
, 0); // Update the autoreplace window
1634 InvalidateWindowClassesData(WC_BUILD_VEHICLE
); // The build windows needs updating as well
1638 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
1641 case WID_BV_SORT_ASCENDING_DESCENDING
:
1642 this->descending_sort_order
^= true;
1643 _engine_sort_last_order
[this->vehicle_type
] = this->descending_sort_order
;
1644 this->eng_list
.ForceRebuild();
1648 case WID_BV_SHOW_HIDDEN_ENGINES
:
1649 this->show_hidden_engines
^= true;
1650 _engine_sort_show_hidden_engines
[this->vehicle_type
] = this->show_hidden_engines
;
1651 this->eng_list
.ForceRebuild();
1652 this->SetWidgetLoweredState(widget
, this->show_hidden_engines
);
1657 EngineID e
= INVALID_ENGINE
;
1658 const auto it
= this->vscroll
->GetScrolledItemFromWidget(this->eng_list
, pt
.y
, this, WID_BV_LIST
);
1659 if (it
!= this->eng_list
.end()) {
1660 const auto &item
= *it
;
1661 const Rect r
= this->GetWidget
<NWidgetBase
>(widget
)->GetCurrentRect().Shrink(WidgetDimensions::scaled
.matrix
).WithWidth(WidgetDimensions::scaled
.hsep_indent
* (item
.indent
+ 1), _current_text_dir
== TD_RTL
);
1662 if (HasFlag(item
.flags
, EngineDisplayFlags::HasVariants
) && IsInsideMM(r
.left
, r
.right
, pt
.x
)) {
1663 /* toggle folded flag on engine */
1664 assert(item
.variant_id
!= INVALID_ENGINE
);
1665 Engine
*engine
= Engine::Get(item
.variant_id
);
1666 engine
->display_flags
^= EngineDisplayFlags::IsFolded
;
1668 InvalidateWindowData(WC_REPLACE_VEHICLE
, this->vehicle_type
, 0); // Update the autoreplace window
1669 InvalidateWindowClassesData(WC_BUILD_VEHICLE
); // The build windows needs updating as well
1672 if (!HasFlag(item
.flags
, EngineDisplayFlags::Shaded
)) e
= item
.engine_id
;
1674 this->SelectEngine(e
);
1676 if (_ctrl_pressed
) {
1677 this->OnClick(pt
, WID_BV_SHOW_HIDE
, 1);
1678 } else if (click_count
> 1 && !this->listview_mode
) {
1679 this->OnClick(pt
, WID_BV_BUILD
, 1);
1684 case WID_BV_SORT_DROPDOWN
: // Select sorting criteria dropdown menu
1685 DisplayVehicleSortDropDown(this, this->vehicle_type
, this->sort_criteria
, WID_BV_SORT_DROPDOWN
);
1688 case WID_BV_CARGO_FILTER_DROPDOWN
: // Select cargo filtering criteria dropdown menu
1689 ShowDropDownList(this, this->BuildCargoDropDownList(), this->cargo_filter_criteria
, widget
);
1692 case WID_BV_SHOW_HIDE
: {
1693 const Engine
*e
= (this->sel_engine
== INVALID_ENGINE
) ? nullptr : Engine::Get(this->sel_engine
);
1695 Command
<CMD_SET_VEHICLE_VISIBILITY
>::Post(this->sel_engine
, !e
->IsHidden(_current_company
));
1701 this->BuildVehicle();
1704 case WID_BV_RENAME
: {
1705 EngineID sel_eng
= this->sel_engine
;
1706 if (sel_eng
!= INVALID_ENGINE
) {
1707 this->rename_engine
= sel_eng
;
1708 SetDParam(0, PackEngineNameDParam(sel_eng
, EngineNameContext::Generic
));
1709 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
);
1717 * Some data on this window has become invalid.
1718 * @param data Information about the changed data.
1719 * @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.
1721 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
1723 if (!gui_scope
) return;
1724 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1725 if (this->vehicle_type
== VEH_ROAD
&&
1726 _settings_game
.vehicle
.roadveh_acceleration_model
== AM_ORIGINAL
&&
1727 this->sort_criteria
> 7) {
1728 this->sort_criteria
= 0;
1729 _engine_sort_last_criteria
[VEH_ROAD
] = 0;
1731 this->eng_list
.ForceRebuild();
1734 void SetStringParameters(WidgetID widget
) const override
1737 case WID_BV_CAPTION
:
1738 if (this->vehicle_type
== VEH_TRAIN
&& !this->listview_mode
) {
1739 const RailTypeInfo
*rti
= GetRailTypeInfo(this->filter
.railtype
);
1740 SetDParam(0, rti
->strings
.build_caption
);
1741 } else if (this->vehicle_type
== VEH_ROAD
&& !this->listview_mode
) {
1742 const RoadTypeInfo
*rti
= GetRoadTypeInfo(this->filter
.roadtype
);
1743 SetDParam(0, rti
->strings
.build_caption
);
1745 SetDParam(0, (this->listview_mode
? STR_VEHICLE_LIST_AVAILABLE_TRAINS
: STR_BUY_VEHICLE_TRAIN_ALL_CAPTION
) + this->vehicle_type
);
1749 case WID_BV_SORT_DROPDOWN
:
1750 SetDParam(0, std::data(_engine_sort_listing
[this->vehicle_type
])[this->sort_criteria
]);
1753 case WID_BV_CARGO_FILTER_DROPDOWN
:
1754 SetDParam(0, this->GetCargoFilterLabel(this->cargo_filter_criteria
));
1757 case WID_BV_SHOW_HIDE
: {
1758 const Engine
*e
= (this->sel_engine
== INVALID_ENGINE
) ? nullptr : Engine::Get(this->sel_engine
);
1759 if (e
!= nullptr && e
->IsHidden(_local_company
)) {
1760 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON
+ this->vehicle_type
);
1762 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON
+ this->vehicle_type
);
1769 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
1773 resize
.height
= GetEngineListHeight(this->vehicle_type
);
1774 size
.height
= 3 * resize
.height
;
1775 size
.width
= std::max(size
.width
, GetVehicleImageCellSize(this->vehicle_type
, EIT_PURCHASE
).extend_left
+ GetVehicleImageCellSize(this->vehicle_type
, EIT_PURCHASE
).extend_right
+ 165) + padding
.width
;
1779 size
.height
= GetCharacterHeight(FS_NORMAL
) * this->details_height
+ padding
.height
;
1782 case WID_BV_SORT_ASCENDING_DESCENDING
: {
1783 Dimension d
= GetStringBoundingBox(this->GetWidget
<NWidgetCore
>(widget
)->widget_data
);
1784 d
.width
+= padding
.width
+ Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1785 d
.height
+= padding
.height
;
1786 size
= maxdim(size
, d
);
1790 case WID_BV_CARGO_FILTER_DROPDOWN
:
1791 size
.width
= std::max(size
.width
, GetDropDownListDimension(this->BuildCargoDropDownList()).width
+ padding
.width
);
1795 size
= GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON
+ this->vehicle_type
);
1796 size
= maxdim(size
, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON
+ this->vehicle_type
));
1797 size
.width
+= padding
.width
;
1798 size
.height
+= padding
.height
;
1801 case WID_BV_SHOW_HIDE
:
1802 size
= GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON
+ this->vehicle_type
);
1803 size
= maxdim(size
, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON
+ this->vehicle_type
));
1804 size
.width
+= padding
.width
;
1805 size
.height
+= padding
.height
;
1810 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
1825 case WID_BV_SORT_ASCENDING_DESCENDING
:
1826 this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING
, this->descending_sort_order
? SBS_DOWN
: SBS_UP
);
1831 void OnPaint() override
1833 this->GenerateBuildList();
1834 this->vscroll
->SetCount(this->eng_list
.size());
1836 this->SetWidgetsDisabledState(this->sel_engine
== INVALID_ENGINE
, WID_BV_SHOW_HIDE
, WID_BV_BUILD
);
1838 /* Disable renaming engines in network games if you are not the server. */
1839 this->SetWidgetDisabledState(WID_BV_RENAME
, this->sel_engine
== INVALID_ENGINE
|| (_networking
&& !_network_server
));
1841 this->DrawWidgets();
1843 if (!this->IsShaded()) {
1844 int needed_height
= this->details_height
;
1845 /* Draw details panels. */
1846 if (this->sel_engine
!= INVALID_ENGINE
) {
1847 const Rect r
= this->GetWidget
<NWidgetBase
>(WID_BV_PANEL
)->GetCurrentRect().Shrink(WidgetDimensions::scaled
.framerect
);
1848 int text_end
= DrawVehiclePurchaseInfo(r
.left
, r
.right
, r
.top
, this->sel_engine
, this->te
);
1849 needed_height
= std::max(needed_height
, (text_end
- r
.top
) / GetCharacterHeight(FS_NORMAL
));
1851 if (needed_height
!= this->details_height
) { // Details window are not high enough, enlarge them.
1852 int resize
= needed_height
- this->details_height
;
1853 this->details_height
= needed_height
;
1854 this->ReInit(0, resize
* GetCharacterHeight(FS_NORMAL
));
1860 void OnQueryTextFinished(std::optional
<std::string
> str
) override
1862 if (!str
.has_value()) return;
1864 Command
<CMD_RENAME_ENGINE
>::Post(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE
+ this->vehicle_type
, this->rename_engine
, *str
);
1867 void OnDropdownSelect(WidgetID widget
, int index
) override
1870 case WID_BV_SORT_DROPDOWN
:
1871 if (this->sort_criteria
!= index
) {
1872 this->sort_criteria
= index
;
1873 _engine_sort_last_criteria
[this->vehicle_type
] = this->sort_criteria
;
1874 this->eng_list
.ForceRebuild();
1878 case WID_BV_CARGO_FILTER_DROPDOWN
: // Select a cargo filter criteria
1879 if (this->cargo_filter_criteria
!= index
) {
1880 this->cargo_filter_criteria
= index
;
1881 _engine_sort_last_cargo_criteria
[this->vehicle_type
] = this->cargo_filter_criteria
;
1882 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1883 this->eng_list
.SetFilterState(this->cargo_filter_criteria
!= CargoFilterCriteria::CF_ANY
);
1884 this->eng_list
.ForceRebuild();
1885 this->SelectEngine(this->sel_engine
);
1892 void OnResize() override
1894 this->vscroll
->SetCapacityFromWidget(this, WID_BV_LIST
);
1897 void OnEditboxChanged(WidgetID wid
) override
1899 if (wid
== WID_BV_FILTER
) {
1900 this->string_filter
.SetFilterTerm(this->vehicle_editbox
.text
.buf
);
1901 this->InvalidateData();
1905 EventState
OnHotkey(int hotkey
) override
1908 case BVHK_FOCUS_FILTER_BOX
:
1909 this->SetFocusedWidget(WID_BV_FILTER
);
1910 SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
1914 return ES_NOT_HANDLED
;
1920 static inline HotkeyList hotkeys
{"buildvehicle", {
1921 Hotkey('F', "focus_filter_box", BVHK_FOCUS_FILTER_BOX
),
1925 static WindowDesc
_build_vehicle_desc(
1926 WDP_AUTO
, "build_vehicle", 240, 268,
1927 WC_BUILD_VEHICLE
, WC_NONE
,
1929 _nested_build_vehicle_widgets
,
1930 &BuildVehicleWindow::hotkeys
1933 void ShowBuildVehicleWindow(TileIndex tile
, VehicleType type
)
1935 /* We want to be able to open both Available Train as Available Ships,
1936 * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1937 * As it always is a low value, it won't collide with any real tile
1939 uint num
= (tile
== INVALID_TILE
) ? (int)type
: tile
.base();
1941 assert(IsCompanyBuildableVehicleType(type
));
1943 CloseWindowById(WC_BUILD_VEHICLE
, num
);
1945 new BuildVehicleWindow(_build_vehicle_desc
, tile
, type
);