1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
8 /** @file build_vehicle_gui.cpp GUI for building vehicles. */
9 
10 #include "stdafx.h"
11 #include "engine_base.h"
12 #include "engine_func.h"
13 #include "station_base.h"
14 #include "network/network.h"
15 #include "articulated_vehicles.h"
16 #include "textbuf_gui.h"
17 #include "command_func.h"
18 #include "company_func.h"
19 #include "vehicle_gui.h"
20 #include "newgrf_engine.h"
21 #include "newgrf_text.h"
22 #include "group.h"
23 #include "string_func.h"
24 #include "strings_func.h"
25 #include "window_func.h"
26 #include "date_func.h"
27 #include "vehicle_func.h"
28 #include "widgets/dropdown_func.h"
29 #include "engine_gui.h"
30 #include "cargotype.h"
31 #include "core/geometry_func.hpp"
32 #include "autoreplace_func.h"
33 
34 #include "widgets/build_vehicle_widget.h"
35 
36 #include "table/strings.h"
37 
38 #include "safeguards.h"
39 
40 /**
41  * Get the height of a single 'entry' in the engine lists.
42  * @param type the vehicle type to get the height of
43  * @return the height for the entry
44  */
GetEngineListHeight(VehicleType type)45 uint GetEngineListHeight(VehicleType type)
46 {
47 	return std::max<uint>(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM, GetVehicleImageCellSize(type, EIT_PURCHASE).height);
48 }
49 
50 static const NWidgetPart _nested_build_vehicle_widgets[] = {
51 	NWidget(NWID_HORIZONTAL),
52 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
53 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
54 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
55 		NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
56 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
57 	EndContainer(),
58 	NWidget(WWT_PANEL, COLOUR_GREY),
59 		NWidget(NWID_VERTICAL),
60 			NWidget(NWID_HORIZONTAL),
61 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
62 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
63 			EndContainer(),
64 			NWidget(NWID_HORIZONTAL),
65 				NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_ENGINES),
66 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
67 			EndContainer(),
68 		EndContainer(),
69 	EndContainer(),
70 	/* Vehicle list. */
71 	NWidget(NWID_HORIZONTAL),
72 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR),
73 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR),
74 	EndContainer(),
75 	/* Panel with details. */
76 	NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
77 	/* Build/rename buttons, resize button. */
78 	NWidget(NWID_HORIZONTAL),
79 		NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL),
80 			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0),
81 		EndContainer(),
82 		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
83 		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0),
84 		NWidget(WWT_RESIZEBOX, COLOUR_GREY),
85 	EndContainer(),
86 };
87 
88 /** Special cargo filter criteria */
89 static const CargoID CF_ANY     = CT_NO_REFIT;   ///< Show all vehicles independent of carried cargo (i.e. no filtering)
90 static const CargoID CF_NONE    = CT_INVALID;    ///< Show only vehicles which do not carry cargo (e.g. train engines)
91 static const CargoID CF_ENGINES = CT_AUTO_REFIT; ///< Show only engines (for rail vehicles only)
92 
93 bool _engine_sort_direction; ///< \c false = descending, \c true = ascending.
94 byte _engine_sort_last_criteria[]       = {0, 0, 0, 0};                 ///< Last set sort criteria, for each vehicle type.
95 bool _engine_sort_last_order[]          = {false, false, false, false}; ///< Last set direction of the sort order, for each vehicle type.
96 bool _engine_sort_show_hidden_engines[] = {false, false, false, false}; ///< Last set 'show hidden engines' setting for each vehicle type.
97 static CargoID _engine_sort_last_cargo_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_ANY}; ///< Last set filter criteria, for each vehicle type.
98 
99 /**
100  * Determines order of engines by engineID
101  * @param a first engine to compare
102  * @param b second engine to compare
103  * @return for descending order: returns true if a < b. Vice versa for ascending order
104  */
EngineNumberSorter(const EngineID & a,const EngineID & b)105 static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
106 {
107 	int r = Engine::Get(a)->list_position - Engine::Get(b)->list_position;
108 
109 	return _engine_sort_direction ? r > 0 : r < 0;
110 }
111 
112 /**
113  * Determines order of engines by introduction date
114  * @param a first engine to compare
115  * @param b second engine to compare
116  * @return for descending order: returns true if a < b. Vice versa for ascending order
117  */
EngineIntroDateSorter(const EngineID & a,const EngineID & b)118 static bool EngineIntroDateSorter(const EngineID &a, const EngineID &b)
119 {
120 	const int va = Engine::Get(a)->intro_date;
121 	const int vb = Engine::Get(b)->intro_date;
122 	const int r = va - vb;
123 
124 	/* Use EngineID to sort instead since we want consistent sorting */
125 	if (r == 0) return EngineNumberSorter(a, b);
126 	return _engine_sort_direction ? r > 0 : r < 0;
127 }
128 
129 /* cached values for EngineNameSorter to spare many GetString() calls */
130 static EngineID _last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
131 
132 /**
133  * Determines order of engines by name
134  * @param a first engine to compare
135  * @param b second engine to compare
136  * @return for descending order: returns true if a < b. Vice versa for ascending order
137  */
EngineNameSorter(const EngineID & a,const EngineID & b)138 static bool EngineNameSorter(const EngineID &a, const EngineID &b)
139 {
140 	static char     last_name[2][64] = { "", "" };
141 
142 	if (a != _last_engine[0]) {
143 		_last_engine[0] = a;
144 		SetDParam(0, a);
145 		GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
146 	}
147 
148 	if (b != _last_engine[1]) {
149 		_last_engine[1] = b;
150 		SetDParam(0, b);
151 		GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
152 	}
153 
154 	int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
155 
156 	/* Use EngineID to sort instead since we want consistent sorting */
157 	if (r == 0) return EngineNumberSorter(a, b);
158 	return _engine_sort_direction ? r > 0 : r < 0;
159 }
160 
161 /**
162  * Determines order of engines by reliability
163  * @param a first engine to compare
164  * @param b second engine to compare
165  * @return for descending order: returns true if a < b. Vice versa for ascending order
166  */
EngineReliabilitySorter(const EngineID & a,const EngineID & b)167 static bool EngineReliabilitySorter(const EngineID &a, const EngineID &b)
168 {
169 	const int va = Engine::Get(a)->reliability;
170 	const int vb = Engine::Get(b)->reliability;
171 	const int r = va - vb;
172 
173 	/* Use EngineID to sort instead since we want consistent sorting */
174 	if (r == 0) return EngineNumberSorter(a, b);
175 	return _engine_sort_direction ? r > 0 : r < 0;
176 }
177 
178 /**
179  * Determines order of engines by purchase cost
180  * @param a first engine to compare
181  * @param b second engine to compare
182  * @return for descending order: returns true if a < b. Vice versa for ascending order
183  */
EngineCostSorter(const EngineID & a,const EngineID & b)184 static bool EngineCostSorter(const EngineID &a, const EngineID &b)
185 {
186 	Money va = Engine::Get(a)->GetCost();
187 	Money vb = Engine::Get(b)->GetCost();
188 	int r = ClampToI32(va - vb);
189 
190 	/* Use EngineID to sort instead since we want consistent sorting */
191 	if (r == 0) return EngineNumberSorter(a, b);
192 	return _engine_sort_direction ? r > 0 : r < 0;
193 }
194 
195 /**
196  * Determines order of engines by speed
197  * @param a first engine to compare
198  * @param b second engine to compare
199  * @return for descending order: returns true if a < b. Vice versa for ascending order
200  */
EngineSpeedSorter(const EngineID & a,const EngineID & b)201 static bool EngineSpeedSorter(const EngineID &a, const EngineID &b)
202 {
203 	int va = Engine::Get(a)->GetDisplayMaxSpeed();
204 	int vb = Engine::Get(b)->GetDisplayMaxSpeed();
205 	int r = va - vb;
206 
207 	/* Use EngineID to sort instead since we want consistent sorting */
208 	if (r == 0) return EngineNumberSorter(a, b);
209 	return _engine_sort_direction ? r > 0 : r < 0;
210 }
211 
212 /**
213  * Determines order of engines by power
214  * @param a first engine to compare
215  * @param b second engine to compare
216  * @return for descending order: returns true if a < b. Vice versa for ascending order
217  */
EnginePowerSorter(const EngineID & a,const EngineID & b)218 static bool EnginePowerSorter(const EngineID &a, const EngineID &b)
219 {
220 	int va = Engine::Get(a)->GetPower();
221 	int vb = Engine::Get(b)->GetPower();
222 	int r = va - vb;
223 
224 	/* Use EngineID to sort instead since we want consistent sorting */
225 	if (r == 0) return EngineNumberSorter(a, b);
226 	return _engine_sort_direction ? r > 0 : r < 0;
227 }
228 
229 /**
230  * Determines order of engines by tractive effort
231  * @param a first engine to compare
232  * @param b second engine to compare
233  * @return for descending order: returns true if a < b. Vice versa for ascending order
234  */
EngineTractiveEffortSorter(const EngineID & a,const EngineID & b)235 static bool EngineTractiveEffortSorter(const EngineID &a, const EngineID &b)
236 {
237 	int va = Engine::Get(a)->GetDisplayMaxTractiveEffort();
238 	int vb = Engine::Get(b)->GetDisplayMaxTractiveEffort();
239 	int r = va - vb;
240 
241 	/* Use EngineID to sort instead since we want consistent sorting */
242 	if (r == 0) return EngineNumberSorter(a, b);
243 	return _engine_sort_direction ? r > 0 : r < 0;
244 }
245 
246 /**
247  * Determines order of engines by running costs
248  * @param a first engine to compare
249  * @param b second engine to compare
250  * @return for descending order: returns true if a < b. Vice versa for ascending order
251  */
EngineRunningCostSorter(const EngineID & a,const EngineID & b)252 static bool EngineRunningCostSorter(const EngineID &a, const EngineID &b)
253 {
254 	Money va = Engine::Get(a)->GetRunningCost();
255 	Money vb = Engine::Get(b)->GetRunningCost();
256 	int r = ClampToI32(va - vb);
257 
258 	/* Use EngineID to sort instead since we want consistent sorting */
259 	if (r == 0) return EngineNumberSorter(a, b);
260 	return _engine_sort_direction ? r > 0 : r < 0;
261 }
262 
263 /**
264  * Determines order of engines by running costs
265  * @param a first engine to compare
266  * @param b second engine to compare
267  * @return for descending order: returns true if a < b. Vice versa for ascending order
268  */
EnginePowerVsRunningCostSorter(const EngineID & a,const EngineID & b)269 static bool EnginePowerVsRunningCostSorter(const EngineID &a, const EngineID &b)
270 {
271 	const Engine *e_a = Engine::Get(a);
272 	const Engine *e_b = Engine::Get(b);
273 	uint p_a = e_a->GetPower();
274 	uint p_b = e_b->GetPower();
275 	Money r_a = e_a->GetRunningCost();
276 	Money r_b = e_b->GetRunningCost();
277 	/* Check if running cost is zero in one or both engines.
278 	 * If only one of them is zero then that one has higher value,
279 	 * else if both have zero cost then compare powers. */
280 	if (r_a == 0) {
281 		if (r_b == 0) {
282 			/* If it is ambiguous which to return go with their ID */
283 			if (p_a == p_b) return EngineNumberSorter(a, b);
284 			return _engine_sort_direction != (p_a < p_b);
285 		}
286 		return !_engine_sort_direction;
287 	}
288 	if (r_b == 0) return _engine_sort_direction;
289 	/* Using double for more precision when comparing close values.
290 	 * This shouldn't have any major effects in performance nor in keeping
291 	 * the game in sync between players since it's used in GUI only in client side */
292 	double v_a = (double)p_a / (double)r_a;
293 	double v_b = (double)p_b / (double)r_b;
294 	/* Use EngineID to sort if both have same power/running cost,
295 	 * since we want consistent sorting.
296 	 * Also if both have no power then sort with reverse of running cost to simulate
297 	 * previous sorting behaviour for wagons. */
298 	if (v_a == 0 && v_b == 0) return !EngineRunningCostSorter(a, b);
299 	if (v_a == v_b)  return EngineNumberSorter(a, b);
300 	return _engine_sort_direction != (v_a < v_b);
301 }
302 
303 /* Train sorting functions */
304 
305 /**
306  * Determines order of train engines by capacity
307  * @param a first engine to compare
308  * @param b second engine to compare
309  * @return for descending order: returns true if a < b. Vice versa for ascending order
310  */
TrainEngineCapacitySorter(const EngineID & a,const EngineID & b)311 static bool TrainEngineCapacitySorter(const EngineID &a, const EngineID &b)
312 {
313 	const RailVehicleInfo *rvi_a = RailVehInfo(a);
314 	const RailVehicleInfo *rvi_b = RailVehInfo(b);
315 
316 	int va = GetTotalCapacityOfArticulatedParts(a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
317 	int vb = GetTotalCapacityOfArticulatedParts(b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
318 	int r = va - vb;
319 
320 	/* Use EngineID to sort instead since we want consistent sorting */
321 	if (r == 0) return EngineNumberSorter(a, b);
322 	return _engine_sort_direction ? r > 0 : r < 0;
323 }
324 
325 /**
326  * Determines order of train engines by engine / wagon
327  * @param a first engine to compare
328  * @param b second engine to compare
329  * @return for descending order: returns true if a < b. Vice versa for ascending order
330  */
TrainEnginesThenWagonsSorter(const EngineID & a,const EngineID & b)331 static bool TrainEnginesThenWagonsSorter(const EngineID &a, const EngineID &b)
332 {
333 	int val_a = (RailVehInfo(a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
334 	int val_b = (RailVehInfo(b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
335 	int r = val_a - val_b;
336 
337 	/* Use EngineID to sort instead since we want consistent sorting */
338 	if (r == 0) return EngineNumberSorter(a, b);
339 	return _engine_sort_direction ? r > 0 : r < 0;
340 }
341 
342 /* Road vehicle sorting functions */
343 
344 /**
345  * Determines order of road vehicles by capacity
346  * @param a first engine to compare
347  * @param b second engine to compare
348  * @return for descending order: returns true if a < b. Vice versa for ascending order
349  */
RoadVehEngineCapacitySorter(const EngineID & a,const EngineID & b)350 static bool RoadVehEngineCapacitySorter(const EngineID &a, const EngineID &b)
351 {
352 	int va = GetTotalCapacityOfArticulatedParts(a);
353 	int vb = GetTotalCapacityOfArticulatedParts(b);
354 	int r = va - vb;
355 
356 	/* Use EngineID to sort instead since we want consistent sorting */
357 	if (r == 0) return EngineNumberSorter(a, b);
358 	return _engine_sort_direction ? r > 0 : r < 0;
359 }
360 
361 /* Ship vehicle sorting functions */
362 
363 /**
364  * Determines order of ships by capacity
365  * @param a first engine to compare
366  * @param b second engine to compare
367  * @return for descending order: returns true if a < b. Vice versa for ascending order
368  */
ShipEngineCapacitySorter(const EngineID & a,const EngineID & b)369 static bool ShipEngineCapacitySorter(const EngineID &a, const EngineID &b)
370 {
371 	const Engine *e_a = Engine::Get(a);
372 	const Engine *e_b = Engine::Get(b);
373 
374 	int va = e_a->GetDisplayDefaultCapacity();
375 	int vb = e_b->GetDisplayDefaultCapacity();
376 	int r = va - vb;
377 
378 	/* Use EngineID to sort instead since we want consistent sorting */
379 	if (r == 0) return EngineNumberSorter(a, b);
380 	return _engine_sort_direction ? r > 0 : r < 0;
381 }
382 
383 /* Aircraft sorting functions */
384 
385 /**
386  * Determines order of aircraft by cargo
387  * @param a first engine to compare
388  * @param b second engine to compare
389  * @return for descending order: returns true if a < b. Vice versa for ascending order
390  */
AircraftEngineCargoSorter(const EngineID & a,const EngineID & b)391 static bool AircraftEngineCargoSorter(const EngineID &a, const EngineID &b)
392 {
393 	const Engine *e_a = Engine::Get(a);
394 	const Engine *e_b = Engine::Get(b);
395 
396 	uint16 mail_a, mail_b;
397 	int va = e_a->GetDisplayDefaultCapacity(&mail_a);
398 	int vb = e_b->GetDisplayDefaultCapacity(&mail_b);
399 	int r = va - vb;
400 
401 	if (r == 0) {
402 		/* The planes have the same passenger capacity. Check mail capacity instead */
403 		r = mail_a - mail_b;
404 
405 		if (r == 0) {
406 			/* Use EngineID to sort instead since we want consistent sorting */
407 			return EngineNumberSorter(a, b);
408 		}
409 	}
410 	return _engine_sort_direction ? r > 0 : r < 0;
411 }
412 
413 /**
414  * Determines order of aircraft by range.
415  * @param a first engine to compare
416  * @param b second engine to compare
417  * @return for descending order: returns true if a < b. Vice versa for ascending order
418  */
AircraftRangeSorter(const EngineID & a,const EngineID & b)419 static bool AircraftRangeSorter(const EngineID &a, const EngineID &b)
420 {
421 	uint16 r_a = Engine::Get(a)->GetRange();
422 	uint16 r_b = Engine::Get(b)->GetRange();
423 
424 	int r = r_a - r_b;
425 
426 	/* Use EngineID to sort instead since we want consistent sorting */
427 	if (r == 0) return EngineNumberSorter(a, b);
428 	return _engine_sort_direction ? r > 0 : r < 0;
429 }
430 
431 /** Sort functions for the vehicle sort criteria, for each vehicle type. */
432 EngList_SortTypeFunction * const _engine_sort_functions[][11] = {{
433 	/* Trains */
434 	&EngineNumberSorter,
435 	&EngineCostSorter,
436 	&EngineSpeedSorter,
437 	&EnginePowerSorter,
438 	&EngineTractiveEffortSorter,
439 	&EngineIntroDateSorter,
440 	&EngineNameSorter,
441 	&EngineRunningCostSorter,
442 	&EnginePowerVsRunningCostSorter,
443 	&EngineReliabilitySorter,
444 	&TrainEngineCapacitySorter,
445 }, {
446 	/* Road vehicles */
447 	&EngineNumberSorter,
448 	&EngineCostSorter,
449 	&EngineSpeedSorter,
450 	&EnginePowerSorter,
451 	&EngineTractiveEffortSorter,
452 	&EngineIntroDateSorter,
453 	&EngineNameSorter,
454 	&EngineRunningCostSorter,
455 	&EnginePowerVsRunningCostSorter,
456 	&EngineReliabilitySorter,
457 	&RoadVehEngineCapacitySorter,
458 }, {
459 	/* Ships */
460 	&EngineNumberSorter,
461 	&EngineCostSorter,
462 	&EngineSpeedSorter,
463 	&EngineIntroDateSorter,
464 	&EngineNameSorter,
465 	&EngineRunningCostSorter,
466 	&EngineReliabilitySorter,
467 	&ShipEngineCapacitySorter,
468 }, {
469 	/* Aircraft */
470 	&EngineNumberSorter,
471 	&EngineCostSorter,
472 	&EngineSpeedSorter,
473 	&EngineIntroDateSorter,
474 	&EngineNameSorter,
475 	&EngineRunningCostSorter,
476 	&EngineReliabilitySorter,
477 	&AircraftEngineCargoSorter,
478 	&AircraftRangeSorter,
479 }};
480 
481 /** Dropdown menu strings for the vehicle sort criteria. */
482 const StringID _engine_sort_listing[][12] = {{
483 	/* Trains */
484 	STR_SORT_BY_ENGINE_ID,
485 	STR_SORT_BY_COST,
486 	STR_SORT_BY_MAX_SPEED,
487 	STR_SORT_BY_POWER,
488 	STR_SORT_BY_TRACTIVE_EFFORT,
489 	STR_SORT_BY_INTRO_DATE,
490 	STR_SORT_BY_NAME,
491 	STR_SORT_BY_RUNNING_COST,
492 	STR_SORT_BY_POWER_VS_RUNNING_COST,
493 	STR_SORT_BY_RELIABILITY,
494 	STR_SORT_BY_CARGO_CAPACITY,
495 	INVALID_STRING_ID
496 }, {
497 	/* Road vehicles */
498 	STR_SORT_BY_ENGINE_ID,
499 	STR_SORT_BY_COST,
500 	STR_SORT_BY_MAX_SPEED,
501 	STR_SORT_BY_POWER,
502 	STR_SORT_BY_TRACTIVE_EFFORT,
503 	STR_SORT_BY_INTRO_DATE,
504 	STR_SORT_BY_NAME,
505 	STR_SORT_BY_RUNNING_COST,
506 	STR_SORT_BY_POWER_VS_RUNNING_COST,
507 	STR_SORT_BY_RELIABILITY,
508 	STR_SORT_BY_CARGO_CAPACITY,
509 	INVALID_STRING_ID
510 }, {
511 	/* Ships */
512 	STR_SORT_BY_ENGINE_ID,
513 	STR_SORT_BY_COST,
514 	STR_SORT_BY_MAX_SPEED,
515 	STR_SORT_BY_INTRO_DATE,
516 	STR_SORT_BY_NAME,
517 	STR_SORT_BY_RUNNING_COST,
518 	STR_SORT_BY_RELIABILITY,
519 	STR_SORT_BY_CARGO_CAPACITY,
520 	INVALID_STRING_ID
521 }, {
522 	/* Aircraft */
523 	STR_SORT_BY_ENGINE_ID,
524 	STR_SORT_BY_COST,
525 	STR_SORT_BY_MAX_SPEED,
526 	STR_SORT_BY_INTRO_DATE,
527 	STR_SORT_BY_NAME,
528 	STR_SORT_BY_RUNNING_COST,
529 	STR_SORT_BY_RELIABILITY,
530 	STR_SORT_BY_CARGO_CAPACITY,
531 	STR_SORT_BY_RANGE,
532 	INVALID_STRING_ID
533 }};
534 
535 /** Filters vehicles by cargo and engine (in case of rail vehicle). */
CargoAndEngineFilter(const EngineID * eid,const CargoID cid)536 static bool CDECL CargoAndEngineFilter(const EngineID *eid, const CargoID cid)
537 {
538 	if (cid == CF_ANY) {
539 		return true;
540 	} else if (cid == CF_ENGINES) {
541 		return Engine::Get(*eid)->GetPower() != 0;
542 	} else {
543 		CargoTypes refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true) & _standard_cargo_mask;
544 		return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
545 	}
546 }
547 
548 static GUIEngineList::FilterFunction * const _filter_funcs[] = {
549 	&CargoAndEngineFilter,
550 };
551 
DrawCargoCapacityInfo(int left,int right,int y,EngineID engine,TestedEngineDetails & te)552 static int DrawCargoCapacityInfo(int left, int right, int y, EngineID engine, TestedEngineDetails &te)
553 {
554 	CargoArray cap;
555 	CargoTypes refits;
556 	GetArticulatedVehicleCargoesAndRefits(engine, &cap, &refits, te.cargo, te.capacity);
557 
558 	for (CargoID c = 0; c < NUM_CARGO; c++) {
559 		if (cap[c] == 0) continue;
560 
561 		SetDParam(0, c);
562 		SetDParam(1, cap[c]);
563 		SetDParam(2, HasBit(refits, c) ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
564 		DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
565 		y += FONT_HEIGHT_NORMAL;
566 	}
567 
568 	return y;
569 }
570 
571 /* Draw rail wagon specific details */
DrawRailWagonPurchaseInfo(int left,int right,int y,EngineID engine_number,const RailVehicleInfo * rvi,TestedEngineDetails & te)572 static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi, TestedEngineDetails &te)
573 {
574 	const Engine *e = Engine::Get(engine_number);
575 
576 	/* Purchase cost */
577 	if (te.cost != 0) {
578 		SetDParam(0, e->GetCost() + te.cost);
579 		SetDParam(1, te.cost);
580 		DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT);
581 	} else {
582 		SetDParam(0, e->GetCost());
583 		DrawString(left, right, y, STR_PURCHASE_INFO_COST);
584 	}
585 	y += FONT_HEIGHT_NORMAL;
586 
587 	/* Wagon weight - (including cargo) */
588 	uint weight = e->GetDisplayWeight();
589 	SetDParam(0, weight);
590 	uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->weight * te.capacity / 16 : 0);
591 	SetDParam(1, cargo_weight + weight);
592 	DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
593 	y += FONT_HEIGHT_NORMAL;
594 
595 	/* Wagon speed limit, displayed if above zero */
596 	if (_settings_game.vehicle.wagon_speed_limits) {
597 		uint max_speed = e->GetDisplayMaxSpeed();
598 		if (max_speed > 0) {
599 			SetDParam(0, max_speed);
600 			DrawString(left, right, y, STR_PURCHASE_INFO_SPEED);
601 			y += FONT_HEIGHT_NORMAL;
602 		}
603 	}
604 
605 	/* Running cost */
606 	if (rvi->running_cost_class != INVALID_PRICE) {
607 		SetDParam(0, e->GetRunningCost());
608 		DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
609 		y += FONT_HEIGHT_NORMAL;
610 	}
611 
612 	return y;
613 }
614 
615 /* Draw locomotive specific details */
DrawRailEnginePurchaseInfo(int left,int right,int y,EngineID engine_number,const RailVehicleInfo * rvi,TestedEngineDetails & te)616 static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi, TestedEngineDetails &te)
617 {
618 	const Engine *e = Engine::Get(engine_number);
619 
620 	/* Purchase Cost - Engine weight */
621 	if (te.cost != 0) {
622 		SetDParam(0, e->GetCost() + te.cost);
623 		SetDParam(1, te.cost);
624 		SetDParam(2, e->GetDisplayWeight());
625 		DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_WEIGHT);
626 	} else {
627 		SetDParam(0, e->GetCost());
628 		SetDParam(1, e->GetDisplayWeight());
629 		DrawString(left, right, y, STR_PURCHASE_INFO_COST_WEIGHT);
630 	}
631 	y += FONT_HEIGHT_NORMAL;
632 
633 	/* Max speed - Engine power */
634 	SetDParam(0, e->GetDisplayMaxSpeed());
635 	SetDParam(1, e->GetPower());
636 	DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
637 	y += FONT_HEIGHT_NORMAL;
638 
639 	/* Max tractive effort - not applicable if old acceleration or maglev */
640 	if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != 2) {
641 		SetDParam(0, e->GetDisplayMaxTractiveEffort());
642 		DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
643 		y += FONT_HEIGHT_NORMAL;
644 	}
645 
646 	/* Running cost */
647 	if (rvi->running_cost_class != INVALID_PRICE) {
648 		SetDParam(0, e->GetRunningCost());
649 		DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
650 		y += FONT_HEIGHT_NORMAL;
651 	}
652 
653 	/* Powered wagons power - Powered wagons extra weight */
654 	if (rvi->pow_wag_power != 0) {
655 		SetDParam(0, rvi->pow_wag_power);
656 		SetDParam(1, rvi->pow_wag_weight);
657 		DrawString(left, right, y, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT);
658 		y += FONT_HEIGHT_NORMAL;
659 	}
660 
661 	return y;
662 }
663 
664 /* Draw road vehicle specific details */
DrawRoadVehPurchaseInfo(int left,int right,int y,EngineID engine_number,TestedEngineDetails & te)665 static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_number, TestedEngineDetails &te)
666 {
667 	const Engine *e = Engine::Get(engine_number);
668 
669 	if (_settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL) {
670 		/* Purchase Cost */
671 		if (te.cost != 0) {
672 			SetDParam(0, e->GetCost() + te.cost);
673 			SetDParam(1, te.cost);
674 			DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT);
675 		} else {
676 			SetDParam(0, e->GetCost());
677 			DrawString(left, right, y, STR_PURCHASE_INFO_COST);
678 		}
679 		y += FONT_HEIGHT_NORMAL;
680 
681 		/* Road vehicle weight - (including cargo) */
682 		int16 weight = e->GetDisplayWeight();
683 		SetDParam(0, weight);
684 		uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(te.cargo)->weight * te.capacity / 16 : 0);
685 		SetDParam(1, cargo_weight + weight);
686 		DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
687 		y += FONT_HEIGHT_NORMAL;
688 
689 		/* Max speed - Engine power */
690 		SetDParam(0, e->GetDisplayMaxSpeed());
691 		SetDParam(1, e->GetPower());
692 		DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
693 		y += FONT_HEIGHT_NORMAL;
694 
695 		/* Max tractive effort */
696 		SetDParam(0, e->GetDisplayMaxTractiveEffort());
697 		DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
698 		y += FONT_HEIGHT_NORMAL;
699 	} else {
700 		/* Purchase cost - Max speed */
701 		if (te.cost != 0) {
702 			SetDParam(0, e->GetCost() + te.cost);
703 			SetDParam(1, te.cost);
704 			SetDParam(2, e->GetDisplayMaxSpeed());
705 			DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_SPEED);
706 		} else {
707 			SetDParam(0, e->GetCost());
708 			SetDParam(1, e->GetDisplayMaxSpeed());
709 			DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
710 		}
711 		y += FONT_HEIGHT_NORMAL;
712 	}
713 
714 	/* Running cost */
715 	SetDParam(0, e->GetRunningCost());
716 	DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
717 	y += FONT_HEIGHT_NORMAL;
718 
719 	return y;
720 }
721 
722 /* Draw ship specific details */
DrawShipPurchaseInfo(int left,int right,int y,EngineID engine_number,bool refittable,TestedEngineDetails & te)723 static int DrawShipPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable, TestedEngineDetails &te)
724 {
725 	const Engine *e = Engine::Get(engine_number);
726 
727 	/* Purchase cost - Max speed */
728 	uint raw_speed = e->GetDisplayMaxSpeed();
729 	uint ocean_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, true);
730 	uint canal_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, false);
731 
732 	if (ocean_speed == canal_speed) {
733 		if (te.cost != 0) {
734 			SetDParam(0, e->GetCost() + te.cost);
735 			SetDParam(1, te.cost);
736 			SetDParam(2, ocean_speed);
737 			DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_SPEED);
738 		} else {
739 			SetDParam(0, e->GetCost());
740 			SetDParam(1, ocean_speed);
741 			DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
742 		}
743 		y += FONT_HEIGHT_NORMAL;
744 	} else {
745 		if (te.cost != 0) {
746 			SetDParam(0, e->GetCost() + te.cost);
747 			SetDParam(1, te.cost);
748 			DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT);
749 		} else {
750 			SetDParam(0, e->GetCost());
751 			DrawString(left, right, y, STR_PURCHASE_INFO_COST);
752 		}
753 		y += FONT_HEIGHT_NORMAL;
754 
755 		SetDParam(0, ocean_speed);
756 		DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_OCEAN);
757 		y += FONT_HEIGHT_NORMAL;
758 
759 		SetDParam(0, canal_speed);
760 		DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_CANAL);
761 		y += FONT_HEIGHT_NORMAL;
762 	}
763 
764 	/* Cargo type + capacity */
765 	SetDParam(0, te.cargo);
766 	SetDParam(1, te.capacity);
767 	SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
768 	DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
769 	y += FONT_HEIGHT_NORMAL;
770 
771 	/* Running cost */
772 	SetDParam(0, e->GetRunningCost());
773 	DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
774 	y += FONT_HEIGHT_NORMAL;
775 
776 	return y;
777 }
778 
779 /**
780  * Draw aircraft specific details in the buy window.
781  * @param left Left edge of the window to draw in.
782  * @param right Right edge of the window to draw in.
783  * @param y Top of the area to draw in.
784  * @param engine_number Engine to display.
785  * @param refittable If set, the aircraft can be refitted.
786  * @return Bottom of the used area.
787  */
DrawAircraftPurchaseInfo(int left,int right,int y,EngineID engine_number,bool refittable,TestedEngineDetails & te)788 static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable, TestedEngineDetails &te)
789 {
790 	const Engine *e = Engine::Get(engine_number);
791 
792 	/* Purchase cost - Max speed */
793 	if (te.cost != 0) {
794 		SetDParam(0, e->GetCost() + te.cost);
795 		SetDParam(1, te.cost);
796 		SetDParam(2, e->GetDisplayMaxSpeed());
797 		DrawString(left, right, y, STR_PURCHASE_INFO_COST_REFIT_SPEED);
798 	} else {
799 		SetDParam(0, e->GetCost());
800 		SetDParam(1, e->GetDisplayMaxSpeed());
801 		DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
802 	}
803 	y += FONT_HEIGHT_NORMAL;
804 
805 	/* Cargo capacity */
806 	if (te.mail_capacity > 0) {
807 		SetDParam(0, te.cargo);
808 		SetDParam(1, te.capacity);
809 		SetDParam(2, CT_MAIL);
810 		SetDParam(3, te.mail_capacity);
811 		DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
812 	} else {
813 		/* Note, if the default capacity is selected by the refit capacity
814 		 * callback, then the capacity shown is likely to be incorrect. */
815 		SetDParam(0, te.cargo);
816 		SetDParam(1, te.capacity);
817 		SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
818 		DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
819 	}
820 	y += FONT_HEIGHT_NORMAL;
821 
822 	/* Running cost */
823 	SetDParam(0, e->GetRunningCost());
824 	DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
825 	y += FONT_HEIGHT_NORMAL;
826 
827 	/* Aircraft type */
828 	SetDParam(0, e->GetAircraftTypeText());
829 	DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_TYPE);
830 	y += FONT_HEIGHT_NORMAL;
831 
832 	/* Aircraft range, if available. */
833 	uint16 range = e->GetRange();
834 	if (range != 0) {
835 		SetDParam(0, range);
836 		DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_RANGE);
837 		y += FONT_HEIGHT_NORMAL;
838 	}
839 
840 	return y;
841 }
842 
843 /**
844  * Display additional text from NewGRF in the purchase information window
845  * @param left   Left border of text bounding box
846  * @param right  Right border of text bounding box
847  * @param y      Top border of text bounding box
848  * @param engine Engine to query the additional purchase information for
849  * @return       Bottom border of text bounding box
850  */
ShowAdditionalText(int left,int right,int y,EngineID engine)851 static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
852 {
853 	uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr);
854 	if (callback == CALLBACK_FAILED || callback == 0x400) return y;
855 	const GRFFile *grffile = Engine::Get(engine)->GetGRF();
856 	if (callback > 0x400) {
857 		ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback);
858 		return y;
859 	}
860 
861 	StartTextRefStackUsage(grffile, 6);
862 	uint result = DrawStringMultiLine(left, right, y, INT32_MAX, GetGRFStringID(grffile->grfid, 0xD000 + callback), TC_BLACK);
863 	StopTextRefStackUsage();
864 	return result;
865 }
866 
867 /**
868  * Draw the purchase info details of a vehicle at a given location.
869  * @param left,right,y location where to draw the info
870  * @param engine_number the engine of which to draw the info of
871  * @return y after drawing all the text
872  */
DrawVehiclePurchaseInfo(int left,int right,int y,EngineID engine_number,TestedEngineDetails & te)873 int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number, TestedEngineDetails &te)
874 {
875 	const Engine *e = Engine::Get(engine_number);
876 	YearMonthDay ymd;
877 	ConvertDateToYMD(e->intro_date, &ymd);
878 	bool refittable = IsArticulatedVehicleRefittable(engine_number);
879 	bool articulated_cargo = false;
880 
881 	switch (e->type) {
882 		default: NOT_REACHED();
883 		case VEH_TRAIN:
884 			if (e->u.rail.railveh_type == RAILVEH_WAGON) {
885 				y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail, te);
886 			} else {
887 				y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail, te);
888 			}
889 			articulated_cargo = true;
890 			break;
891 
892 		case VEH_ROAD:
893 			y = DrawRoadVehPurchaseInfo(left, right, y, engine_number, te);
894 			articulated_cargo = true;
895 			break;
896 
897 		case VEH_SHIP:
898 			y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable, te);
899 			break;
900 
901 		case VEH_AIRCRAFT:
902 			y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable, te);
903 			break;
904 	}
905 
906 	if (articulated_cargo) {
907 		/* Cargo type + capacity, or N/A */
908 		int new_y = DrawCargoCapacityInfo(left, right, y, engine_number, te);
909 
910 		if (new_y == y) {
911 			SetDParam(0, CT_INVALID);
912 			SetDParam(2, STR_EMPTY);
913 			DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
914 			y += FONT_HEIGHT_NORMAL;
915 		} else {
916 			y = new_y;
917 		}
918 	}
919 
920 	/* Draw details that apply to all types except rail wagons. */
921 	if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
922 		/* Design date - Life length */
923 		SetDParam(0, ymd.year);
924 		SetDParam(1, e->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR);
925 		DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
926 		y += FONT_HEIGHT_NORMAL;
927 
928 		/* Reliability */
929 		SetDParam(0, ToPercent16(e->reliability));
930 		DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
931 		y += FONT_HEIGHT_NORMAL;
932 	}
933 
934 	if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
935 
936 	/* Additional text from NewGRF */
937 	y = ShowAdditionalText(left, right, y, engine_number);
938 
939 	/* The NewGRF's name which the vehicle comes from */
940 	const GRFConfig *config = GetGRFConfig(e->GetGRFID());
941 	if (_settings_client.gui.show_newgrf_name && config != nullptr)
942 	{
943 		DrawString(left, right, y, config->GetName(), TC_BLACK);
944 		y += FONT_HEIGHT_NORMAL;
945 	}
946 
947 	return y;
948 }
949 
950 /**
951  * Engine drawing loop
952  * @param type Type of vehicle (VEH_*)
953  * @param l The left most location of the list
954  * @param r The right most location of the list
955  * @param y The top most location of the list
956  * @param eng_list What engines to draw
957  * @param min where to start in the list
958  * @param max where in the list to end
959  * @param selected_id what engine to highlight as selected, if any
960  * @param show_count Whether to show the amount of engines or not
961  * @param selected_group the group to list the engines of
962  */
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)963 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)
964 {
965 	static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
966 
967 	/* Obligatory sanity checks! */
968 	assert(max <= eng_list->size());
969 
970 	bool rtl = _current_text_dir == TD_RTL;
971 	int step_size = GetEngineListHeight(type);
972 	int sprite_left  = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
973 	int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
974 	int sprite_width = sprite_left + sprite_right;
975 
976 	int sprite_x        = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
977 	int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
978 
979 	Dimension replace_icon = {0, 0};
980 	int count_width = 0;
981 	if (show_count) {
982 		replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
983 		SetDParamMaxDigits(0, 3, FS_SMALL);
984 		count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
985 	}
986 
987 	int text_left  = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
988 	int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
989 	int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
990 	int count_left = l;
991 	int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
992 
993 	int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
994 	int small_text_y_offset  = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
995 	int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
996 
997 	for (; min < max; min++, y += step_size) {
998 		const EngineID engine = (*eng_list)[min];
999 		/* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
1000 		const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
1001 
1002 		const Engine *e = Engine::Get(engine);
1003 		bool hidden = HasBit(e->company_hidden, _local_company);
1004 		StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
1005 		TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK));
1006 
1007 		SetDParam(0, engine);
1008 		DrawString(text_left, text_right, y + normal_text_y_offset, str, tc);
1009 		DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
1010 		if (show_count) {
1011 			SetDParam(0, num_engines);
1012 			DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
1013 			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);
1014 		}
1015 	}
1016 }
1017 
1018 /**
1019  * Display the dropdown for the vehicle sort criteria.
1020  * @param w Parent window (holds the dropdown button).
1021  * @param vehicle_type %Vehicle type being sorted.
1022  * @param selected Currently selected sort criterium.
1023  * @param button Widget button.
1024  */
DisplayVehicleSortDropDown(Window * w,VehicleType vehicle_type,int selected,int button)1025 void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, int button)
1026 {
1027 	uint32 hidden_mask = 0;
1028 	/* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
1029 	if (vehicle_type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
1030 		SetBit(hidden_mask, 3); // power
1031 		SetBit(hidden_mask, 4); // tractive effort
1032 		SetBit(hidden_mask, 8); // power by running costs
1033 	}
1034 	/* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
1035 	if (vehicle_type == VEH_TRAIN && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
1036 		SetBit(hidden_mask, 4); // tractive effort
1037 	}
1038 	ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask);
1039 }
1040 
1041 /** GUI for building vehicles. */
1042 struct BuildVehicleWindow : Window {
1043 	VehicleType vehicle_type;                   ///< Type of vehicles shown in the window.
1044 	union {
1045 		RailType railtype;   ///< Rail type to show, or #INVALID_RAILTYPE.
1046 		RoadType roadtype;   ///< Road type to show, or #INVALID_ROADTYPE.
1047 	} filter;                                   ///< Filter to apply.
1048 	bool descending_sort_order;                 ///< Sort direction, @see _engine_sort_direction
1049 	byte sort_criteria;                         ///< Current sort criterium.
1050 	bool show_hidden_engines;                   ///< State of the 'show hidden engines' button.
1051 	bool listview_mode;                         ///< If set, only display the available vehicles and do not show a 'build' button.
1052 	EngineID sel_engine;                        ///< Currently selected engine, or #INVALID_ENGINE
1053 	EngineID rename_engine;                     ///< Engine being renamed.
1054 	GUIEngineList eng_list;
1055 	CargoID cargo_filter[NUM_CARGO + 3];        ///< Available cargo filters; CargoID or CF_ANY or CF_NONE or CF_ENGINES
1056 	StringID cargo_filter_texts[NUM_CARGO + 4]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
1057 	byte cargo_filter_criteria;                 ///< Selected cargo filter
1058 	int details_height;                         ///< Minimal needed height of the details panels, in text lines (found so far).
1059 	Scrollbar *vscroll;
1060 	TestedEngineDetails te;                     ///< Tested cost and capacity after refit.
1061 
SetBuyVehicleTextBuildVehicleWindow1062 	void SetBuyVehicleText()
1063 	{
1064 		NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_BUILD);
1065 
1066 		bool refit = this->sel_engine != INVALID_ENGINE && this->cargo_filter[this->cargo_filter_criteria] != CF_ANY && this->cargo_filter[this->cargo_filter_criteria] != CF_NONE;
1067 		if (refit) refit = Engine::Get(this->sel_engine)->GetDefaultCargoType() != this->cargo_filter[this->cargo_filter_criteria];
1068 
1069 		if (refit) {
1070 			widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type;
1071 			widget->tool_tip    = STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP + this->vehicle_type;
1072 		} else {
1073 			widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type;
1074 			widget->tool_tip    = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + this->vehicle_type;
1075 		}
1076 	}
1077 
BuildVehicleWindowBuildVehicleWindow1078 	BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
1079 	{
1080 		this->vehicle_type = type;
1081 		this->listview_mode = tile == INVALID_TILE;
1082 		this->window_number = this->listview_mode ? (int)type : tile;
1083 
1084 		this->sel_engine = INVALID_ENGINE;
1085 
1086 		this->sort_criteria         = _engine_sort_last_criteria[type];
1087 		this->descending_sort_order = _engine_sort_last_order[type];
1088 		this->show_hidden_engines   = _engine_sort_show_hidden_engines[type];
1089 
1090 		this->UpdateFilterByTile();
1091 
1092 		this->CreateNestedTree();
1093 
1094 		this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
1095 
1096 		/* If we are just viewing the list of vehicles, we do not need the Build button.
1097 		 * So we just hide it, and enlarge the Rename button by the now vacant place. */
1098 		if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE);
1099 
1100 		NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_LIST);
1101 		widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
1102 
1103 		widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE);
1104 		widget->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
1105 
1106 		widget = this->GetWidget<NWidgetCore>(WID_BV_RENAME);
1107 		widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
1108 		widget->tool_tip    = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
1109 
1110 		widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_ENGINES);
1111 		widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
1112 		widget->tool_tip    = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
1113 		widget->SetLowered(this->show_hidden_engines);
1114 
1115 		this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9);
1116 
1117 		this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
1118 
1119 		this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
1120 
1121 		this->eng_list.ForceRebuild();
1122 		this->GenerateBuildList(); // generate the list, since we need it in the next line
1123 		/* Select the first engine in the list as default when opening the window */
1124 		if (this->eng_list.size() > 0) {
1125 			this->SelectEngine(this->eng_list[0]);
1126 		} else {
1127 			this->SelectEngine(INVALID_ENGINE);
1128 		}
1129 	}
1130 
1131 	/** Set the filter type according to the depot type */
UpdateFilterByTileBuildVehicleWindow1132 	void UpdateFilterByTile()
1133 	{
1134 		switch (this->vehicle_type) {
1135 			default: NOT_REACHED();
1136 			case VEH_TRAIN:
1137 				if (this->listview_mode) {
1138 					this->filter.railtype = INVALID_RAILTYPE;
1139 				} else {
1140 					this->filter.railtype = GetRailType(this->window_number);
1141 				}
1142 				break;
1143 
1144 			case VEH_ROAD:
1145 				if (this->listview_mode) {
1146 					this->filter.roadtype = INVALID_ROADTYPE;
1147 				} else {
1148 					this->filter.roadtype = GetRoadTypeRoad(this->window_number);
1149 					if (this->filter.roadtype == INVALID_ROADTYPE) {
1150 						this->filter.roadtype = GetRoadTypeTram(this->window_number);
1151 					}
1152 				}
1153 				break;
1154 
1155 			case VEH_SHIP:
1156 			case VEH_AIRCRAFT:
1157 				break;
1158 		}
1159 	}
1160 
1161 	/** Populate the filter list and set the cargo filter criteria. */
SetCargoFilterArrayBuildVehicleWindow1162 	void SetCargoFilterArray()
1163 	{
1164 		uint filter_items = 0;
1165 
1166 		/* Add item for disabling filtering. */
1167 		this->cargo_filter[filter_items] = CF_ANY;
1168 		this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
1169 		filter_items++;
1170 
1171 		/* Specific filters for trains. */
1172 		if (this->vehicle_type == VEH_TRAIN) {
1173 			/* Add item for locomotives only in case of trains. */
1174 			this->cargo_filter[filter_items] = CF_ENGINES;
1175 			this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ENGINES_ONLY;
1176 			filter_items++;
1177 
1178 			/* Add item for vehicles not carrying anything, e.g. train engines.
1179 			 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1180 			this->cargo_filter[filter_items] = CF_NONE;
1181 			this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_NONE;
1182 			filter_items++;
1183 		}
1184 
1185 		/* Collect available cargo types for filtering. */
1186 		for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
1187 			this->cargo_filter[filter_items] = cs->Index();
1188 			this->cargo_filter_texts[filter_items] = cs->name;
1189 			filter_items++;
1190 		}
1191 
1192 		/* Terminate the filter list. */
1193 		this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1194 
1195 		/* If not found, the cargo criteria will be set to all cargoes. */
1196 		this->cargo_filter_criteria = 0;
1197 
1198 		/* Find the last cargo filter criteria. */
1199 		for (uint i = 0; i < filter_items; i++) {
1200 			if (this->cargo_filter[i] == _engine_sort_last_cargo_criteria[this->vehicle_type]) {
1201 				this->cargo_filter_criteria = i;
1202 				break;
1203 			}
1204 		}
1205 
1206 		this->eng_list.SetFilterFuncs(_filter_funcs);
1207 		this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1208 	}
1209 
SelectEngineBuildVehicleWindow1210 	void SelectEngine(EngineID engine)
1211 	{
1212 		CargoID cargo = this->cargo_filter[this->cargo_filter_criteria];
1213 		if (cargo == CF_ANY) cargo = CF_NONE;
1214 
1215 		this->sel_engine = engine;
1216 		this->SetBuyVehicleText();
1217 
1218 		if (this->sel_engine == INVALID_ENGINE) return;
1219 
1220 		const Engine *e = Engine::Get(this->sel_engine);
1221 		if (!e->CanCarryCargo()) {
1222 			this->te.cost = 0;
1223 			this->te.cargo = CT_INVALID;
1224 			return;
1225 		}
1226 
1227 		if (!this->listview_mode) {
1228 			/* Query for cost and refitted capacity */
1229 			CommandCost ret = DoCommand(this->window_number, this->sel_engine | (cargo << 24), 0, DC_QUERY_COST, GetCmdBuildVeh(this->vehicle_type));
1230 			if (ret.Succeeded()) {
1231 				this->te.cost          = ret.GetCost() - e->GetCost();
1232 				this->te.capacity      = _returned_refit_capacity;
1233 				this->te.mail_capacity = _returned_mail_refit_capacity;
1234 				this->te.cargo         = (cargo == CT_INVALID) ? e->GetDefaultCargoType() : cargo;
1235 				return;
1236 			}
1237 		}
1238 
1239 		/* Purchase test was not possible or failed, fill in the defaults instead. */
1240 		this->te.cost     = 0;
1241 		this->te.capacity = e->GetDisplayDefaultCapacity(&this->te.mail_capacity);
1242 		this->te.cargo    = e->GetDefaultCargoType();
1243 	}
1244 
OnInitBuildVehicleWindow1245 	void OnInit() override
1246 	{
1247 		this->SetCargoFilterArray();
1248 	}
1249 
1250 	/** Filter the engine list against the currently selected cargo filter */
FilterEngineListBuildVehicleWindow1251 	void FilterEngineList()
1252 	{
1253 		this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
1254 		if (0 == this->eng_list.size()) { // no engine passed through the filter, invalidate the previously selected engine
1255 			this->SelectEngine(INVALID_ENGINE);
1256 		} 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
1257 			this->SelectEngine(this->eng_list[0]);
1258 		}
1259 	}
1260 
1261 	/** Filter a single engine */
FilterSingleEngineBuildVehicleWindow1262 	bool FilterSingleEngine(EngineID eid)
1263 	{
1264 		CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
1265 		return CargoAndEngineFilter(&eid, filter_type);
1266 	}
1267 
1268 	/* Figure out what train EngineIDs to put in the list */
GenerateBuildTrainListBuildVehicleWindow1269 	void GenerateBuildTrainList()
1270 	{
1271 		EngineID sel_id = INVALID_ENGINE;
1272 		int num_engines = 0;
1273 		int num_wagons  = 0;
1274 
1275 		this->eng_list.clear();
1276 
1277 		/* Make list of all available train engines and wagons.
1278 		 * Also check to see if the previously selected engine is still available,
1279 		 * and if not, reset selection to INVALID_ENGINE. This could be the case
1280 		 * when engines become obsolete and are removed */
1281 		for (const Engine *e : Engine::IterateType(VEH_TRAIN)) {
1282 			if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1283 			EngineID eid = e->index;
1284 			const RailVehicleInfo *rvi = &e->u.rail;
1285 
1286 			if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
1287 			if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
1288 
1289 			/* Filter now! So num_engines and num_wagons is valid */
1290 			if (!FilterSingleEngine(eid)) continue;
1291 
1292 			this->eng_list.push_back(eid);
1293 
1294 			if (rvi->railveh_type != RAILVEH_WAGON) {
1295 				num_engines++;
1296 			} else {
1297 				num_wagons++;
1298 			}
1299 
1300 			if (eid == this->sel_engine) sel_id = eid;
1301 		}
1302 
1303 		this->SelectEngine(sel_id);
1304 
1305 		/* invalidate cached values for name sorter - engine names could change */
1306 		_last_engine[0] = _last_engine[1] = INVALID_ENGINE;
1307 
1308 		/* make engines first, and then wagons, sorted by selected sort_criteria */
1309 		_engine_sort_direction = false;
1310 		EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
1311 
1312 		/* and then sort engines */
1313 		_engine_sort_direction = this->descending_sort_order;
1314 		EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
1315 
1316 		/* and finally sort wagons */
1317 		EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
1318 	}
1319 
1320 	/* Figure out what road vehicle EngineIDs to put in the list */
GenerateBuildRoadVehListBuildVehicleWindow1321 	void GenerateBuildRoadVehList()
1322 	{
1323 		EngineID sel_id = INVALID_ENGINE;
1324 
1325 		this->eng_list.clear();
1326 
1327 		for (const Engine *e : Engine::IterateType(VEH_ROAD)) {
1328 			if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1329 			EngineID eid = e->index;
1330 			if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
1331 			if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue;
1332 
1333 			this->eng_list.push_back(eid);
1334 
1335 			if (eid == this->sel_engine) sel_id = eid;
1336 		}
1337 		this->SelectEngine(sel_id);
1338 	}
1339 
1340 	/* Figure out what ship EngineIDs to put in the list */
GenerateBuildShipListBuildVehicleWindow1341 	void GenerateBuildShipList()
1342 	{
1343 		EngineID sel_id = INVALID_ENGINE;
1344 		this->eng_list.clear();
1345 
1346 		for (const Engine *e : Engine::IterateType(VEH_SHIP)) {
1347 			if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1348 			EngineID eid = e->index;
1349 			if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
1350 			this->eng_list.push_back(eid);
1351 
1352 			if (eid == this->sel_engine) sel_id = eid;
1353 		}
1354 		this->SelectEngine(sel_id);
1355 	}
1356 
1357 	/* Figure out what aircraft EngineIDs to put in the list */
GenerateBuildAircraftListBuildVehicleWindow1358 	void GenerateBuildAircraftList()
1359 	{
1360 		EngineID sel_id = INVALID_ENGINE;
1361 
1362 		this->eng_list.clear();
1363 
1364 		const Station *st = this->listview_mode ? nullptr : Station::GetByTile(this->window_number);
1365 
1366 		/* Make list of all available planes.
1367 		 * Also check to see if the previously selected plane is still available,
1368 		 * and if not, reset selection to INVALID_ENGINE. This could be the case
1369 		 * when planes become obsolete and are removed */
1370 		for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
1371 			if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1372 			EngineID eid = e->index;
1373 			if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
1374 			/* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1375 			if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
1376 
1377 			this->eng_list.push_back(eid);
1378 			if (eid == this->sel_engine) sel_id = eid;
1379 		}
1380 
1381 		this->SelectEngine(sel_id);
1382 	}
1383 
1384 	/* Generate the list of vehicles */
GenerateBuildListBuildVehicleWindow1385 	void GenerateBuildList()
1386 	{
1387 		if (!this->eng_list.NeedRebuild()) return;
1388 
1389 		/* Update filter type in case the road/railtype of the depot got converted */
1390 		this->UpdateFilterByTile();
1391 
1392 		switch (this->vehicle_type) {
1393 			default: NOT_REACHED();
1394 			case VEH_TRAIN:
1395 				this->GenerateBuildTrainList();
1396 				this->eng_list.shrink_to_fit();
1397 				this->eng_list.RebuildDone();
1398 				return; // trains should not reach the last sorting
1399 			case VEH_ROAD:
1400 				this->GenerateBuildRoadVehList();
1401 				break;
1402 			case VEH_SHIP:
1403 				this->GenerateBuildShipList();
1404 				break;
1405 			case VEH_AIRCRAFT:
1406 				this->GenerateBuildAircraftList();
1407 				break;
1408 		}
1409 
1410 		this->FilterEngineList();
1411 
1412 		_engine_sort_direction = this->descending_sort_order;
1413 		EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]);
1414 
1415 		this->eng_list.shrink_to_fit();
1416 		this->eng_list.RebuildDone();
1417 	}
1418 
OnClickBuildVehicleWindow1419 	void OnClick(Point pt, int widget, int click_count) override
1420 	{
1421 		switch (widget) {
1422 			case WID_BV_SORT_ASCENDING_DESCENDING:
1423 				this->descending_sort_order ^= true;
1424 				_engine_sort_last_order[this->vehicle_type] = this->descending_sort_order;
1425 				this->eng_list.ForceRebuild();
1426 				this->SetDirty();
1427 				break;
1428 
1429 			case WID_BV_SHOW_HIDDEN_ENGINES:
1430 				this->show_hidden_engines ^= true;
1431 				_engine_sort_show_hidden_engines[this->vehicle_type] = this->show_hidden_engines;
1432 				this->eng_list.ForceRebuild();
1433 				this->SetWidgetLoweredState(widget, this->show_hidden_engines);
1434 				this->SetDirty();
1435 				break;
1436 
1437 			case WID_BV_LIST: {
1438 				uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
1439 				size_t num_items = this->eng_list.size();
1440 				this->SelectEngine((i < num_items) ? this->eng_list[i] : INVALID_ENGINE);
1441 				this->SetDirty();
1442 				if (_ctrl_pressed) {
1443 					this->OnClick(pt, WID_BV_SHOW_HIDE, 1);
1444 				} else if (click_count > 1 && !this->listview_mode) {
1445 					this->OnClick(pt, WID_BV_BUILD, 1);
1446 				}
1447 				break;
1448 			}
1449 
1450 			case WID_BV_SORT_DROPDOWN: // Select sorting criteria dropdown menu
1451 				DisplayVehicleSortDropDown(this, this->vehicle_type, this->sort_criteria, WID_BV_SORT_DROPDOWN);
1452 				break;
1453 
1454 			case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
1455 				ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_BV_CARGO_FILTER_DROPDOWN, 0, 0);
1456 				break;
1457 
1458 			case WID_BV_SHOW_HIDE: {
1459 				const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine);
1460 				if (e != nullptr) {
1461 					DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
1462 				}
1463 				break;
1464 			}
1465 
1466 			case WID_BV_BUILD: {
1467 				EngineID sel_eng = this->sel_engine;
1468 				if (sel_eng != INVALID_ENGINE) {
1469 					CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
1470 					CargoID cargo = this->cargo_filter[this->cargo_filter_criteria];
1471 					if (cargo == CF_ANY || cargo == CF_ENGINES) cargo = CF_NONE;
1472 					DoCommandP(this->window_number, sel_eng | (cargo << 24), 0, GetCmdBuildVeh(this->vehicle_type), callback);
1473 				}
1474 				break;
1475 			}
1476 
1477 			case WID_BV_RENAME: {
1478 				EngineID sel_eng = this->sel_engine;
1479 				if (sel_eng != INVALID_ENGINE) {
1480 					this->rename_engine = sel_eng;
1481 					SetDParam(0, sel_eng);
1482 					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);
1483 				}
1484 				break;
1485 			}
1486 		}
1487 	}
1488 
1489 	/**
1490 	 * Some data on this window has become invalid.
1491 	 * @param data Information about the changed data.
1492 	 * @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.
1493 	 */
OnInvalidateDataBuildVehicleWindow1494 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
1495 	{
1496 		if (!gui_scope) return;
1497 		/* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1498 		if (this->vehicle_type == VEH_ROAD &&
1499 				_settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL &&
1500 				this->sort_criteria > 7) {
1501 			this->sort_criteria = 0;
1502 			_engine_sort_last_criteria[VEH_ROAD] = 0;
1503 		}
1504 		this->eng_list.ForceRebuild();
1505 	}
1506 
SetStringParametersBuildVehicleWindow1507 	void SetStringParameters(int widget) const override
1508 	{
1509 		switch (widget) {
1510 			case WID_BV_CAPTION:
1511 				if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
1512 					const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
1513 					SetDParam(0, rti->strings.build_caption);
1514 				} else if (this->vehicle_type == VEH_ROAD && !this->listview_mode) {
1515 					const RoadTypeInfo *rti = GetRoadTypeInfo(this->filter.roadtype);
1516 					SetDParam(0, rti->strings.build_caption);
1517 				} else {
1518 					SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
1519 				}
1520 				break;
1521 
1522 			case WID_BV_SORT_DROPDOWN:
1523 				SetDParam(0, _engine_sort_listing[this->vehicle_type][this->sort_criteria]);
1524 				break;
1525 
1526 			case WID_BV_CARGO_FILTER_DROPDOWN:
1527 				SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]);
1528 				break;
1529 
1530 			case WID_BV_SHOW_HIDE: {
1531 				const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine);
1532 				if (e != nullptr && e->IsHidden(_local_company)) {
1533 					SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
1534 				} else {
1535 					SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1536 				}
1537 				break;
1538 			}
1539 		}
1540 	}
1541 
UpdateWidgetSizeBuildVehicleWindow1542 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1543 	{
1544 		switch (widget) {
1545 			case WID_BV_LIST:
1546 				resize->height = GetEngineListHeight(this->vehicle_type);
1547 				size->height = 3 * resize->height;
1548 				size->width = std::max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165);
1549 				break;
1550 
1551 			case WID_BV_PANEL:
1552 				size->height = FONT_HEIGHT_NORMAL * this->details_height + padding.height;
1553 				break;
1554 
1555 			case WID_BV_SORT_ASCENDING_DESCENDING: {
1556 				Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1557 				d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1558 				d.height += padding.height;
1559 				*size = maxdim(*size, d);
1560 				break;
1561 			}
1562 
1563 			case WID_BV_BUILD:
1564 				*size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type);
1565 				*size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type));
1566 				size->width += padding.width;
1567 				size->height += padding.height;
1568 				break;
1569 
1570 			case WID_BV_SHOW_HIDE:
1571 				*size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1572 				*size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
1573 				size->width += padding.width;
1574 				size->height += padding.height;
1575 				break;
1576 		}
1577 	}
1578 
DrawWidgetBuildVehicleWindow1579 	void DrawWidget(const Rect &r, int widget) const override
1580 	{
1581 		switch (widget) {
1582 			case WID_BV_LIST:
1583 				DrawEngineList(
1584 					this->vehicle_type,
1585 					r.left + WD_FRAMERECT_LEFT,
1586 					r.right - WD_FRAMERECT_RIGHT,
1587 					r.top + WD_FRAMERECT_TOP,
1588 					&this->eng_list,
1589 					this->vscroll->GetPosition(),
1590 					static_cast<uint16>(std::min<size_t>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->eng_list.size())),
1591 					this->sel_engine,
1592 					false,
1593 					DEFAULT_GROUP
1594 				);
1595 				break;
1596 
1597 			case WID_BV_SORT_ASCENDING_DESCENDING:
1598 				this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
1599 				break;
1600 		}
1601 	}
1602 
OnPaintBuildVehicleWindow1603 	void OnPaint() override
1604 	{
1605 		this->GenerateBuildList();
1606 		this->vscroll->SetCount((uint)this->eng_list.size());
1607 
1608 		this->SetWidgetsDisabledState(this->sel_engine == INVALID_ENGINE, WID_BV_SHOW_HIDE, WID_BV_BUILD, WIDGET_LIST_END);
1609 
1610 		/* Disable renaming engines in network games if you are not the server. */
1611 		this->SetWidgetDisabledState(WID_BV_RENAME, this->sel_engine == INVALID_ENGINE || (_networking && !_network_server));
1612 
1613 		this->DrawWidgets();
1614 
1615 		if (!this->IsShaded()) {
1616 			int needed_height = this->details_height;
1617 			/* Draw details panels. */
1618 			if (this->sel_engine != INVALID_ENGINE) {
1619 				NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
1620 				int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
1621 						nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine, this->te);
1622 				needed_height = std::max(needed_height, (text_end - (int)nwi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL);
1623 			}
1624 			if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
1625 				int resize = needed_height - this->details_height;
1626 				this->details_height = needed_height;
1627 				this->ReInit(0, resize * FONT_HEIGHT_NORMAL);
1628 				return;
1629 			}
1630 		}
1631 	}
1632 
OnQueryTextFinishedBuildVehicleWindow1633 	void OnQueryTextFinished(char *str) override
1634 	{
1635 		if (str == nullptr) return;
1636 
1637 		DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str);
1638 	}
1639 
OnDropdownSelectBuildVehicleWindow1640 	void OnDropdownSelect(int widget, int index) override
1641 	{
1642 		switch (widget) {
1643 			case WID_BV_SORT_DROPDOWN:
1644 				if (this->sort_criteria != index) {
1645 					this->sort_criteria = index;
1646 					_engine_sort_last_criteria[this->vehicle_type] = this->sort_criteria;
1647 					this->eng_list.ForceRebuild();
1648 				}
1649 				break;
1650 
1651 			case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
1652 				if (this->cargo_filter_criteria != index) {
1653 					this->cargo_filter_criteria = index;
1654 					_engine_sort_last_cargo_criteria[this->vehicle_type] = this->cargo_filter[this->cargo_filter_criteria];
1655 					/* deactivate filter if criteria is 'Show All', activate it otherwise */
1656 					this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1657 					this->eng_list.ForceRebuild();
1658 					this->SelectEngine(this->sel_engine);
1659 				}
1660 				break;
1661 		}
1662 		this->SetDirty();
1663 	}
1664 
OnResizeBuildVehicleWindow1665 	void OnResize() override
1666 	{
1667 		this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
1668 	}
1669 };
1670 
1671 static WindowDesc _build_vehicle_desc(
1672 	WDP_AUTO, "build_vehicle", 240, 268,
1673 	WC_BUILD_VEHICLE, WC_NONE,
1674 	WDF_CONSTRUCTION,
1675 	_nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets)
1676 );
1677 
ShowBuildVehicleWindow(TileIndex tile,VehicleType type)1678 void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
1679 {
1680 	/* We want to be able to open both Available Train as Available Ships,
1681 	 *  so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1682 	 *  As it always is a low value, it won't collide with any real tile
1683 	 *  number. */
1684 	uint num = (tile == INVALID_TILE) ? (int)type : tile;
1685 
1686 	assert(IsCompanyBuildableVehicleType(type));
1687 
1688 	CloseWindowById(WC_BUILD_VEHICLE, num);
1689 
1690 	new BuildVehicleWindow(&_build_vehicle_desc, tile, type);
1691 }
1692