# HG changeset patch # Parent c6f5e728a61e4ad2344175ba00dcf6603cbcf554 diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -351,6 +351,7 @@ + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -282,6 +282,9 @@ Source Files + + Source Files + Source Files diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -675,6 +675,10 @@ > + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -672,6 +672,10 @@ > + + diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -59,6 +59,7 @@ network/network_gamelist.cpp network/network_server.cpp network/network_udp.cpp +news.cpp openttd.cpp order_backup.cpp os_timer.cpp diff --git a/src/news.cpp b/src/news.cpp new file mode 100644 --- /dev/null +++ b/src/news.cpp @@ -0,0 +1,520 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file news.cpp Functions related to news. */ + +#include "stdafx.h" +#include "command_func.h" +#include "company_base.h" +#include "company_func.h" +#include "date_func.h" +#include "engine_base.h" +#include "industry.h" +#include "news_func.h" +#include "news_gui.h" +#include "news_type.h" +#include "settings_internal.h" +#include "station_base.h" +#include "statusbar_gui.h" +#include "string_func.h" +#include "strings_func.h" +#include "town.h" +#include "vehicle_base.h" +#include "window_func.h" + +#include "widgets/news_widget.h" + +#include "table/strings.h" + +static const int MIN_NEWS_AMOUNT = 30; ///< Preferred minimum amount of news messages. + +/** + * Forced news item, that the user asked for. + * Users can force an item by accessing the history or "last message". + * If the message being shown was forced by the user, a pointer is stored + * in _forced_news. Otherwise, \a _forced_news variable is NULL. + */ +const NewsItem *_forced_news = NULL; +uint _total_news = 0; ///< current number of news items +NewsItem *_oldest_news = NULL; ///< head of news items queue +NewsItem *_latest_news = NULL; ///< tail of news items queue + +/** Current news item (last item shown regularly). */ +static const NewsItem *_current_news = NULL; + +/** + * Get the position a news-reference is referencing. + * @param reftype The type of reference. + * @param ref The reference. + * @return A tile for the referenced object, or INVALID_TILE if none. + */ +TileIndex GetReferenceTile(NewsReferenceType reftype, uint32 ref) +{ + switch (reftype) { + case NR_TILE: return (TileIndex)ref; + case NR_STATION: return Station::Get((StationID)ref)->xy; + case NR_INDUSTRY: return Industry::Get((IndustryID)ref)->location.tile + TileDiffXY(1, 1); + case NR_TOWN: return Town::Get((TownID)ref)->xy; + default: return INVALID_TILE; + } +} + +/** + * Return the news display option. + * @return display options + */ +NewsDisplay NewsTypeData::GetDisplay() const +{ + uint index; + const SettingDesc *sd = GetSettingFromName(this->name, &index); + assert(sd != NULL); + void *ptr = GetVariableAddress(NULL, &sd->save); + return (NewsDisplay)ReadValue(ptr, sd->save.conv); +} + +/** Initialize the news-items data structures */ +void InitNewsItemStructs() +{ + for (NewsItem *ni = _oldest_news; ni != NULL; ) { + NewsItem *next = ni->next; + delete ni; + ni = next; + } + + _total_news = 0; + _oldest_news = NULL; + _latest_news = NULL; + _forced_news = NULL; + _current_news = NULL; + _statusbar_news_item = NULL; + NewsWindow::duration = 0; +} + +/** + * Are we ready to show another news item? + * Only if nothing is in the newsticker and no newspaper is displayed + */ +static bool ReadyForNextItem() +{ + const NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news; + if (ni == NULL) return true; + + /* Ticker message + * Check if the status bar message is still being displayed? */ + if (IsNewsTickerShown()) return false; + + /* Newspaper message, decrement duration counter */ + if (NewsWindow::duration != 0) NewsWindow::duration--; + + /* neither newsticker nor newspaper are running */ + return (NewsWindow::duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL); +} + +/** Move to the next news item */ +static void MoveToNextItem() +{ + InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // Invalidate the statusbar. + DeleteWindowById(WC_NEWS_WINDOW, 0); // Close the newspapers window if shown. + _forced_news = NULL; + _statusbar_news_item = NULL; + + /* If we're not at the last item, then move on. */ + if (_current_news != _latest_news) { + _current_news = (_current_news == NULL) ? _oldest_news : _current_news->next; + const NewsItem *ni = _current_news; + const NewsType type = ni->type; + + /* Check the date, don't show too old items. */ + if (_date - _news_type_data[type].age > ni->date) return; + + switch (_news_type_data[type].GetDisplay()) { + default: NOT_REACHED(); + case ND_OFF: // Off - show nothing only a small reminder in the status bar. + InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER); + break; + + case ND_SUMMARY: // Summary - show ticker. + ShowTicker(ni); + break; + + case ND_FULL: // Full - show newspaper. + ShowNewspaper(ni); + break; + } + } +} + +/** + * Add a new newsitem to be shown. + * @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 deleteing 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 deleteing the news when the object is deleted. + * @param free_data Pointer to data that must be freed once the news message is cleared + * + * @see NewsSubtype + */ +void AddNewsItem(StringID string, NewsType type, NewsFlag flags, NewsReferenceType reftype1, uint32 ref1, NewsReferenceType reftype2, uint32 ref2, void *free_data) +{ + if (_game_mode == GM_MENU) return; + + /* Create new news item node. */ + NewsItem *ni = new NewsItem; + + ni->string_id = string; + ni->type = type; + ni->flags = flags; + + /* Show this news message in colour? */ + if (_cur_year >= _settings_client.gui.coloured_news_year) ni->flags |= NF_INCOLOUR; + + ni->reftype1 = reftype1; + ni->reftype2 = reftype2; + ni->ref1 = ref1; + ni->ref2 = ref2; + ni->free_data = free_data; + ni->date = _date; + CopyOutDParam(ni->params, 0, lengthof(ni->params)); + + if (_total_news++ == 0) { + assert(_oldest_news == NULL); + _oldest_news = ni; + ni->prev = NULL; + } else { + assert(_latest_news->next == NULL); + _latest_news->next = ni; + ni->prev = _latest_news; + } + + ni->next = NULL; + _latest_news = ni; + + SetWindowDirty(WC_MESSAGE_HISTORY, 0); +} + +/** + * Create a new custom news item. + * @param tile unused + * @param flags type of operation + * @param p1 various bitstuffed elements + * - p1 = (bit 0 - 7) - NewsType of the message. + * - p1 = (bit 8 - 15) - NewsReferenceType of first reference. + * - p1 = (bit 16 - 23) - Company this news message is for. + * @param p2 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(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + + NewsType type = (NewsType)GB(p1, 0, 8); + NewsReferenceType reftype1 = (NewsReferenceType)GB(p1, 8, 8); + CompanyID company = (CompanyID)GB(p1, 16, 8); + + if (company != INVALID_OWNER && !Company::IsValidID(company)) return CMD_ERROR; + if (type >= NT_END) return CMD_ERROR; + if (StrEmpty(text)) return CMD_ERROR; + + switch (reftype1) { + case NR_NONE: break; + case NR_TILE: + if (!IsValidTile(p2)) return CMD_ERROR; + break; + + case NR_VEHICLE: + if (!Vehicle::IsValidID(p2)) return CMD_ERROR; + break; + + case NR_STATION: + if (!Station::IsValidID(p2)) return CMD_ERROR; + break; + + case NR_INDUSTRY: + if (!Industry::IsValidID(p2)) return CMD_ERROR; + break; + + case NR_TOWN: + if (!Town::IsValidID(p2)) return CMD_ERROR; + break; + + case NR_ENGINE: + if (!Engine::IsValidID(p2)) return CMD_ERROR; + break; + + default: return CMD_ERROR; + } + + if (company != INVALID_OWNER && company != _local_company) return CommandCost(); + + if (flags & DC_EXEC) { + char *news = strdup(text); + SetDParamStr(0, news); + AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NF_NORMAL, reftype1, p2, NR_NONE, UINT32_MAX, news); + } + + return CommandCost(); +} + +/** Delete a news item from the queue */ +static void DeleteNewsItem(NewsItem *ni) +{ + /* Delete the news from the news queue. */ + if (ni->prev != NULL) { + ni->prev->next = ni->next; + } else { + assert(_oldest_news == ni); + _oldest_news = ni->next; + } + + if (ni->next != NULL) { + ni->next->prev = ni->prev; + } else { + assert(_latest_news == ni); + _latest_news = ni->prev; + } + + _total_news--; + + if (_forced_news == ni || _current_news == ni || _statusbar_news_item == ni) { + /* When we're the current news, go to the previous item first; + * we just possibly made that the last news item. */ + if (_current_news == ni) _current_news = ni->prev; + + /* About to remove the currently forced item (shown as newspapers) || + * about to remove the currently displayed item (newspapers, ticker, or just a reminder) */ + MoveToNextItem(); + } + + delete ni; + + SetWindowDirty(WC_MESSAGE_HISTORY, 0); +} + +/** + * Delete a news item type about a vehicle. + * When the news item type is INVALID_STRING_ID all news about the vehicle gets deleted. + * @param vid The vehicle to remove the news for. + * @param news The news type to remove. + */ +void DeleteVehicleNews(VehicleID vid, StringID news) +{ + NewsItem *ni = _oldest_news; + + while (ni != NULL) { + NewsItem *next = ni->next; + if (((ni->reftype1 == NR_VEHICLE && ni->ref1 == vid) || (ni->reftype2 == NR_VEHICLE && ni->ref2 == vid)) && + (news == INVALID_STRING_ID || ni->string_id == news)) { + DeleteNewsItem(ni); + } + ni = next; + } +} + +/** + * Remove news regarding given station so there are no 'unknown station now accepts Mail' + * or 'First train arrived at unknown station' news items. + * @param sid station to remove news about + */ +void DeleteStationNews(StationID sid) +{ + NewsItem *ni = _oldest_news; + + while (ni != NULL) { + NewsItem *next = ni->next; + if ((ni->reftype1 == NR_STATION && ni->ref1 == sid) || (ni->reftype2 == NR_STATION && ni->ref2 == sid)) { + DeleteNewsItem(ni); + } + ni = next; + } +} + +/** + * Remove news regarding given industry + * @param iid industry to remove news about + */ +void DeleteIndustryNews(IndustryID iid) +{ + NewsItem *ni = _oldest_news; + + while (ni != NULL) { + NewsItem *next = ni->next; + if ((ni->reftype1 == NR_INDUSTRY && ni->ref1 == iid) || (ni->reftype2 == NR_INDUSTRY && ni->ref2 == iid)) { + DeleteNewsItem(ni); + } + ni = next; + } +} + +/** + * Remove engine announcements for invalid engines. + */ +void DeleteInvalidEngineNews() +{ + NewsItem *ni = _oldest_news; + + while (ni != NULL) { + NewsItem *next = ni->next; + if ((ni->reftype1 == NR_ENGINE && (!Engine::IsValidID(ni->ref1) || !Engine::Get(ni->ref1)->IsEnabled())) || + (ni->reftype2 == NR_ENGINE && (!Engine::IsValidID(ni->ref2) || !Engine::Get(ni->ref2)->IsEnabled()))) { + DeleteNewsItem(ni); + } + ni = next; + } +} + +static void RemoveOldNewsItems() +{ + NewsItem *next; + for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) { + next = cur->next; + if (_date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur); + } +} + +/** + * Report a change in vehicle IDs (due to autoreplace) to affected vehicle news. + * @note Viewports of currently displayed news is changed via #ChangeVehicleViewports + * @param from_index the old vehicle ID + * @param to_index the new vehicle ID + */ +void ChangeVehicleNews(VehicleID from_index, VehicleID to_index) +{ + for (NewsItem *ni = _oldest_news; ni != NULL; ni = ni->next) { + if (ni->reftype1 == NR_VEHICLE && ni->ref1 == from_index) ni->ref1 = to_index; + if (ni->reftype2 == NR_VEHICLE && ni->ref2 == from_index) ni->ref2 = to_index; + if (ni->flags & NF_VEHICLE_PARAM0 && ni->params[0] == from_index) ni->params[0] = to_index; + } +} + +void NewsLoop() +{ + /* no news item yet */ + if (_total_news == 0) return; + + /* There is no status bar, so no reason to show news; + * especially important with the end game screen when + * there is no status bar but possible news. */ + if (FindWindowById(WC_STATUS_BAR, 0) == NULL) return; + + static byte _last_clean_month = 0; + + if (_last_clean_month != _cur_month) { + RemoveOldNewsItems(); + _last_clean_month = _cur_month; + } + + if (ReadyForNextItem()) MoveToNextItem(); +} + +/** Do a forced show of a specific message */ +void ShowNewsMessage(const NewsItem *ni) +{ + assert(_total_news != 0); + + /* Delete the news window */ + DeleteWindowById(WC_NEWS_WINDOW, 0); + + /* Setup forced news item */ + _forced_news = ni; + + if (_forced_news != NULL) { + DeleteWindowById(WC_NEWS_WINDOW, 0); + ShowNewspaper(ni); + } +} + +/** Show previous news item */ +void ShowLastNewsMessage() +{ + const NewsItem *ni = NULL; + if (_total_news == 0) { + return; + } else if (_forced_news == NULL) { + /* Not forced any news yet, show the current one, unless a news window is + * open (which can only be the current one), then show the previous item */ + if (_current_news == NULL) { + /* No news were shown yet resp. the last shown one was already deleted. + * Threat this as if _forced_news reached _oldest_news; so, wrap around and start anew with the latest. */ + ni = _latest_news; + } else { + const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); + ni = (w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev; + } + } else if (_forced_news == _oldest_news) { + /* We have reached the oldest news, start anew with the latest */ + ni = _latest_news; + } else { + /* 'Scrolling' through news history show each one in turn */ + ni = _forced_news->prev; + } + bool wrap = false; + for (;;) { + if (_news_type_data[ni->type].GetDisplay() != ND_OFF) { + ShowNewsMessage(ni); + break; + } + + ni = ni->prev; + if (ni == NULL) { + if (wrap) break; + /* We have reached the oldest news, start anew with the latest */ + ni = _latest_news; + wrap = true; + } + } +} + +/** + * Draw an unformatted news message truncated to a maximum length. If + * length exceeds maximum length it will be postfixed by '...' + * @param left the left most location for the string + * @param right the right most location for the string + * @param y position of the string + * @param colour the colour the string will be shown in + * @param *ni NewsItem being printed + * @param maxw maximum width of string in pixels + */ +void DrawNewsString(uint left, uint right, int y, TextColour colour, const NewsItem *ni) +{ + char buffer[512], buffer2[512]; + StringID str; + + CopyInDParam(0, ni->params, lengthof(ni->params)); + str = ni->string_id; + + GetString(buffer, str, lastof(buffer)); + /* Copy the just gotten string to another buffer to remove any formatting + * from it such as big fonts, etc. */ + const char *ptr = buffer; + char *dest = buffer2; + WChar c_last = '\0'; + for (;;) { + WChar c = Utf8Consume(&ptr); + if (c == 0) break; + /* Make a space from a newline, but ignore multiple newlines */ + if (c == '\n' && c_last != '\n') { + dest[0] = ' '; + dest++; + } else if (c == '\r') { + dest[0] = dest[1] = dest[2] = dest[3] = ' '; + dest += 4; + } else if (IsPrintable(c)) { + dest += Utf8Encode(dest, c); + } + c_last = c; + } + + *dest = '\0'; + /* Truncate and show string; postfixed by '...' if necessary */ + DrawString(left, right, y, buffer2, colour); +} diff --git a/src/news_func.h b/src/news_func.h --- a/src/news_func.h +++ b/src/news_func.h @@ -16,6 +16,7 @@ #include "vehicle_type.h" #include "station_type.h" #include "industry_type.h" +#include "gfx_type.h" void AddNewsItem(StringID string, NewsType type, NewsFlag flags, NewsReferenceType reftype1 = NR_NONE, uint32 ref1 = UINT32_MAX, NewsReferenceType reftype2 = NR_NONE, uint32 ref2 = UINT32_MAX, void *free_data = NULL); @@ -26,7 +27,6 @@ /** * Adds a newsitem referencing a vehicle. - * * @warning The DParams may not reference the vehicle due to autoreplace stuff. See AddVehicleAdviceNewsItem for how that can be done. */ static inline void AddVehicleNewsItem(StringID string, NewsType type, VehicleID vehicle, StationID station = INVALID_STATION) @@ -36,7 +36,6 @@ /** * Adds a vehicle-advice news item. - * * @warning DParam 0 must reference the vehicle! */ static inline void AddVehicleAdviceNewsItem(StringID string, VehicleID vehicle) @@ -57,6 +56,14 @@ void NewsLoop(); void InitNewsItemStructs(); +TileIndex GetReferenceTile(NewsReferenceType reftype, uint32 ref); +void DrawNewsString(uint left, uint right, int y, TextColour colour, const NewsItem *ni); +void ShowNewsMessage(const NewsItem *ni); + +extern const NewsItem *_forced_news; +extern uint _total_news; +extern NewsItem *_oldest_news; +extern NewsItem *_latest_news; extern const NewsItem *_statusbar_news_item; void DeleteInvalidEngineNews(); diff --git a/src/news_gui.cpp b/src/news_gui.cpp --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -18,62 +18,23 @@ #include "vehicle_base.h" #include "vehicle_func.h" #include "vehicle_gui.h" -#include "station_base.h" -#include "industry.h" #include "town.h" #include "sound_func.h" +#include "statusbar_gui.h" #include "string_func.h" -#include "widgets/dropdown_func.h" -#include "statusbar_gui.h" #include "company_manager_face.h" #include "company_func.h" -#include "engine_base.h" #include "engine_gui.h" #include "core/geometry_func.hpp" -#include "command_func.h" -#include "company_base.h" -#include "settings_internal.h" +#include "news_gui.h" +#include "widgets/dropdown_func.h" #include "widgets/news_widget.h" #include "table/strings.h" const NewsItem *_statusbar_news_item = NULL; -static uint MIN_NEWS_AMOUNT = 30; ///< preferred minimum amount of news messages -static uint _total_news = 0; ///< current number of news items -static NewsItem *_oldest_news = NULL; ///< head of news items queue -static NewsItem *_latest_news = NULL; ///< tail of news items queue - -/** - * Forced news item. - * Users can force an item by accessing the history or "last message". - * If the message being shown was forced by the user, a pointer is stored - * in _forced_news. Otherwise, \a _forced_news variable is NULL. - */ -static const NewsItem *_forced_news = NULL; ///< item the user has asked for - -/** Current news item (last item shown regularly). */ -static const NewsItem *_current_news = NULL; - - -/** - * Get the position a news-reference is referencing. - * @param reftype The type of reference. - * @param ref The reference. - * @return A tile for the referenced object, or INVALID_TILE if none. - */ -static TileIndex GetReferenceTile(NewsReferenceType reftype, uint32 ref) -{ - switch (reftype) { - case NR_TILE: return (TileIndex)ref; - case NR_STATION: return Station::Get((StationID)ref)->xy; - case NR_INDUSTRY: return Industry::Get((IndustryID)ref)->location.tile + TileDiffXY(1, 1); - case NR_TOWN: return Town::Get((TownID)ref)->xy; - default: return INVALID_TILE; - } -} - /* Normal news items. */ static const NWidgetPart _nested_normal_news_widgets[] = { NWidget(WWT_PANEL, COLOUR_WHITE, WID_N_PANEL), @@ -219,316 +180,270 @@ return _news_window_layout[layout]; } -/** - * Per-NewsType data - */ -static NewsTypeData _news_type_data[] = { - /* name, age, sound, */ - NewsTypeData("news_display.arrival_player", 60, SND_1D_APPLAUSE ), ///< NT_ARRIVAL_COMPANY - NewsTypeData("news_display.arrival_other", 60, SND_1D_APPLAUSE ), ///< NT_ARRIVAL_OTHER - NewsTypeData("news_display.accident", 90, SND_BEGIN ), ///< NT_ACCIDENT - NewsTypeData("news_display.company_info", 60, SND_BEGIN ), ///< NT_COMPANY_INFO - NewsTypeData("news_display.open", 90, SND_BEGIN ), ///< NT_INDUSTRY_OPEN - NewsTypeData("news_display.close", 90, SND_BEGIN ), ///< NT_INDUSTRY_CLOSE - NewsTypeData("news_display.economy", 30, SND_BEGIN ), ///< NT_ECONOMY - NewsTypeData("news_display.production_player", 30, SND_BEGIN ), ///< NT_INDUSTRY_COMPANY - NewsTypeData("news_display.production_other", 30, SND_BEGIN ), ///< NT_INDUSTRY_OTHER - NewsTypeData("news_display.production_nobody", 30, SND_BEGIN ), ///< NT_INDUSTRY_NOBODY - NewsTypeData("news_display.advice", 150, SND_BEGIN ), ///< NT_ADVICE - NewsTypeData("news_display.new_vehicles", 30, SND_1E_OOOOH ), ///< NT_NEW_VEHICLES - NewsTypeData("news_display.acceptance", 90, SND_BEGIN ), ///< NT_ACCEPTANCE - NewsTypeData("news_display.subsidies", 180, SND_BEGIN ), ///< NT_SUBSIDIES - NewsTypeData("news_display.general", 60, SND_BEGIN ), ///< NT_GENERAL -}; +/** Window class displaying a news item. */ +NewsWindow::NewsWindow(WindowDesc *desc, const NewsItem *ni) : Window(desc), ni(ni) +{ + NewsWindow::duration = 555; + const Window *w = FindWindowByClass(WC_SEND_NETWORK_MSG); + this->chat_height = (w != NULL) ? w->height : 0; + this->status_height = FindWindowById(WC_STATUS_BAR, 0)->height; -assert_compile(lengthof(_news_type_data) == NT_END); + this->flags |= WF_DISABLE_VP_SCROLL; -/** - * Return the news display option. - * @return display options - */ -NewsDisplay NewsTypeData::GetDisplay() const -{ - uint index; - const SettingDesc *sd = GetSettingFromName(this->name, &index); - assert(sd != NULL); - void *ptr = GetVariableAddress(NULL, &sd->save); - return (NewsDisplay)ReadValue(ptr, sd->save.conv); -} + this->CreateNestedTree(); -/** Window class displaying a news item. */ -struct NewsWindow : Window { - uint16 chat_height; ///< Height of the chat window. - uint16 status_height; ///< Height of the status bar window - const NewsItem *ni; ///< News item to display. - static uint duration; ///< Remaining time for showing current news message (may only be accessed while a news item is displayed). + /* For company news with a face we have a separate headline in param[0] */ + if (desc == &_company_news_desc) this->GetWidget(WID_N_TITLE)->widget_data = this->ni->params[0]; - NewsWindow(WindowDesc *desc, const NewsItem *ni) : Window(desc), ni(ni) - { - NewsWindow::duration = 555; - const Window *w = FindWindowByClass(WC_SEND_NETWORK_MSG); - this->chat_height = (w != NULL) ? w->height : 0; - this->status_height = FindWindowById(WC_STATUS_BAR, 0)->height; + this->FinishInitNested(0); - this->flags |= WF_DISABLE_VP_SCROLL; - - this->CreateNestedTree(); - - /* For company news with a face we have a separate headline in param[0] */ - if (desc == &_company_news_desc) this->GetWidget(WID_N_TITLE)->widget_data = this->ni->params[0]; - - this->FinishInitNested(0); - - /* Initialize viewport if it exists. */ - NWidgetViewport *nvp = this->GetWidget(WID_N_VIEWPORT); - if (nvp != NULL) { - nvp->InitializeViewport(this, ni->reftype1 == NR_VEHICLE ? 0x80000000 | ni->ref1 : GetReferenceTile(ni->reftype1, ni->ref1), ZOOM_LVL_NEWS); - if (this->ni->flags & NF_NO_TRANSPARENT) nvp->disp_flags |= ND_NO_TRANSPARENCY; - if ((this->ni->flags & NF_INCOLOUR) == 0) { - nvp->disp_flags |= ND_SHADE_GREY; - } else if (this->ni->flags & NF_SHADE) { - nvp->disp_flags |= ND_SHADE_DIMMED; - } - } - - PositionNewsMessage(this); - } - - void DrawNewsBorder(const Rect &r) const - { - GfxFillRect(r.left, r.top, r.right, r.bottom, PC_WHITE); - - GfxFillRect(r.left, r.top, r.left, r.bottom, PC_BLACK); - GfxFillRect(r.right, r.top, r.right, r.bottom, PC_BLACK); - GfxFillRect(r.left, r.top, r.right, r.top, PC_BLACK); - GfxFillRect(r.left, r.bottom, r.right, r.bottom, PC_BLACK); - } - - virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number) - { - Point pt = { 0, _screen.height }; - return pt; - } - - virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) - { - StringID str = STR_NULL; - switch (widget) { - case WID_N_MESSAGE: - CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); - str = this->ni->string_id; - break; - - case WID_N_COMPANY_MSG: - str = this->GetCompanyMessageString(); - break; - - case WID_N_VEH_NAME: - case WID_N_VEH_TITLE: - str = this->GetNewVehicleMessageString(widget); - break; - - case WID_N_VEH_INFO: { - assert(this->ni->reftype1 == NR_ENGINE); - EngineID engine = this->ni->ref1; - str = GetEngineInfoString(engine); - break; - } - default: - return; // Do nothing - } - - /* Update minimal size with length of the multi-line string. */ - Dimension d = *size; - d.width = (d.width >= padding.width) ? d.width - padding.width : 0; - d.height = (d.height >= padding.height) ? d.height - padding.height : 0; - d = GetStringMultiLineBoundingBox(str, d); - d.width += padding.width; - d.height += padding.height; - *size = maxdim(*size, d); - } - - virtual void SetStringParameters(int widget) const - { - if (widget == WID_N_DATE) SetDParam(0, this->ni->date); - } - - virtual void DrawWidget(const Rect &r, int widget) const - { - switch (widget) { - case WID_N_CAPTION: - DrawCaption(r, COLOUR_LIGHT_BLUE, this->owner, STR_NEWS_MESSAGE_CAPTION); - break; - - case WID_N_PANEL: - this->DrawNewsBorder(r); - break; - - case WID_N_MESSAGE: - CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->ni->string_id, TC_FROMSTRING, SA_CENTER); - break; - - case WID_N_MGR_FACE: { - const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data; - DrawCompanyManagerFace(cni->face, cni->colour, r.left, r.top); - GfxFillRect(r.left + 1, r.top, r.left + 1 + 91, r.top + 118, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); - break; - } - case WID_N_MGR_NAME: { - const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data; - SetDParamStr(0, cni->president_name); - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER); - break; - } - case WID_N_COMPANY_MSG: - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetCompanyMessageString(), TC_FROMSTRING, SA_CENTER); - break; - - case WID_N_VEH_BKGND: - GfxFillRect(r.left, r.top, r.right, r.bottom, PC_GREY); - break; - - case WID_N_VEH_NAME: - case WID_N_VEH_TITLE: - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetNewVehicleMessageString(widget), TC_FROMSTRING, SA_CENTER); - break; - - case WID_N_VEH_SPR: { - assert(this->ni->reftype1 == NR_ENGINE); - EngineID engine = this->ni->ref1; - DrawVehicleEngine(r.left, r.right, (r.left + r.right) / 2, (r.top + r.bottom) / 2, 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 == NR_ENGINE); - EngineID engine = this->ni->ref1; - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER); - break; - } + /* Initialize viewport if it exists. */ + NWidgetViewport *nvp = this->GetWidget(WID_N_VIEWPORT); + if (nvp != NULL) { + nvp->InitializeViewport(this, ni->reftype1 == NR_VEHICLE ? 0x80000000 | ni->ref1 : GetReferenceTile(ni->reftype1, ni->ref1), ZOOM_LVL_NEWS); + if (this->ni->flags & NF_NO_TRANSPARENT) nvp->disp_flags |= ND_NO_TRANSPARENCY; + if ((this->ni->flags & NF_INCOLOUR) == 0) { + nvp->disp_flags |= ND_SHADE_GREY; + } else if (this->ni->flags & NF_SHADE) { + nvp->disp_flags |= ND_SHADE_DIMMED; } } - virtual void OnClick(Point pt, int widget, int click_count) - { - switch (widget) { - case WID_N_CLOSEBOX: - NewsWindow::duration = 0; - delete this; - _forced_news = NULL; - break; + PositionNewsMessage(this); +} - case WID_N_CAPTION: - if (this->ni->reftype1 == NR_VEHICLE) { - const Vehicle *v = Vehicle::Get(this->ni->ref1); - ShowVehicleViewWindow(v); - } - break; +void NewsWindow::DrawNewsBorder(const Rect &r) const +{ + GfxFillRect(r.left, r.top, r.right, r.bottom, PC_WHITE); - case WID_N_VIEWPORT: - break; // Ignore clicks + GfxFillRect(r.left, r.top, r.left, r.bottom, PC_BLACK); + GfxFillRect(r.right, r.top, r.right, r.bottom, PC_BLACK); + GfxFillRect(r.left, r.top, r.right, r.top, PC_BLACK); + GfxFillRect(r.left, r.bottom, r.right, r.bottom, PC_BLACK); +} - default: - if (this->ni->reftype1 == NR_VEHICLE) { - const Vehicle *v = Vehicle::Get(this->ni->ref1); - ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos); +/* virtual */ Point NewsWindow::OnInitialPosition(int16 sm_width, int16 sm_height, int window_number) +{ + Point pt = { 0, _screen.height }; + return pt; +} + +/* virtual */ void NewsWindow::UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) +{ + StringID str = STR_NULL; + switch (widget) { + case WID_N_MESSAGE: + CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); + str = this->ni->string_id; + break; + + case WID_N_COMPANY_MSG: + str = this->GetCompanyMessageString(); + break; + + case WID_N_VEH_NAME: + case WID_N_VEH_TITLE: + str = this->GetNewVehicleMessageString(widget); + break; + + case WID_N_VEH_INFO: { + assert(this->ni->reftype1 == NR_ENGINE); + EngineID engine = this->ni->ref1; + str = GetEngineInfoString(engine); + break; + } + default: + return; // Do nothing + } + + /* Update minimal size with length of the multi-line string. */ + Dimension d = *size; + d.width = (d.width >= padding.width) ? d.width - padding.width : 0; + d.height = (d.height >= padding.height) ? d.height - padding.height : 0; + d = GetStringMultiLineBoundingBox(str, d); + d.width += padding.width; + d.height += padding.height; + *size = maxdim(*size, d); +} + +/* virtual */ void NewsWindow::SetStringParameters(int widget) const +{ + if (widget == WID_N_DATE) SetDParam(0, this->ni->date); +} + +/* virtual */ void NewsWindow::DrawWidget(const Rect &r, int widget) const +{ + switch (widget) { + case WID_N_CAPTION: + DrawCaption(r, COLOUR_LIGHT_BLUE, this->owner, STR_NEWS_MESSAGE_CAPTION); + break; + + case WID_N_PANEL: + this->DrawNewsBorder(r); + break; + + case WID_N_MESSAGE: + CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->ni->string_id, TC_FROMSTRING, SA_CENTER); + break; + + case WID_N_MGR_FACE: { + const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data; + DrawCompanyManagerFace(cni->face, cni->colour, r.left, r.top); + GfxFillRect(r.left + 1, r.top, r.left + 1 + 91, r.top + 118, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); + break; + } + case WID_N_MGR_NAME: { + const CompanyNewsInformation *cni = (const CompanyNewsInformation*)this->ni->free_data; + SetDParamStr(0, cni->president_name); + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_JUST_RAW_STRING, TC_FROMSTRING, SA_CENTER); + break; + } + case WID_N_COMPANY_MSG: + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetCompanyMessageString(), TC_FROMSTRING, SA_CENTER); + break; + + case WID_N_VEH_BKGND: + GfxFillRect(r.left, r.top, r.right, r.bottom, PC_GREY); + break; + + case WID_N_VEH_NAME: + case WID_N_VEH_TITLE: + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->GetNewVehicleMessageString(widget), TC_FROMSTRING, SA_CENTER); + break; + + case WID_N_VEH_SPR: { + assert(this->ni->reftype1 == NR_ENGINE); + EngineID engine = this->ni->ref1; + DrawVehicleEngine(r.left, r.right, (r.left + r.right) / 2, (r.top + r.bottom) / 2, 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 == NR_ENGINE); + EngineID engine = this->ni->ref1; + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, GetEngineInfoString(engine), TC_FROMSTRING, SA_CENTER); + break; + } + } +} + +/* virtual */ void NewsWindow::OnClick(Point pt, int widget, int click_count) +{ + switch (widget) { + case WID_N_CLOSEBOX: + NewsWindow::duration = 0; + delete this; + _forced_news = NULL; + break; + + case WID_N_CAPTION: + if (this->ni->reftype1 == NR_VEHICLE) { + const Vehicle *v = Vehicle::Get(this->ni->ref1); + ShowVehicleViewWindow(v); + } + break; + + case WID_N_VIEWPORT: + break; // Ignore clicks + + default: + if (this->ni->reftype1 == NR_VEHICLE) { + const Vehicle *v = Vehicle::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); + if (_ctrl_pressed) { + if (tile1 != INVALID_TILE) ShowExtraViewPortWindow(tile1); + if (tile2 != INVALID_TILE) ShowExtraViewPortWindow(tile2); } else { - TileIndex tile1 = GetReferenceTile(this->ni->reftype1, this->ni->ref1); - TileIndex tile2 = GetReferenceTile(this->ni->reftype2, this->ni->ref2); - if (_ctrl_pressed) { - if (tile1 != INVALID_TILE) ShowExtraViewPortWindow(tile1); - if (tile2 != INVALID_TILE) ShowExtraViewPortWindow(tile2); - } else { - if ((tile1 == INVALID_TILE || !ScrollMainWindowToTile(tile1)) && tile2 != INVALID_TILE) { - ScrollMainWindowToTile(tile2); - } + if ((tile1 == INVALID_TILE || !ScrollMainWindowToTile(tile1)) && tile2 != INVALID_TILE) { + ScrollMainWindowToTile(tile2); } } - break; - } + } + break; } +} - virtual EventState OnKeyPress(WChar key, uint16 keycode) - { - if (keycode == WKC_SPACE) { - /* Don't continue. */ - delete this; - return ES_HANDLED; - } - return ES_NOT_HANDLED; +/* virtual */ EventState NewsWindow::OnKeyPress(WChar key, uint16 keycode) +{ + if (keycode == WKC_SPACE) { + /* Don't continue. */ + delete this; + return ES_HANDLED; } + return ES_NOT_HANDLED; +} - /** - * Some data on this window has become invalid. - * @param data Information about the changed data. - * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. - */ - virtual void OnInvalidateData(int data = 0, bool gui_scope = true) - { - if (!gui_scope) return; - /* The chatbar has notified us that is was either created or closed */ - int newtop = this->top + this->chat_height - data; - this->chat_height = data; - this->SetWindowTop(newtop); +/** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ +/* virtual */ void NewsWindow::OnInvalidateData(int data, bool gui_scope) +{ + if (!gui_scope) return; + /* The chatbar has notified us that is was either created or closed */ + int newtop = this->top + this->chat_height - data; + this->chat_height = data; + this->SetWindowTop(newtop); +} + +/* virtual */ void NewsWindow::OnTick() +{ + /* Scroll up newsmessages from the bottom in steps of 4 pixels */ + int newtop = max(this->top - 4, _screen.height - this->height - this->status_height - this->chat_height); + this->SetWindowTop(newtop); +} + +/** + * Moves the window so #newtop is new 'top' coordinate. Makes screen dirty where needed. + * @param newtop new top coordinate + */ +void NewsWindow::SetWindowTop(int newtop) +{ + if (this->top == newtop) return; + + int mintop = min(newtop, this->top); + int maxtop = max(newtop, this->top); + if (this->viewport != NULL) this->viewport->top += newtop - this->top; + this->top = newtop; + + SetDirtyBlocks(this->left, mintop, this->left + this->width, maxtop + this->height); +} + +StringID NewsWindow::GetCompanyMessageString() const +{ + /* Company news with a face have a separate headline, so the normal message is shifted by two params */ + CopyInDParam(0, this->ni->params + 2, lengthof(this->ni->params) - 2); + return this->ni->params[1]; +} + +StringID NewsWindow::GetNewVehicleMessageString(int widget) const +{ + assert(this->ni->reftype1 == NR_ENGINE); + EngineID engine = this->ni->ref1; + + switch (widget) { + case WID_N_VEH_TITLE: + SetDParam(0, GetEngineCategoryName(engine)); + return STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE; + + case WID_N_VEH_NAME: + SetDParam(0, engine); + return STR_NEWS_NEW_VEHICLE_TYPE; + + default: + NOT_REACHED(); } - - virtual void OnTick() - { - /* Scroll up newsmessages from the bottom in steps of 4 pixels */ - int newtop = max(this->top - 4, _screen.height - this->height - this->status_height - this->chat_height); - this->SetWindowTop(newtop); - } - -private: - /** - * Moves the window so #newtop is new 'top' coordinate. Makes screen dirty where needed. - * @param newtop new top coordinate - */ - void SetWindowTop(int newtop) - { - if (this->top == newtop) return; - - int mintop = min(newtop, this->top); - int maxtop = max(newtop, this->top); - if (this->viewport != NULL) this->viewport->top += newtop - this->top; - this->top = newtop; - - SetDirtyBlocks(this->left, mintop, this->left + this->width, maxtop + this->height); - } - - StringID GetCompanyMessageString() const - { - /* Company news with a face have a separate headline, so the normal message is shifted by two params */ - CopyInDParam(0, this->ni->params + 2, lengthof(this->ni->params) - 2); - return this->ni->params[1]; - } - - StringID GetNewVehicleMessageString(int widget) const - { - assert(this->ni->reftype1 == NR_ENGINE); - EngineID engine = this->ni->ref1; - - switch (widget) { - case WID_N_VEH_TITLE: - SetDParam(0, GetEngineCategoryName(engine)); - return STR_NEWS_NEW_VEHICLE_NOW_AVAILABLE; - - case WID_N_VEH_NAME: - SetDParam(0, engine); - return STR_NEWS_NEW_VEHICLE_TYPE; - - default: - NOT_REACHED(); - } - } -}; - +} /* static */ uint NewsWindow::duration = 0; // Instance creation. /** Open up an own newspaper window for the news item */ -static void ShowNewspaper(const NewsItem *ni) +void ShowNewspaper(const NewsItem *ni) { SoundFx sound = _news_type_data[ni->type].sound; if (sound != 0 && _settings_client.sound.news_full) SndPlayFx(sound); @@ -537,7 +452,7 @@ } /** Show news item in the ticker */ -static void ShowTicker(const NewsItem *ni) +void ShowTicker(const NewsItem *ni) { if (_settings_client.sound.news_ticker) SndPlayFx(SND_16_MORSE); @@ -545,448 +460,6 @@ InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_TICKER); } -/** Initialize the news-items data structures */ -void InitNewsItemStructs() -{ - for (NewsItem *ni = _oldest_news; ni != NULL; ) { - NewsItem *next = ni->next; - delete ni; - ni = next; - } - - _total_news = 0; - _oldest_news = NULL; - _latest_news = NULL; - _forced_news = NULL; - _current_news = NULL; - _statusbar_news_item = NULL; - NewsWindow::duration = 0; -} - -/** - * Are we ready to show another news item? - * Only if nothing is in the newsticker and no newspaper is displayed - */ -static bool ReadyForNextItem() -{ - const NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news; - if (ni == NULL) return true; - - /* Ticker message - * Check if the status bar message is still being displayed? */ - if (IsNewsTickerShown()) return false; - - /* Newspaper message, decrement duration counter */ - if (NewsWindow::duration != 0) NewsWindow::duration--; - - /* neither newsticker nor newspaper are running */ - return (NewsWindow::duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL); -} - -/** Move to the next news item */ -static void MoveToNextItem() -{ - InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED); // invalidate the statusbar - DeleteWindowById(WC_NEWS_WINDOW, 0); // close the newspapers window if shown - _forced_news = NULL; - _statusbar_news_item = NULL; - - /* if we're not at the last item, then move on */ - if (_current_news != _latest_news) { - _current_news = (_current_news == NULL) ? _oldest_news : _current_news->next; - const NewsItem *ni = _current_news; - const NewsType type = ni->type; - - /* check the date, don't show too old items */ - if (_date - _news_type_data[type].age > ni->date) return; - - switch (_news_type_data[type].GetDisplay()) { - default: NOT_REACHED(); - case ND_OFF: // Off - show nothing only a small reminder in the status bar - InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER); - break; - - case ND_SUMMARY: // Summary - show ticker - ShowTicker(ni); - break; - - case ND_FULL: // Full - show newspaper - ShowNewspaper(ni); - break; - } - } -} - -/** - * Add a new newsitem to be shown. - * @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 deleteing 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 deleteing the news when the object is deleted. - * @param free_data Pointer to data that must be freed once the news message is cleared - * - * @see NewsSubtype - */ -void AddNewsItem(StringID string, NewsType type, NewsFlag flags, NewsReferenceType reftype1, uint32 ref1, NewsReferenceType reftype2, uint32 ref2, void *free_data) -{ - if (_game_mode == GM_MENU) return; - - /* Create new news item node */ - NewsItem *ni = new NewsItem; - - ni->string_id = string; - ni->type = type; - ni->flags = flags; - - /* show this news message in colour? */ - if (_cur_year >= _settings_client.gui.coloured_news_year) ni->flags |= NF_INCOLOUR; - - ni->reftype1 = reftype1; - ni->reftype2 = reftype2; - ni->ref1 = ref1; - ni->ref2 = ref2; - ni->free_data = free_data; - ni->date = _date; - CopyOutDParam(ni->params, 0, lengthof(ni->params)); - - if (_total_news++ == 0) { - assert(_oldest_news == NULL); - _oldest_news = ni; - ni->prev = NULL; - } else { - assert(_latest_news->next == NULL); - _latest_news->next = ni; - ni->prev = _latest_news; - } - - ni->next = NULL; - _latest_news = ni; - - SetWindowDirty(WC_MESSAGE_HISTORY, 0); -} - -/** - * Create a new custom news item. - * @param tile unused - * @param flags type of operation - * @param p1 various bitstuffed elements - * - p1 = (bit 0 - 7) - NewsType of the message. - * - p1 = (bit 8 - 15) - NewsReferenceType of first reference. - * - p1 = (bit 16 - 23) - Company this news message is for. - * @param p2 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(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) -{ - if (_current_company != OWNER_DEITY) return CMD_ERROR; - - NewsType type = (NewsType)GB(p1, 0, 8); - NewsReferenceType reftype1 = (NewsReferenceType)GB(p1, 8, 8); - CompanyID company = (CompanyID)GB(p1, 16, 8); - - if (company != INVALID_OWNER && !Company::IsValidID(company)) return CMD_ERROR; - if (type >= NT_END) return CMD_ERROR; - if (StrEmpty(text)) return CMD_ERROR; - - switch (reftype1) { - case NR_NONE: break; - case NR_TILE: - if (!IsValidTile(p2)) return CMD_ERROR; - break; - - case NR_VEHICLE: - if (!Vehicle::IsValidID(p2)) return CMD_ERROR; - break; - - case NR_STATION: - if (!Station::IsValidID(p2)) return CMD_ERROR; - break; - - case NR_INDUSTRY: - if (!Industry::IsValidID(p2)) return CMD_ERROR; - break; - - case NR_TOWN: - if (!Town::IsValidID(p2)) return CMD_ERROR; - break; - - case NR_ENGINE: - if (!Engine::IsValidID(p2)) return CMD_ERROR; - break; - - default: return CMD_ERROR; - } - - if (company != INVALID_OWNER && company != _local_company) return CommandCost(); - - if (flags & DC_EXEC) { - char *news = strdup(text); - SetDParamStr(0, news); - AddNewsItem(STR_NEWS_CUSTOM_ITEM, type, NF_NORMAL, reftype1, p2, NR_NONE, UINT32_MAX, news); - } - - return CommandCost(); -} - -/** Delete a news item from the queue */ -static void DeleteNewsItem(NewsItem *ni) -{ - /* Delete the news from the news queue. */ - if (ni->prev != NULL) { - ni->prev->next = ni->next; - } else { - assert(_oldest_news == ni); - _oldest_news = ni->next; - } - - if (ni->next != NULL) { - ni->next->prev = ni->prev; - } else { - assert(_latest_news == ni); - _latest_news = ni->prev; - } - - _total_news--; - - if (_forced_news == ni || _current_news == ni || _statusbar_news_item == ni) { - /* When we're the current news, go to the previous item first; - * we just possibly made that the last news item. */ - if (_current_news == ni) _current_news = ni->prev; - - /* About to remove the currently forced item (shown as newspapers) || - * about to remove the currently displayed item (newspapers, ticker, or just a reminder) */ - MoveToNextItem(); - } - - delete ni; - - SetWindowDirty(WC_MESSAGE_HISTORY, 0); -} - -/** - * Delete a news item type about a vehicle. - * When the news item type is INVALID_STRING_ID all news about the vehicle gets deleted. - * @param vid The vehicle to remove the news for. - * @param news The news type to remove. - */ -void DeleteVehicleNews(VehicleID vid, StringID news) -{ - NewsItem *ni = _oldest_news; - - while (ni != NULL) { - NewsItem *next = ni->next; - if (((ni->reftype1 == NR_VEHICLE && ni->ref1 == vid) || (ni->reftype2 == NR_VEHICLE && ni->ref2 == vid)) && - (news == INVALID_STRING_ID || ni->string_id == news)) { - DeleteNewsItem(ni); - } - ni = next; - } -} - -/** - * Remove news regarding given station so there are no 'unknown station now accepts Mail' - * or 'First train arrived at unknown station' news items. - * @param sid station to remove news about - */ -void DeleteStationNews(StationID sid) -{ - NewsItem *ni = _oldest_news; - - while (ni != NULL) { - NewsItem *next = ni->next; - if ((ni->reftype1 == NR_STATION && ni->ref1 == sid) || (ni->reftype2 == NR_STATION && ni->ref2 == sid)) { - DeleteNewsItem(ni); - } - ni = next; - } -} - -/** - * Remove news regarding given industry - * @param iid industry to remove news about - */ -void DeleteIndustryNews(IndustryID iid) -{ - NewsItem *ni = _oldest_news; - - while (ni != NULL) { - NewsItem *next = ni->next; - if ((ni->reftype1 == NR_INDUSTRY && ni->ref1 == iid) || (ni->reftype2 == NR_INDUSTRY && ni->ref2 == iid)) { - DeleteNewsItem(ni); - } - ni = next; - } -} - -/** - * Remove engine announcements for invalid engines. - */ -void DeleteInvalidEngineNews() -{ - NewsItem *ni = _oldest_news; - - while (ni != NULL) { - NewsItem *next = ni->next; - if ((ni->reftype1 == NR_ENGINE && (!Engine::IsValidID(ni->ref1) || !Engine::Get(ni->ref1)->IsEnabled())) || - (ni->reftype2 == NR_ENGINE && (!Engine::IsValidID(ni->ref2) || !Engine::Get(ni->ref2)->IsEnabled()))) { - DeleteNewsItem(ni); - } - ni = next; - } -} - -static void RemoveOldNewsItems() -{ - NewsItem *next; - for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) { - next = cur->next; - if (_date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur); - } -} - -/** - * Report a change in vehicle IDs (due to autoreplace) to affected vehicle news. - * @note Viewports of currently displayed news is changed via #ChangeVehicleViewports - * @param from_index the old vehicle ID - * @param to_index the new vehicle ID - */ -void ChangeVehicleNews(VehicleID from_index, VehicleID to_index) -{ - for (NewsItem *ni = _oldest_news; ni != NULL; ni = ni->next) { - if (ni->reftype1 == NR_VEHICLE && ni->ref1 == from_index) ni->ref1 = to_index; - if (ni->reftype2 == NR_VEHICLE && ni->ref2 == from_index) ni->ref2 = to_index; - if (ni->flags & NF_VEHICLE_PARAM0 && ni->params[0] == from_index) ni->params[0] = to_index; - } -} - -void NewsLoop() -{ - /* no news item yet */ - if (_total_news == 0) return; - - /* There is no status bar, so no reason to show news; - * especially important with the end game screen when - * there is no status bar but possible news. */ - if (FindWindowById(WC_STATUS_BAR, 0) == NULL) return; - - static byte _last_clean_month = 0; - - if (_last_clean_month != _cur_month) { - RemoveOldNewsItems(); - _last_clean_month = _cur_month; - } - - if (ReadyForNextItem()) MoveToNextItem(); -} - -/** Do a forced show of a specific message */ -static void ShowNewsMessage(const NewsItem *ni) -{ - assert(_total_news != 0); - - /* Delete the news window */ - DeleteWindowById(WC_NEWS_WINDOW, 0); - - /* setup forced news item */ - _forced_news = ni; - - if (_forced_news != NULL) { - DeleteWindowById(WC_NEWS_WINDOW, 0); - ShowNewspaper(ni); - } -} - -/** Show previous news item */ -void ShowLastNewsMessage() -{ - const NewsItem *ni = NULL; - if (_total_news == 0) { - return; - } else if (_forced_news == NULL) { - /* Not forced any news yet, show the current one, unless a news window is - * open (which can only be the current one), then show the previous item */ - if (_current_news == NULL) { - /* No news were shown yet resp. the last shown one was already deleted. - * Threat this as if _forced_news reached _oldest_news; so, wrap around and start anew with the latest. */ - ni = _latest_news; - } else { - const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); - ni = (w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev; - } - } else if (_forced_news == _oldest_news) { - /* We have reached the oldest news, start anew with the latest */ - ni = _latest_news; - } else { - /* 'Scrolling' through news history show each one in turn */ - ni = _forced_news->prev; - } - bool wrap = false; - for (;;) { - if (_news_type_data[ni->type].GetDisplay() != ND_OFF) { - ShowNewsMessage(ni); - break; - } - - ni = ni->prev; - if (ni == NULL) { - if (wrap) break; - /* We have reached the oldest news, start anew with the latest */ - ni = _latest_news; - wrap = true; - } - } -} - - -/** - * Draw an unformatted news message truncated to a maximum length. If - * length exceeds maximum length it will be postfixed by '...' - * @param left the left most location for the string - * @param right the right most location for the string - * @param y position of the string - * @param colour the colour the string will be shown in - * @param *ni NewsItem being printed - * @param maxw maximum width of string in pixels - */ -static void DrawNewsString(uint left, uint right, int y, TextColour colour, const NewsItem *ni) -{ - char buffer[512], buffer2[512]; - StringID str; - - CopyInDParam(0, ni->params, lengthof(ni->params)); - str = ni->string_id; - - GetString(buffer, str, lastof(buffer)); - /* Copy the just gotten string to another buffer to remove any formatting - * from it such as big fonts, etc. */ - const char *ptr = buffer; - char *dest = buffer2; - WChar c_last = '\0'; - for (;;) { - WChar c = Utf8Consume(&ptr); - if (c == 0) break; - /* Make a space from a newline, but ignore multiple newlines */ - if (c == '\n' && c_last != '\n') { - dest[0] = ' '; - dest++; - } else if (c == '\r') { - dest[0] = dest[1] = dest[2] = dest[3] = ' '; - dest += 4; - } else if (IsPrintable(c)) { - dest += Utf8Encode(dest, c); - } - c_last = c; - } - - *dest = '\0'; - /* Truncate and show string; postfixed by '...' if necessary */ - DrawString(left, right, y, buffer2, colour); -} - struct MessageHistoryWindow : Window { static const int top_spacing; ///< Additional spacing at the top of the #WID_MH_BACKGROUND widget. static const int bottom_spacing; ///< Additional spacing at the bottom of the #WID_MH_BACKGROUND widget. diff --git a/src/news_gui.h b/src/news_gui.h --- a/src/news_gui.h +++ b/src/news_gui.h @@ -12,6 +12,36 @@ #ifndef NEWS_GUI_H #define NEWS_GUI_H +#include "news_func.h" +#include "window_gui.h" + +/** Window class displaying a news item. */ +struct NewsWindow : Window { + uint16 chat_height; ///< Height of the chat window. + uint16 status_height; ///< Height of the status bar window + const NewsItem *ni; ///< News item to display. + static uint duration; ///< Remaining time for showing current news message (may only be accessed while a news item is displayed). + + NewsWindow(WindowDesc *desc, const NewsItem *ni); + void DrawNewsBorder(const Rect &r) const; + + virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number); + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize); + virtual void SetStringParameters(int widget) const; + virtual void DrawWidget(const Rect &r, int widget) const; + virtual void OnClick(Point pt, int widget, int click_count); + virtual EventState OnKeyPress(WChar key, uint16 keycode); + virtual void OnInvalidateData(int data = 0, bool gui_scope = true); + virtual void OnTick(); + +private: + void SetWindowTop(int newtop); + StringID GetCompanyMessageString() const; + StringID GetNewVehicleMessageString(int widget) const; +}; + +void ShowNewspaper(const NewsItem *ni); +void ShowTicker(const NewsItem *ni); void ShowLastNewsMessage(); void ShowMessageHistory(); diff --git a/src/news_type.h b/src/news_type.h --- a/src/news_type.h +++ b/src/news_type.h @@ -130,6 +130,29 @@ NewsDisplay GetDisplay() const; }; +/** + * Per-NewsType data + */ +static NewsTypeData _news_type_data[] = { + /* name, age, sound, */ + NewsTypeData("news_display.arrival_player", 60, SND_1D_APPLAUSE ), ///< NT_ARRIVAL_COMPANY + NewsTypeData("news_display.arrival_other", 60, SND_1D_APPLAUSE ), ///< NT_ARRIVAL_OTHER + NewsTypeData("news_display.accident", 90, SND_BEGIN ), ///< NT_ACCIDENT + NewsTypeData("news_display.company_info", 60, SND_BEGIN ), ///< NT_COMPANY_INFO + NewsTypeData("news_display.open", 90, SND_BEGIN ), ///< NT_INDUSTRY_OPEN + NewsTypeData("news_display.close", 90, SND_BEGIN ), ///< NT_INDUSTRY_CLOSE + NewsTypeData("news_display.economy", 30, SND_BEGIN ), ///< NT_ECONOMY + NewsTypeData("news_display.production_player", 30, SND_BEGIN ), ///< NT_INDUSTRY_COMPANY + NewsTypeData("news_display.production_other", 30, SND_BEGIN ), ///< NT_INDUSTRY_OTHER + NewsTypeData("news_display.production_nobody", 30, SND_BEGIN ), ///< NT_INDUSTRY_NOBODY + NewsTypeData("news_display.advice", 150, SND_BEGIN ), ///< NT_ADVICE + NewsTypeData("news_display.new_vehicles", 30, SND_1E_OOOOH ), ///< NT_NEW_VEHICLES + NewsTypeData("news_display.acceptance", 90, SND_BEGIN ), ///< NT_ACCEPTANCE + NewsTypeData("news_display.subsidies", 180, SND_BEGIN ), ///< NT_SUBSIDIES + NewsTypeData("news_display.general", 60, SND_BEGIN ), ///< NT_GENERAL +}; +assert_compile(lengthof(_news_type_data) == NT_END); + /** Information about a single item of news. */ struct NewsItem { NewsItem *prev; ///< Previous news item