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)), _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 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 int px
= rtl
? max_width
+ skip
: -skip
;
110 int y
= r
.Height() / 2;
111 bool sel_articulated
= false;
112 bool dragging
= (drag_dest
!= INVALID_VEHICLE
);
113 bool drag_at_end_of_train
= (drag_dest
== v
->index
); // Head index is used to mark dragging at end of train.
114 for (; v
!= nullptr && (rtl
? px
> 0 : px
< max_width
); v
= v
->Next()) {
115 if (dragging
&& !drag_at_end_of_train
&& drag_dest
== v
->index
) {
116 /* Highlight the drag-and-drop destination inside the train. */
117 int drag_hlight_width
= HighlightDragPosition(px
, max_width
, y
, selection
, _cursor
.vehchain
);
118 px
+= rtl
? -drag_hlight_width
: drag_hlight_width
;
122 int width
= Train::From(v
)->GetDisplayImageWidth(&offset
);
124 if (rtl
? px
+ width
> 0 : px
- width
< max_width
) {
125 PaletteID pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
126 VehicleSpriteSeq seq
;
127 v
->GetImage(dir
, image_type
, &seq
);
128 seq
.Draw(px
+ (rtl
? -offset
.x
: offset
.x
), y
+ offset
.y
, pal
, (v
->vehstatus
& VS_CRASHED
) != 0);
131 if (!v
->IsArticulatedPart()) sel_articulated
= false;
133 if (v
->index
== selection
) {
134 /* Set the highlight position */
135 highlight_l
= rtl
? px
- width
: px
;
136 highlight_r
= rtl
? px
- 1 : px
+ width
- 1;
137 sel_articulated
= true;
138 } else if ((_cursor
.vehchain
&& highlight_r
!= 0) || sel_articulated
) {
140 highlight_l
-= width
;
142 highlight_r
+= width
;
146 px
+= rtl
? -width
: width
;
149 if (dragging
&& drag_at_end_of_train
) {
150 /* Highlight the drag-and-drop destination at the end of the train. */
151 HighlightDragPosition(px
, max_width
, y
, selection
, _cursor
.vehchain
);
155 if (highlight_l
!= highlight_r
) {
156 /* Draw the highlight. Now done after drawing all the engines, as
157 * the next engine after the highlight could overlap it. */
158 int height
= ScaleSpriteTrad(12);
159 Rect hr
= {highlight_l
, 0, highlight_r
, height
- 1};
160 DrawFrameRect(hr
.Translate(r
.left
, CenterBounds(r
.top
, r
.bottom
, height
)).Expand(WidgetDimensions::scaled
.bevel
), COLOUR_WHITE
, FR_BORDERONLY
);
164 /** Helper struct for the cargo details information */
165 struct CargoSummaryItem
{
166 CargoID cargo
; ///< The cargo that is carried
167 StringID subtype
; ///< STR_EMPTY if none
168 uint capacity
; ///< Amount that can be carried
169 uint amount
; ///< Amount that is carried
170 StationID source
; ///< One of the source stations
172 /** Used by CargoSummary::Find() and similar functions */
173 inline bool operator != (const CargoSummaryItem
&other
) const
175 return this->cargo
!= other
.cargo
|| this->subtype
!= other
.subtype
;
178 /** Used by std::find() and similar functions */
179 inline bool operator == (const CargoSummaryItem
&other
) const
181 return !(this->cargo
!= other
.cargo
);
185 static const uint TRAIN_DETAILS_MIN_INDENT
= 32; ///< Minimum indent level in the train details window
186 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
188 /** Container for the cargo summary information. */
189 typedef std::vector
<CargoSummaryItem
> CargoSummary
;
190 /** Reused container of cargo details */
191 static CargoSummary _cargo_summary
;
194 * Draw the details cargo tab for the given vehicle at the given position
196 * @param item Data to draw
197 * @param left The left most coordinate to draw
198 * @param right The right most coordinate to draw
199 * @param y The y coordinate
201 static void TrainDetailsCargoTab(const CargoSummaryItem
*item
, int left
, int right
, int y
)
204 if (item
->amount
> 0) {
205 SetDParam(0, item
->cargo
);
206 SetDParam(1, item
->amount
);
207 SetDParam(2, item
->source
);
208 SetDParam(3, _settings_game
.vehicle
.freight_trains
);
209 str
= FreightWagonMult(item
->cargo
) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT
: STR_VEHICLE_DETAILS_CARGO_FROM
;
211 str
= !IsValidCargoID(item
->cargo
) ? STR_QUANTITY_N_A
: STR_VEHICLE_DETAILS_CARGO_EMPTY
;
214 DrawString(left
, right
, y
, str
, TC_LIGHT_BLUE
);
218 * Draw the details info tab for the given vehicle at the given position
220 * @param v current vehicle
221 * @param left The left most coordinate to draw
222 * @param right The right most coordinate to draw
223 * @param y The y coordinate
225 static void TrainDetailsInfoTab(const Vehicle
*v
, int left
, int right
, int y
)
227 if (RailVehInfo(v
->engine_type
)->railveh_type
== RAILVEH_WAGON
) {
228 SetDParam(0, PackEngineNameDParam(v
->engine_type
, EngineNameContext::VehicleDetails
));
229 SetDParam(1, v
->value
);
230 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE
);
232 SetDParam(0, PackEngineNameDParam(v
->engine_type
, EngineNameContext::VehicleDetails
));
233 SetDParam(1, v
->build_year
);
234 SetDParam(2, v
->value
);
235 DrawString(left
, right
, y
, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE
);
240 * Draw the details capacity tab for the given vehicle at the given position
242 * @param item Data to draw
243 * @param left The left most coordinate to draw
244 * @param right The right most coordinate to draw
245 * @param y The y coordinate
247 static void TrainDetailsCapacityTab(const CargoSummaryItem
*item
, int left
, int right
, int y
)
250 if (IsValidCargoID(item
->cargo
)) {
251 SetDParam(0, item
->cargo
);
252 SetDParam(1, item
->capacity
);
253 SetDParam(4, item
->subtype
);
254 SetDParam(5, _settings_game
.vehicle
.freight_trains
);
255 str
= FreightWagonMult(item
->cargo
) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT
: STR_VEHICLE_INFO_CAPACITY
;
257 /* Draw subtype only */
258 SetDParam(0, item
->subtype
);
259 str
= STR_VEHICLE_INFO_NO_CAPACITY
;
261 DrawString(left
, right
, y
, str
);
265 * Collects the cargo transported
266 * @param v Vehicle to process
267 * @param summary Space for the result
269 static void GetCargoSummaryOfArticulatedVehicle(const Train
*v
, CargoSummary
&summary
)
273 if (!v
->GetEngine()->CanCarryCargo()) continue;
275 CargoSummaryItem new_item
;
276 new_item
.cargo
= v
->cargo_cap
> 0 ? v
->cargo_type
: INVALID_CARGO
;
277 new_item
.subtype
= GetCargoSubtypeText(v
);
278 if (!IsValidCargoID(new_item
.cargo
) && new_item
.subtype
== STR_EMPTY
) continue;
280 auto item
= std::find(std::begin(summary
), std::end(summary
), new_item
);
281 if (item
== std::end(summary
)) {
282 item
= summary
.emplace(std::end(summary
));
283 item
->cargo
= new_item
.cargo
;
284 item
->subtype
= new_item
.subtype
;
287 item
->source
= INVALID_STATION
;
290 item
->capacity
+= v
->cargo_cap
;
291 item
->amount
+= v
->cargo
.StoredCount();
292 if (item
->source
== INVALID_STATION
) item
->source
= v
->cargo
.GetFirstStation();
293 } while ((v
= v
->Next()) != nullptr && v
->IsArticulatedPart());
297 * Get the length of an articulated vehicle.
298 * @param v the vehicle to get the length of.
299 * @return the length in pixels.
301 static uint
GetLengthOfArticulatedVehicle(const Train
*v
)
306 length
+= v
->GetDisplayImageWidth();
307 } while ((v
= v
->Next()) != nullptr && v
->IsArticulatedPart());
313 * Determines the number of lines in the train details window
314 * @param veh_id Train
315 * @param det_tab Selected details tab
316 * @return Number of line
318 int GetTrainDetailsWndVScroll(VehicleID veh_id
, TrainDetailsWindowTabs det_tab
)
322 if (det_tab
== TDW_TAB_TOTALS
) { // Total cargo tab
323 CargoArray max_cargo
{};
324 for (const Vehicle
*v
= Vehicle::Get(veh_id
); v
!= nullptr; v
= v
->Next()) {
325 max_cargo
[v
->cargo_type
] += v
->cargo_cap
;
328 num
= max_cargo
.GetCount();
329 num
++; // needs one more because first line is description string
331 for (const Train
*v
= Train::Get(veh_id
); v
!= nullptr; v
= v
->GetNextVehicle()) {
332 GetCargoSummaryOfArticulatedVehicle(v
, _cargo_summary
);
333 num
+= std::max(1u, (unsigned)_cargo_summary
.size());
335 uint length
= GetLengthOfArticulatedVehicle(v
);
336 if (length
> (uint
)ScaleSpriteTrad(TRAIN_DETAILS_MAX_INDENT
)) num
++;
344 * Draw the details for the given vehicle at the given position
346 * @param v current vehicle
347 * @param r the Rect to draw within
348 * @param vscroll_pos Position of scrollbar
349 * @param vscroll_cap Number of lines currently displayed
350 * @param det_tab Selected details tab
352 void DrawTrainDetails(const Train
*v
, const Rect
&r
, int vscroll_pos
, uint16_t vscroll_cap
, TrainDetailsWindowTabs det_tab
)
354 bool rtl
= _current_text_dir
== TD_RTL
;
355 int line_height
= r
.Height();
356 int sprite_y_offset
= line_height
/ 2;
357 int text_y_offset
= (line_height
- GetCharacterHeight(FS_NORMAL
)) / 2;
359 /* draw the first 3 details tabs */
360 if (det_tab
!= TDW_TAB_TOTALS
) {
361 Direction dir
= rtl
? DIR_E
: DIR_W
;
362 int x
= rtl
? r
.right
: r
.left
;
363 for (; v
!= nullptr && vscroll_pos
> -vscroll_cap
; v
= v
->GetNextVehicle()) {
364 GetCargoSummaryOfArticulatedVehicle(v
, _cargo_summary
);
372 int width
= u
->GetDisplayImageWidth(&offset
);
373 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
375 const Engine
*e
= Engine::Get(v
->engine_type
);
376 if (e
->GetGRF() != nullptr) {
377 pitch
= ScaleSpriteTrad(e
->GetGRF()->traininfo_vehicle_pitch
);
379 PaletteID pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(u
);
380 VehicleSpriteSeq seq
;
381 u
->GetImage(dir
, EIT_IN_DETAILS
, &seq
);
382 seq
.Draw(px
+ (rtl
? -offset
.x
: offset
.x
), r
.top
- line_height
* vscroll_pos
+ sprite_y_offset
+ pitch
, pal
, (v
->vehstatus
& VS_CRASHED
) != 0);
384 px
+= rtl
? -width
: width
;
387 } while (u
!= nullptr && u
->IsArticulatedPart());
389 bool separate_sprite_row
= (dx
> (uint
)ScaleSpriteTrad(TRAIN_DETAILS_MAX_INDENT
));
390 if (separate_sprite_row
) {
395 int sprite_width
= std::max
<int>(dx
, ScaleSpriteTrad(TRAIN_DETAILS_MIN_INDENT
)) + WidgetDimensions::scaled
.hsep_normal
;
396 Rect dr
= r
.Indent(sprite_width
, rtl
);
397 uint num_lines
= std::max(1u, (unsigned)_cargo_summary
.size());
398 for (uint i
= 0; i
< num_lines
; i
++) {
399 if (vscroll_pos
<= 0 && vscroll_pos
> -vscroll_cap
) {
400 int py
= r
.top
- line_height
* vscroll_pos
+ text_y_offset
;
401 if (i
> 0 || separate_sprite_row
) {
402 if (vscroll_pos
!= 0) GfxFillRect(r
.left
, py
- WidgetDimensions::scaled
.matrix
.top
- 1, r
.right
, py
- WidgetDimensions::scaled
.matrix
.top
, _colour_gradient
[COLOUR_GREY
][5]);
406 if (i
< _cargo_summary
.size()) {
407 TrainDetailsCargoTab(&_cargo_summary
[i
], dr
.left
, dr
.right
, py
);
409 DrawString(dr
.left
, dr
.right
, py
, STR_QUANTITY_N_A
, TC_LIGHT_BLUE
);
414 if (i
== 0) TrainDetailsInfoTab(v
, dr
.left
, dr
.right
, py
);
417 case TDW_TAB_CAPACITY
:
418 if (i
< _cargo_summary
.size()) {
419 TrainDetailsCapacityTab(&_cargo_summary
[i
], dr
.left
, dr
.right
, py
);
421 SetDParam(0, STR_EMPTY
);
422 DrawString(dr
.left
, dr
.right
, py
, STR_VEHICLE_INFO_NO_CAPACITY
);
426 default: NOT_REACHED();
434 CargoArray act_cargo
{};
435 CargoArray max_cargo
{};
436 Money feeder_share
= 0;
438 for (const Vehicle
*u
= v
; u
!= nullptr; u
= u
->Next()) {
439 act_cargo
[u
->cargo_type
] += u
->cargo
.StoredCount();
440 max_cargo
[u
->cargo_type
] += u
->cargo_cap
;
441 feeder_share
+= u
->cargo
.GetFeederShare();
444 /* draw total cargo tab */
445 DrawString(r
.left
, r
.right
, y
+ text_y_offset
, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT
);
448 /* Indent the total cargo capacity details */
449 Rect ir
= r
.Indent(WidgetDimensions::scaled
.hsep_indent
, rtl
);
450 for (const CargoSpec
*cs
: _sorted_cargo_specs
) {
451 CargoID cid
= cs
->Index();
452 if (max_cargo
[cid
] > 0 && --vscroll_pos
< 0 && vscroll_pos
> -vscroll_cap
) {
453 SetDParam(0, cid
); // {CARGO} #1
454 SetDParam(1, act_cargo
[cid
]); // {CARGO} #2
455 SetDParam(2, cid
); // {SHORTCARGO} #1
456 SetDParam(3, max_cargo
[cid
]); // {SHORTCARGO} #2
457 SetDParam(4, _settings_game
.vehicle
.freight_trains
);
458 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
);
462 SetDParam(0, feeder_share
);
463 DrawString(r
.left
, r
.right
, y
+ text_y_offset
, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE
);