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/>.
8 /** @file train_gui.cpp GUI for trains. */
11 #include "window_gui.h"
12 #include "command_func.h"
14 #include "strings_func.h"
15 #include "vehicle_func.h"
16 #include "zoom_func.h"
17 #include "train_cmd.h"
19 #include "table/strings.h"
21 #include "safeguards.h"
24 * Callback for building wagons.
25 * @param result The result of the command.
26 * @param new_veh_id ID of the ne vehicle.
27 * @param tile The tile the command was executed on.
29 void CcBuildWagon(Commands
, const CommandCost
&result
, VehicleID new_veh_id
, uint
, uint16_t, CargoArray
, TileIndex tile
, EngineID
, bool, CargoID
, ClientID
)
31 if (result
.Failed()) return;
33 /* find a locomotive in the depot. */
34 const Vehicle
*found
= nullptr;
35 for (const Train
*t
: Train::Iterate()) {
36 if (t
->IsFrontEngine() && t
->tile
== tile
&& t
->IsStoppedInDepot()) {
37 if (found
!= nullptr) return; // must be exactly one.
42 /* if we found a loco, */
43 if (found
!= nullptr) {
44 found
= found
->Last();
45 /* put the new wagon at the end of the loco. */
46 Command
<CMD_MOVE_RAIL_VEHICLE
>::Post(found
->tile
, new_veh_id
, found
->index
, false);
47 InvalidateWindowClassesData(WC_TRAINS_LIST
, 0);
52 * Highlight the position where a rail vehicle is dragged over by drawing a light gray background.
53 * @param px The current x position to draw from.
54 * @param max_width The maximum space available to draw.
55 * @param y The vertical centre position to draw from.
56 * @param selection Selected vehicle that is dragged.
57 * @param chain Whether a whole chain is dragged.
58 * @return The width of the highlight mark.
60 static int HighlightDragPosition(int px
, int max_width
, int y
, VehicleID selection
, bool chain
)
62 bool rtl
= _current_text_dir
== TD_RTL
;
64 assert(selection
!= INVALID_VEHICLE
);
65 int dragged_width
= 0;
66 for (Train
*t
= Train::Get(selection
); t
!= nullptr; t
= chain
? t
->Next() : (t
->HasArticulatedPart() ? t
->GetNextArticulatedPart() : nullptr)) {
67 dragged_width
+= t
->GetDisplayImageWidth(nullptr);
70 int drag_hlight_left
= rtl
? std::max(px
- dragged_width
+ 1, 0) : px
;
71 int drag_hlight_right
= rtl
? px
: std::min(px
+ dragged_width
, max_width
) - 1;
72 int drag_hlight_width
= std::max(drag_hlight_right
- drag_hlight_left
+ 1, 0);
74 if (drag_hlight_width
> 0) {
75 int height
= ScaleSpriteTrad(12);
76 int top
= y
- height
/ 2;
77 Rect r
= {drag_hlight_left
, top
, drag_hlight_right
, top
+ height
- 1};
78 /* Sprite-scaling is used here as the area is from sprite size */
79 GfxFillRect(r
.Shrink(ScaleSpriteTrad(1)), GetColourGradient(COLOUR_GREY
, SHADE_LIGHTEST
));
82 return drag_hlight_width
;
86 * Draws an image of a whole train
87 * @param v Front vehicle
88 * @param r Rect 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
, const Rect
&r
, VehicleID selection
, EngineImageType image_type
, 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
;
99 /* Position of highlight box */
102 int max_width
= r
.Width();
104 if (!FillDrawPixelInfo(&tmp_dpi
, r
)) return;
107 AutoRestoreBackup
dpi_backup(_cur_dpi
, &tmp_dpi
);
109 bool do_overlays
= ShowCargoIconOverlay();
110 /* List of overlays, only used if cargo icon overlays are enabled. */
111 static std::vector
<CargoIconOverlay
> overlays
;
113 int px
= rtl
? max_width
+ skip
: -skip
;
114 int y
= r
.Height() / 2;
115 bool sel_articulated
= false;
116 bool dragging
= (drag_dest
!= INVALID_VEHICLE
);
117 bool drag_at_end_of_train
= (drag_dest
== v
->index
); // Head index is used to mark dragging at end of train.
118 for (; v
!= nullptr && (rtl
? px
> 0 : px
< max_width
); v
= v
->Next()) {
119 if (dragging
&& !drag_at_end_of_train
&& drag_dest
== v
->index
) {
120 /* Highlight the drag-and-drop destination inside the train. */
121 int drag_hlight_width
= HighlightDragPosition(px
, max_width
, y
, selection
, _cursor
.vehchain
);
122 px
+= rtl
? -drag_hlight_width
: drag_hlight_width
;
126 int width
= Train::From(v
)->GetDisplayImageWidth(&offset
);
128 if (rtl
? px
+ width
> 0 : px
- width
< max_width
) {
129 PaletteID pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
130 VehicleSpriteSeq seq
;
131 v
->GetImage(dir
, image_type
, &seq
);
132 seq
.Draw(px
+ (rtl
? -offset
.x
: offset
.x
), y
+ offset
.y
, pal
, (v
->vehstatus
& VS_CRASHED
) != 0);
135 if (!v
->IsArticulatedPart()) sel_articulated
= false;
137 if (v
->index
== selection
) {
138 /* Set the highlight position */
139 highlight_l
= rtl
? px
- width
: px
;
140 highlight_r
= rtl
? px
- 1 : px
+ width
- 1;
141 sel_articulated
= true;
142 } else if ((_cursor
.vehchain
&& highlight_r
!= 0) || sel_articulated
) {
144 highlight_l
-= width
;
146 highlight_r
+= width
;
150 if (do_overlays
) AddCargoIconOverlay(overlays
, px
, width
, v
);
151 px
+= rtl
? -width
: width
;
155 DrawCargoIconOverlays(overlays
, y
);
159 if (dragging
&& drag_at_end_of_train
) {
160 /* Highlight the drag-and-drop destination at the end of the train. */
161 HighlightDragPosition(px
, max_width
, y
, selection
, _cursor
.vehchain
);
165 if (highlight_l
!= highlight_r
) {
166 /* Draw the highlight. Now done after drawing all the engines, as
167 * the next engine after the highlight could overlap it. */
168 int height
= ScaleSpriteTrad(12);
169 Rect hr
= {highlight_l
, 0, highlight_r
, height
- 1};
170 DrawFrameRect(hr
.Translate(r
.left
, CenterBounds(r
.top
, r
.bottom
, height
)).Expand(WidgetDimensions::scaled
.bevel
), COLOUR_WHITE
, FR_BORDERONLY
);
174 /** Helper struct for the cargo details information */
175 struct CargoSummaryItem
{
176 CargoID cargo
; ///< The cargo that is carried
177 StringID subtype
; ///< STR_EMPTY if none
178 uint capacity
; ///< Amount that can be carried
179 uint amount
; ///< Amount that is carried
180 StationID source
; ///< One of the source stations
182 /** Used by CargoSummary::Find() and similar functions */
183 inline bool operator != (const CargoSummaryItem
&other
) const
185 return this->cargo
!= other
.cargo
|| this->subtype
!= other
.subtype
;
188 /** Used by std::find() and similar functions */
189 inline bool operator == (const CargoSummaryItem
&other
) const
191 return !(this->cargo
!= other
.cargo
);
195 static const uint TRAIN_DETAILS_MIN_INDENT
= 32; ///< Minimum indent level in the train details window
196 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
198 /** Container for the cargo summary information. */
199 typedef std::vector
<CargoSummaryItem
> CargoSummary
;
200 /** Reused container of cargo details */
201 static CargoSummary _cargo_summary
;
204 * Draw the details cargo tab for the given vehicle at the given position
206 * @param item Data to draw
207 * @param left The left most coordinate to draw
208 * @param right The right most coordinate to draw
209 * @param y The y coordinate
211 static void TrainDetailsCargoTab(const CargoSummaryItem
*item
, int left
, int right
, int y
)
214 if (item
->amount
> 0) {
215 SetDParam(0, item
->cargo
);
216 SetDParam(1, item
->amount
);
217 SetDParam(2, item
->source
);
218 SetDParam(3, _settings_game
.vehicle
.freight_trains
);
219 str
= FreightWagonMult(item
->cargo
) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT
: STR_VEHICLE_DETAILS_CARGO_FROM
;
221 str
= !IsValidCargoID(item
->cargo
) ? STR_QUANTITY_N_A
: STR_VEHICLE_DETAILS_CARGO_EMPTY
;
224 DrawString(left
, right
, y
, str
, TC_LIGHT_BLUE
);
228 * Draw the details info tab for the given vehicle at the given position
230 * @param v current vehicle
231 * @param left The left most coordinate to draw
232 * @param right The right most coordinate to draw
233 * @param y The y coordinate
235 static void TrainDetailsInfoTab(const Vehicle
*v
, int left
, int right
, int y
)
237 if (RailVehInfo(v
->engine_type
)->railveh_type
== RAILVEH_WAGON
) {
238 SetDParam(0, PackEngineNameDParam(v
->engine_type
, EngineNameContext::VehicleDetails
));
239 SetDParam(1, v
->value
);
240 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE
);
242 SetDParam(0, PackEngineNameDParam(v
->engine_type
, EngineNameContext::VehicleDetails
));
243 SetDParam(1, v
->build_year
);
244 SetDParam(2, v
->value
);
245 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE
);
250 * Draw the details capacity tab for the given vehicle at the given position
252 * @param item Data to draw
253 * @param left The left most coordinate to draw
254 * @param right The right most coordinate to draw
255 * @param y The y coordinate
257 static void TrainDetailsCapacityTab(const CargoSummaryItem
*item
, int left
, int right
, int y
)
260 if (IsValidCargoID(item
->cargo
)) {
261 SetDParam(0, item
->cargo
);
262 SetDParam(1, item
->capacity
);
263 SetDParam(4, item
->subtype
);
264 SetDParam(5, _settings_game
.vehicle
.freight_trains
);
265 str
= FreightWagonMult(item
->cargo
) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT
: STR_VEHICLE_INFO_CAPACITY
;
267 /* Draw subtype only */
268 SetDParam(0, item
->subtype
);
269 str
= STR_VEHICLE_INFO_NO_CAPACITY
;
271 DrawString(left
, right
, y
, str
);
275 * Collects the cargo transported
276 * @param v Vehicle to process
277 * @param summary Space for the result
279 static void GetCargoSummaryOfArticulatedVehicle(const Train
*v
, CargoSummary
&summary
)
283 if (!v
->GetEngine()->CanCarryCargo()) continue;
285 CargoSummaryItem new_item
;
286 new_item
.cargo
= v
->cargo_cap
> 0 ? v
->cargo_type
: INVALID_CARGO
;
287 new_item
.subtype
= GetCargoSubtypeText(v
);
288 if (!IsValidCargoID(new_item
.cargo
) && new_item
.subtype
== STR_EMPTY
) continue;
290 auto item
= std::find(std::begin(summary
), std::end(summary
), new_item
);
291 if (item
== std::end(summary
)) {
292 item
= summary
.emplace(std::end(summary
));
293 item
->cargo
= new_item
.cargo
;
294 item
->subtype
= new_item
.subtype
;
297 item
->source
= INVALID_STATION
;
300 item
->capacity
+= v
->cargo_cap
;
301 item
->amount
+= v
->cargo
.StoredCount();
302 if (item
->source
== INVALID_STATION
) item
->source
= v
->cargo
.GetFirstStation();
303 } while ((v
= v
->Next()) != nullptr && v
->IsArticulatedPart());
307 * Get the length of an articulated vehicle.
308 * @param v the vehicle to get the length of.
309 * @return the length in pixels.
311 static uint
GetLengthOfArticulatedVehicle(const Train
*v
)
316 length
+= v
->GetDisplayImageWidth();
317 } while ((v
= v
->Next()) != nullptr && v
->IsArticulatedPart());
323 * Determines the number of lines in the train details window
324 * @param veh_id Train
325 * @param det_tab Selected details tab
326 * @return Number of line
328 int GetTrainDetailsWndVScroll(VehicleID veh_id
, TrainDetailsWindowTabs det_tab
)
332 if (det_tab
== TDW_TAB_TOTALS
) { // Total cargo tab
333 CargoArray max_cargo
{};
334 for (const Vehicle
*v
= Vehicle::Get(veh_id
); v
!= nullptr; v
= v
->Next()) {
335 max_cargo
[v
->cargo_type
] += v
->cargo_cap
;
338 num
= max_cargo
.GetCount();
339 num
++; // needs one more because first line is description string
341 for (const Train
*v
= Train::Get(veh_id
); v
!= nullptr; v
= v
->GetNextVehicle()) {
342 GetCargoSummaryOfArticulatedVehicle(v
, _cargo_summary
);
343 num
+= std::max(1u, (unsigned)_cargo_summary
.size());
345 uint length
= GetLengthOfArticulatedVehicle(v
);
346 if (length
> (uint
)ScaleSpriteTrad(TRAIN_DETAILS_MAX_INDENT
)) num
++;
354 * Draw the details for the given vehicle at the given position
356 * @param v current vehicle
357 * @param r the Rect to draw within
358 * @param vscroll_pos Position of scrollbar
359 * @param vscroll_cap Number of lines currently displayed
360 * @param det_tab Selected details tab
362 void DrawTrainDetails(const Train
*v
, const Rect
&r
, int vscroll_pos
, uint16_t vscroll_cap
, TrainDetailsWindowTabs det_tab
)
364 bool rtl
= _current_text_dir
== TD_RTL
;
365 int line_height
= r
.Height();
366 int sprite_y_offset
= line_height
/ 2;
367 int text_y_offset
= (line_height
- GetCharacterHeight(FS_NORMAL
)) / 2;
369 /* draw the first 3 details tabs */
370 if (det_tab
!= TDW_TAB_TOTALS
) {
371 Direction dir
= rtl
? DIR_E
: DIR_W
;
372 int x
= rtl
? r
.right
: r
.left
;
373 for (; v
!= nullptr && vscroll_pos
> -vscroll_cap
; v
= v
->GetNextVehicle()) {
374 GetCargoSummaryOfArticulatedVehicle(v
, _cargo_summary
);
382 int width
= u
->GetDisplayImageWidth(&offset
);
383 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
385 const Engine
*e
= Engine::Get(v
->engine_type
);
386 if (e
->GetGRF() != nullptr) {
387 pitch
= ScaleSpriteTrad(e
->GetGRF()->traininfo_vehicle_pitch
);
389 PaletteID pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(u
);
390 VehicleSpriteSeq seq
;
391 u
->GetImage(dir
, EIT_IN_DETAILS
, &seq
);
392 seq
.Draw(px
+ (rtl
? -offset
.x
: offset
.x
), r
.top
- line_height
* vscroll_pos
+ sprite_y_offset
+ pitch
, pal
, (v
->vehstatus
& VS_CRASHED
) != 0);
394 px
+= rtl
? -width
: width
;
397 } while (u
!= nullptr && u
->IsArticulatedPart());
399 bool separate_sprite_row
= (dx
> (uint
)ScaleSpriteTrad(TRAIN_DETAILS_MAX_INDENT
));
400 if (separate_sprite_row
) {
405 int sprite_width
= std::max
<int>(dx
, ScaleSpriteTrad(TRAIN_DETAILS_MIN_INDENT
)) + WidgetDimensions::scaled
.hsep_normal
;
406 Rect dr
= r
.Indent(sprite_width
, rtl
);
407 uint num_lines
= std::max(1u, (unsigned)_cargo_summary
.size());
408 for (uint i
= 0; i
< num_lines
; i
++) {
409 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
410 int py
= r
.top
- line_height
* vscroll_pos
+ text_y_offset
;
411 if (i
> 0 || separate_sprite_row
) {
412 if (vscroll_pos
!= 0) GfxFillRect(r
.left
, py
- WidgetDimensions::scaled
.matrix
.top
- 1, r
.right
, py
- WidgetDimensions::scaled
.matrix
.top
, GetColourGradient(COLOUR_GREY
, SHADE_LIGHT
));
416 if (i
< _cargo_summary
.size()) {
417 TrainDetailsCargoTab(&_cargo_summary
[i
], dr
.left
, dr
.right
, py
);
419 DrawString(dr
.left
, dr
.right
, py
, STR_QUANTITY_N_A
, TC_LIGHT_BLUE
);
424 if (i
== 0) TrainDetailsInfoTab(v
, dr
.left
, dr
.right
, py
);
427 case TDW_TAB_CAPACITY
:
428 if (i
< _cargo_summary
.size()) {
429 TrainDetailsCapacityTab(&_cargo_summary
[i
], dr
.left
, dr
.right
, py
);
431 SetDParam(0, STR_EMPTY
);
432 DrawString(dr
.left
, dr
.right
, py
, STR_VEHICLE_INFO_NO_CAPACITY
);
436 default: NOT_REACHED();
444 CargoArray act_cargo
{};
445 CargoArray max_cargo
{};
446 Money feeder_share
= 0;
448 for (const Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
449 act_cargo
[u
->cargo_type
] += u
->cargo
.StoredCount();
450 max_cargo
[u
->cargo_type
] += u
->cargo_cap
;
451 feeder_share
+= u
->cargo
.GetFeederShare();
454 /* draw total cargo tab */
455 DrawString(r
.left
, r
.right
, y
+ text_y_offset
, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT
);
458 /* Indent the total cargo capacity details */
459 Rect ir
= r
.Indent(WidgetDimensions::scaled
.hsep_indent
, rtl
);
460 for (const CargoSpec
*cs
: _sorted_cargo_specs
) {
461 CargoID cid
= cs
->Index();
462 if (max_cargo
[cid
] > 0 && --vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
463 SetDParam(0, cid
); // {CARGO} #1
464 SetDParam(1, act_cargo
[cid
]); // {CARGO} #2
465 SetDParam(2, cid
); // {SHORTCARGO} #1
466 SetDParam(3, max_cargo
[cid
]); // {SHORTCARGO} #2
467 SetDParam(4, _settings_game
.vehicle
.freight_trains
);
468 DrawString(ir
.left
, ir
.right
, y
+ text_y_offset
, FreightWagonMult(cid
) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT
: STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY
);
472 SetDParam(0, feeder_share
);
473 DrawString(r
.left
, r
.right
, y
+ text_y_offset
, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE
);