diff --git a/src/lang/english.txt b/src/lang/english.txt index d21907da7d..8c0e8ead06 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1464,6 +1464,12 @@ STR_CONFIG_SETTING_VEHICLE_NAMES_LONG :Long STR_CONFIG_SETTING_SHADED_TREES_ON_SLOPES :Shade trees on slopes: {STRING2} STR_CONFIG_SETTING_SHADED_TREES_ON_SLOPES_HELPTEXT :Change brightness of trees drawn on slopes. Improves the look of tree cover in mountainous areas. +STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE :Station rating tooltips: {STRING2} +STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE_HELPTEXT :Set whether station rating tooltips are shown and the level of information detail. +STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE_OFF :Off +STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE_SIMPLE :Simple +STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE_DETAILED :Detailed + STR_CONFIG_SETTING_ADV_SIG_BRIDGE_TUN_MODES :Enable signals on bridges/tunnels advanced modes: {STRING2} STR_CONFIG_SETTING_ADV_SIG_BRIDGE_TUN_MODES_HELPTEXT :Enables use of advanced modes of signal simulation on bridges and tunnels. When disabled, bridges/tunnels which are not already in an advanced mode cannot be changed to an advanced mode, however other players may choose to enable this setting and use an advanced mode. @@ -6521,6 +6527,61 @@ STR_ZONING_2x2_GRID :2x2 town road g STR_ZONING_3x3_GRID :3x3 town road grid STR_ZONING_ONE_WAY_ROAD :One way roads +STR_STATION_RATING_TOOLTIP_RATING_DETAILS :{STRING} Rating Details +STR_STATION_RATING_TOOLTIP_TOTAL_RATING :Total target rating: {LTBLUE}{NUM}% + +STR_STATION_RATING_TOOLTIP_USING_CHEAT :Cheat active + +STR_STATION_RATING_TOOLTIP_NEWGRF_RATING :NewGRF station rating: {STRING1} {BLACK}based on +STR_STATION_RATING_TOOLTIP_NEWGRF_RATING_0 :{GOLD}{PLUS_NUM}% +STR_STATION_RATING_TOOLTIP_NEWGRF_RATING_1 :{LTBLUE}{PLUS_NUM}% +STR_STATION_RATING_TOOLTIP_NEWGRF_SPEED :Max speed of last vehicle: {LTBLUE}{VELOCITY} ({STRING2}) +STR_STATION_RATING_TOOLTIP_NEWGRF_WAITUNITS :Cargo waiting (on average per next stop): {LTBLUE}{NUM} +STR_STATION_RATING_TOOLTIP_NEWGRF_WAITTIME :Time since last pickup: {LTBLUE}{NUM} day{P "" s} + +STR_STATION_RATING_MAX_PERCENTAGE : (max {PLUS_NUM}%) +STR_STATION_RATING_MAX_PERCENTAGE_COMMA :, max {PLUS_NUM}% +STR_STATION_RATING_PERCENTAGE_COMMA :, {PLUS_NUM}% + +STR_STATION_RATING_TOOLTIP_SPEED :Max speed of last vehicle{STRING1}: {STRING3} ({STRING}) +STR_STATION_RATING_TOOLTIP_SPEED_ZERO :{RED}{VELOCITY}{STRING1} +STR_STATION_RATING_TOOLTIP_SPEED_0 :{ORANGE}{VELOCITY}{STRING1} +STR_STATION_RATING_TOOLTIP_SPEED_1 :{GOLD}{VELOCITY}{STRING1} +STR_STATION_RATING_TOOLTIP_SPEED_2 :{YELLOW}{VELOCITY}{STRING1} +STR_STATION_RATING_TOOLTIP_SPEED_3 :{GREEN}{VELOCITY}{STRING1} + +STR_STATION_RATING_TOOLTIP_AGE :Age of last vehicle{STRING1}: {STRING3} +STR_STATION_RATING_TOOLTIP_AGE_0 :{ORANGE}{NUM} year{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_AGE_1 :{GOLD}{NUM} year{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_AGE_2 :{YELLOW}{NUM} year{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_AGE_3 :{GREEN}{NUM} year{P "" s}{STRING1} + +STR_STATION_RATING_TOOLTIP_WAITTIME :Time since last pickup{STRING1}: {STRING3} +STR_STATION_RATING_TOOLTIP_WAITTIME_SHIP :Time since last pickup{STRING1}: {STRING3} (by ship) +STR_STATION_RATING_TOOLTIP_WAITTIME_0 :{RED}{NUM} day{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITTIME_1 :{ORANGE}{NUM} day{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITTIME_2 :{GOLD}{NUM} day{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITTIME_3 :{YELLOW}{NUM} day{P "" s}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITTIME_4 :{GREEN}{NUM} day{P "" s}{STRING1} + +STR_STATION_RATING_TOOLTIP_WAITUNITS :Cargo waiting (on average per next stop{STRING1}): {STRING3} +STR_STATION_RATING_TOOLTIP_WAITUNITS_0 :{RED}{NUM}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITUNITS_1 :{ORANGE}{NUM}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITUNITS_2 :{GOLD}{NUM}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITUNITS_3 :{YELLOW}{NUM}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITUNITS_4 :{YELLOW}{NUM}{STRING1} +STR_STATION_RATING_TOOLTIP_WAITUNITS_5 :{GREEN}{NUM}{STRING1} + +STR_STATION_RATING_TOOLTIP_STATUE :Statue in town{STRING1}: {STRING2} +STR_STATION_RATING_TOOLTIP_STATUE_NO :{GOLD}no{STRING1} +STR_STATION_RATING_TOOLTIP_STATUE_YES :{GREEN}yes{STRING1} + +STR_STATION_RATING_TOOLTIP_TRAIN :Train +STR_STATION_RATING_TOOLTIP_ROAD_VEHICLE :Road Vehicle +STR_STATION_RATING_TOOLTIP_SHIP :Ship +STR_STATION_RATING_TOOLTIP_AIRCRAFT :Aircraft +STR_STATION_RATING_TOOLTIP_INVALID :N/A + STR_TMPL_RPL_TITLE :{WHITE}Template Replacement STR_TMPL_TEMPLATE_REPLACEMENT :Template Replacement STR_TMPL_TRAINS_IN_GROUP :{BLACK}Trains in group diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 7dc7f9ec70..49d67da2cc 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1839,6 +1839,7 @@ static SettingsContainer &GetSettingsTree() interface->Add(new SettingEntry("gui.show_depot_sell_gui")); interface->Add(new SettingEntry("gui.open_vehicle_gui_clone_share")); interface->Add(new SettingEntry("gui.vehicle_names")); + interface->Add(new SettingEntry("gui.station_rating_tooltip_mode")); } SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS)); diff --git a/src/settings_type.h b/src/settings_type.h index e38788d014..68b9001265 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -220,6 +220,7 @@ struct GUISettings : public TimeSettings { uint8 linkgraph_colours; ///< linkgraph overlay colours uint8 vehicle_names; ///< Vehicle naming scheme bool shade_trees_on_slopes; ///< Shade trees on slopes + uint8 station_rating_tooltip_mode; ///< Station rating tooltip mode uint16 console_backlog_timeout; ///< the minimum amount of time items should be in the console backlog before they will be removed in ~3 seconds granularity. uint16 console_backlog_length; ///< the minimum amount of items in the console backlog before items will be removed. diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 1f23d69e92..966312d04c 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -3767,6 +3767,136 @@ static void TruncateCargo(const CargoSpec *cs, GoodsEntry *ge, uint amount = UIN } } +bool GetNewGrfRating(const Station *st, const CargoSpec *cs, const GoodsEntry *ge, int *new_grf_rating) +{ + *new_grf_rating = 0; + bool is_using_newgrf_rating = false; + + /* Perform custom station rating. If it succeeds the speed, days in transit and + * waiting cargo ratings must not be executed. */ + + /* NewGRFs expect last speed to be 0xFF when no vehicle has arrived yet. */ + uint last_speed = ge->HasVehicleEverTriedLoading() && ge->IsSupplyAllowed() ? ge->last_speed : 0xFF; + + uint32 var18 = std::min(ge->time_since_pickup, 0xFFu) + | (std::min(ge->max_waiting_cargo, 0xFFFFu) << 8) + | (std::min(last_speed, 0xFFu) << 24); + /* Convert to the 'old' vehicle types */ + uint32 var10 = (ge->last_vehicle_type == VEH_INVALID) ? 0x0 : (ge->last_vehicle_type + 0x10); + uint16 callback = GetCargoCallback(CBID_CARGO_STATION_RATING_CALC, var10, var18, cs); + if (callback != CALLBACK_FAILED) { + is_using_newgrf_rating = true; + *new_grf_rating = GB(callback, 0, 14); + + /* Simulate a 15 bit signed value */ + if (HasBit(callback, 14)) *new_grf_rating -= 0x4000; + } + + return is_using_newgrf_rating; +} + +int GetSpeedRating(const GoodsEntry *ge) +{ + const int b = ge->last_speed - 85; + + return (b >= 0) ? (b >> 2) : 0; +} + +int GetWaitTimeRating(const CargoSpec *cs, const GoodsEntry *ge) +{ + int rating = 0; + + uint wait_time = ge->time_since_pickup; + + if (_settings_game.station.cargo_class_rating_wait_time) { + if (cs->classes & CC_PASSENGERS) { + wait_time *= 3; + } else if (cs->classes & CC_REFRIGERATED) { + wait_time *= 2; + } else if (cs->classes & (CC_MAIL | CC_ARMOURED | CC_EXPRESS)) { + wait_time += (wait_time >> 1); + } else if (cs->classes & (CC_BULK | CC_LIQUID)) { + wait_time >>= 2; + } + } + + if (ge->last_vehicle_type == VEH_SHIP) wait_time >>= 2; + if (wait_time <= 21) rating += 25; + if (wait_time <= 12) rating += 25; + if (wait_time <= 6) rating += 45; + if (wait_time <= 3) rating += 35; + + return rating; +} + +int GetWaitingCargoRating(const Station *st, const GoodsEntry *ge) +{ + int rating = -90; + + uint normalised_max_waiting_cargo = ge->max_waiting_cargo; + + if (_settings_game.station.station_size_rating_cargo_amount) { + normalised_max_waiting_cargo *= 8; + if (st->station_tiles > 1) normalised_max_waiting_cargo /= st->station_tiles; + } + + if (normalised_max_waiting_cargo <= 1500) rating += 55; + if (normalised_max_waiting_cargo <= 1000) rating += 35; + if (normalised_max_waiting_cargo <= 600) rating += 10; + if (normalised_max_waiting_cargo <= 300) rating += 20; + if (normalised_max_waiting_cargo <= 100) rating += 10; + + return rating; +} + +int GetStatueRating(const Station *st) +{ + return Company::IsValidID(st->owner) && HasBit(st->town->statues, st->owner) ? 26 : 0; +} + +int GetVehicleAgeRating(const GoodsEntry *ge) +{ + int rating = 0; + + const byte age = ge->last_age; + + if (age < 30) rating += 10; + if (age < 20) rating += 10; + if (age < 10) rating += 13; + + return rating; +} + +int GetTargetRating(const Station *st, const CargoSpec *cs, const GoodsEntry *ge) +{ + bool skip = false; + int rating = 0; + + if (_extra_cheats.station_rating.value) { + rating = 255; + skip = true; + } else if (HasBit(cs->callback_mask, CBM_CARGO_STATION_RATING_CALC)) { + + int new_grf_rating; + + if (GetNewGrfRating(st, cs, ge, &new_grf_rating)) { + skip = true; + rating += new_grf_rating; + } + } + + if (!skip) { + rating += GetSpeedRating(ge); + rating += GetWaitTimeRating(cs, ge); + rating += GetWaitingCargoRating(st, ge); + } + + rating += GetStatueRating(st); + rating += GetVehicleAgeRating(ge); + + return Clamp(rating, 0, 255); +} + static void UpdateStationRating(Station *st) { bool waiting_changed = false; @@ -3776,6 +3906,7 @@ static void UpdateStationRating(Station *st) for (const CargoSpec *cs : CargoSpec::Iterate()) { GoodsEntry *ge = &st->goods[cs->Index()]; + /* Slowly increase the rating back to his original level in the case we * didn't deliver cargo yet to this station. This happens when a bribe * failed while you didn't moved that cargo yet to a station. */ @@ -3786,6 +3917,7 @@ static void UpdateStationRating(Station *st) /* Only change the rating if we are moving this cargo */ if (ge->HasRating()) { byte_inc_sat(&ge->time_since_pickup); + if (ge->time_since_pickup == 255 && _settings_game.order.selectgoods) { ClrBit(ge->status, GoodsEntry::GES_RATING); ge->last_speed = 0; @@ -3794,95 +3926,28 @@ static void UpdateStationRating(Station *st) continue; } - bool skip = false; - int rating = 0; - uint waiting = ge->cargo.AvailableCount(); - - /* num_dests is at least 1 if there is any cargo as - * INVALID_STATION is also a destination. - */ - uint num_dests = (uint)ge->cargo.Packets()->MapSize(); - - /* Average amount of cargo per next hop, but prefer solitary stations - * with only one or two next hops. They are allowed to have more - * cargo waiting per next hop. - * With manual cargo distribution waiting_avg = waiting / 2 as then - * INVALID_STATION is the only destination. - */ - uint waiting_avg = waiting / (num_dests + 1); - - if (_extra_cheats.station_rating.value) { - ge->rating = rating = 255; - skip = true; - } else if (HasBit(cs->callback_mask, CBM_CARGO_STATION_RATING_CALC)) { - /* Perform custom station rating. If it succeeds the speed, days in transit and - * waiting cargo ratings must not be executed. */ - - /* NewGRFs expect last speed to be 0xFF when no vehicle has arrived yet. */ - uint last_speed = ge->HasVehicleEverTriedLoading() && ge->IsSupplyAllowed() ? ge->last_speed : 0xFF; - - uint32 var18 = std::min(ge->time_since_pickup, 0xFFu) - | (std::min(ge->max_waiting_cargo, 0xFFFFu) << 8) - | (std::min(last_speed, 0xFFu) << 24); - /* Convert to the 'old' vehicle types */ - uint32 var10 = (ge->last_vehicle_type == VEH_INVALID) ? 0x0 : (ge->last_vehicle_type + 0x10); - uint16 callback = GetCargoCallback(CBID_CARGO_STATION_RATING_CALC, var10, var18, cs); - if (callback != CALLBACK_FAILED) { - skip = true; - rating = GB(callback, 0, 14); - - /* Simulate a 15 bit signed value */ - if (HasBit(callback, 14)) rating -= 0x4000; - } - } - - if (!skip) { - int b = ge->last_speed - 85; - if (b >= 0) rating += b >> 2; - - uint waittime = ge->time_since_pickup; - if (_settings_game.station.cargo_class_rating_wait_time) { - if (cs->classes & CC_PASSENGERS) { - waittime *= 3; - } else if (cs->classes & CC_REFRIGERATED) { - waittime *= 2; - } else if (cs->classes & (CC_MAIL | CC_ARMOURED | CC_EXPRESS)) { - waittime += (waittime >> 1); - } else if (cs->classes & (CC_BULK | CC_LIQUID)) { - waittime >>= 2; - } - } - if (ge->last_vehicle_type == VEH_SHIP) waittime >>= 2; - if (waittime <= 21) rating += 25; - if (waittime <= 12) rating += 25; - if (waittime <= 6) rating += 45; - if (waittime <= 3) rating += 35; - - rating -= 90; - uint normalised_max_waiting_cargo = ge->max_waiting_cargo; - if (_settings_game.station.station_size_rating_cargo_amount) { - normalised_max_waiting_cargo *= 8; - if (st->station_tiles > 1) normalised_max_waiting_cargo /= st->station_tiles; - } - if (normalised_max_waiting_cargo <= 1500) rating += 55; - if (normalised_max_waiting_cargo <= 1000) rating += 35; - if (normalised_max_waiting_cargo <= 600) rating += 10; - if (normalised_max_waiting_cargo <= 300) rating += 20; - if (normalised_max_waiting_cargo <= 100) rating += 10; - } - - if (Company::IsValidID(st->owner) && HasBit(st->town->statues, st->owner)) rating += 26; - - byte age = ge->last_age; - if (age < 3) rating += 10; - if (age < 2) rating += 10; - if (age < 1) rating += 13; - { - int or_ = ge->rating; // old rating + int rating = GetTargetRating(st, cs, ge); + + uint waiting = ge->cargo.AvailableCount(); + + /* num_dests is at least 1 if there is any cargo as + * INVALID_STATION is also a destination. + */ + const uint num_dests = (uint)ge->cargo.Packets()->MapSize(); + + /* Average amount of cargo per next hop, but prefer solitary stations + * with only one or two next hops. They are allowed to have more + * cargo waiting per next hop. + * With manual cargo distribution waiting_avg = waiting / 2 as then + * INVALID_STATION is the only destination. + */ + const uint waiting_avg = waiting / (num_dests + 1); + + const int old_rating = ge->rating; // old rating /* only modify rating in steps of -2, -1, 0, 1 or 2 */ - ge->rating = rating = or_ + Clamp(Clamp(rating, 0, 255) - or_, -2, 2); + ge->rating = rating = old_rating + Clamp(rating - old_rating, -2, 2); /* if rating is <= 64 and more than 100 items waiting on average per destination, * remove some random amount of goods from the station */ @@ -3941,6 +4006,7 @@ static void UpdateStationRating(Station *st) } StationID index = st->index; + if (waiting_changed) { SetWindowDirty(WC_STATION_VIEW, index); // update whole window } else { diff --git a/src/station_gui.cpp b/src/station_gui.cpp index ba7f238303..cf2ff5d1f9 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -42,7 +42,17 @@ #include #include +#include "cheat_func.h" +#include "newgrf_callbacks.h" +#include "newgrf_cargo.h" #include "safeguards.h" +#include "widgets/misc_widget.h" + +enum StationRatingTooltipMode { + SRTM_OFF, + SRTM_SIMPLE, + SRTM_DETAILED, +}; /** * Calculates and draws the accepted or supplied cargo around the selected tile(s) @@ -1313,6 +1323,7 @@ struct StationViewWindow : public Window { int scroll_to_row; ///< If set, scroll the main viewport to the station pointed to by this row. int grouping_index; ///< Currently selected entry in the grouping drop down. + int ratings_list_y = 0; ///< Y coordinate of first line in station ratings panel. Mode current_mode; ///< Currently selected display mode of cargo view. Grouping groupings[NUM_COLUMNS]; ///< Grouping modes for the different columns. @@ -1435,6 +1446,31 @@ struct StationViewWindow : public Window { } } + bool OnTooltip(Point pt, int widget, TooltipCloseCondition close_cond) override + { + if (widget != WID_SV_ACCEPT_RATING_LIST || this->GetWidget(WID_SV_ACCEPTS_RATINGS)->widget_data == STR_STATION_VIEW_RATINGS_BUTTON || + _settings_client.gui.station_rating_tooltip_mode == SRTM_OFF) { + return false; + } + + int ofs_y = pt.y - this->ratings_list_y; + if (ofs_y < 0) return false; + + const Station *st = Station::Get(this->window_number); + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + const GoodsEntry *ge = &st->goods[cs->Index()]; + if (!ge->HasRating()) continue; + ofs_y -= FONT_HEIGHT_NORMAL; + if (ofs_y < 0) { + GuiShowStationRatingTooltip(this, st, cs); + break; + } + } + + return true; + } + void OnPaint() override { const Station *st = Station::Get(this->window_number); @@ -1869,7 +1905,7 @@ struct StationViewWindow : public Window { * @param r Rectangle of the widget. * @return Number of lines needed for drawing the cargo ratings. */ - int DrawCargoRatings(const Rect &r) const + int DrawCargoRatings(const Rect &r) { const Station *st = Station::Get(this->window_number); int y = r.top + WD_FRAMERECT_TOP; @@ -1883,6 +1919,8 @@ struct StationViewWindow : public Window { DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_SUPPLY_RATINGS_TITLE); y += FONT_HEIGHT_NORMAL; + this->ratings_list_y = y; + const CargoSpec *cs; FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { const GoodsEntry *ge = &st->goods[cs->Index()]; @@ -2538,3 +2576,373 @@ void ShowSelectWaypointIfNeeded(const CommandContainer &cmd, TileArea ta) { ShowSelectBaseStationIfNeeded(cmd, ta); } + +static const NWidgetPart _nested_station_rating_tooltip_widgets[] = { + NWidget(WWT_PANEL, COLOUR_GREY, WID_TT_BACKGROUND), SetMinimalSize(64, 32), EndContainer(), +}; + +static WindowDesc _station_rating_tooltip_desc( + WDP_MANUAL, nullptr, 0, 0, + WC_STATION_RATING_TOOLTIP, WC_NONE, + 0, + _nested_station_rating_tooltip_widgets, lengthof(_nested_station_rating_tooltip_widgets) + ); + +bool GetNewGrfRating(const Station *st, const CargoSpec *cs, const GoodsEntry *ge, int *new_grf_rating); +int GetSpeedRating(const GoodsEntry *ge); +int GetWaitTimeRating(const CargoSpec *cs, const GoodsEntry *ge); +int GetWaitingCargoRating(const Station *st, const GoodsEntry *ge); +int GetStatueRating(const Station *st); +int GetVehicleAgeRating(const GoodsEntry *ge); + +struct StationRatingTooltipWindow : public Window +{ +private: + const Station *st; + const CargoSpec *cs; + bool newgrf_rating_used; + + static const uint RATING_TOOLTIP_LINE_BUFF_SIZE = 512; + static const uint RATING_TOOLTIP_MAX_LINES = 9; + static const uint RATING_TOOLTIP_NEWGRF_INDENT = 20; + +public: + char data[RATING_TOOLTIP_MAX_LINES + 1][RATING_TOOLTIP_LINE_BUFF_SIZE] {}; + + StationRatingTooltipWindow(Window *parent, const Station *st, const CargoSpec *cs) : Window(&_station_rating_tooltip_desc) + { + this->parent = parent; + this->st = st; + this->cs = cs; + this->newgrf_rating_used = false; + this->InitNested(); + CLRBITS(this->flags, WF_WHITE_BORDER); + } + + Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number) override + { + const int scr_top = GetMainViewTop() + 2; + const int scr_bot = GetMainViewBottom() - 2; + + Point pt {}; + pt.y = Clamp(_cursor.pos.y + _cursor.total_size.y + _cursor.total_offs.y + 5, scr_top, scr_bot); + if (pt.y + sm_height > scr_bot) pt.y = std::min(_cursor.pos.y + _cursor.total_offs.y - 5, scr_bot) - sm_height; + pt.x = sm_width >= _screen.width ? 0 : Clamp(_cursor.pos.x - (sm_width >> 1), 0, _screen.width - sm_width); + + return pt; + } + + static int RoundRating(const int rating) { + return RoundDivSU(rating * 101, 256); + } + + void OnInit() override + { + const GoodsEntry *ge = &this->st->goods[this->cs->Index()]; + + SetDParam(0, this->cs->name); + GetString(this->data[0], STR_STATION_RATING_TOOLTIP_RATING_DETAILS, lastof(this->data[0])); + + if (!ge->HasRating()) { + this->data[1][0] = '\0'; + return; + } + + uint line_nr = 1; + + // Calculate target rating. + bool skip = false; + int total_rating = 0; + + const bool detailed = _settings_client.gui.station_rating_tooltip_mode == SRTM_DETAILED; + + if (_extra_cheats.station_rating.value) { + total_rating = 255; + skip = true; + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_USING_CHEAT, lastof(this->data[line_nr])); + line_nr++; + } else if (HasBit(cs->callback_mask, CBM_CARGO_STATION_RATING_CALC)) { + + int new_grf_rating; + this->newgrf_rating_used = GetNewGrfRating(st, cs, ge, &new_grf_rating); + + if (this->newgrf_rating_used) { + skip = true; + total_rating += new_grf_rating; + new_grf_rating = RoundRating(new_grf_rating); + + SetDParam(0, STR_STATION_RATING_TOOLTIP_NEWGRF_RATING_0 + (new_grf_rating <= 0 ? 0 : 1)); + SetDParam(1, new_grf_rating); + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_NEWGRF_RATING, lastof(this->data[line_nr])); + line_nr++; + + const uint last_speed = ge->HasVehicleEverTriedLoading() && ge->IsSupplyAllowed() ? ge->last_speed : 0xFF; + SetDParam(0, std::min(last_speed, 0xFFu)); + + switch (ge->last_vehicle_type) + { + case VEH_TRAIN: + SetDParam(1, STR_STATION_RATING_TOOLTIP_TRAIN); + break; + case VEH_ROAD: + SetDParam(1, STR_STATION_RATING_TOOLTIP_ROAD_VEHICLE); + break; + case VEH_SHIP: + SetDParam(1, STR_STATION_RATING_TOOLTIP_SHIP); + break; + case VEH_AIRCRAFT: + SetDParam(1, STR_STATION_RATING_TOOLTIP_AIRCRAFT); + break; + default: + SetDParam(1, STR_STATION_RATING_TOOLTIP_INVALID); + break; + } + + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_NEWGRF_SPEED, lastof(this->data[line_nr])); + line_nr++; + + SetDParam(0, std::min(ge->max_waiting_cargo, 0xFFFFu)); + GetString(this->data[line_nr], + STR_STATION_RATING_TOOLTIP_NEWGRF_WAITUNITS, + lastof(this->data[line_nr])); + line_nr++; + + SetDParam(0, ge->time_since_pickup * STATION_RATING_TICKS / (DAY_TICKS * _settings_game.economy.day_length_factor)); + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_NEWGRF_WAITTIME, lastof(this->data[line_nr])); + line_nr++; + } + } + + if (!skip) { + // Speed + { + const auto speed_rating = GetSpeedRating(ge); + const auto rounded_speed_rating = RoundRating(speed_rating); + + SetDParam(0, detailed ? STR_STATION_RATING_MAX_PERCENTAGE : STR_EMPTY); + SetDParam(1, 17); + + if (ge->last_speed == 255) { + SetDParam(2, STR_STATION_RATING_TOOLTIP_SPEED_3); + } + else if (rounded_speed_rating == 0) { + SetDParam(2, STR_STATION_RATING_TOOLTIP_SPEED_ZERO); + } + else { + SetDParam(2, STR_STATION_RATING_TOOLTIP_SPEED_0 + std::min(3, speed_rating / 42)); + } + + SetDParam(3, ge->last_speed); + SetDParam(4, detailed ? STR_STATION_RATING_PERCENTAGE_COMMA : STR_EMPTY); + SetDParam(5, rounded_speed_rating); + + switch (ge->last_vehicle_type) + { + case VEH_TRAIN: + SetDParam(6, STR_STATION_RATING_TOOLTIP_TRAIN); + break; + case VEH_ROAD: + SetDParam(6, STR_STATION_RATING_TOOLTIP_ROAD_VEHICLE); + break; + case VEH_SHIP: + SetDParam(6, STR_STATION_RATING_TOOLTIP_SHIP); + break; + case VEH_AIRCRAFT: + SetDParam(6, STR_STATION_RATING_TOOLTIP_AIRCRAFT); + break; + default: + SetDParam(6, STR_STATION_RATING_TOOLTIP_INVALID); + break; + } + + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_SPEED, lastof(this->data[line_nr])); + line_nr++; + + total_rating += speed_rating; + } + + // Wait time + { + const auto wait_time_rating = GetWaitTimeRating(cs, ge); + + int wait_time_stage = 0; + + if (wait_time_rating >= 130) { + wait_time_stage = 4; + } else if (wait_time_rating >= 95) { + wait_time_stage = 3; + } else if (wait_time_rating >= 50) { + wait_time_stage = 2; + } else if (wait_time_rating >= 25) { + wait_time_stage = 1; + } + + SetDParam(0, detailed ? STR_STATION_RATING_MAX_PERCENTAGE : STR_EMPTY); + SetDParam(1, 51); + SetDParam(2, STR_STATION_RATING_TOOLTIP_WAITTIME_0 + wait_time_stage); + SetDParam(3, ge->max_waiting_cargo); + SetDParam(4, detailed ? STR_STATION_RATING_PERCENTAGE_COMMA : STR_EMPTY); + SetDParam(5, RoundRating(wait_time_rating)); + GetString(this->data[line_nr], + (ge->last_vehicle_type == VEH_SHIP) ? + STR_STATION_RATING_TOOLTIP_WAITTIME_SHIP : + STR_STATION_RATING_TOOLTIP_WAITTIME, + lastof(this->data[line_nr])); + line_nr++; + + total_rating += wait_time_rating; + } + + // Waiting cargo + { + const auto cargo_rating = GetWaitingCargoRating(st, ge); + + int wait_units_stage = 0; + + if (cargo_rating >= 40) { + wait_units_stage = 5; + } else if (cargo_rating >= 30) { + wait_units_stage = 4; + } else if (cargo_rating >= 10) { + wait_units_stage = 3; + } else if (cargo_rating >= 0) { + wait_units_stage = 2; + } else if (cargo_rating >= -35) { + wait_units_stage = 1; + } + + SetDParam(0, detailed ? STR_STATION_RATING_MAX_PERCENTAGE_COMMA : STR_EMPTY); + SetDParam(1, 16); + SetDParam(2, STR_STATION_RATING_TOOLTIP_WAITUNITS_0 + wait_units_stage); + SetDParam(3, ge->max_waiting_cargo); + SetDParam(4, detailed ? STR_STATION_RATING_PERCENTAGE_COMMA : STR_EMPTY); + SetDParam(5, RoundRating(cargo_rating)); + GetString(this->data[line_nr], + STR_STATION_RATING_TOOLTIP_WAITUNITS, + lastof(this->data[line_nr])); + line_nr++; + + total_rating += cargo_rating; + } + } + + if (!_extra_cheats.station_rating.value) { + // Statue + const auto statue_rating = GetStatueRating(st); + if (statue_rating > 0 || detailed) { + SetDParam(0, detailed ? STR_STATION_RATING_MAX_PERCENTAGE : STR_EMPTY); + SetDParam(1, 10); + SetDParam(2, (statue_rating > 0) ? STR_STATION_RATING_TOOLTIP_STATUE_YES : STR_STATION_RATING_TOOLTIP_STATUE_NO); + SetDParam(3, detailed ? STR_STATION_RATING_PERCENTAGE_COMMA : STR_EMPTY); + SetDParam(4, (statue_rating > 0) ? 10 : 0); + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_STATUE, lastof(this->data[line_nr])); + line_nr++; + + total_rating += statue_rating; + } + + // Vehicle age + { + const auto age_rating = GetVehicleAgeRating(ge); + + int age_stage = 0; + + if (age_rating >= 33) { + age_stage = 3; + } else if (age_rating >= 20) { + age_stage = 2; + } else if (age_rating >= 10) { + age_stage = 1; + } + + SetDParam(0, detailed ? STR_STATION_RATING_MAX_PERCENTAGE : STR_EMPTY); + SetDParam(1, 13); + SetDParam(2, STR_STATION_RATING_TOOLTIP_AGE_0 + age_stage); + SetDParam(3, ge->last_age); + SetDParam(4, detailed ? STR_STATION_RATING_PERCENTAGE_COMMA : STR_EMPTY); + SetDParam(5, RoundRating(age_rating)); + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_AGE, lastof(this->data[line_nr])); + line_nr++; + + total_rating += age_rating; + } + } + + total_rating = Clamp(total_rating, 0, 255); + + if (detailed) { + SetDParam(0, ToPercent8(total_rating)); + GetString(this->data[line_nr], STR_STATION_RATING_TOOLTIP_TOTAL_RATING, lastof(this->data[line_nr])); + line_nr++; + } + + this->data[line_nr][0] = '\0'; + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget != 0) return; + + size->height = WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + 2; + + for (uint i = 0; i <= RATING_TOOLTIP_MAX_LINES; i++) { + if (StrEmpty(this->data[i])) break; + + uint width = GetStringBoundingBox(this->data[i]).width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT + 2; + if (this->newgrf_rating_used && i >= 2 && i <= 4) { + width += RATING_TOOLTIP_NEWGRF_INDENT; + } + size->width = std::max(size->width, width); + size->height += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL; + } + + size->height -= WD_PAR_VSEP_NORMAL; + } + + void DrawWidget(const Rect &r, int widget) const override + { + GfxDrawLine(r.left, r.top, r.right, r.top, PC_BLACK); + GfxDrawLine(r.left, r.bottom, r.right, r.bottom, PC_BLACK); + GfxDrawLine(r.left, r.top, r.left, r.bottom, PC_BLACK); + GfxDrawLine(r.right, r.top, r.right, r.bottom, PC_BLACK); + + int y = r.top + WD_FRAMETEXT_TOP + 1; + const int left0 = r.left + WD_FRAMETEXT_LEFT + 1; + const int right0 = r.right - WD_FRAMETEXT_RIGHT - 1; + + DrawString(left0, right0, y, this->data[0], TC_LIGHT_BLUE, SA_CENTER); + + y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL; + + for (uint i = 1; i <= RATING_TOOLTIP_MAX_LINES; i++) { + if (StrEmpty(this->data[i])) break; + + int left = left0, right = right0; + + if (this->newgrf_rating_used && i >= 2 && i <= 4) { + if (_current_text_dir == TD_RTL) { + right -= RATING_TOOLTIP_NEWGRF_INDENT; + } + else { + left += RATING_TOOLTIP_NEWGRF_INDENT; + } + } + + DrawString(left, right, y, this->data[i], TC_BLACK); + + y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL; + } + } + + void OnMouseLoop() override + { + if (!_cursor.in_window || !_mouse_hovering) { + delete this; + } + } +}; + +void GuiShowStationRatingTooltip(Window *parent, const Station *st, const CargoSpec *cs) { + DeleteWindowById(WC_STATION_RATING_TOOLTIP, 0); + new StationRatingTooltipWindow(parent, st, cs); +} diff --git a/src/station_gui.h b/src/station_gui.h index a6aae8cef9..345c99279c 100644 --- a/src/station_gui.h +++ b/src/station_gui.h @@ -14,6 +14,9 @@ #include "tilearea_type.h" #include "window_type.h" +struct Station; +struct CargoSpec; + /** Types of cargo to display for station coverage. */ enum StationCoverageType { @@ -28,4 +31,6 @@ void CheckRedrawStationCoverage(Window *w); void ShowSelectStationIfNeeded(const CommandContainer &cmd, TileArea ta); void ShowSelectWaypointIfNeeded(const CommandContainer &cmd, TileArea ta); +void GuiShowStationRatingTooltip(Window *parent, const Station *st, const CargoSpec *cs); + #endif /* STATION_GUI_H */ diff --git a/src/strings.cpp b/src/strings.cpp index 869ebbac42..ca4bf881d0 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -1331,6 +1331,15 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg buff = FormatNoCommaNumber(buff, args->GetInt64(SCC_NUM), last); break; + case SCC_PLUS_NUM: { // {PLUS_NUM} + int64 num = args->GetInt64(SCC_PLUS_NUM); + if (num > 0) { + buff += seprintf(buff, last, "+"); + } + buff = FormatNoCommaNumber(buff, num, last); + break; + } + case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM} int64 num = args->GetInt64(); buff = FormatZerofillNumber(buff, num, args->GetInt64(), last); diff --git a/src/table/control_codes.h b/src/table/control_codes.h index 3d59a6caf5..69838c5bb5 100644 --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -96,6 +96,7 @@ enum StringControlCode { SCC_DECIMAL, SCC_DECIMAL1, SCC_NUM, + SCC_PLUS_NUM, SCC_ZEROFILL_NUM, SCC_HEX, SCC_BYTES, diff --git a/src/table/settings.ini b/src/table/settings.ini index 34a555cca3..75259fed95 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -5468,6 +5468,19 @@ strhelp = STR_CONFIG_SETTING_SHADED_TREES_ON_SLOPES_HELPTEXT proc = RedrawScreen cat = SC_BASIC +[SDTC_VAR] +var = gui.station_rating_tooltip_mode +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_MULTISTRING +def = 1 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE +strhelp = STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE_HELPTEXT +strval = STR_CONFIG_SETTING_STATION_RATING_TOOLTIP_MODE_OFF +cat = SC_BASIC + ; For the dedicated build we'll enable dates in logs by default. [SDTC_BOOL] ifdef = DEDICATED diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h index de3b96c114..f0a0f868bb 100644 --- a/src/table/strgen_tables.h +++ b/src/table/strgen_tables.h @@ -111,6 +111,7 @@ static const CmdStruct _cmd_structs[] = { {"DECIMAL", EmitSingleChar, SCC_DECIMAL, 2, 0, C_NONE}, // Number with comma and fractional part. Second parameter is number of fractional digits, first parameter is number times 10**(second parameter). {"DECIMAL1", EmitSingleChar, SCC_DECIMAL1, 1, 0, C_NONE}, // Decimal with fixed second parameter of 1 {"NUM", EmitSingleChar, SCC_NUM, 1, 0, C_NONE}, // Signed number + {"PLUS_NUM", EmitSingleChar, SCC_PLUS_NUM, 1, 0, C_NONE}, // Signed number, with sign (+ or -) shown for both positive and negative numbers {"ZEROFILL_NUM", EmitSingleChar, SCC_ZEROFILL_NUM, 2, 0, C_NONE}, // Unsigned number with zero fill, e.g. "02". First parameter is number, second minimum length {"BYTES", EmitSingleChar, SCC_BYTES, 1, 0, C_NONE}, // Unsigned number with "bytes", i.e. "1.02 MiB or 123 KiB" {"HEX", EmitSingleChar, SCC_HEX, 1, 0, C_NONE}, // Hexadecimally printed number diff --git a/src/window_type.h b/src/window_type.h index 6bd572edb2..c84d6c8a9c 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -107,6 +107,12 @@ enum WindowClass { * - 0 = #ToolTipsWidgets */ WC_TOOLTIPS, + + /** + * Station rating tooltip window; %Window numbers: + * - 0 = #ToolTipsWidgets + */ + WC_STATION_RATING_TOOLTIP, /** * Query string window; %Window numbers: