diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index f6158ba6b2..34410e069c 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -1357,7 +1357,7 @@ static void CrashAirplane(Aircraft *v) newstype = NewsType::AccidentOther; } - AddTileNewsItem(newsitem, newstype, vt, nullptr, st != nullptr ? st->index : INVALID_STATION); + AddTileNewsItem(newsitem, newstype, vt, st != nullptr ? st->index : INVALID_STATION); ModifyStationRatingAround(vt, v->owner, -160, 30); if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v); diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 176ca755db..562591a0f7 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -425,7 +425,7 @@ set_name:; SetDParam(1, STR_NEWS_COMPANY_LAUNCH_DESCRIPTION); SetDParamStr(2, cni->company_name); SetDParam(3, t->index); - AddNewsItem(STR_MESSAGE_NEWS_FORMAT, NewsType::CompanyInfo, NewsStyle::Company, {}, NewsReferenceType::Tile, c->last_build_coordinate.base(), NewsReferenceType::None, UINT32_MAX, std::move(cni)); + AddNewsItem(STR_MESSAGE_NEWS_FORMAT, NewsType::CompanyInfo, NewsStyle::Company, {}, c->last_build_coordinate, {}, std::move(cni)); } return; } diff --git a/src/crashlog.cpp b/src/crashlog.cpp index 7e57ef2cf0..66cd09e78c 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -18,6 +18,7 @@ #include "saveload/saveload.h" #include "screenshot.h" #include "network/network_survey.h" +#include "news_func.h" #include "news_gui.h" #include "fileio_func.h" #include "fileio_type.h" @@ -60,7 +61,8 @@ static void SurveyRecentNews(nlohmann::json &json) TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(news.date); json.push_back(fmt::format("({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}", ymd.year, ymd.month + 1, ymd.day, news.string_id, news.type, - news.reftype1, news.ref1, news.reftype2, news.ref2)); + news.ref1.index(), SerialiseNewsReference(news.ref1), + news.ref2.index(), SerialiseNewsReference(news.ref2))); if (++i > 32) break; } } diff --git a/src/engine.cpp b/src/engine.cpp index 400475a331..ccd5f0d914 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1131,7 +1131,7 @@ static void NewVehicleAvailable(Engine *e) if (!IsVehicleTypeDisabled(e->type, false) && !e->info.extra_flags.Test(ExtraEngineFlag::NoNews)) { SetDParam(0, GetEngineCategoryName(index)); SetDParam(1, PackEngineNameDParam(index, EngineNameContext::PreviewNews)); - AddNewsItem(STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE_WITH_TYPE, NewsType::NewVehicles, NewsStyle::Vehicle, {}, NewsReferenceType::Engine, index); + AddNewsItem(STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE_WITH_TYPE, NewsType::NewVehicles, NewsStyle::Vehicle, {}, index); } /* Update the toolbar. */ diff --git a/src/news_cmd.h b/src/news_cmd.h index 28a27eb026..2653c3660a 100644 --- a/src/news_cmd.h +++ b/src/news_cmd.h @@ -10,11 +10,11 @@ #ifndef NEWS_CMD_H #define NEWS_CMD_H -#include "command_type.h" +#include "command_func.h" #include "company_type.h" -#include "news_type.h" +#include "news_func.h" -CommandCost CmdCustomNewsItem(DoCommandFlags flags, NewsType type, NewsReferenceType reftype1, CompanyID company, uint32_t reference, const std::string &text); +CommandCost CmdCustomNewsItem(DoCommandFlags flags, NewsType type, CompanyID company, NewsReference reference, const std::string &text); DEF_CMD_TRAIT(CMD_CUSTOM_NEWS_ITEM, CmdCustomNewsItem, CommandFlags({CommandFlag::StrCtrl, CommandFlag::Deity}), CMDT_OTHER_MANAGEMENT) diff --git a/src/news_func.h b/src/news_func.h index 61cff880cc..a9503b035d 100644 --- a/src/news_func.h +++ b/src/news_func.h @@ -15,11 +15,11 @@ #include "station_type.h" #include "industry_type.h" -void AddNewsItem(StringID string, NewsType type, NewsStyle style, NewsFlags flags, NewsReferenceType reftype1 = NewsReferenceType::None, uint32_t ref1 = UINT32_MAX, NewsReferenceType reftype2 = NewsReferenceType::None, uint32_t ref2 = UINT32_MAX, std::unique_ptr &&data = nullptr, AdviceType advice_type = AdviceType::Invalid); +void AddNewsItem(StringID string, NewsType type, NewsStyle style, NewsFlags flags, NewsReference ref1 = {}, NewsReference ref2 = {}, std::unique_ptr &&data = nullptr, AdviceType advice_type = AdviceType::Invalid); inline void AddCompanyNewsItem(StringID string, std::unique_ptr cni) { - AddNewsItem(string, NewsType::CompanyInfo, NewsStyle::Company, {}, NewsReferenceType::None, UINT32_MAX, NewsReferenceType::None, UINT32_MAX, std::move(cni)); + AddNewsItem(string, NewsType::CompanyInfo, NewsStyle::Company, {}, {}, {}, std::move(cni)); } /** @@ -29,7 +29,7 @@ inline void AddCompanyNewsItem(StringID string, std::unique_ptr &&data = nullptr, StationID station = INVALID_STATION) +inline void AddTileNewsItem(StringID string, NewsType type, TileIndex tile, StationID station = INVALID_STATION) { - AddNewsItem(string, type, NewsStyle::Thin, {NewsFlag::NoTransparency, NewsFlag::Shaded}, NewsReferenceType::Tile, tile.base(), station == INVALID_STATION ? NewsReferenceType::None : NewsReferenceType::Station, station, std::move(data)); + AddNewsItem(string, type, NewsStyle::Thin, {NewsFlag::NoTransparency, NewsFlag::Shaded}, tile, station == INVALID_STATION ? NewsReference{} : station); } -inline void AddIndustryNewsItem(StringID string, NewsType type, IndustryID industry, std::unique_ptr &&data = nullptr) +inline void AddIndustryNewsItem(StringID string, NewsType type, IndustryID industry) { - AddNewsItem(string, type, NewsStyle::Thin, {NewsFlag::NoTransparency, NewsFlag::Shaded}, NewsReferenceType::Industry, industry, NewsReferenceType::None, UINT32_MAX, std::move(data)); + AddNewsItem(string, type, NewsStyle::Thin, {NewsFlag::NoTransparency, NewsFlag::Shaded}, industry, {}); } void NewsLoop(); @@ -62,4 +62,6 @@ void DeleteVehicleNews(VehicleID vid, AdviceType advice_type = AdviceType::Inval void DeleteStationNews(StationID sid); void DeleteIndustryNews(IndustryID iid); +uint32_t SerialiseNewsReference(const NewsReference &reference); + #endif /* NEWS_FUNC_H */ diff --git a/src/news_gui.cpp b/src/news_gui.cpp index 57e5761f1f..8a4486c1e6 100644 --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -83,19 +83,22 @@ const NewsContainer &GetNews() /** * Get the position a news-reference is referencing. - * @param reftype The type of reference. - * @param ref The reference. + * @param reference The reference. * @return A tile for the referenced object, or INVALID_TILE if none. */ -static TileIndex GetReferenceTile(NewsReferenceType reftype, uint32_t ref) +static TileIndex GetReferenceTile(const NewsReference &reference) { - switch (reftype) { - case NewsReferenceType::Tile: return (TileIndex)ref; - case NewsReferenceType::Station: return Station::Get((StationID)ref)->xy; - case NewsReferenceType::Industry: return Industry::Get((IndustryID)ref)->location.tile + TileDiffXY(1, 1); - case NewsReferenceType::Town: return Town::Get((TownID)ref)->xy; - default: return INVALID_TILE; - } + struct visitor { + TileIndex operator()(const std::monostate &) { return INVALID_TILE; } + TileIndex operator()(const TileIndex &t) { return t; } + TileIndex operator()(const VehicleID) { return INVALID_TILE; } + TileIndex operator()(const StationID s) { return Station::Get(s)->xy; } + TileIndex operator()(const IndustryID i) { return Industry::Get(i)->location.tile + TileDiffXY(1, 1); } + TileIndex operator()(const TownID t) { return Town::Get(t)->xy; } + TileIndex operator()(const EngineID) { return INVALID_TILE; } + }; + + return std::visit(visitor{}, reference); } /* Normal news items. */ @@ -366,8 +369,8 @@ struct NewsWindow : Window { if (&desc == &_company_news_desc) this->GetWidget(WID_N_TITLE)->SetString(static_cast(std::get(this->ni->params[0]))); NWidgetCore *nwid = this->GetWidget(WID_N_SHOW_GROUP); - if (ni->reftype1 == NewsReferenceType::Vehicle && nwid != nullptr) { - const Vehicle *v = Vehicle::Get(ni->ref1); + if (std::holds_alternative(ni->ref1) && nwid != nullptr) { + const Vehicle *v = Vehicle::Get(std::get(ni->ref1)); switch (v->type) { case VEH_TRAIN: nwid->SetString(STR_TRAIN); @@ -391,10 +394,10 @@ struct NewsWindow : Window { /* Initialize viewport if it exists. */ NWidgetViewport *nvp = this->GetWidget(WID_N_VIEWPORT); if (nvp != nullptr) { - if (ni->reftype1 == NewsReferenceType::Vehicle) { - nvp->InitializeViewport(this, static_cast(ni->ref1), ScaleZoomGUI(ZOOM_LVL_NEWS)); + if (std::holds_alternative(ni->ref1)) { + nvp->InitializeViewport(this, std::get(ni->ref1), ScaleZoomGUI(ZOOM_LVL_NEWS)); } else { - nvp->InitializeViewport(this, GetReferenceTile(ni->reftype1, ni->ref1), ScaleZoomGUI(ZOOM_LVL_NEWS)); + nvp->InitializeViewport(this, GetReferenceTile(ni->ref1), ScaleZoomGUI(ZOOM_LVL_NEWS)); } if (this->ni->flags.Test(NewsFlag::NoTransparency)) nvp->disp_flags.Set(NWidgetDisplayFlag::NoTransparency); if (!this->ni->flags.Test(NewsFlag::InColour)) { @@ -457,14 +460,14 @@ struct NewsWindow : Window { break; case WID_N_VEH_INFO: { - assert(this->ni->reftype1 == NewsReferenceType::Engine); - EngineID engine = static_cast(this->ni->ref1); + assert(std::holds_alternative(ni->ref1)); + EngineID engine = std::get(this->ni->ref1); str = GetEngineInfoString(engine); break; } case WID_N_SHOW_GROUP: - if (this->ni->reftype1 == NewsReferenceType::Vehicle) { + if (std::holds_alternative(ni->ref1)) { Dimension d2 = GetStringBoundingBox(this->GetWidget(WID_N_SHOW_GROUP)->GetString()); d2.height += WidgetDimensions::scaled.captiontext.Vertical(); d2.width += WidgetDimensions::scaled.captiontext.Horizontal(); @@ -541,15 +544,15 @@ struct NewsWindow : Window { break; case WID_N_VEH_SPR: { - assert(this->ni->reftype1 == NewsReferenceType::Engine); - EngineID engine = static_cast(this->ni->ref1); + assert(std::holds_alternative(ni->ref1)); + EngineID engine = std::get(this->ni->ref1); DrawVehicleEngine(r.left, r.right, CenterBounds(r.left, r.right, 0), CenterBounds(r.top, r.bottom, 0), engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW); GfxFillRect(r.left, r.top, r.right, r.bottom, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); break; } case WID_N_VEH_INFO: { - assert(this->ni->reftype1 == NewsReferenceType::Engine); - EngineID engine = static_cast(this->ni->ref1); + assert(std::holds_alternative(ni->ref1)); + EngineID engine = std::get(this->ni->ref1); DrawStringMultiLine(r.left, r.right, r.top, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER); break; } @@ -566,8 +569,8 @@ struct NewsWindow : Window { break; case WID_N_CAPTION: - if (this->ni->reftype1 == NewsReferenceType::Vehicle) { - const Vehicle *v = Vehicle::Get(this->ni->ref1); + if (std::holds_alternative(ni->ref1)) { + const Vehicle *v = Vehicle::Get(std::get(this->ni->ref1)); ShowVehicleViewWindow(v); } break; @@ -576,18 +579,18 @@ struct NewsWindow : Window { break; // Ignore clicks case WID_N_SHOW_GROUP: - if (this->ni->reftype1 == NewsReferenceType::Vehicle) { - const Vehicle *v = Vehicle::Get(this->ni->ref1); + if (std::holds_alternative(ni->ref1)) { + const Vehicle *v = Vehicle::Get(std::get(this->ni->ref1)); ShowCompanyGroupForVehicle(v); } break; default: - if (this->ni->reftype1 == NewsReferenceType::Vehicle) { - const Vehicle *v = Vehicle::Get(this->ni->ref1); + if (std::holds_alternative(ni->ref1)) { + const Vehicle *v = Vehicle::Get(std::get(this->ni->ref1)); ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos); } else { - TileIndex tile1 = GetReferenceTile(this->ni->reftype1, this->ni->ref1); - TileIndex tile2 = GetReferenceTile(this->ni->reftype2, this->ni->ref2); + TileIndex tile1 = GetReferenceTile(this->ni->ref1); + TileIndex tile2 = GetReferenceTile(this->ni->ref2); if (_ctrl_pressed) { if (tile1 != INVALID_TILE) ShowExtraViewportWindow(tile1); if (tile2 != INVALID_TILE) ShowExtraViewportWindow(tile2); @@ -607,8 +610,8 @@ struct NewsWindow : Window { NWidgetViewport *nvp = this->GetWidget(WID_N_VIEWPORT); nvp->UpdateViewportCoordinates(this); - if (ni->reftype1 != NewsReferenceType::Vehicle) { - ScrollWindowToTile(GetReferenceTile(ni->reftype1, ni->ref1), this, true); // Re-center viewport. + if (!std::holds_alternative(ni->ref1)) { + ScrollWindowToTile(GetReferenceTile(ni->ref1), this, true); // Re-center viewport. } } @@ -677,8 +680,8 @@ private: StringID GetNewVehicleMessageString(WidgetID widget) const { - assert(this->ni->reftype1 == NewsReferenceType::Engine); - EngineID engine = static_cast(this->ni->ref1); + assert(std::holds_alternative(ni->ref1)); + EngineID engine = std::get(this->ni->ref1); switch (widget) { case WID_N_VEH_TITLE: @@ -865,17 +868,15 @@ static std::list::iterator DeleteNewsItem(std::list::iterato * @param string_id String to display. * @param type The type of news. * @param flags Flags related to how to display the news. - * @param reftype1 Type of ref1. * @param ref1 Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleting the news when the object is deleted. - * @param reftype2 Type of ref2. * @param ref2 Reference 2 to some object: Used for scrolling after clicking on the news, and for deleting the news when the object is deleted. * @param data Pointer to data that must be released once the news message is cleared. * @param advice_type Sub-type in case the news type is #NewsType::Advice. * * @see NewsSubtype */ -NewsItem::NewsItem(StringID string_id, NewsType type, NewsStyle style, NewsFlags flags, NewsReferenceType reftype1, uint32_t ref1, NewsReferenceType reftype2, uint32_t ref2, std::unique_ptr &&data, AdviceType advice_type) : - string_id(string_id), date(TimerGameCalendar::date), economy_date(TimerGameEconomy::date), type(type), advice_type(advice_type), style(style), flags(flags), reftype1(reftype1), reftype2(reftype2), ref1(ref1), ref2(ref2), data(std::move(data)) +NewsItem::NewsItem(StringID string_id, NewsType type, NewsStyle style, NewsFlags flags, NewsReference ref1, NewsReference ref2, std::unique_ptr &&data, AdviceType advice_type) : + string_id(string_id), date(TimerGameCalendar::date), economy_date(TimerGameEconomy::date), type(type), advice_type(advice_type), style(style), flags(flags), ref1(ref1), ref2(ref2), data(std::move(data)) { /* show this news message in colour? */ if (TimerGameCalendar::year >= _settings_client.gui.coloured_news_year) this->flags.Set(NewsFlag::InColour); @@ -887,21 +888,19 @@ NewsItem::NewsItem(StringID string_id, NewsType type, NewsStyle style, NewsFlags * @param string String to display * @param type news category * @param flags display flags for the news - * @param reftype1 Type of ref1 * @param ref1 Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleting the news when the object is deleted. - * @param reftype2 Type of ref2 * @param ref2 Reference 2 to some object: Used for scrolling after clicking on the news, and for deleting the news when the object is deleted. * @param data Pointer to data that must be released once the news message is cleared. * @param advice_type Sub-type in case the news type is #NewsType::Advice. * * @see NewsSubtype */ -void AddNewsItem(StringID string, NewsType type, NewsStyle style, NewsFlags flags, NewsReferenceType reftype1, uint32_t ref1, NewsReferenceType reftype2, uint32_t ref2, std::unique_ptr &&data, AdviceType advice_type) +void AddNewsItem(StringID string, NewsType type, NewsStyle style, NewsFlags flags, NewsReference ref1, NewsReference ref2, std::unique_ptr &&data, AdviceType advice_type) { if (_game_mode == GM_MENU) return; /* Create new news item node */ - _news.emplace_front(string, type, style, flags, reftype1, ref1, reftype2, ref2, std::move(data), advice_type); + _news.emplace_front(string, type, style, flags, ref1, ref2, std::move(data), advice_type); /* Keep the number of stored news items to a manageable number */ if (std::size(_news) > MAX_NEWS_AMOUNT) { @@ -911,17 +910,37 @@ void AddNewsItem(StringID string, NewsType type, NewsStyle style, NewsFlags flag InvalidateWindowData(WC_MESSAGE_HISTORY, 0); } +/** + * Encode a NewsReference for serialisation, e.g. for writing in the crash log. + * @param reference The reference to serialise. + * @return Reference serialised into a single uint32_t. + */ +uint32_t SerialiseNewsReference(const NewsReference &reference) +{ + struct visitor { + uint32_t operator()(const std::monostate &) { return 0; } + uint32_t operator()(const TileIndex &t) { return t.base(); } + uint32_t operator()(const VehicleID v) { return v; } + uint32_t operator()(const StationID s) { return s; } + uint32_t operator()(const IndustryID i) { return i; } + uint32_t operator()(const TownID t) { return t; } + uint32_t operator()(const EngineID e) { return e; } + }; + + return std::visit(visitor{}, reference); +} + /** * Create a new custom news item. * @param flags type of operation * @aram type NewsType of the message. * @param reftype1 NewsReferenceType of first reference. * @param company Company this news message is for. - * @param reference First reference of the news message. + * @param reference_id First reference of the news message. * @param text The text of the news message. * @return the cost of this operation or an error */ -CommandCost CmdCustomNewsItem(DoCommandFlags flags, NewsType type, NewsReferenceType reftype1, CompanyID company, uint32_t reference, const std::string &text) +CommandCost CmdCustomNewsItem(DoCommandFlags flags, NewsType type, CompanyID company, NewsReference reference, const std::string &text) { if (_current_company != OWNER_DEITY) return CMD_ERROR; @@ -929,40 +948,23 @@ CommandCost CmdCustomNewsItem(DoCommandFlags flags, NewsType type, NewsReference if (type >= NewsType::End) return CMD_ERROR; if (text.empty()) return CMD_ERROR; - switch (reftype1) { - case NewsReferenceType::None: break; - case NewsReferenceType::Tile: - if (!IsValidTile(reference)) return CMD_ERROR; - break; + struct visitor { + bool operator()(const std::monostate &) { return true; } + bool operator()(const TileIndex t) { return IsValidTile(t); } + bool operator()(const VehicleID v) { return Vehicle::IsValidID(v); } + bool operator()(const StationID s) { return Station::IsValidID(s); } + bool operator()(const IndustryID i) { return Industry::IsValidID(i); } + bool operator()(const TownID t) { return Town::IsValidID(t); } + bool operator()(const EngineID e) { return Engine::IsValidID(e); } + }; - case NewsReferenceType::Vehicle: - if (!Vehicle::IsValidID(reference)) return CMD_ERROR; - break; - - case NewsReferenceType::Station: - if (!Station::IsValidID(reference)) return CMD_ERROR; - break; - - case NewsReferenceType::Industry: - if (!Industry::IsValidID(reference)) return CMD_ERROR; - break; - - case NewsReferenceType::Town: - if (!Town::IsValidID(reference)) return CMD_ERROR; - break; - - case NewsReferenceType::Engine: - if (!Engine::IsValidID(reference)) return CMD_ERROR; - break; - - default: return CMD_ERROR; - } + if (!std::visit(visitor{}, reference)) return CMD_ERROR; if (company != INVALID_OWNER && company != _local_company) return CommandCost(); if (flags.Test(DoCommandFlag::Execute)) { SetDParamStr(0, text); - AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NewsStyle::Normal, {}, reftype1, reference, NewsReferenceType::None, UINT32_MAX); + AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NewsStyle::Normal, {}, reference, {}); } return CommandCost(); @@ -991,6 +993,18 @@ void DeleteNews(Tpredicate predicate) if (dirty) InvalidateWindowData(WC_MESSAGE_HISTORY, 0); } +template +static bool IsReferenceObject(const NewsReference &reference, T id) +{ + return std::holds_alternative(reference) && std::get(reference) == id; +} + +template +static bool HasReferenceObject(const NewsItem &ni, T id) +{ + return IsReferenceObject(ni.ref1, id) || IsReferenceObject(ni.ref2, id); +} + /** * Delete news with a given advice type about a vehicle. * When the advice_type is #AdviceType::Invalid all news about the vehicle gets deleted. @@ -1000,7 +1014,7 @@ void DeleteNews(Tpredicate predicate) void DeleteVehicleNews(VehicleID vid, AdviceType advice_type) { DeleteNews([&](const auto &ni) { - return ((ni.reftype1 == NewsReferenceType::Vehicle && ni.ref1 == vid) || (ni.reftype2 == NewsReferenceType::Vehicle && ni.ref2 == vid)) && (advice_type == AdviceType::Invalid || ni.advice_type == advice_type); + return HasReferenceObject(ni, vid) && (advice_type == AdviceType::Invalid || ni.advice_type == advice_type); }); } @@ -1012,7 +1026,7 @@ void DeleteVehicleNews(VehicleID vid, AdviceType advice_type) void DeleteStationNews(StationID sid) { DeleteNews([&](const auto &ni) { - return (ni.reftype1 == NewsReferenceType::Station && ni.ref1 == sid) || (ni.reftype2 == NewsReferenceType::Station && ni.ref2 == sid); + return HasReferenceObject(ni, sid); }); } @@ -1023,18 +1037,25 @@ void DeleteStationNews(StationID sid) void DeleteIndustryNews(IndustryID iid) { DeleteNews([&](const auto &ni) { - return (ni.reftype1 == NewsReferenceType::Industry && ni.ref1 == iid) || (ni.reftype2 == NewsReferenceType::Industry && ni.ref2 == iid); + return HasReferenceObject(ni, iid); }); } +bool IsInvalidEngineNews(const NewsReference &reference) +{ + if (!std::holds_alternative(reference)) return false; + + EngineID eid = std::get(reference); + return !Engine::IsValidID(eid) || !Engine::Get(eid)->IsEnabled(); +} + /** * Remove engine announcements for invalid engines. */ void DeleteInvalidEngineNews() { DeleteNews([](const auto &ni) { - return (ni.reftype1 == NewsReferenceType::Engine && (!Engine::IsValidID(ni.ref1) || !Engine::Get(ni.ref1)->IsEnabled())) || - (ni.reftype2 == NewsReferenceType::Engine && (!Engine::IsValidID(ni.ref2) || !Engine::Get(ni.ref2)->IsEnabled())); + return IsInvalidEngineNews(ni.ref1) || IsInvalidEngineNews(ni.ref2); }); } @@ -1045,6 +1066,13 @@ static void RemoveOldNewsItems() }); } +template +static void ChangeObject(NewsReference reference, T from, T to) +{ + if (!std::holds_alternative(reference)) return; + if (std::get(reference) == from) reference = to; +} + /** * Report a change in vehicle IDs (due to autoreplace) to affected vehicle news. * @note Viewports of currently displayed news is changed via #ChangeVehicleViewports @@ -1054,8 +1082,8 @@ static void RemoveOldNewsItems() void ChangeVehicleNews(VehicleID from_index, VehicleID to_index) { for (auto &ni : _news) { - if (ni.reftype1 == NewsReferenceType::Vehicle && ni.ref1 == from_index) ni.ref1 = to_index; - if (ni.reftype2 == NewsReferenceType::Vehicle && ni.ref2 == from_index) ni.ref2 = to_index; + ChangeObject(ni.ref1, from_index, to_index); + ChangeObject(ni.ref2, from_index, to_index); if (ni.flags.Test(NewsFlag::VehicleParam0) && std::get(ni.params[0]) == from_index) ni.params[0] = to_index; } } diff --git a/src/news_type.h b/src/news_type.h index 1cca28a5ad..f43beed11b 100644 --- a/src/news_type.h +++ b/src/news_type.h @@ -11,11 +11,16 @@ #define NEWS_TYPE_H #include "core/enum_type.hpp" +#include "engine_type.h" +#include "industry_type.h" #include "gfx_type.h" +#include "sound_type.h" +#include "station_type.h" +#include "strings_type.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" -#include "strings_type.h" -#include "sound_type.h" +#include "town_type.h" +#include "vehicle_type.h" /** * Type of news. @@ -65,15 +70,7 @@ enum class AdviceType : uint8_t { * You have to make sure, #ChangeVehicleNews catches the DParams of your message. * This is NOT ensured by the references. */ -enum class NewsReferenceType : uint8_t { - None, ///< Empty reference - Tile, ///< Reference tile. Scroll to tile when clicking on the news. - Vehicle, ///< Reference vehicle. Scroll to vehicle when clicking on the news. Delete news when vehicle is deleted. - Station, ///< Reference station. Scroll to station when clicking on the news. Delete news when station is deleted. - Industry, ///< Reference industry. Scroll to industry when clicking on the news. Delete news when industry is deleted. - Town, ///< Reference town. Scroll to town when clicking on the news. - Engine, ///< Reference engine. -}; +using NewsReference = std::variant; /** News Window Styles. */ enum class NewsStyle : uint8_t { @@ -145,16 +142,14 @@ struct NewsItem { NewsStyle style; /// Window style for the news. NewsFlags flags; ///< NewsFlags bits @see NewsFlag - NewsReferenceType reftype1; ///< Type of ref1 - NewsReferenceType reftype2; ///< Type of ref2 - uint32_t ref1; ///< Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleting the news when the object is deleted. - uint32_t ref2; ///< Reference 2 to some object: Used for scrolling after clicking on the news, and for deleting the news when the object is deleted. + NewsReference ref1; ///< Reference 1 to some object: Used for a possible viewport, scrolling after clicking on the news, and for deleting the news when the object is deleted. + NewsReference ref2; ///< Reference 2 to some object: Used for scrolling after clicking on the news, and for deleting the news when the object is deleted. std::unique_ptr data; ///< Custom data for the news item that will be deallocated (deleted) when the news item has reached its end. std::vector params; ///< Parameters for string resolving. - NewsItem(StringID string_id, NewsType type, NewsStyle style, NewsFlags flags, NewsReferenceType reftype1, uint32_t ref1, NewsReferenceType reftype2, uint32_t ref2, std::unique_ptr &&data, AdviceType advice_type); + NewsItem(StringID string_id, NewsType type, NewsStyle style, NewsFlags flags, NewsReference ref1, NewsReference ref2, std::unique_ptr &&data, AdviceType advice_type); }; /** diff --git a/src/script/api/script_news.cpp b/src/script/api/script_news.cpp index 7d880f036c..98956bacbf 100644 --- a/src/script/api/script_news.cpp +++ b/src/script/api/script_news.cpp @@ -20,6 +20,18 @@ #include "../../safeguards.h" +static NewsReference CreateReference(ScriptNews::NewsReferenceType ref_type, SQInteger reference) +{ + switch (ref_type) { + case ScriptNews::NR_NONE: return {}; + case ScriptNews::NR_TILE: return ::TileIndex(reference); + case ScriptNews::NR_STATION: return static_cast(reference); + case ScriptNews::NR_INDUSTRY: return static_cast(reference); + case ScriptNews::NR_TOWN: return static_cast(reference); + default: NOT_REACHED(); + } +} + /* static */ bool ScriptNews::Create(NewsType type, Text *text, ScriptCompany::CompanyID company, NewsReferenceType ref_type, SQInteger reference) { ScriptObjectRef counter(text); @@ -38,6 +50,5 @@ ::CompanyID c = ScriptCompany::FromScriptCompanyID(company); - if (ref_type == NR_NONE) reference = 0; - return ScriptObject::Command::Do((::NewsType)type, (::NewsReferenceType)ref_type, c, reference, encoded); + return ScriptObject::Command::Do((::NewsType)type, c, CreateReference(ref_type, reference), encoded); } diff --git a/src/script/api/script_news.hpp b/src/script/api/script_news.hpp index e3d3635a85..269cccd0cf 100644 --- a/src/script/api/script_news.hpp +++ b/src/script/api/script_news.hpp @@ -38,11 +38,11 @@ public: */ enum NewsReferenceType { /* Selection of useful game elements to refer to. */ - NR_NONE = to_underlying(::NewsReferenceType::None), ///< No reference supplied. - NR_TILE = to_underlying(::NewsReferenceType::Tile), ///< Reference location, scroll to the location when clicking on the news. - NR_STATION = to_underlying(::NewsReferenceType::Station), ///< Reference station, scroll to the station when clicking on the news. Delete news when the station is deleted. - NR_INDUSTRY = to_underlying(::NewsReferenceType::Industry), ///< Reference industry, scrolls to the industry when clicking on the news. Delete news when the industry is deleted. - NR_TOWN = to_underlying(::NewsReferenceType::Town), ///< Reference town, scroll to the town when clicking on the news. + NR_NONE, ///< No reference supplied. + NR_TILE, ///< Reference location, scroll to the location when clicking on the news. + NR_STATION, ///< Reference station, scroll to the station when clicking on the news. Delete news when the station is deleted. + NR_INDUSTRY, ///< Reference industry, scrolls to the industry when clicking on the news. Delete news when the industry is deleted. + NR_TOWN, ///< Reference town, scroll to the town when clicking on the news. }; /** diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 01eff04757..41692abfca 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -532,7 +532,7 @@ static void ShowRejectOrAcceptNews(const Station *st, CargoTypes cargoes, bool r SetDParam(0, st->index); SetDParam(1, cargoes); StringID msg = reject ? STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO_LIST : STR_NEWS_STATION_NOW_ACCEPTS_CARGO_LIST; - AddNewsItem(msg, NewsType::Acceptance, NewsStyle::Small, NewsFlag::InColour, NewsReferenceType::Station, st->index); + AddNewsItem(msg, NewsType::Acceptance, NewsStyle::Small, NewsFlag::InColour, st->index); } /** diff --git a/src/subsidy.cpp b/src/subsidy.cpp index 24078ed766..945a00852b 100644 --- a/src/subsidy.cpp +++ b/src/subsidy.cpp @@ -50,13 +50,13 @@ void Subsidy::AwardTo(CompanyID company) std::string company_name = GetString(STR_COMPANY_NAME, company); /* Add a news item */ - std::pair reftype = SetupSubsidyDecodeParam(this, SubsidyDecodeParamType::NewsAwarded, 1); + std::pair references = SetupSubsidyDecodeParam(this, SubsidyDecodeParamType::NewsAwarded, 1); SetDParamStr(0, company_name); AddNewsItem( STR_NEWS_SERVICE_SUBSIDY_AWARDED_HALF + _settings_game.difficulty.subsidy_multiplier, NewsType::Subsidies, NewsStyle::Normal, {}, - reftype.first, this->src.id, reftype.second, this->dst.id + references.first, references.second ); AI::BroadcastNewEvent(new ScriptEventSubsidyAwarded(this->index)); Game::NewEvent(new ScriptEventSubsidyAwarded(this->index)); @@ -71,10 +71,10 @@ void Subsidy::AwardTo(CompanyID company) * @param parameter_offset The location/index in the String DParams to start decoding the subsidy's parameters. Defaults to 0. * @return Reference of the subsidy in the news system. */ -std::pair SetupSubsidyDecodeParam(const Subsidy *s, SubsidyDecodeParamType mode, uint parameter_offset) +std::pair SetupSubsidyDecodeParam(const Subsidy *s, SubsidyDecodeParamType mode, uint parameter_offset) { - NewsReferenceType reftype1 = NewsReferenceType::None; - NewsReferenceType reftype2 = NewsReferenceType::None; + NewsReference reference1{}; + NewsReference reference2{}; /* Always use the plural form of the cargo name - trying to decide between plural or singular causes issues for translations */ const CargoSpec *cs = CargoSpec::Get(s->cargo_type); @@ -82,11 +82,11 @@ std::pair SetupSubsidyDecodeParam(const Su switch (s->src.type) { case SourceType::Industry: - reftype1 = NewsReferenceType::Industry; + reference1 = static_cast(s->src.id); SetDParam(parameter_offset + 1, STR_INDUSTRY_NAME); break; case SourceType::Town: - reftype1 = NewsReferenceType::Town; + reference1 = static_cast(s->src.id); SetDParam(parameter_offset + 1, STR_TOWN_NAME); break; default: NOT_REACHED(); @@ -95,11 +95,11 @@ std::pair SetupSubsidyDecodeParam(const Su switch (s->dst.type) { case SourceType::Industry: - reftype2 = NewsReferenceType::Industry; + reference2 = static_cast(s->dst.id); SetDParam(parameter_offset + 4, STR_INDUSTRY_NAME); break; case SourceType::Town: - reftype2 = NewsReferenceType::Town; + reference2 = static_cast(s->dst.id); SetDParam(parameter_offset + 4, STR_TOWN_NAME); break; default: NOT_REACHED(); @@ -111,7 +111,7 @@ std::pair SetupSubsidyDecodeParam(const Su SetDParam(parameter_offset + 7, _settings_game.difficulty.subsidy_duration); } - return std::pair(reftype1, reftype2); + return {reference1, reference2}; } /** @@ -208,8 +208,8 @@ void CreateSubsidy(CargoType cargo_type, Source src, Source dst) s->remaining = SUBSIDY_OFFER_MONTHS; s->awarded = INVALID_COMPANY; - std::pair reftype = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsOffered); - AddNewsItem(STR_NEWS_SERVICE_SUBSIDY_OFFERED, NewsType::Subsidies, NewsStyle::Normal, {}, reftype.first, s->src.id, reftype.second, s->dst.id); + std::pair references = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsOffered); + AddNewsItem(STR_NEWS_SERVICE_SUBSIDY_OFFERED, NewsType::Subsidies, NewsStyle::Normal, {}, references.first, references.second); SetPartOfSubsidyFlag(s->src, POS_SRC); SetPartOfSubsidyFlag(s->dst, POS_DST); AI::BroadcastNewEvent(new ScriptEventSubsidyOffer(s->index)); @@ -462,14 +462,14 @@ static IntervalTimer _economy_subsidies_monthly({TimerGameEcon for (Subsidy *s : Subsidy::Iterate()) { if (--s->remaining == 0) { if (!s->IsAwarded()) { - std::pair reftype = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsWithdrawn); - AddNewsItem(STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED, NewsType::Subsidies, NewsStyle::Normal, {}, reftype.first, s->src.id, reftype.second, s->dst.id); + std::pair references = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsWithdrawn); + AddNewsItem(STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED, NewsType::Subsidies, NewsStyle::Normal, {}, references.first, references.second); AI::BroadcastNewEvent(new ScriptEventSubsidyOfferExpired(s->index)); Game::NewEvent(new ScriptEventSubsidyOfferExpired(s->index)); } else { if (s->awarded == _local_company) { - std::pair reftype = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsWithdrawn); - AddNewsItem(STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE, NewsType::Subsidies, NewsStyle::Normal, {}, reftype.first, s->src.id, reftype.second, s->dst.id); + std::pair references = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsWithdrawn); + AddNewsItem(STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE, NewsType::Subsidies, NewsStyle::Normal, {}, references.first, references.second); } AI::BroadcastNewEvent(new ScriptEventSubsidyExpired(s->index)); Game::NewEvent(new ScriptEventSubsidyExpired(s->index)); diff --git a/src/subsidy_func.h b/src/subsidy_func.h index 0a47c3e693..fdf121b8c0 100644 --- a/src/subsidy_func.h +++ b/src/subsidy_func.h @@ -17,7 +17,7 @@ #include "news_type.h" #include "subsidy_base.h" -std::pair SetupSubsidyDecodeParam(const struct Subsidy *s, SubsidyDecodeParamType mode, uint parameter_offset = 0); +std::pair SetupSubsidyDecodeParam(const struct Subsidy *s, SubsidyDecodeParamType mode, uint parameter_offset = 0); void DeleteSubsidyWith(Source src); bool CheckSubsidised(CargoType cargo_type, CompanyID company, Source src, const Station *st); void RebuildSubsidisedSourceAndDestinationCache(); diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 52a983a432..fac2ae7f59 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -3416,7 +3416,7 @@ static CommandCost TownActionRoadRebuild(Town *t, DoCommandFlags flags) AddNewsItem( TimerGameEconomy::UsingWallclockUnits() ? STR_NEWS_ROAD_REBUILDING_MINUTES : STR_NEWS_ROAD_REBUILDING_MONTHS, - NewsType::General, NewsStyle::Normal, {}, NewsReferenceType::Town, t->index, NewsReferenceType::None, UINT32_MAX); + NewsType::General, NewsStyle::Normal, {}, t->index); AI::BroadcastNewEvent(new ScriptEventRoadReconstruction(_current_company, t->index)); Game::NewEvent(new ScriptEventRoadReconstruction(_current_company, t->index)); } @@ -3571,7 +3571,7 @@ static CommandCost TownActionBuyRights(Town *t, DoCommandFlags flags) SetDParam(1, TimerGameEconomy::UsingWallclockUnits() ? STR_NEWS_EXCLUSIVE_RIGHTS_DESCRIPTION_MINUTES : STR_NEWS_EXCLUSIVE_RIGHTS_DESCRIPTION_MONTHS); SetDParam(2, t->index); SetDParamStr(3, cni->company_name); - AddNewsItem(STR_MESSAGE_NEWS_FORMAT, NewsType::General, NewsStyle::Company, {}, NewsReferenceType::Town, t->index, NewsReferenceType::None, UINT32_MAX, std::move(cni)); + AddNewsItem(STR_MESSAGE_NEWS_FORMAT, NewsType::General, NewsStyle::Company, {}, t->index, {}, std::move(cni)); AI::BroadcastNewEvent(new ScriptEventExclusiveTransportRights(_current_company, t->index)); Game::NewEvent(new ScriptEventExclusiveTransportRights(_current_company, t->index)); }