4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file build_vehicle_gui.cpp GUI for building vehicles. */
13 #include "engine_base.h"
14 #include "engine_func.h"
15 #include "station_base.h"
16 #include "network/network.h"
17 #include "articulated_vehicles.h"
18 #include "textbuf_gui.h"
19 #include "command_func.h"
20 #include "company_func.h"
21 #include "vehicle_gui.h"
22 #include "newgrf_engine.h"
23 #include "newgrf_text.h"
25 #include "string_func.h"
26 #include "strings_func.h"
27 #include "window_func.h"
28 #include "date_func.h"
29 #include "vehicle_func.h"
30 #include "widgets/dropdown_func.h"
31 #include "engine_gui.h"
32 #include "cargotype.h"
33 #include "core/geometry_func.hpp"
34 #include "autoreplace_func.h"
36 #include "widgets/build_vehicle_widget.h"
38 #include "table/strings.h"
40 #include "safeguards.h"
43 * Get the height of a single 'entry' in the engine lists.
44 * @param type the vehicle type to get the height of
45 * @return the height for the entry
47 uint
GetEngineListHeight(VehicleType type
)
49 return max
<uint
>(FONT_HEIGHT_NORMAL
+ WD_MATRIX_TOP
+ WD_MATRIX_BOTTOM
, GetVehicleImageCellSize(type
, EIT_PURCHASE
).height
);
52 static const NWidgetPart _nested_build_vehicle_widgets
[] = {
53 NWidget(NWID_HORIZONTAL
),
54 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
55 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_BV_CAPTION
), SetDataTip(STR_WHITE_STRING
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
56 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
57 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
58 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
60 NWidget(WWT_PANEL
, COLOUR_GREY
),
61 NWidget(NWID_VERTICAL
),
62 NWidget(NWID_HORIZONTAL
),
63 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_SORT_ASCENDING_DESCENDING
), SetDataTip(STR_BUTTON_SORT_BY
, STR_TOOLTIP_SORT_ORDER
),
64 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_BV_SORT_DROPDOWN
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_SORT_CRITERIA
),
66 NWidget(NWID_HORIZONTAL
),
67 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_BV_SHOW_HIDDEN_ENGINES
),
68 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_BV_CARGO_FILTER_DROPDOWN
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_TOOLTIP_FILTER_CRITERIA
),
73 NWidget(NWID_HORIZONTAL
),
74 NWidget(WWT_MATRIX
, COLOUR_GREY
, WID_BV_LIST
), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL
), SetScrollbar(WID_BV_SCROLLBAR
),
75 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_BV_SCROLLBAR
),
77 /* Panel with details. */
78 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_BV_PANEL
), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
79 /* Build/rename buttons, resize button. */
80 NWidget(NWID_HORIZONTAL
),
81 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_BV_BUILD_SEL
),
82 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_BUILD
), SetResize(1, 0), SetFill(1, 0),
84 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_SHOW_HIDE
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING
, STR_NULL
),
85 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BV_RENAME
), SetResize(1, 0), SetFill(1, 0),
86 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
90 /** Special cargo filter criteria */
91 static const CargoID CF_ANY
= CT_NO_REFIT
; ///< Show all vehicles independent of carried cargo (i.e. no filtering)
92 static const CargoID CF_NONE
= CT_INVALID
; ///< Show only vehicles which do not carry cargo (e.g. train engines)
94 bool _engine_sort_direction
; ///< \c false = descending, \c true = ascending.
95 byte _engine_sort_last_criteria
[] = {0, 0, 0, 0}; ///< Last set sort criteria, for each vehicle type.
96 bool _engine_sort_last_order
[] = {false, false, false, false}; ///< Last set direction of the sort order, for each vehicle type.
97 bool _engine_sort_show_hidden_engines
[] = {false, false, false, false}; ///< Last set 'show hidden engines' setting for each vehicle type.
98 static CargoID _engine_sort_last_cargo_criteria
[] = {CF_ANY
, CF_ANY
, CF_ANY
, CF_ANY
}; ///< Last set filter criteria, for each vehicle type.
101 * Determines order of engines by engineID
102 * @param *a first engine to compare
103 * @param *b second engine to compare
104 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
106 static int CDECL
EngineNumberSorter(const EngineID
*a
, const EngineID
*b
)
108 int r
= Engine::Get(*a
)->list_position
- Engine::Get(*b
)->list_position
;
110 return _engine_sort_direction
? -r
: r
;
114 * Determines order of engines by introduction date
115 * @param *a first engine to compare
116 * @param *b second engine to compare
117 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
119 static int CDECL
EngineIntroDateSorter(const EngineID
*a
, const EngineID
*b
)
121 const int va
= Engine::Get(*a
)->intro_date
;
122 const int vb
= Engine::Get(*b
)->intro_date
;
123 const int r
= va
- vb
;
125 /* Use EngineID to sort instead since we want consistent sorting */
126 if (r
== 0) return EngineNumberSorter(a
, b
);
127 return _engine_sort_direction
? -r
: r
;
131 * Determines order of engines by name
132 * @param *a first engine to compare
133 * @param *b second engine to compare
134 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
136 static int CDECL
EngineNameSorter(const EngineID
*a
, const EngineID
*b
)
138 static EngineID last_engine
[2] = { INVALID_ENGINE
, INVALID_ENGINE
};
139 static char last_name
[2][64] = { "\0", "\0" };
141 const EngineID va
= *a
;
142 const EngineID vb
= *b
;
144 if (va
!= last_engine
[0]) {
147 GetString(last_name
[0], STR_ENGINE_NAME
, lastof(last_name
[0]));
150 if (vb
!= last_engine
[1]) {
153 GetString(last_name
[1], STR_ENGINE_NAME
, lastof(last_name
[1]));
156 int r
= strnatcmp(last_name
[0], last_name
[1]); // Sort by name (natural sorting).
158 /* Use EngineID to sort instead since we want consistent sorting */
159 if (r
== 0) return EngineNumberSorter(a
, b
);
160 return _engine_sort_direction
? -r
: r
;
164 * Determines order of engines by reliability
165 * @param *a first engine to compare
166 * @param *b second engine to compare
167 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
169 static int CDECL
EngineReliabilitySorter(const EngineID
*a
, const EngineID
*b
)
171 const int va
= Engine::Get(*a
)->reliability
;
172 const int vb
= Engine::Get(*b
)->reliability
;
173 const int r
= va
- vb
;
175 /* Use EngineID to sort instead since we want consistent sorting */
176 if (r
== 0) return EngineNumberSorter(a
, b
);
177 return _engine_sort_direction
? -r
: r
;
181 * Determines order of engines by purchase cost
182 * @param *a first engine to compare
183 * @param *b second engine to compare
184 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
186 static int CDECL
EngineCostSorter(const EngineID
*a
, const EngineID
*b
)
188 Money va
= Engine::Get(*a
)->GetCost();
189 Money vb
= Engine::Get(*b
)->GetCost();
190 int r
= ClampToI32(va
- vb
);
192 /* Use EngineID to sort instead since we want consistent sorting */
193 if (r
== 0) return EngineNumberSorter(a
, b
);
194 return _engine_sort_direction
? -r
: r
;
198 * Determines order of engines by speed
199 * @param *a first engine to compare
200 * @param *b second engine to compare
201 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
203 static int CDECL
EngineSpeedSorter(const EngineID
*a
, const EngineID
*b
)
205 int va
= Engine::Get(*a
)->GetDisplayMaxSpeed();
206 int vb
= Engine::Get(*b
)->GetDisplayMaxSpeed();
209 /* Use EngineID to sort instead since we want consistent sorting */
210 if (r
== 0) return EngineNumberSorter(a
, b
);
211 return _engine_sort_direction
? -r
: r
;
215 * Determines order of engines by power
216 * @param *a first engine to compare
217 * @param *b second engine to compare
218 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
220 static int CDECL
EnginePowerSorter(const EngineID
*a
, const EngineID
*b
)
222 int va
= Engine::Get(*a
)->GetPower();
223 int vb
= Engine::Get(*b
)->GetPower();
226 /* Use EngineID to sort instead since we want consistent sorting */
227 if (r
== 0) return EngineNumberSorter(a
, b
);
228 return _engine_sort_direction
? -r
: r
;
232 * Determines order of engines by tractive effort
233 * @param *a first engine to compare
234 * @param *b second engine to compare
235 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
237 static int CDECL
EngineTractiveEffortSorter(const EngineID
*a
, const EngineID
*b
)
239 int va
= Engine::Get(*a
)->GetDisplayMaxTractiveEffort();
240 int vb
= Engine::Get(*b
)->GetDisplayMaxTractiveEffort();
243 /* Use EngineID to sort instead since we want consistent sorting */
244 if (r
== 0) return EngineNumberSorter(a
, b
);
245 return _engine_sort_direction
? -r
: r
;
249 * Determines order of engines by running costs
250 * @param *a first engine to compare
251 * @param *b second engine to compare
252 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
254 static int CDECL
EngineRunningCostSorter(const EngineID
*a
, const EngineID
*b
)
256 Money va
= Engine::Get(*a
)->GetRunningCost();
257 Money vb
= Engine::Get(*b
)->GetRunningCost();
258 int r
= ClampToI32(va
- vb
);
260 /* Use EngineID to sort instead since we want consistent sorting */
261 if (r
== 0) return EngineNumberSorter(a
, b
);
262 return _engine_sort_direction
? -r
: r
;
266 * Determines order of engines by running costs
267 * @param *a first engine to compare
268 * @param *b second engine to compare
269 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
271 static int CDECL
EnginePowerVsRunningCostSorter(const EngineID
*a
, const EngineID
*b
)
273 const Engine
*e_a
= Engine::Get(*a
);
274 const Engine
*e_b
= Engine::Get(*b
);
276 /* Here we are using a few tricks to get the right sort.
277 * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
278 * we will actually calculate cunning cost/power (to make it more than 1).
279 * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
280 * Another thing is that both power and running costs should be doubled for multiheaded engines.
281 * Since it would be multiplying with 2 in both numerator and denominator, it will even themselves out and we skip checking for multiheaded. */
282 Money va
= (e_a
->GetRunningCost()) / max(1U, (uint
)e_a
->GetPower());
283 Money vb
= (e_b
->GetRunningCost()) / max(1U, (uint
)e_b
->GetPower());
284 int r
= ClampToI32(vb
- va
);
286 /* Use EngineID to sort instead since we want consistent sorting */
287 if (r
== 0) return EngineNumberSorter(a
, b
);
288 return _engine_sort_direction
? -r
: r
;
291 /* Train sorting functions */
294 * Determines order of train engines by capacity
295 * @param *a first engine to compare
296 * @param *b second engine to compare
297 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
299 static int CDECL
TrainEngineCapacitySorter(const EngineID
*a
, const EngineID
*b
)
301 const RailVehicleInfo
*rvi_a
= RailVehInfo(*a
);
302 const RailVehicleInfo
*rvi_b
= RailVehInfo(*b
);
304 int va
= GetTotalCapacityOfArticulatedParts(*a
) * (rvi_a
->railveh_type
== RAILVEH_MULTIHEAD
? 2 : 1);
305 int vb
= GetTotalCapacityOfArticulatedParts(*b
) * (rvi_b
->railveh_type
== RAILVEH_MULTIHEAD
? 2 : 1);
308 /* Use EngineID to sort instead since we want consistent sorting */
309 if (r
== 0) return EngineNumberSorter(a
, b
);
310 return _engine_sort_direction
? -r
: r
;
314 * Determines order of train engines by engine / wagon
315 * @param *a first engine to compare
316 * @param *b second engine to compare
317 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
319 static int CDECL
TrainEnginesThenWagonsSorter(const EngineID
*a
, const EngineID
*b
)
321 int val_a
= (RailVehInfo(*a
)->railveh_type
== RAILVEH_WAGON
? 1 : 0);
322 int val_b
= (RailVehInfo(*b
)->railveh_type
== RAILVEH_WAGON
? 1 : 0);
323 int r
= val_a
- val_b
;
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
: r
;
330 /* Road vehicle sorting functions */
333 * Determines order of road vehicles by capacity
334 * @param *a first engine to compare
335 * @param *b second engine to compare
336 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
338 static int CDECL
RoadVehEngineCapacitySorter(const EngineID
*a
, const EngineID
*b
)
340 int va
= GetTotalCapacityOfArticulatedParts(*a
);
341 int vb
= GetTotalCapacityOfArticulatedParts(*b
);
344 /* Use EngineID to sort instead since we want consistent sorting */
345 if (r
== 0) return EngineNumberSorter(a
, b
);
346 return _engine_sort_direction
? -r
: r
;
349 /* Ship vehicle sorting functions */
352 * Determines order of ships by capacity
353 * @param *a first engine to compare
354 * @param *b second engine to compare
355 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
357 static int CDECL
ShipEngineCapacitySorter(const EngineID
*a
, const EngineID
*b
)
359 const Engine
*e_a
= Engine::Get(*a
);
360 const Engine
*e_b
= Engine::Get(*b
);
362 int va
= e_a
->GetDisplayDefaultCapacity();
363 int vb
= e_b
->GetDisplayDefaultCapacity();
366 /* Use EngineID to sort instead since we want consistent sorting */
367 if (r
== 0) return EngineNumberSorter(a
, b
);
368 return _engine_sort_direction
? -r
: r
;
371 /* Aircraft sorting functions */
374 * Determines order of aircraft by cargo
375 * @param *a first engine to compare
376 * @param *b second engine to compare
377 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
379 static int CDECL
AircraftEngineCargoSorter(const EngineID
*a
, const EngineID
*b
)
381 const Engine
*e_a
= Engine::Get(*a
);
382 const Engine
*e_b
= Engine::Get(*b
);
384 uint16 mail_a
, mail_b
;
385 int va
= e_a
->GetDisplayDefaultCapacity(&mail_a
);
386 int vb
= e_b
->GetDisplayDefaultCapacity(&mail_b
);
390 /* The planes have the same passenger capacity. Check mail capacity instead */
394 /* Use EngineID to sort instead since we want consistent sorting */
395 return EngineNumberSorter(a
, b
);
398 return _engine_sort_direction
? -r
: r
;
402 * Determines order of aircraft by range.
403 * @param *a first engine to compare.
404 * @param *b second engine to compare.
405 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal.
407 static int CDECL
AircraftRangeSorter(const EngineID
*a
, const EngineID
*b
)
409 uint16 r_a
= Engine::Get(*a
)->GetRange();
410 uint16 r_b
= Engine::Get(*b
)->GetRange();
414 /* Use EngineID to sort instead since we want consistent sorting */
415 if (r
== 0) return EngineNumberSorter(a
, b
);
416 return _engine_sort_direction
? -r
: r
;
419 /** Sort functions for the vehicle sort criteria, for each vehicle type. */
420 EngList_SortTypeFunction
* const _engine_sort_functions
[][11] = {{
426 &EngineTractiveEffortSorter
,
427 &EngineIntroDateSorter
,
429 &EngineRunningCostSorter
,
430 &EnginePowerVsRunningCostSorter
,
431 &EngineReliabilitySorter
,
432 &TrainEngineCapacitySorter
,
439 &EngineTractiveEffortSorter
,
440 &EngineIntroDateSorter
,
442 &EngineRunningCostSorter
,
443 &EnginePowerVsRunningCostSorter
,
444 &EngineReliabilitySorter
,
445 &RoadVehEngineCapacitySorter
,
451 &EngineIntroDateSorter
,
453 &EngineRunningCostSorter
,
454 &EngineReliabilitySorter
,
455 &ShipEngineCapacitySorter
,
461 &EngineIntroDateSorter
,
463 &EngineRunningCostSorter
,
464 &EngineReliabilitySorter
,
465 &AircraftEngineCargoSorter
,
466 &AircraftRangeSorter
,
469 /** Dropdown menu strings for the vehicle sort criteria. */
470 const StringID _engine_sort_listing
[][12] = {{
472 STR_SORT_BY_ENGINE_ID
,
474 STR_SORT_BY_MAX_SPEED
,
476 STR_SORT_BY_TRACTIVE_EFFORT
,
477 STR_SORT_BY_INTRO_DATE
,
479 STR_SORT_BY_RUNNING_COST
,
480 STR_SORT_BY_POWER_VS_RUNNING_COST
,
481 STR_SORT_BY_RELIABILITY
,
482 STR_SORT_BY_CARGO_CAPACITY
,
486 STR_SORT_BY_ENGINE_ID
,
488 STR_SORT_BY_MAX_SPEED
,
490 STR_SORT_BY_TRACTIVE_EFFORT
,
491 STR_SORT_BY_INTRO_DATE
,
493 STR_SORT_BY_RUNNING_COST
,
494 STR_SORT_BY_POWER_VS_RUNNING_COST
,
495 STR_SORT_BY_RELIABILITY
,
496 STR_SORT_BY_CARGO_CAPACITY
,
500 STR_SORT_BY_ENGINE_ID
,
502 STR_SORT_BY_MAX_SPEED
,
503 STR_SORT_BY_INTRO_DATE
,
505 STR_SORT_BY_RUNNING_COST
,
506 STR_SORT_BY_RELIABILITY
,
507 STR_SORT_BY_CARGO_CAPACITY
,
511 STR_SORT_BY_ENGINE_ID
,
513 STR_SORT_BY_MAX_SPEED
,
514 STR_SORT_BY_INTRO_DATE
,
516 STR_SORT_BY_RUNNING_COST
,
517 STR_SORT_BY_RELIABILITY
,
518 STR_SORT_BY_CARGO_CAPACITY
,
523 /** Cargo filter functions */
524 static bool CDECL
CargoFilter(const EngineID
*eid
, const CargoID cid
)
526 if (cid
== CF_ANY
) return true;
527 uint32 refit_mask
= GetUnionOfArticulatedRefitMasks(*eid
, true) & _standard_cargo_mask
;
528 return (cid
== CF_NONE
? refit_mask
== 0 : HasBit(refit_mask
, cid
));
531 static GUIEngineList::FilterFunction
* const _filter_funcs
[] = {
535 static int DrawCargoCapacityInfo(int left
, int right
, int y
, EngineID engine
)
539 GetArticulatedVehicleCargoesAndRefits(engine
, &cap
, &refits
);
541 for (CargoID c
= 0; c
< NUM_CARGO
; c
++) {
542 if (cap
[c
] == 0) continue;
545 SetDParam(1, cap
[c
]);
546 SetDParam(2, HasBit(refits
, c
) ? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
547 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
548 y
+= FONT_HEIGHT_NORMAL
;
554 /* Draw rail wagon specific details */
555 static int DrawRailWagonPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, const RailVehicleInfo
*rvi
)
557 const Engine
*e
= Engine::Get(engine_number
);
560 SetDParam(0, e
->GetCost());
561 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
562 y
+= FONT_HEIGHT_NORMAL
;
564 /* Wagon weight - (including cargo) */
565 uint weight
= e
->GetDisplayWeight();
566 SetDParam(0, weight
);
567 uint cargo_weight
= (e
->CanCarryCargo() ? CargoSpec::Get(e
->GetDefaultCargoType())->weight
* GetTotalCapacityOfArticulatedParts(engine_number
) / 16 : 0);
568 SetDParam(1, cargo_weight
+ weight
);
569 DrawString(left
, right
, y
, STR_PURCHASE_INFO_WEIGHT_CWEIGHT
);
570 y
+= FONT_HEIGHT_NORMAL
;
572 /* Wagon speed limit, displayed if above zero */
573 if (_settings_game
.vehicle
.wagon_speed_limits
) {
574 uint max_speed
= e
->GetDisplayMaxSpeed();
576 SetDParam(0, max_speed
);
577 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED
);
578 y
+= FONT_HEIGHT_NORMAL
;
583 if (rvi
->running_cost_class
!= INVALID_PRICE
) {
584 SetDParam(0, e
->GetRunningCost());
585 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
586 y
+= FONT_HEIGHT_NORMAL
;
592 /* Draw locomotive specific details */
593 static int DrawRailEnginePurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, const RailVehicleInfo
*rvi
)
595 const Engine
*e
= Engine::Get(engine_number
);
597 /* Purchase Cost - Engine weight */
598 SetDParam(0, e
->GetCost());
599 SetDParam(1, e
->GetDisplayWeight());
600 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_WEIGHT
);
601 y
+= FONT_HEIGHT_NORMAL
;
603 /* Max speed - Engine power */
604 SetDParam(0, e
->GetDisplayMaxSpeed());
605 SetDParam(1, e
->GetPower());
606 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_POWER
);
607 y
+= FONT_HEIGHT_NORMAL
;
609 /* Max tractive effort - not applicable if old acceleration or maglev */
610 if (_settings_game
.vehicle
.train_acceleration_model
!= AM_ORIGINAL
&& GetRailTypeInfo(rvi
->railtype
)->acceleration_type
!= 2) {
611 SetDParam(0, e
->GetDisplayMaxTractiveEffort());
612 DrawString(left
, right
, y
, STR_PURCHASE_INFO_MAX_TE
);
613 y
+= FONT_HEIGHT_NORMAL
;
617 if (rvi
->running_cost_class
!= INVALID_PRICE
) {
618 SetDParam(0, e
->GetRunningCost());
619 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
620 y
+= FONT_HEIGHT_NORMAL
;
623 /* Powered wagons power - Powered wagons extra weight */
624 if (rvi
->pow_wag_power
!= 0) {
625 SetDParam(0, rvi
->pow_wag_power
);
626 SetDParam(1, rvi
->pow_wag_weight
);
627 DrawString(left
, right
, y
, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT
);
628 y
+= FONT_HEIGHT_NORMAL
;
634 /* Draw road vehicle specific details */
635 static int DrawRoadVehPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
)
637 const Engine
*e
= Engine::Get(engine_number
);
639 if (_settings_game
.vehicle
.roadveh_acceleration_model
!= AM_ORIGINAL
) {
641 SetDParam(0, e
->GetCost());
642 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
643 y
+= FONT_HEIGHT_NORMAL
;
645 /* Road vehicle weight - (including cargo) */
646 int16 weight
= e
->GetDisplayWeight();
647 SetDParam(0, weight
);
648 uint cargo_weight
= (e
->CanCarryCargo() ? CargoSpec::Get(e
->GetDefaultCargoType())->weight
* GetTotalCapacityOfArticulatedParts(engine_number
) / 16 : 0);
649 SetDParam(1, cargo_weight
+ weight
);
650 DrawString(left
, right
, y
, STR_PURCHASE_INFO_WEIGHT_CWEIGHT
);
651 y
+= FONT_HEIGHT_NORMAL
;
653 /* Max speed - Engine power */
654 SetDParam(0, e
->GetDisplayMaxSpeed());
655 SetDParam(1, e
->GetPower());
656 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_POWER
);
657 y
+= FONT_HEIGHT_NORMAL
;
659 /* Max tractive effort */
660 SetDParam(0, e
->GetDisplayMaxTractiveEffort());
661 DrawString(left
, right
, y
, STR_PURCHASE_INFO_MAX_TE
);
662 y
+= FONT_HEIGHT_NORMAL
;
664 /* Purchase cost - Max speed */
665 SetDParam(0, e
->GetCost());
666 SetDParam(1, e
->GetDisplayMaxSpeed());
667 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
668 y
+= FONT_HEIGHT_NORMAL
;
672 SetDParam(0, e
->GetRunningCost());
673 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
674 y
+= FONT_HEIGHT_NORMAL
;
679 /* Draw ship specific details */
680 static int DrawShipPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, bool refittable
)
682 const Engine
*e
= Engine::Get(engine_number
);
684 /* Purchase cost - Max speed */
685 uint raw_speed
= e
->GetDisplayMaxSpeed();
686 uint ocean_speed
= e
->u
.ship
.ApplyWaterClassSpeedFrac(raw_speed
, true);
687 uint canal_speed
= e
->u
.ship
.ApplyWaterClassSpeedFrac(raw_speed
, false);
689 SetDParam(0, e
->GetCost());
690 if (ocean_speed
== canal_speed
) {
691 SetDParam(1, ocean_speed
);
692 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
693 y
+= FONT_HEIGHT_NORMAL
;
695 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST
);
696 y
+= FONT_HEIGHT_NORMAL
;
698 SetDParam(0, ocean_speed
);
699 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_OCEAN
);
700 y
+= FONT_HEIGHT_NORMAL
;
702 SetDParam(0, canal_speed
);
703 DrawString(left
, right
, y
, STR_PURCHASE_INFO_SPEED_CANAL
);
704 y
+= FONT_HEIGHT_NORMAL
;
707 /* Cargo type + capacity */
708 SetDParam(0, e
->GetDefaultCargoType());
709 SetDParam(1, e
->GetDisplayDefaultCapacity());
710 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
711 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
712 y
+= FONT_HEIGHT_NORMAL
;
715 SetDParam(0, e
->GetRunningCost());
716 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
717 y
+= FONT_HEIGHT_NORMAL
;
723 * Draw aircraft specific details in the buy window.
724 * @param left Left edge of the window to draw in.
725 * @param right Right edge of the window to draw in.
726 * @param y Top of the area to draw in.
727 * @param engine_number Engine to display.
728 * @param refittable If set, the aircraft can be refitted.
729 * @return Bottom of the used area.
731 static int DrawAircraftPurchaseInfo(int left
, int right
, int y
, EngineID engine_number
, bool refittable
)
733 const Engine
*e
= Engine::Get(engine_number
);
734 CargoID cargo
= e
->GetDefaultCargoType();
736 /* Purchase cost - Max speed */
737 SetDParam(0, e
->GetCost());
738 SetDParam(1, e
->GetDisplayMaxSpeed());
739 DrawString(left
, right
, y
, STR_PURCHASE_INFO_COST_SPEED
);
740 y
+= FONT_HEIGHT_NORMAL
;
743 uint16 mail_capacity
;
744 uint capacity
= e
->GetDisplayDefaultCapacity(&mail_capacity
);
745 if (mail_capacity
> 0) {
747 SetDParam(1, capacity
);
748 SetDParam(2, CT_MAIL
);
749 SetDParam(3, mail_capacity
);
750 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY
);
752 /* Note, if the default capacity is selected by the refit capacity
753 * callback, then the capacity shown is likely to be incorrect. */
755 SetDParam(1, capacity
);
756 SetDParam(2, refittable
? STR_PURCHASE_INFO_REFITTABLE
: STR_EMPTY
);
757 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
759 y
+= FONT_HEIGHT_NORMAL
;
762 SetDParam(0, e
->GetRunningCost());
763 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RUNNINGCOST
);
764 y
+= FONT_HEIGHT_NORMAL
;
767 SetDParam(0, e
->GetAircraftTypeText());
768 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_TYPE
);
769 y
+= FONT_HEIGHT_NORMAL
;
771 /* Aircraft range, if available. */
772 uint16 range
= e
->GetRange();
775 DrawString(left
, right
, y
, STR_PURCHASE_INFO_AIRCRAFT_RANGE
);
776 y
+= FONT_HEIGHT_NORMAL
;
783 * Display additional text from NewGRF in the purchase information window
784 * @param left Left border of text bounding box
785 * @param right Right border of text bounding box
786 * @param y Top border of text bounding box
787 * @param engine Engine to query the additional purchase information for
788 * @return Bottom border of text bounding box
790 static uint
ShowAdditionalText(int left
, int right
, int y
, EngineID engine
)
792 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT
, 0, 0, engine
, NULL
);
793 if (callback
== CALLBACK_FAILED
|| callback
== 0x400) return y
;
794 const GRFFile
*grffile
= Engine::Get(engine
)->GetGRF();
795 if (callback
> 0x400) {
796 ErrorUnknownCallbackResult(grffile
->grfid
, CBID_VEHICLE_ADDITIONAL_TEXT
, callback
);
800 StartTextRefStackUsage(grffile
, 6);
801 uint result
= DrawStringMultiLine(left
, right
, y
, INT32_MAX
, GetGRFStringID(grffile
->grfid
, 0xD000 + callback
), TC_BLACK
);
802 StopTextRefStackUsage();
807 * Draw the purchase info details of a vehicle at a given location.
808 * @param left,right,y location where to draw the info
809 * @param engine_number the engine of which to draw the info of
810 * @return y after drawing all the text
812 int DrawVehiclePurchaseInfo(int left
, int right
, int y
, EngineID engine_number
)
814 const Engine
*e
= Engine::Get(engine_number
);
816 ConvertDateToYMD(e
->intro_date
, &ymd
);
817 bool refittable
= IsArticulatedVehicleRefittable(engine_number
);
818 bool articulated_cargo
= false;
821 default: NOT_REACHED();
823 if (e
->u
.rail
.railveh_type
== RAILVEH_WAGON
) {
824 y
= DrawRailWagonPurchaseInfo(left
, right
, y
, engine_number
, &e
->u
.rail
);
826 y
= DrawRailEnginePurchaseInfo(left
, right
, y
, engine_number
, &e
->u
.rail
);
828 articulated_cargo
= true;
832 y
= DrawRoadVehPurchaseInfo(left
, right
, y
, engine_number
);
833 articulated_cargo
= true;
837 y
= DrawShipPurchaseInfo(left
, right
, y
, engine_number
, refittable
);
841 y
= DrawAircraftPurchaseInfo(left
, right
, y
, engine_number
, refittable
);
845 if (articulated_cargo
) {
846 /* Cargo type + capacity, or N/A */
847 int new_y
= DrawCargoCapacityInfo(left
, right
, y
, engine_number
);
850 SetDParam(0, CT_INVALID
);
851 SetDParam(2, STR_EMPTY
);
852 DrawString(left
, right
, y
, STR_PURCHASE_INFO_CAPACITY
);
853 y
+= FONT_HEIGHT_NORMAL
;
859 /* Draw details that apply to all types except rail wagons. */
860 if (e
->type
!= VEH_TRAIN
|| e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
) {
861 /* Design date - Life length */
862 SetDParam(0, ymd
.year
);
863 SetDParam(1, e
->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR
);
864 DrawString(left
, right
, y
, STR_PURCHASE_INFO_DESIGNED_LIFE
);
865 y
+= FONT_HEIGHT_NORMAL
;
868 SetDParam(0, ToPercent16(e
->reliability
));
869 DrawString(left
, right
, y
, STR_PURCHASE_INFO_RELIABILITY
);
870 y
+= FONT_HEIGHT_NORMAL
;
873 if (refittable
) y
= ShowRefitOptionsList(left
, right
, y
, engine_number
);
875 /* Additional text from NewGRF */
876 y
= ShowAdditionalText(left
, right
, y
, engine_number
);
882 * Engine drawing loop
883 * @param type Type of vehicle (VEH_*)
884 * @param l The left most location of the list
885 * @param r The right most location of the list
886 * @param y The top most location of the list
887 * @param eng_list What engines to draw
888 * @param min where to start in the list
889 * @param max where in the list to end
890 * @param selected_id what engine to highlight as selected, if any
891 * @param show_count Whether to show the amount of engines or not
892 * @param selected_group the group to list the engines of
894 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
)
896 static const int sprite_y_offsets
[] = { -1, -1, -2, -2 };
898 /* Obligatory sanity checks! */
899 assert(max
<= eng_list
->Length());
901 bool rtl
= _current_text_dir
== TD_RTL
;
902 int step_size
= GetEngineListHeight(type
);
903 int sprite_left
= GetVehicleImageCellSize(type
, EIT_PURCHASE
).extend_left
;
904 int sprite_right
= GetVehicleImageCellSize(type
, EIT_PURCHASE
).extend_right
;
905 int sprite_width
= sprite_left
+ sprite_right
;
907 int sprite_x
= rtl
? r
- sprite_right
- 1 : l
+ sprite_left
+ 1;
908 int sprite_y_offset
= sprite_y_offsets
[type
] + step_size
/ 2;
910 Dimension replace_icon
= {0, 0};
913 replace_icon
= GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE
);
914 SetDParamMaxDigits(0, 3, FS_SMALL
);
915 count_width
= GetStringBoundingBox(STR_TINY_BLACK_COMA
).width
;
918 int text_left
= l
+ (rtl
? WD_FRAMERECT_LEFT
+ replace_icon
.width
+ 8 + count_width
: sprite_width
+ WD_FRAMETEXT_LEFT
);
919 int text_right
= r
- (rtl
? sprite_width
+ WD_FRAMETEXT_RIGHT
: WD_FRAMERECT_RIGHT
+ replace_icon
.width
+ 8 + count_width
);
920 int replace_icon_left
= rtl
? l
+ WD_FRAMERECT_LEFT
: r
- WD_FRAMERECT_RIGHT
- replace_icon
.width
;
922 int count_right
= rtl
? text_left
: r
- WD_FRAMERECT_RIGHT
- replace_icon
.width
- 8;
924 int normal_text_y_offset
= (step_size
- FONT_HEIGHT_NORMAL
) / 2;
925 int small_text_y_offset
= step_size
- FONT_HEIGHT_SMALL
- WD_FRAMERECT_BOTTOM
- 1;
926 int replace_icon_y_offset
= (step_size
- replace_icon
.height
) / 2 - 1;
928 for (; min
< max
; min
++, y
+= step_size
) {
929 const EngineID engine
= (*eng_list
)[min
];
930 /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
931 const uint num_engines
= GetGroupNumEngines(_local_company
, selected_group
, engine
);
933 const Engine
*e
= Engine::Get(engine
);
934 bool hidden
= HasBit(e
->company_hidden
, _local_company
);
935 StringID str
= hidden
? STR_HIDDEN_ENGINE_NAME
: STR_ENGINE_NAME
;
936 TextColour tc
= (engine
== selected_id
) ? TC_WHITE
: (TC_NO_SHADE
| (hidden
? TC_GREY
: TC_BLACK
));
938 SetDParam(0, engine
);
939 DrawString(text_left
, text_right
, y
+ normal_text_y_offset
, str
, tc
);
940 DrawVehicleEngine(l
, r
, sprite_x
, y
+ sprite_y_offset
, engine
, (show_count
&& num_engines
== 0) ? PALETTE_CRASH
: GetEnginePalette(engine
, _local_company
), EIT_PURCHASE
);
942 SetDParam(0, num_engines
);
943 DrawString(count_left
, count_right
, y
+ small_text_y_offset
, STR_TINY_BLACK_COMA
, TC_FROMSTRING
, SA_RIGHT
| SA_FORCE
);
944 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
);
950 * Display the dropdown for the vehicle sort criteria.
951 * @param w Parent window (holds the dropdown button).
952 * @param vehicle_type %Vehicle type being sorted.
953 * @param selected Currently selected sort criterium.
954 * @param button Widget button.
956 void DisplayVehicleSortDropDown(Window
*w
, VehicleType vehicle_type
, int selected
, int button
)
958 uint32 hidden_mask
= 0;
959 /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
960 if (vehicle_type
== VEH_ROAD
&& _settings_game
.vehicle
.roadveh_acceleration_model
== AM_ORIGINAL
) {
961 SetBit(hidden_mask
, 3); // power
962 SetBit(hidden_mask
, 4); // tractive effort
963 SetBit(hidden_mask
, 8); // power by running costs
965 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
966 if (vehicle_type
== VEH_TRAIN
&& _settings_game
.vehicle
.train_acceleration_model
== AM_ORIGINAL
) {
967 SetBit(hidden_mask
, 4); // tractive effort
969 ShowDropDownMenu(w
, _engine_sort_listing
[vehicle_type
], selected
, button
, 0, hidden_mask
);
972 /** GUI for building vehicles. */
973 struct BuildVehicleWindow
: Window
{
974 VehicleType vehicle_type
; ///< Type of vehicles shown in the window.
976 RailTypeByte railtype
; ///< Rail type to show, or #RAILTYPE_END.
977 RoadTypes roadtypes
; ///< Road type to show, or #ROADTYPES_ALL.
978 } filter
; ///< Filter to apply.
979 bool descending_sort_order
; ///< Sort direction, @see _engine_sort_direction
980 byte sort_criteria
; ///< Current sort criterium.
981 bool show_hidden_engines
; ///< State of the 'show hidden engines' button.
982 bool listview_mode
; ///< If set, only display the available vehicles and do not show a 'build' button.
983 EngineID sel_engine
; ///< Currently selected engine, or #INVALID_ENGINE
984 EngineID rename_engine
; ///< Engine being renamed.
985 GUIEngineList eng_list
;
986 CargoID cargo_filter
[NUM_CARGO
+ 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
987 StringID cargo_filter_texts
[NUM_CARGO
+ 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
988 byte cargo_filter_criteria
; ///< Selected cargo filter
989 int details_height
; ///< Minimal needed height of the details panels (found so far).
992 BuildVehicleWindow(WindowDesc
*desc
, TileIndex tile
, VehicleType type
) : Window(desc
)
994 this->vehicle_type
= type
;
995 this->window_number
= tile
== INVALID_TILE
? (int)type
: tile
;
997 this->sel_engine
= INVALID_ENGINE
;
999 this->sort_criteria
= _engine_sort_last_criteria
[type
];
1000 this->descending_sort_order
= _engine_sort_last_order
[type
];
1001 this->show_hidden_engines
= _engine_sort_show_hidden_engines
[type
];
1004 default: NOT_REACHED();
1006 this->filter
.railtype
= (tile
== INVALID_TILE
) ? RAILTYPE_END
: GetRailType(tile
);
1009 this->filter
.roadtypes
= (tile
== INVALID_TILE
) ? ROADTYPES_ALL
: GetRoadTypes(tile
);
1015 this->listview_mode
= (this->window_number
<= VEH_END
);
1017 this->CreateNestedTree();
1019 this->vscroll
= this->GetScrollbar(WID_BV_SCROLLBAR
);
1021 /* If we are just viewing the list of vehicles, we do not need the Build button.
1022 * So we just hide it, and enlarge the Rename button by the now vacant place. */
1023 if (this->listview_mode
) this->GetWidget
<NWidgetStacked
>(WID_BV_BUILD_SEL
)->SetDisplayedPlane(SZSP_NONE
);
1025 /* disable renaming engines in network games if you are not the server */
1026 this->SetWidgetDisabledState(WID_BV_RENAME
, _networking
&& !_network_server
);
1028 NWidgetCore
*widget
= this->GetWidget
<NWidgetCore
>(WID_BV_LIST
);
1029 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP
+ type
;
1031 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_SHOW_HIDE
);
1032 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP
+ type
;
1034 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_BUILD
);
1035 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON
+ type
;
1036 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP
+ type
;
1038 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_RENAME
);
1039 widget
->widget_data
= STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON
+ type
;
1040 widget
->tool_tip
= STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP
+ type
;
1042 widget
= this->GetWidget
<NWidgetCore
>(WID_BV_SHOW_HIDDEN_ENGINES
);
1043 widget
->widget_data
= STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN
+ type
;
1044 widget
->tool_tip
= STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP
+ type
;
1045 widget
->SetLowered(this->show_hidden_engines
);
1047 this->details_height
= ((this->vehicle_type
== VEH_TRAIN
) ? 10 : 9) * FONT_HEIGHT_NORMAL
+ WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
1049 this->FinishInitNested(tile
== INVALID_TILE
? (int)type
: tile
);
1051 this->owner
= (tile
!= INVALID_TILE
) ? GetTileOwner(tile
) : _local_company
;
1053 this->eng_list
.ForceRebuild();
1054 this->GenerateBuildList(); // generate the list, since we need it in the next line
1055 /* Select the first engine in the list as default when opening the window */
1056 if (this->eng_list
.Length() > 0) this->sel_engine
= this->eng_list
[0];
1059 /** Populate the filter list and set the cargo filter criteria. */
1060 void SetCargoFilterArray()
1062 uint filter_items
= 0;
1064 /* Add item for disabling filtering. */
1065 this->cargo_filter
[filter_items
] = CF_ANY
;
1066 this->cargo_filter_texts
[filter_items
] = STR_PURCHASE_INFO_ALL_TYPES
;
1069 /* Add item for vehicles not carrying anything, e.g. train engines.
1070 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1071 if (this->vehicle_type
== VEH_TRAIN
) {
1072 this->cargo_filter
[filter_items
] = CF_NONE
;
1073 this->cargo_filter_texts
[filter_items
] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE
;
1077 /* Collect available cargo types for filtering. */
1078 const CargoSpec
*cs
;
1079 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs
) {
1080 this->cargo_filter
[filter_items
] = cs
->Index();
1081 this->cargo_filter_texts
[filter_items
] = cs
->name
;
1085 /* Terminate the filter list. */
1086 this->cargo_filter_texts
[filter_items
] = INVALID_STRING_ID
;
1088 /* If not found, the cargo criteria will be set to all cargoes. */
1089 this->cargo_filter_criteria
= 0;
1091 /* Find the last cargo filter criteria. */
1092 for (uint i
= 0; i
< filter_items
; i
++) {
1093 if (this->cargo_filter
[i
] == _engine_sort_last_cargo_criteria
[this->vehicle_type
]) {
1094 this->cargo_filter_criteria
= i
;
1099 this->eng_list
.SetFilterFuncs(_filter_funcs
);
1100 this->eng_list
.SetFilterState(this->cargo_filter
[this->cargo_filter_criteria
] != CF_ANY
);
1105 this->SetCargoFilterArray();
1108 /** Filter the engine list against the currently selected cargo filter */
1109 void FilterEngineList()
1111 this->eng_list
.Filter(this->cargo_filter
[this->cargo_filter_criteria
]);
1112 if (0 == this->eng_list
.Length()) { // no engine passed through the filter, invalidate the previously selected engine
1113 this->sel_engine
= INVALID_ENGINE
;
1114 } else if (!this->eng_list
.Contains(this->sel_engine
)) { // previously selected engine didn't pass the filter, select the first engine of the list
1115 this->sel_engine
= this->eng_list
[0];
1119 /** Filter a single engine */
1120 bool FilterSingleEngine(EngineID eid
)
1122 CargoID filter_type
= this->cargo_filter
[this->cargo_filter_criteria
];
1123 return (filter_type
== CF_ANY
|| CargoFilter(&eid
, filter_type
));
1126 /* Figure out what train EngineIDs to put in the list */
1127 void GenerateBuildTrainList()
1129 EngineID sel_id
= INVALID_ENGINE
;
1130 int num_engines
= 0;
1133 this->filter
.railtype
= (this->listview_mode
) ? RAILTYPE_END
: GetRailType(this->window_number
);
1135 this->eng_list
.Clear();
1137 /* Make list of all available train engines and wagons.
1138 * Also check to see if the previously selected engine is still available,
1139 * and if not, reset selection to INVALID_ENGINE. This could be the case
1140 * when engines become obsolete and are removed */
1142 FOR_ALL_ENGINES_OF_TYPE(e
, VEH_TRAIN
) {
1143 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1144 EngineID eid
= e
->index
;
1145 const RailVehicleInfo
*rvi
= &e
->u
.rail
;
1147 if (this->filter
.railtype
!= RAILTYPE_END
&& !HasPowerOnRail(rvi
->railtype
, this->filter
.railtype
)) continue;
1148 if (!IsEngineBuildable(eid
, VEH_TRAIN
, _local_company
)) continue;
1150 /* Filter now! So num_engines and num_wagons is valid */
1151 if (!FilterSingleEngine(eid
)) continue;
1153 *this->eng_list
.Append() = eid
;
1155 if (rvi
->railveh_type
!= RAILVEH_WAGON
) {
1161 if (eid
== this->sel_engine
) sel_id
= eid
;
1164 this->sel_engine
= sel_id
;
1166 /* make engines first, and then wagons, sorted by selected sort_criteria */
1167 _engine_sort_direction
= false;
1168 EngList_Sort(&this->eng_list
, TrainEnginesThenWagonsSorter
);
1170 /* and then sort engines */
1171 _engine_sort_direction
= this->descending_sort_order
;
1172 EngList_SortPartial(&this->eng_list
, _engine_sort_functions
[0][this->sort_criteria
], 0, num_engines
);
1174 /* and finally sort wagons */
1175 EngList_SortPartial(&this->eng_list
, _engine_sort_functions
[0][this->sort_criteria
], num_engines
, num_wagons
);
1178 /* Figure out what road vehicle EngineIDs to put in the list */
1179 void GenerateBuildRoadVehList()
1181 EngineID sel_id
= INVALID_ENGINE
;
1183 this->eng_list
.Clear();
1186 FOR_ALL_ENGINES_OF_TYPE(e
, VEH_ROAD
) {
1187 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1188 EngineID eid
= e
->index
;
1189 if (!IsEngineBuildable(eid
, VEH_ROAD
, _local_company
)) continue;
1190 if (!HasBit(this->filter
.roadtypes
, HasBit(EngInfo(eid
)->misc_flags
, EF_ROAD_TRAM
) ? ROADTYPE_TRAM
: ROADTYPE_ROAD
)) continue;
1191 *this->eng_list
.Append() = eid
;
1193 if (eid
== this->sel_engine
) sel_id
= eid
;
1195 this->sel_engine
= sel_id
;
1198 /* Figure out what ship EngineIDs to put in the list */
1199 void GenerateBuildShipList()
1201 EngineID sel_id
= INVALID_ENGINE
;
1202 this->eng_list
.Clear();
1205 FOR_ALL_ENGINES_OF_TYPE(e
, VEH_SHIP
) {
1206 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1207 EngineID eid
= e
->index
;
1208 if (!IsEngineBuildable(eid
, VEH_SHIP
, _local_company
)) continue;
1209 *this->eng_list
.Append() = eid
;
1211 if (eid
== this->sel_engine
) sel_id
= eid
;
1213 this->sel_engine
= sel_id
;
1216 /* Figure out what aircraft EngineIDs to put in the list */
1217 void GenerateBuildAircraftList()
1219 EngineID sel_id
= INVALID_ENGINE
;
1221 this->eng_list
.Clear();
1223 const Station
*st
= this->listview_mode
? NULL
: Station::GetByTile(this->window_number
);
1225 /* Make list of all available planes.
1226 * Also check to see if the previously selected plane is still available,
1227 * and if not, reset selection to INVALID_ENGINE. This could be the case
1228 * when planes become obsolete and are removed */
1230 FOR_ALL_ENGINES_OF_TYPE(e
, VEH_AIRCRAFT
) {
1231 if (!this->show_hidden_engines
&& e
->IsHidden(_local_company
)) continue;
1232 EngineID eid
= e
->index
;
1233 if (!IsEngineBuildable(eid
, VEH_AIRCRAFT
, _local_company
)) continue;
1234 /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1235 if (!this->listview_mode
&& !CanVehicleUseStation(eid
, st
)) continue;
1237 *this->eng_list
.Append() = eid
;
1238 if (eid
== this->sel_engine
) sel_id
= eid
;
1241 this->sel_engine
= sel_id
;
1244 /* Generate the list of vehicles */
1245 void GenerateBuildList()
1247 if (!this->eng_list
.NeedRebuild()) return;
1248 switch (this->vehicle_type
) {
1249 default: NOT_REACHED();
1251 this->GenerateBuildTrainList();
1252 this->eng_list
.Compact();
1253 this->eng_list
.RebuildDone();
1254 return; // trains should not reach the last sorting
1256 this->GenerateBuildRoadVehList();
1259 this->GenerateBuildShipList();
1262 this->GenerateBuildAircraftList();
1266 this->FilterEngineList();
1268 _engine_sort_direction
= this->descending_sort_order
;
1269 EngList_Sort(&this->eng_list
, _engine_sort_functions
[this->vehicle_type
][this->sort_criteria
]);
1271 this->eng_list
.Compact();
1272 this->eng_list
.RebuildDone();
1275 void OnClick(Point pt
, int widget
, int click_count
)
1278 case WID_BV_SORT_ASCENDING_DESCENDING
:
1279 this->descending_sort_order
^= true;
1280 _engine_sort_last_order
[this->vehicle_type
] = this->descending_sort_order
;
1281 this->eng_list
.ForceRebuild();
1285 case WID_BV_SHOW_HIDDEN_ENGINES
:
1286 this->show_hidden_engines
^= true;
1287 _engine_sort_show_hidden_engines
[this->vehicle_type
] = this->show_hidden_engines
;
1288 this->eng_list
.ForceRebuild();
1289 this->SetWidgetLoweredState(widget
, this->show_hidden_engines
);
1294 uint i
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_BV_LIST
);
1295 size_t num_items
= this->eng_list
.Length();
1296 this->sel_engine
= (i
< num_items
) ? this->eng_list
[i
] : INVALID_ENGINE
;
1298 if (_ctrl_pressed
) {
1299 this->OnClick(pt
, WID_BV_SHOW_HIDE
, 1);
1300 } else if (click_count
> 1 && !this->listview_mode
) {
1301 this->OnClick(pt
, WID_BV_BUILD
, 1);
1306 case WID_BV_SORT_DROPDOWN
: // Select sorting criteria dropdown menu
1307 DisplayVehicleSortDropDown(this, this->vehicle_type
, this->sort_criteria
, WID_BV_SORT_DROPDOWN
);
1310 case WID_BV_CARGO_FILTER_DROPDOWN
: // Select cargo filtering criteria dropdown menu
1311 ShowDropDownMenu(this, this->cargo_filter_texts
, this->cargo_filter_criteria
, WID_BV_CARGO_FILTER_DROPDOWN
, 0, 0);
1314 case WID_BV_SHOW_HIDE
: {
1315 const Engine
*e
= (this->sel_engine
== INVALID_ENGINE
) ? NULL
: Engine::Get(this->sel_engine
);
1317 DoCommandP(0, 0, this->sel_engine
| (e
->IsHidden(_current_company
) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY
);
1322 case WID_BV_BUILD
: {
1323 EngineID sel_eng
= this->sel_engine
;
1324 if (sel_eng
!= INVALID_ENGINE
) {
1325 CommandCallback
*callback
= (this->vehicle_type
== VEH_TRAIN
&& RailVehInfo(sel_eng
)->railveh_type
== RAILVEH_WAGON
) ? CcBuildWagon
: CcBuildPrimaryVehicle
;
1326 DoCommandP(this->window_number
, sel_eng
, 0, GetCmdBuildVeh(this->vehicle_type
), callback
);
1331 case WID_BV_RENAME
: {
1332 EngineID sel_eng
= this->sel_engine
;
1333 if (sel_eng
!= INVALID_ENGINE
) {
1334 this->rename_engine
= sel_eng
;
1335 SetDParam(0, sel_eng
);
1336 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
);
1344 * Some data on this window has become invalid.
1345 * @param data Information about the changed data.
1346 * @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.
1348 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
1350 if (!gui_scope
) return;
1351 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1352 if (this->vehicle_type
== VEH_ROAD
&&
1353 _settings_game
.vehicle
.roadveh_acceleration_model
== AM_ORIGINAL
&&
1354 this->sort_criteria
> 7) {
1355 this->sort_criteria
= 0;
1356 _engine_sort_last_criteria
[VEH_ROAD
] = 0;
1358 this->eng_list
.ForceRebuild();
1361 virtual void SetStringParameters(int widget
) const
1364 case WID_BV_CAPTION
:
1365 if (this->vehicle_type
== VEH_TRAIN
&& !this->listview_mode
) {
1366 const RailtypeInfo
*rti
= GetRailTypeInfo(this->filter
.railtype
);
1367 SetDParam(0, rti
->strings
.build_caption
);
1369 SetDParam(0, (this->listview_mode
? STR_VEHICLE_LIST_AVAILABLE_TRAINS
: STR_BUY_VEHICLE_TRAIN_ALL_CAPTION
) + this->vehicle_type
);
1373 case WID_BV_SORT_DROPDOWN
:
1374 SetDParam(0, _engine_sort_listing
[this->vehicle_type
][this->sort_criteria
]);
1377 case WID_BV_CARGO_FILTER_DROPDOWN
:
1378 SetDParam(0, this->cargo_filter_texts
[this->cargo_filter_criteria
]);
1381 case WID_BV_SHOW_HIDE
: {
1382 const Engine
*e
= (this->sel_engine
== INVALID_ENGINE
) ? NULL
: Engine::Get(this->sel_engine
);
1383 if (e
!= NULL
&& e
->IsHidden(_local_company
)) {
1384 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON
+ this->vehicle_type
);
1386 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON
+ this->vehicle_type
);
1393 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
1397 resize
->height
= GetEngineListHeight(this->vehicle_type
);
1398 size
->height
= 3 * resize
->height
;
1399 size
->width
= max(size
->width
, GetVehicleImageCellSize(this->vehicle_type
, EIT_PURCHASE
).extend_left
+ GetVehicleImageCellSize(this->vehicle_type
, EIT_PURCHASE
).extend_right
+ 165);
1403 size
->height
= this->details_height
;
1406 case WID_BV_SORT_ASCENDING_DESCENDING
: {
1407 Dimension d
= GetStringBoundingBox(this->GetWidget
<NWidgetCore
>(widget
)->widget_data
);
1408 d
.width
+= padding
.width
+ Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1409 d
.height
+= padding
.height
;
1410 *size
= maxdim(*size
, d
);
1414 case WID_BV_SHOW_HIDE
:
1415 *size
= GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON
+ this->vehicle_type
);
1416 *size
= maxdim(*size
, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON
+ this->vehicle_type
));
1417 size
->width
+= padding
.width
;
1418 size
->height
+= padding
.height
;
1423 virtual void DrawWidget(const Rect
&r
, int widget
) const
1427 DrawEngineList(this->vehicle_type
, r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, &this->eng_list
, this->vscroll
->GetPosition(), min(this->vscroll
->GetPosition() + this->vscroll
->GetCapacity(), this->eng_list
.Length()), this->sel_engine
, false, DEFAULT_GROUP
);
1430 case WID_BV_SORT_ASCENDING_DESCENDING
:
1431 this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING
, this->descending_sort_order
? SBS_DOWN
: SBS_UP
);
1436 virtual void OnPaint()
1438 this->GenerateBuildList();
1439 this->vscroll
->SetCount(this->eng_list
.Length());
1441 this->SetWidgetDisabledState(WID_BV_SHOW_HIDE
, this->sel_engine
== INVALID_ENGINE
);
1443 this->DrawWidgets();
1445 if (!this->IsShaded()) {
1446 int needed_height
= this->details_height
;
1447 /* Draw details panels. */
1448 if (this->sel_engine
!= INVALID_ENGINE
) {
1449 NWidgetBase
*nwi
= this->GetWidget
<NWidgetBase
>(WID_BV_PANEL
);
1450 int text_end
= DrawVehiclePurchaseInfo(nwi
->pos_x
+ WD_FRAMETEXT_LEFT
, nwi
->pos_x
+ nwi
->current_x
- WD_FRAMETEXT_RIGHT
,
1451 nwi
->pos_y
+ WD_FRAMERECT_TOP
, this->sel_engine
);
1452 needed_height
= max(needed_height
, text_end
- (int)nwi
->pos_y
+ WD_FRAMERECT_BOTTOM
);
1454 if (needed_height
!= this->details_height
) { // Details window are not high enough, enlarge them.
1455 int resize
= needed_height
- this->details_height
;
1456 this->details_height
= needed_height
;
1457 this->ReInit(0, resize
);
1463 virtual void OnQueryTextFinished(char *str
)
1465 if (str
== NULL
) return;
1467 DoCommandP(0, this->rename_engine
, 0, CMD_RENAME_ENGINE
| CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE
+ this->vehicle_type
), NULL
, str
);
1470 virtual void OnDropdownSelect(int widget
, int index
)
1473 case WID_BV_SORT_DROPDOWN
:
1474 if (this->sort_criteria
!= index
) {
1475 this->sort_criteria
= index
;
1476 _engine_sort_last_criteria
[this->vehicle_type
] = this->sort_criteria
;
1477 this->eng_list
.ForceRebuild();
1481 case WID_BV_CARGO_FILTER_DROPDOWN
: // Select a cargo filter criteria
1482 if (this->cargo_filter_criteria
!= index
) {
1483 this->cargo_filter_criteria
= index
;
1484 _engine_sort_last_cargo_criteria
[this->vehicle_type
] = this->cargo_filter
[this->cargo_filter_criteria
];
1485 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1486 this->eng_list
.SetFilterState(this->cargo_filter
[this->cargo_filter_criteria
] != CF_ANY
);
1487 this->eng_list
.ForceRebuild();
1494 virtual void OnResize()
1496 this->vscroll
->SetCapacityFromWidget(this, WID_BV_LIST
);
1500 static WindowDesc
_build_vehicle_desc(
1501 WDP_AUTO
, "build_vehicle", 240, 268,
1502 WC_BUILD_VEHICLE
, WC_NONE
,
1504 _nested_build_vehicle_widgets
, lengthof(_nested_build_vehicle_widgets
)
1507 void ShowBuildVehicleWindow(TileIndex tile
, VehicleType type
)
1509 /* We want to be able to open both Available Train as Available Ships,
1510 * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1511 * As it always is a low value, it won't collide with any real tile
1513 uint num
= (tile
== INVALID_TILE
) ? (int)type
: tile
;
1515 assert(IsCompanyBuildableVehicleType(type
));
1517 DeleteWindowById(WC_BUILD_VEHICLE
, num
);
1519 new BuildVehicleWindow(&_build_vehicle_desc
, tile
, type
);