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"
15 #include "command_func.h"
16 #include "vehicle_gui.h"
18 #include "strings_func.h"
19 #include "vehicle_func.h"
20 #include "window_func.h"
22 #include "table/sprites.h"
23 #include "table/strings.h"
26 * Callback for building wagons.
27 * @param result The result of the command.
28 * @param tile The tile the command was executed on.
29 * @param p1 Additional data for the command (for the #CommandProc)
30 * @param p2 Additional data for the command (for the #CommandProc)
32 void CcBuildWagon(const CommandCost
&result
, TileIndex tile
, uint32 p1
, uint32 p2
)
34 if (result
.Failed()) return;
36 /* find a locomotive in the depot. */
37 const Vehicle
*found
= NULL
;
40 if (t
->IsFrontEngine() && t
->tile
== tile
&&
41 t
->track
== TRACK_BIT_DEPOT
) {
42 if (found
!= NULL
) return; // must be exactly one.
47 /* if we found a loco, */
49 found
= found
->Last();
50 /* put the new wagon at the end of the loco. */
51 DoCommandP(0, _new_vehicle_id
, found
->index
, CMD_MOVE_RAIL_VEHICLE
);
52 InvalidateWindowClassesData(WC_TRAINS_LIST
, 0);
57 * Highlight the position where a rail vehicle is dragged over by drawing a light gray background.
58 * @param px The current x position to draw from.
59 * @param max_width The maximum space available to draw.
60 * @param selection Selected vehicle that is dragged.
61 * @return The width of the highlight mark.
63 static int HighlightDragPosition(int px
, int max_width
, VehicleID selection
)
65 bool rtl
= _current_text_dir
== TD_RTL
;
67 assert(selection
!= INVALID_VEHICLE
);
69 int dragged_width
= Train::Get(selection
)->GetDisplayImageWidth(&offset
) + WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
71 int drag_hlight_left
= rtl
? max(px
-dragged_width
, 0) : px
;
72 int drag_hlight_right
= rtl
? px
: min(px
+ dragged_width
, max_width
);
73 int drag_hlight_width
= max(drag_hlight_right
- drag_hlight_left
, 0);
75 if (drag_hlight_width
> 0) {
76 GfxFillRect(drag_hlight_left
+ WD_FRAMERECT_LEFT
, WD_FRAMERECT_TOP
+ 1,
77 drag_hlight_right
- WD_FRAMERECT_RIGHT
, 13 - WD_FRAMERECT_BOTTOM
, _colour_gradient
[COLOUR_GREY
][7]);
80 return drag_hlight_width
;
84 * Draws an image of a whole train
85 * @param v Front vehicle
86 * @param left The minimum horizontal position
87 * @param right The maximum horizontal position
88 * @param y Vertical position to draw at
89 * @param selection Selected vehicle to draw a frame around
90 * @param skip Number of pixels to skip at the front (for scrolling)
91 * @param drag_dest The vehicle another one is dragged over, \c INVALID_VEHICLE if none.
93 void DrawTrainImage(const Train
*v
, int left
, int right
, int y
, VehicleID selection
, int skip
, VehicleID drag_dest
)
95 bool rtl
= _current_text_dir
== TD_RTL
;
96 Direction dir
= rtl
? DIR_E
: DIR_W
;
98 DrawPixelInfo tmp_dpi
, *old_dpi
;
99 /* Position of highlight box */
102 int max_width
= right
- left
+ 1;
104 if (!FillDrawPixelInfo(&tmp_dpi
, left
, y
, max_width
, 14)) return;
109 int px
= rtl
? max_width
+ skip
: -skip
;
110 bool sel_articulated
= false;
111 bool dragging
= (drag_dest
!= INVALID_VEHICLE
);
112 bool drag_at_end_of_train
= (drag_dest
== v
->index
); // Head index is used to mark dragging at end of train.
113 for (; v
!= NULL
&& (rtl
? px
> 0 : px
< max_width
); v
= v
->Next()) {
114 if (dragging
&& !drag_at_end_of_train
&& drag_dest
== v
->index
) {
115 /* Highlight the drag-and-drop destination inside the train. */
116 int drag_hlight_width
= HighlightDragPosition(px
, max_width
, selection
);
117 px
+= rtl
? -drag_hlight_width
: drag_hlight_width
;
121 int width
= Train::From(v
)->GetDisplayImageWidth(&offset
);
123 if (rtl
? px
+ width
> 0 : px
- width
< max_width
) {
124 PaletteID pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
125 DrawSprite(v
->GetImage(dir
), pal
, px
+ (rtl
? -offset
.x
: offset
.x
), 7 + offset
.y
);
128 if (!v
->IsArticulatedPart()) sel_articulated
= false;
130 if (v
->index
== selection
) {
131 /* Set the highlight position */
132 highlight_l
= rtl
? px
- width
: px
;
133 highlight_r
= rtl
? px
- 1 : px
+ width
- 1;
134 sel_articulated
= true;
135 } else if ((_cursor
.vehchain
&& highlight_r
!= 0) || sel_articulated
) {
137 highlight_l
-= width
;
139 highlight_r
+= width
;
143 px
+= rtl
? -width
: width
;
146 if (dragging
&& drag_at_end_of_train
) {
147 /* Highlight the drag-and-drop destination at the end of the train. */
148 HighlightDragPosition(px
, max_width
, selection
);
151 if (highlight_l
!= highlight_r
) {
152 /* Draw the highlight. Now done after drawing all the engines, as
153 * the next engine after the highlight could overlap it. */
154 DrawFrameRect(highlight_l
, 0, highlight_r
, 13, COLOUR_WHITE
, FR_BORDERONLY
);
160 /** Helper struct for the cargo details information */
161 struct CargoSummaryItem
{
162 CargoID cargo
; ///< The cargo that is carried
163 StringID subtype
; ///< STR_EMPTY if none
164 uint capacity
; ///< Amount that can be carried
165 uint amount
; ///< Amount that is carried
166 StationID source
; ///< One of the source stations
168 /** Used by CargoSummary::Find() and similiar functions */
169 FORCEINLINE
bool operator != (const CargoSummaryItem
&other
) const
171 return this->cargo
!= other
.cargo
|| this->subtype
!= other
.subtype
;
175 static const uint TRAIN_DETAILS_MIN_INDENT
= 32; ///< Minimum indent level in the train details window
176 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
178 /** Container for the cargo summary information. */
179 typedef SmallVector
<CargoSummaryItem
, 2> CargoSummary
;
180 /** Reused container of cargo details */
181 static CargoSummary _cargo_summary
;
184 * Draw the details cargo tab for the given vehicle at the given position
186 * @param item Data to draw
187 * @param left The left most coordinate to draw
188 * @param right The right most coordinate to draw
189 * @param y The y coordinate
191 static void TrainDetailsCargoTab(const CargoSummaryItem
*item
, int left
, int right
, int y
)
193 StringID str
= STR_VEHICLE_DETAILS_CARGO_EMPTY
;
195 if (item
->amount
> 0) {
196 SetDParam(0, item
->cargo
);
197 SetDParam(1, item
->amount
);
198 SetDParam(2, item
->source
);
199 SetDParam(3, _settings_game
.vehicle
.freight_trains
);
200 str
= FreightWagonMult(item
->cargo
) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT
: STR_VEHICLE_DETAILS_CARGO_FROM
;
203 DrawString(left
, right
, y
, str
);
207 * Draw the details info tab for the given vehicle at the given position
209 * @param v current vehicle
210 * @param left The left most coordinate to draw
211 * @param right The right most coordinate to draw
212 * @param y The y coordinate
214 static void TrainDetailsInfoTab(const Vehicle
*v
, int left
, int right
, int y
)
216 if (RailVehInfo(v
->engine_type
)->railveh_type
== RAILVEH_WAGON
) {
217 SetDParam(0, v
->engine_type
);
218 SetDParam(1, v
->value
);
219 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE
, TC_FROMSTRING
, SA_LEFT
| SA_STRIP
);
221 SetDParam(0, v
->engine_type
);
222 SetDParam(1, v
->build_year
);
223 SetDParam(2, v
->value
);
224 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE
, TC_FROMSTRING
, SA_LEFT
| SA_STRIP
);
229 * Draw the details capacity tab for the given vehicle at the given position
231 * @param item Data to draw
232 * @param left The left most coordinate to draw
233 * @param right The right most coordinate to draw
234 * @param y The y coordinate
236 static void TrainDetailsCapacityTab(const CargoSummaryItem
*item
, int left
, int right
, int y
)
238 SetDParam(0, item
->cargo
);
239 SetDParam(1, item
->capacity
);
240 SetDParam(4, item
->subtype
);
241 SetDParam(5, _settings_game
.vehicle
.freight_trains
);
242 DrawString(left
, right
, y
, FreightWagonMult(item
->cargo
) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT
: STR_VEHICLE_INFO_CAPACITY
);
246 * Collects the cargo transportet
247 * @param v Vehicle to process
248 * @param summary Space for the result
250 static void GetCargoSummaryOfArticulatedVehicle(const Train
*v
, CargoSummary
*summary
)
254 if (v
->cargo_cap
== 0) continue;
256 CargoSummaryItem new_item
;
257 new_item
.cargo
= v
->cargo_type
;
258 new_item
.subtype
= GetCargoSubtypeText(v
);
260 CargoSummaryItem
*item
= summary
->Find(new_item
);
261 if (item
== summary
->End()) {
262 item
= summary
->Append();
263 item
->cargo
= new_item
.cargo
;
264 item
->subtype
= new_item
.subtype
;
267 item
->source
= INVALID_STATION
;
270 item
->capacity
+= v
->cargo_cap
;
271 item
->amount
+= v
->cargo
.Count();
272 if (item
->source
== INVALID_STATION
) item
->source
= v
->cargo
.Source();
273 } while ((v
= v
->Next()) != NULL
&& v
->IsArticulatedPart());
277 * Get the length of an articulated vehicle.
278 * @param v the vehicle to get the length of.
279 * @return the length in pixels.
281 static uint
GetLengthOfArticulatedVehicle(const Train
*v
)
286 length
+= v
->GetDisplayImageWidth();
287 } while ((v
= v
->Next()) != NULL
&& v
->IsArticulatedPart());
293 * Determines the number of lines in the train details window
294 * @param veh_id Train
295 * @param det_tab Selected details tab
296 * @return Number of line
298 int GetTrainDetailsWndVScroll(VehicleID veh_id
, TrainDetailsWindowTabs det_tab
)
302 if (det_tab
== TDW_TAB_TOTALS
) { // Total cargo tab
303 CargoArray act_cargo
;
304 CargoArray max_cargo
;
305 for (const Vehicle
*v
= Vehicle::Get(veh_id
); v
!= NULL
; v
= v
->Next()) {
306 act_cargo
[v
->cargo_type
] += v
->cargo
.Count();
307 max_cargo
[v
->cargo_type
] += v
->cargo_cap
;
310 /* Set scroll-amount seperately from counting, as to not compute num double
311 * for more carriages of the same type
313 for (CargoID i
= 0; i
< NUM_CARGO
; i
++) {
314 if (max_cargo
[i
] > 0) num
++; // only count carriages that the train has
316 num
++; // needs one more because first line is description string
318 for (const Train
*v
= Train::Get(veh_id
); v
!= NULL
; v
= v
->GetNextVehicle()) {
319 GetCargoSummaryOfArticulatedVehicle(v
, &_cargo_summary
);
320 num
+= max(1u, _cargo_summary
.Length());
322 uint length
= GetLengthOfArticulatedVehicle(v
);
323 if (length
> TRAIN_DETAILS_MAX_INDENT
) num
++;
331 * Draw the details for the given vehicle at the given position
333 * @param v current vehicle
334 * @param left The left most coordinate to draw
335 * @param right The right most coordinate to draw
336 * @param y The y coordinate
337 * @param vscroll_pos Position of scrollbar
338 * @param vscroll_cap Number of lines currently displayed
339 * @param det_tab Selected details tab
341 void DrawTrainDetails(const Train
*v
, int left
, int right
, int y
, int vscroll_pos
, uint16 vscroll_cap
, TrainDetailsWindowTabs det_tab
)
343 /* draw the first 3 details tabs */
344 if (det_tab
!= TDW_TAB_TOTALS
) {
345 bool rtl
= _current_text_dir
== TD_RTL
;
346 Direction dir
= rtl
? DIR_E
: DIR_W
;
347 int x
= rtl
? right
: left
;
348 int sprite_y_offset
= 4 + (FONT_HEIGHT_NORMAL
- 10) / 2;
349 int line_height
= WD_MATRIX_TOP
+ FONT_HEIGHT_NORMAL
+ WD_MATRIX_BOTTOM
;
350 for (; v
!= NULL
&& vscroll_pos
> -vscroll_cap
; v
= v
->GetNextVehicle()) {
351 GetCargoSummaryOfArticulatedVehicle(v
, &_cargo_summary
);
359 int width
= u
->GetDisplayImageWidth(&offset
);
360 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
361 PaletteID pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
362 DrawSprite(u
->GetImage(dir
), pal
, px
+ (rtl
? -offset
.x
: offset
.x
), y
- line_height
* vscroll_pos
+ sprite_y_offset
+ offset
.y
);
364 px
+= rtl
? -width
: width
;
367 } while (u
!= NULL
&& u
->IsArticulatedPart());
369 bool separate_sprite_row
= (dx
> TRAIN_DETAILS_MAX_INDENT
);
370 if (separate_sprite_row
) {
375 uint num_lines
= max(1u, _cargo_summary
.Length());
376 for (uint i
= 0; i
< num_lines
; i
++) {
377 int sprite_width
= max
<int>(dx
, TRAIN_DETAILS_MIN_INDENT
) + 3;
378 int data_left
= left
+ (rtl
? 0 : sprite_width
);
379 int data_right
= right
- (rtl
? sprite_width
: 0);
380 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
381 int py
= y
- line_height
* vscroll_pos
;
382 if (i
> 0 || separate_sprite_row
) {
383 if (vscroll_pos
!= 0) GfxFillRect(left
, py
- WD_MATRIX_TOP
- 1, right
, py
- WD_MATRIX_TOP
, _colour_gradient
[COLOUR_GREY
][5]);
387 if (i
< _cargo_summary
.Length()) {
388 TrainDetailsCargoTab(&_cargo_summary
[i
], data_left
, data_right
, py
);
390 DrawString(data_left
, data_right
, py
, STR_QUANTITY_N_A
, TC_LIGHT_BLUE
);
395 if (i
== 0) TrainDetailsInfoTab(v
, data_left
, data_right
, py
);
398 case TDW_TAB_CAPACITY
:
399 if (i
< _cargo_summary
.Length()) {
400 TrainDetailsCapacityTab(&_cargo_summary
[i
], data_left
, data_right
, py
);
402 DrawString(data_left
, data_right
, py
, STR_VEHICLE_INFO_NO_CAPACITY
);
406 default: NOT_REACHED();
413 CargoArray act_cargo
;
414 CargoArray max_cargo
;
415 Money feeder_share
= 0;
417 for (const Vehicle
*u
= v
; u
!= NULL
; u
= u
->Next()) {
418 act_cargo
[u
->cargo_type
] += u
->cargo
.Count();
419 max_cargo
[u
->cargo_type
] += u
->cargo_cap
;
420 feeder_share
+= u
->cargo
.FeederShare();
423 /* draw total cargo tab */
424 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT
);
425 y
+= WD_MATRIX_TOP
+ FONT_HEIGHT_NORMAL
+ WD_MATRIX_BOTTOM
;
427 for (CargoID i
= 0; i
< NUM_CARGO
; i
++) {
428 if (max_cargo
[i
] > 0 && --vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
429 SetDParam(0, i
); // {CARGO} #1
430 SetDParam(1, act_cargo
[i
]); // {CARGO} #2
431 SetDParam(2, i
); // {SHORTCARGO} #1
432 SetDParam(3, max_cargo
[i
]); // {SHORTCARGO} #2
433 SetDParam(4, _settings_game
.vehicle
.freight_trains
);
434 DrawString(left
, right
, y
, FreightWagonMult(i
) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT
: STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY
);
435 y
+= WD_MATRIX_TOP
+ FONT_HEIGHT_NORMAL
+ WD_MATRIX_BOTTOM
;
438 SetDParam(0, feeder_share
);
439 DrawString(left
, right
, y
, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE
);