Fix crash when setting separation mode for vehicles with no orders list.
[openttd-joker.git] / src / train_gui.cpp
blobb8b9e216fa40a26322a2b03208d5f521e0d168b0
1 /* $Id: train_gui.cpp 25454 2013-06-24 18:39:19Z rubidium $ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file train_gui.cpp GUI for trains. */
12 #include "stdafx.h"
13 #include "window_gui.h"
14 #include "command_func.h"
15 #include "train.h"
16 #include "strings_func.h"
17 #include "vehicle_func.h"
18 #include "zoom_func.h"
20 #include "table/strings.h"
22 #include "safeguards.h"
24 /**
25 * Callback for building wagons.
26 * @param result The result of the command.
27 * @param tile The tile the command was executed on.
28 * @param p1 Additional data for the command (for the #CommandProc)
29 * @param p2 Additional data for the command (for the #CommandProc)
31 void CcBuildWagon(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
33 if (result.Failed()) return;
35 /* find a locomotive in the depot. */
36 const Vehicle *found = nullptr;
37 const Train *t;
38 FOR_ALL_TRAINS(t) {
39 if (t->IsFrontEngine() && t->tile == tile && t->IsStoppedInDepot()) {
40 if (found != nullptr) return; // must be exactly one.
41 found = t;
45 /* if we found a loco, */
46 if (found != nullptr) {
47 found = found->Last();
48 /* put the new wagon at the end of the loco. */
49 DoCommandP(0, _new_vehicle_id, found->index, CMD_MOVE_RAIL_VEHICLE);
50 InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
51 InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS);
55 /**
56 * Highlight the position where a rail vehicle is dragged over by drawing a light gray background.
57 * @param px The current x position to draw from.
58 * @param max_width The maximum space available to draw.
59 * @param selection Selected vehicle that is dragged.
60 * @param chain Whether a whole chain is dragged.
61 * @return The width of the highlight mark.
63 static int HighlightDragPosition(int px, int max_width, VehicleID selection, bool chain)
65 bool rtl = _current_text_dir == TD_RTL;
67 assert(selection != INVALID_VEHICLE);
68 int dragged_width = 0;
69 for (Train *t = Train::Get(selection); t != nullptr; t = chain ? t->Next() : (t->HasArticulatedPart() ? t->GetNextArticulatedPart() : nullptr)) {
70 dragged_width += t->GetDisplayImageWidth(nullptr);
73 int drag_hlight_left = rtl ? max(px - dragged_width + 1, 0) : px;
74 int drag_hlight_right = rtl ? px : min(px + dragged_width, max_width) - 1;
75 int drag_hlight_width = max(drag_hlight_right - drag_hlight_left + 1, 0);
77 if (drag_hlight_width > 0) {
78 GfxFillRect(drag_hlight_left + WD_FRAMERECT_LEFT, WD_FRAMERECT_TOP + 1,
79 drag_hlight_right - WD_FRAMERECT_RIGHT, ScaleGUITrad(13) - WD_FRAMERECT_BOTTOM, _colour_gradient[COLOUR_GREY][7]);
82 return drag_hlight_width;
85 /**
86 * Draws an image of a whole train
87 * @param v Front vehicle
88 * @param left The minimum horizontal position
89 * @param right The maximum horizontal position
90 * @param y Vertical position to draw at
91 * @param selection Selected vehicle to draw a frame around
92 * @param skip Number of pixels to skip at the front (for scrolling)
93 * @param drag_dest The vehicle another one is dragged over, \c INVALID_VEHICLE if none.
95 void DrawTrainImage(const Train *v, int left, int right, int y, VehicleID selection, EngineImageType image_type, int skip, VehicleID drag_dest)
97 bool rtl = _current_text_dir == TD_RTL;
98 Direction dir = rtl ? DIR_E : DIR_W;
100 DrawPixelInfo tmp_dpi, *old_dpi;
101 /* Position of highlight box */
102 int highlight_l = 0;
103 int highlight_r = 0;
104 int max_width = right - left + 1;
105 int height = ScaleGUITrad(14);
107 if (!FillDrawPixelInfo(&tmp_dpi, left, y, max_width, height)) return;
109 old_dpi = _cur_dpi;
110 _cur_dpi = &tmp_dpi;
112 int px = rtl ? max_width + skip : -skip;
113 bool sel_articulated = false;
114 bool dragging = (drag_dest != INVALID_VEHICLE);
115 bool drag_at_end_of_train = (drag_dest == v->index); // Head index is used to mark dragging at end of train.
116 for (; v != nullptr && (rtl ? px > 0 : px < max_width); v = v->Next()) {
117 if (dragging && !drag_at_end_of_train && drag_dest == v->index) {
118 /* Highlight the drag-and-drop destination inside the train. */
119 int drag_hlight_width = HighlightDragPosition(px, max_width, selection, _cursor.vehchain);
120 px += rtl ? -drag_hlight_width : drag_hlight_width;
123 Point offset;
124 int width = Train::From(v)->GetDisplayImageWidth(&offset);
126 if (rtl ? px + width > 0 : px - width < max_width) {
127 PaletteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
128 VehicleSpriteSeq seq;
129 v->GetImage(dir, image_type, &seq);
130 seq.Draw(px + (rtl ? -offset.x : offset.x), height / 2 + offset.y, pal, (v->vehstatus & VS_CRASHED) != 0);
133 if (!v->IsArticulatedPart()) sel_articulated = false;
135 if (v->index == selection) {
136 /* Set the highlight position */
137 highlight_l = rtl ? px - width : px;
138 highlight_r = rtl ? px - 1 : px + width - 1;
139 sel_articulated = true;
140 } else if ((_cursor.vehchain && highlight_r != 0) || sel_articulated) {
141 if (rtl) {
142 highlight_l -= width;
143 } else {
144 highlight_r += width;
148 px += rtl ? -width : width;
151 if (dragging && drag_at_end_of_train) {
152 /* Highlight the drag-and-drop destination at the end of the train. */
153 HighlightDragPosition(px, max_width, selection, _cursor.vehchain);
156 if (highlight_l != highlight_r) {
157 /* Draw the highlight. Now done after drawing all the engines, as
158 * the next engine after the highlight could overlap it. */
159 DrawFrameRect(highlight_l, 0, highlight_r, height - 1, COLOUR_WHITE, FR_BORDERONLY);
162 _cur_dpi = old_dpi;
165 /** Helper struct for the cargo details information */
166 struct CargoSummaryItem {
167 CargoID cargo; ///< The cargo that is carried
168 StringID subtype; ///< STR_EMPTY if none
169 uint capacity; ///< Amount that can be carried
170 uint amount; ///< Amount that is carried
171 StationID source; ///< One of the source stations
173 /** Used by CargoSummary::Find() and similar functions */
174 inline bool operator != (const CargoSummaryItem &other) const
176 return this->cargo != other.cargo || this->subtype != other.subtype;
180 static const uint TRAIN_DETAILS_MIN_INDENT = 32; ///< Minimum indent level in the train details window
181 static const uint TRAIN_DETAILS_MAX_INDENT = 72; ///< Maximum indent level in the train details window; wider than this and we start on a new line
183 /** Container for the cargo summary information. */
184 typedef SmallVector<CargoSummaryItem, 2> CargoSummary;
185 /** Reused container of cargo details */
186 static CargoSummary _cargo_summary;
189 * Draw the details cargo tab for the given vehicle at the given position
191 * @param item Data to draw
192 * @param left The left most coordinate to draw
193 * @param right The right most coordinate to draw
194 * @param y The y coordinate
196 static void TrainDetailsCargoTab(const CargoSummaryItem *item, int left, int right, int y)
198 StringID str;
199 if (item->amount > 0) {
200 SetDParam(0, item->cargo);
201 SetDParam(1, item->amount);
202 SetDParam(2, item->source);
203 SetDParam(3, _settings_game.vehicle.freight_trains);
204 str = FreightWagonMult(item->cargo) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT : STR_VEHICLE_DETAILS_CARGO_FROM;
205 } else {
206 SetDParam(0, STR_QUANTITY_N_A);
207 str = item->cargo == INVALID_CARGO ? STR_LTBLUE_STRING : STR_VEHICLE_DETAILS_CARGO_EMPTY;
210 DrawString(left, right, y, str);
214 * Draw the details info tab for the given vehicle at the given position
216 * @param v current vehicle
217 * @param left The left most coordinate to draw
218 * @param right The right most coordinate to draw
219 * @param y The y coordinate
221 static void TrainDetailsInfoTab(const Vehicle *v, int left, int right, int y)
223 if (RailVehInfo(v->engine_type)->railveh_type == RAILVEH_WAGON) {
224 SetDParam(0, v->engine_type);
225 SetDParam(1, v->value);
226 SetDParam(2, Train::From(v)->GetEmptyWeight());
227 SetDParam(3, Train::From(v)->GetLoadedWeight());
228 SetDParam(4, CeilDiv(Train::From(v)->gcache.cached_veh_length * 100, TILE_SIZE));
229 SetDParam(5, 2);
230 DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_EXT);
231 } else {
232 SetDParam(0, v->engine_type);
233 SetDParam(1, v->build_year);
234 SetDParam(2, v->value);
235 SetDParam(3, Train::From(v)->GetEmptyWeight());
236 SetDParam(4, Train::From(v)->GetLoadedWeight());
238 const Vehicle* u = v;
239 uint16 length = 0;
241 do {
242 length += CeilDiv(Train::From(u)->gcache.cached_veh_length * 100, TILE_SIZE);
243 } while ((u = u->Next()) != nullptr && u->IsArticulatedPart());
245 SetDParam(5, length);
246 SetDParam(6, 2);
247 DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE_EXT);
252 * Draw the details capacity tab for the given vehicle at the given position
254 * @param item Data to draw
255 * @param left The left most coordinate to draw
256 * @param right The right most coordinate to draw
257 * @param y The y coordinate
259 static void TrainDetailsCapacityTab(const CargoSummaryItem *item, int left, int right, int y)
261 StringID str;
262 if (item->cargo != INVALID_CARGO) {
263 SetDParam(0, item->cargo);
264 SetDParam(1, item->capacity);
265 SetDParam(4, item->subtype);
266 SetDParam(5, _settings_game.vehicle.freight_trains);
267 str = FreightWagonMult(item->cargo) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT : STR_VEHICLE_INFO_CAPACITY;
268 } else {
269 /* Draw subtype only */
270 SetDParam(0, item->subtype);
271 str = STR_VEHICLE_INFO_NO_CAPACITY;
273 DrawString(left, right, y, str);
277 * Collects the cargo transported
278 * @param v Vehicle to process
279 * @param summary Space for the result
281 static void GetCargoSummaryOfArticulatedVehicle(const Train *v, CargoSummary *summary)
283 summary->Clear();
284 do {
285 if (!v->GetEngine()->CanCarryCargo()) continue;
287 CargoSummaryItem new_item;
288 new_item.cargo = v->cargo_cap > 0 ? v->cargo_type : INVALID_CARGO;
289 new_item.subtype = GetCargoSubtypeText(v);
290 if (new_item.cargo == INVALID_CARGO && new_item.subtype == STR_EMPTY) continue;
292 CargoSummaryItem *item = summary->Find(new_item);
293 if (item == summary->End()) {
294 item = summary->Append();
295 item->cargo = new_item.cargo;
296 item->subtype = new_item.subtype;
297 item->capacity = 0;
298 item->amount = 0;
299 item->source = INVALID_STATION;
302 item->capacity += v->cargo_cap;
303 item->amount += v->cargo.StoredCount();
304 if (item->source == INVALID_STATION) item->source = v->cargo.Source();
305 } while ((v = v->Next()) != nullptr && v->IsArticulatedPart());
309 * Get the length of an articulated vehicle.
310 * @param v the vehicle to get the length of.
311 * @return the length in pixels.
313 static uint GetLengthOfArticulatedVehicle(const Train *v)
315 uint length = 0;
317 do {
318 length += v->GetDisplayImageWidth();
319 } while ((v = v->Next()) != nullptr && v->IsArticulatedPart());
321 return length;
325 * Determines the number of lines in the train details window
326 * @param veh_id Train
327 * @param det_tab Selected details tab
328 * @return Number of line
330 int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab)
332 int num = 0;
334 if (det_tab == TDW_TAB_TOTALS) { // Total cargo tab
335 CargoArray act_cargo;
336 CargoArray max_cargo;
337 for (const Vehicle *v = Vehicle::Get(veh_id); v != nullptr; v = v->Next()) {
338 act_cargo[v->cargo_type] += v->cargo.StoredCount();
339 max_cargo[v->cargo_type] += v->cargo_cap;
342 /* Set scroll-amount separately from counting, as to not compute num double
343 * for more carriages of the same type
345 for (CargoID i = 0; i < NUM_CARGO; i++) {
346 if (max_cargo[i] > 0) num++; // only count carriages that the train has
348 num += 10; // needs seven more because first line is description string and we have the weight and speed info
349 } else {
350 for (const Train *v = Train::Get(veh_id); v != nullptr; v = v->GetNextVehicle()) {
351 GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary);
352 num += max(1u, _cargo_summary.Length());
354 uint length = GetLengthOfArticulatedVehicle(v);
355 if (length > TRAIN_DETAILS_MAX_INDENT) num++;
359 return num;
363 * Draw the details for the given vehicle at the given position
365 * @param v current vehicle
366 * @param left The left most coordinate to draw
367 * @param right The right most coordinate to draw
368 * @param y The y coordinate
369 * @param vscroll_pos Position of scrollbar
370 * @param vscroll_cap Number of lines currently displayed
371 * @param det_tab Selected details tab
373 void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_pos, uint16 vscroll_cap, TrainDetailsWindowTabs det_tab)
375 /* get rid of awkward offset */
376 y -= WD_MATRIX_TOP;
378 int sprite_height = ScaleGUITrad(GetVehicleHeight(VEH_TRAIN));
379 int line_height = max(sprite_height, WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM);
380 int sprite_y_offset = line_height / 2;
381 int text_y_offset = (line_height - FONT_HEIGHT_NORMAL) / 2;
383 /* draw the first 3 details tabs */
384 if (det_tab != TDW_TAB_TOTALS) {
385 bool rtl = _current_text_dir == TD_RTL;
386 Direction dir = rtl ? DIR_E : DIR_W;
387 int x = rtl ? right : left;
388 for (; v != nullptr && vscroll_pos > -vscroll_cap; v = v->GetNextVehicle()) {
389 GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary);
391 /* Draw sprites */
392 uint dx = 0;
393 int px = x;
394 const Train *u = v;
395 do {
396 Point offset;
397 int width = u->GetDisplayImageWidth(&offset);
398 if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) {
399 int pitch = 0;
400 const Engine *e = Engine::Get(v->engine_type);
401 if (e->GetGRF() != nullptr) {
402 pitch = ScaleGUITrad(e->GetGRF()->traininfo_vehicle_pitch);
404 PaletteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
405 VehicleSpriteSeq seq;
406 u->GetImage(dir, EIT_IN_DETAILS, &seq);
407 seq.Draw(px + (rtl ? -offset.x : offset.x), y - line_height * vscroll_pos + sprite_y_offset + pitch, pal, (v->vehstatus & VS_CRASHED) != 0);
409 px += rtl ? -width : width;
410 dx += width;
411 u = u->Next();
412 } while (u != nullptr && u->IsArticulatedPart());
414 bool separate_sprite_row = (dx > (uint)ScaleGUITrad(TRAIN_DETAILS_MAX_INDENT));
415 if (separate_sprite_row) {
416 vscroll_pos--;
417 dx = 0;
420 uint num_lines = max(1u, _cargo_summary.Length());
421 for (uint i = 0; i < num_lines; i++) {
422 int sprite_width = max<int>(dx, ScaleGUITrad(TRAIN_DETAILS_MIN_INDENT)) + 3;
423 int data_left = left + (rtl ? 0 : sprite_width);
424 int data_right = right - (rtl ? sprite_width : 0);
425 if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) {
426 int py = y - line_height * vscroll_pos + text_y_offset;
427 if (i > 0 || separate_sprite_row) {
428 if (vscroll_pos != 0) GfxFillRect(left, py - WD_MATRIX_TOP - 1, right, py - WD_MATRIX_TOP, _colour_gradient[COLOUR_GREY][5]);
430 switch (det_tab) {
431 case TDW_TAB_CARGO:
432 if (i < _cargo_summary.Length()) {
433 TrainDetailsCargoTab(&_cargo_summary[i], data_left, data_right, py);
434 } else {
435 DrawString(data_left, data_right, py, STR_QUANTITY_N_A, TC_LIGHT_BLUE);
437 break;
439 case TDW_TAB_INFO:
440 if (i == 0) TrainDetailsInfoTab(v, data_left, data_right, py);
441 break;
443 case TDW_TAB_CAPACITY:
444 if (i < _cargo_summary.Length()) {
445 TrainDetailsCapacityTab(&_cargo_summary[i], data_left, data_right, py);
446 } else {
447 SetDParam(0, STR_EMPTY);
448 DrawString(data_left, data_right, py, STR_VEHICLE_INFO_NO_CAPACITY);
450 break;
452 default: NOT_REACHED();
455 vscroll_pos--;
458 } else {
459 CargoArray act_cargo;
460 CargoArray max_cargo;
461 Money feeder_share = 0;
462 uint16 empty_weight = 0;
463 uint16 loaded_weight = 0;
464 uint16 empty_max_speed = 0;
465 uint16 loaded_max_speed = 0;
467 for (const Vehicle *u = v; u != nullptr; u = u->Next()) {
468 act_cargo[u->cargo_type] += u->cargo.StoredCount();
469 max_cargo[u->cargo_type] += u->cargo_cap;
470 feeder_share += u->cargo.FeederShare();
471 empty_weight += Train::From(u)->GetEmptyWeight();
472 loaded_weight += Train::From(u)->GetLoadedWeight();
475 double tractive_effort_empty = empty_weight * v->GetRollingFriction();
476 double tractive_effort_loaded = loaded_weight * v->GetRollingFriction();
477 double power = v->gcache.cached_power * 746ll;
478 double max_te = v->gcache.cached_max_te;
479 empty_max_speed = min(v->GetDisplayMaxSpeed(), (tractive_effort_empty == 0 || tractive_effort_empty > max_te) ? 0 : (3.6 * (power / tractive_effort_empty)));
480 loaded_max_speed = min(v->GetDisplayMaxSpeed(), (tractive_effort_loaded == 0 || tractive_effort_loaded > max_te) ? 0 : (3.6 * (power / tractive_effort_loaded)));
482 /* draw performance tab */
483 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
484 SetDParam(0, CeilDiv(Train::From(v)->gcache.cached_total_length * 100, TILE_SIZE));
485 SetDParam(1, 2);
486 DrawString(left, right, y + text_y_offset, STR_VEHICLE_DETAILS_TRAIN_TOTAL_LENGTH);
487 y += line_height;
490 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
491 y += line_height;
494 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
495 SetDParam(0, empty_weight);
496 DrawString(left, right, y + text_y_offset, STR_VEHICLE_DETAILS_TRAIN_TOTAL_EMPTY_WEIGHT);
497 y += line_height;
500 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
501 SetDParam(0, loaded_weight);
502 DrawString(left, right, y + text_y_offset, STR_VEHICLE_DETAILS_TRAIN_TOTAL_LOADED_WEIGHT);
503 y += line_height;
506 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
507 y += line_height;
510 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
511 SetDParam(0, empty_max_speed);
512 DrawString(left, right, y + text_y_offset, empty_max_speed > 0 ? STR_VEHICLE_DETAILS_TRAIN_MAX_EMPTY_SPEED : STR_VEHICLE_DETAILS_TRAIN_MAX_EMPTY_SPEED_ZERO);
513 y += line_height;
516 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
517 SetDParam(0, loaded_max_speed);
518 DrawString(left, right, y + text_y_offset, loaded_max_speed > 0 ? STR_VEHICLE_DETAILS_TRAIN_MAX_LOADED_SPEED : STR_VEHICLE_DETAILS_TRAIN_MAX_LOADED_SPEED_ZERO);
519 y += line_height;
522 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
523 y += line_height;
526 if (--vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
527 DrawString(left, right, y + text_y_offset, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT);
528 y += line_height;
531 for (CargoID i = 0; i < NUM_CARGO; i++) {
532 if (max_cargo[i] > 0 && --vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
533 SetDParam(0, i); // {CARGO} #1
534 SetDParam(1, act_cargo[i]); // {CARGO} #2
535 SetDParam(2, i); // {SHORTCARGO} #1
536 SetDParam(3, max_cargo[i]); // {SHORTCARGO} #2
537 SetDParam(4, _settings_game.vehicle.freight_trains);
538 DrawString(left, right, y + text_y_offset, FreightWagonMult(i) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT : STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY);
539 y += line_height;