diff --git a/src/lang/english.txt b/src/lang/english.txt index 40153acf1b..251423ca25 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2933,6 +2933,7 @@ STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK}Profit t STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK}Reliability: {LTBLUE}{COMMA}% {BLACK}Breakdowns since last service: {LTBLUE}{COMMA} STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK}Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY} +STR_VEHICLE_INFO_NO_CAPACITY :{BLACK}Capacity: {LTBLUE}None STR_VEHICLE_INFO_CAPACITY :{BLACK}Capacity: {LTBLUE}{CARGO}{4:STRING} STR_VEHICLE_INFO_CAPACITY_MULT :{BLACK}Capacity: {LTBLUE}{CARGO}{4:STRING} (x{5:NUM}) STR_VEHICLE_INFO_CAPACITY_CAPACITY :{BLACK}Capacity: {LTBLUE}{CARGO}, {CARGO}{STRING} diff --git a/src/train_gui.cpp b/src/train_gui.cpp index f1c5d93d2c..9efe67d514 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -122,28 +122,52 @@ void DrawTrainImage(const Train *v, int left, int right, int y, VehicleID select _cur_dpi = old_dpi; } +/** Helper struct for the cargo details information */ +struct CargoSummaryItem { + CargoID cargo; ///< The cargo that is carried + StringID subtype; ///< STR_EMPTY if none + uint capacity; ///< Amount that can be carried + uint amount; ///< Amount that is carried + StationID source; ///< One of the source stations + + /** Used by CargoSummary::Find() and similiar functions */ + FORCEINLINE bool operator != (const CargoSummaryItem &other) const + { + return this->cargo != other.cargo || this->subtype != other.subtype; + } +}; + +enum { + TRAIN_DETAILS_MIN_INDENT = 32, ///< Minimum indent level in the train details window + TRAIN_DETAILS_MAX_INDENT = 72, ///< Maximum indent level in the train details window; wider than this and we start on a new line +}; + +/** Container for the cargo summary information. */ +typedef SmallVector CargoSummary; +/** Reused container of cargo details */ +static CargoSummary _cargo_summary; + /** * Draw the details cargo tab for the given vehicle at the given position * - * @param v current vehicle + * @param item Data to draw * @param left The left most coordinate to draw * @param right The right most coordinate to draw * @param y The y coordinate */ -static void TrainDetailsCargoTab(const Vehicle *v, int left, int right, int y) +static void TrainDetailsCargoTab(const CargoSummaryItem *item, int left, int right, int y) { - if (v->cargo_cap != 0) { - StringID str = STR_VEHICLE_DETAILS_CARGO_EMPTY; + StringID str = STR_VEHICLE_DETAILS_CARGO_EMPTY; - if (!v->cargo.Empty()) { - SetDParam(0, v->cargo_type); - SetDParam(1, v->cargo.Count()); - SetDParam(2, v->cargo.Source()); - SetDParam(3, _settings_game.vehicle.freight_trains); - str = FreightWagonMult(v->cargo_type) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT : STR_VEHICLE_DETAILS_CARGO_FROM; - } - DrawString(left, right, y, str); + if (item->amount > 0) { + SetDParam(0, item->cargo); + SetDParam(1, item->amount); + SetDParam(2, item->source); + SetDParam(3, _settings_game.vehicle.freight_trains); + str = FreightWagonMult(item->cargo) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT : STR_VEHICLE_DETAILS_CARGO_FROM; } + + DrawString(left, right, y, str); } /** @@ -171,20 +195,65 @@ static void TrainDetailsInfoTab(const Vehicle *v, int left, int right, int y) /** * Draw the details capacity tab for the given vehicle at the given position * - * @param v current vehicle + * @param item Data to draw * @param left The left most coordinate to draw * @param right The right most coordinate to draw * @param y The y coordinate */ -static void TrainDetailsCapacityTab(const Vehicle *v, int left, int right, int y) +static void TrainDetailsCapacityTab(const CargoSummaryItem *item, int left, int right, int y) { - if (v->cargo_cap != 0) { - SetDParam(0, v->cargo_type); - SetDParam(1, v->cargo_cap); - SetDParam(4, GetCargoSubtypeText(v)); - SetDParam(5, _settings_game.vehicle.freight_trains); - DrawString(left, right, y, FreightWagonMult(v->cargo_type) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT : STR_VEHICLE_INFO_CAPACITY); - } + SetDParam(0, item->cargo); + SetDParam(1, item->capacity); + SetDParam(4, item->subtype); + SetDParam(5, _settings_game.vehicle.freight_trains); + DrawString(left, right, y, FreightWagonMult(item->cargo) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT : STR_VEHICLE_INFO_CAPACITY); +} + +/** + * Collects the cargo transportet + * @param v Vehicle to process + * @param summary Space for the result + */ +static void GetCargoSummaryOfArticulatedVehicle(const Train *v, CargoSummary *summary) +{ + summary->Clear(); + do { + if (v->cargo_cap == 0) continue; + + CargoSummaryItem new_item; + new_item.cargo = v->cargo_type; + new_item.subtype = GetCargoSubtypeText(v); + + CargoSummaryItem *item = summary->Find(new_item); + if (item == summary->End()) { + item = summary->Append(); + item->cargo = new_item.cargo; + item->subtype = new_item.subtype; + item->capacity = 0; + item->amount = 0; + item->source = INVALID_STATION; + } + + item->capacity += v->cargo_cap; + item->amount += v->cargo.Count(); + if (item->source == INVALID_STATION) item->source = v->cargo.Source(); + } while ((v = v->Next()) != NULL && v->IsArticulatedPart()); +} + +/** + * Get the length of an articulated vehicle. + * @param v the vehicle to get the length of. + * @return the length in pixels. + */ +static uint GetLengthOfArticulatedVehicle(const Train *v) +{ + uint length = 0; + + do { + length += v->GetDisplayImageWidth(); + } while ((v = v->Next()) != NULL && v->IsArticulatedPart()); + + return length; } /** @@ -213,8 +282,12 @@ int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab) } num++; // needs one more because first line is description string } else { - for (const Train *v = Train::Get(veh_id); v != NULL; v = v->Next()) { - if (!v->IsArticulatedPart() || v->cargo_cap != 0) num++; + for (const Train *v = Train::Get(veh_id); v != NULL; v = v->GetNextVehicle()) { + GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary); + num += max(1u, _cargo_summary.Length()); + + uint length = GetLengthOfArticulatedVehicle(v); + if (length > TRAIN_DETAILS_MAX_INDENT) num++; } } @@ -238,53 +311,70 @@ void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_po if (det_tab != TDW_TAB_TOTALS) { bool rtl = _dynlang.text_dir == TD_RTL; Direction dir = rtl ? DIR_E : DIR_W; - const Train *u = v; int x = rtl ? right : left; int sprite_y_offset = 4 + (FONT_HEIGHT_NORMAL - 10) / 2; - for (;;) { - if (--vscroll_pos < 0 && vscroll_pos >= -vscroll_cap) { - int px = x; + int line_height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; + for (; v != NULL && vscroll_pos > -vscroll_cap; v = v->GetNextVehicle()) { + GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary); - u = v; - do { - Point offset; - int width = u->GetDisplayImageWidth(&offset); - SpriteID pal = (u->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(u); - DrawSprite(u->GetImage(dir), pal, px + (rtl ? -offset.x : offset.x), y + sprite_y_offset + offset.y); - px += rtl ? -width : width; - u = u->Next(); - } while (u != NULL && u->IsArticulatedPart() && u->cargo_cap == 0); - - px += rtl ? -2 : 2; - int py = y; - switch (det_tab) { - default: NOT_REACHED(); - - case TDW_TAB_CARGO: - TrainDetailsCargoTab(v, rtl ? left : px, rtl ? px : right, py); - break; - - case TDW_TAB_INFO: - /* Only show name and value for the 'real' part */ - if (!v->IsArticulatedPart()) { - TrainDetailsInfoTab(v, rtl ? left : px, rtl ? px : right, py); - } - break; - - case TDW_TAB_CAPACITY: - TrainDetailsCapacityTab(v, rtl ? left : px, rtl ? px : right, py); - break; + /* Draw sprites */ + int dx = 0; + int px = x; + const Train *u = v; + do { + Point offset; + int width = u->GetDisplayImageWidth(&offset); + if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) { + SpriteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v); + DrawSprite(u->GetImage(dir), pal, px + (rtl ? -offset.x : offset.x), y - line_height * vscroll_pos + sprite_y_offset + offset.y); } - y += WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; + px += rtl ? -width : width; + dx += width; + u = u->Next(); + } while (u != NULL && u->IsArticulatedPart()); - v = u; - } else { - /* Move to the next line */ - do { - v = v->Next(); - } while (v != NULL && v->IsArticulatedPart() && v->cargo_cap == 0); + bool separate_sprite_row = (dx > TRAIN_DETAILS_MAX_INDENT); + if (separate_sprite_row) { + vscroll_pos--; + dx = 0; + } + + uint num_lines = max(1u, _cargo_summary.Length()); + for (uint i = 0; i < num_lines; i++) { + int sprite_width = max(dx, TRAIN_DETAILS_MIN_INDENT) + 3; + int data_left = left + (rtl ? 0 : sprite_width); + int data_right = right - (rtl ? sprite_width : 0); + if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) { + int py = y - line_height * vscroll_pos; + if (i > 0 || separate_sprite_row) { + if (vscroll_pos != 0) GfxFillRect(left, py - WD_MATRIX_TOP - 1, right, py - WD_MATRIX_TOP, _colour_gradient[COLOUR_GREY][5]); + } + switch (det_tab) { + case TDW_TAB_CARGO: + if (i < _cargo_summary.Length()) { + TrainDetailsCargoTab(&_cargo_summary[i], data_left, data_right, py); + } else { + DrawString(data_left, data_right, py, STR_QUANTITY_N_A, TC_LIGHT_BLUE); + } + break; + + case TDW_TAB_INFO: + if (i == 0) TrainDetailsInfoTab(v, data_left, data_right, py); + break; + + case TDW_TAB_CAPACITY: + if (i < _cargo_summary.Length()) { + TrainDetailsCapacityTab(&_cargo_summary[i], data_left, data_right, py); + } else { + DrawString(data_left, data_right, py, STR_VEHICLE_INFO_NO_CAPACITY); + } + break; + + default: NOT_REACHED(); + } + } + vscroll_pos--; } - if (v == NULL) return; } } else { CargoArray act_cargo;