1 /* $Id: train_gui.cpp 25454 2013-06-24 18:39:19Z rubidium $ */
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 train_gui.cpp GUI for trains. */
13 #include "window_gui.h"
14 #include "command_func.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"
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;
39 if (t
->IsFrontEngine() && t
->tile
== tile
&& t
->IsStoppedInDepot()) {
40 if (found
!= nullptr) return; // must be exactly one.
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
);
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
;
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 */
104 int max_width
= right
- left
+ 1;
105 int height
= ScaleGUITrad(14);
107 if (!FillDrawPixelInfo(&tmp_dpi
, left
, y
, max_width
, height
)) return;
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
;
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
) {
142 highlight_l
-= width
;
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
);
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
)
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
;
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
));
230 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_EXT
);
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
;
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
);
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
)
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
;
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
)
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
;
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
)
318 length
+= v
->GetDisplayImageWidth();
319 } while ((v
= v
->Next()) != nullptr && v
->IsArticulatedPart());
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
)
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
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
++;
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 */
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
);
397 int width
= u
->GetDisplayImageWidth(&offset
);
398 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
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
;
412 } while (u
!= nullptr && u
->IsArticulatedPart());
414 bool separate_sprite_row
= (dx
> (uint
)ScaleGUITrad(TRAIN_DETAILS_MAX_INDENT
));
415 if (separate_sprite_row
) {
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]);
432 if (i
< _cargo_summary
.Length()) {
433 TrainDetailsCargoTab(&_cargo_summary
[i
], data_left
, data_right
, py
);
435 DrawString(data_left
, data_right
, py
, STR_QUANTITY_N_A
, TC_LIGHT_BLUE
);
440 if (i
== 0) TrainDetailsInfoTab(v
, data_left
, data_right
, py
);
443 case TDW_TAB_CAPACITY
:
444 if (i
< _cargo_summary
.Length()) {
445 TrainDetailsCapacityTab(&_cargo_summary
[i
], data_left
, data_right
, py
);
447 SetDParam(0, STR_EMPTY
);
448 DrawString(data_left
, data_right
, py
, STR_VEHICLE_INFO_NO_CAPACITY
);
452 default: NOT_REACHED();
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
));
486 DrawString(left
, right
, y
+ text_y_offset
, STR_VEHICLE_DETAILS_TRAIN_TOTAL_LENGTH
);
490 if (--vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
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
);
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
);
506 if (--vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
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
);
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
);
522 if (--vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
526 if (--vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
527 DrawString(left
, right
, y
+ text_y_offset
, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT
);
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
);