diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 795b3ddbf4..ef8cbac5e0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -219,6 +219,11 @@ add_files( landscape.h landscape_type.h language.h + league_base.h + league_cmd.cpp + league_gui.h + league_gui.cpp + league_type.h livery.h main_gui.cpp map.cpp diff --git a/src/command.cpp b/src/command.cpp index e1a1834f6d..96f67ca44d 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -258,6 +258,12 @@ CommandProc CmdSetTimetableStart; CommandProc CmdOpenCloseAirport; +CommandProc CmdCreateLeagueTable; +CommandProcEx CmdCreateLeagueTableElement; +CommandProc CmdUpdateLeagueTableElementData; +CommandProcEx CmdUpdateLeagueTableElementScore; +CommandProc CmdRemoveLeagueTableElement; + CommandProc CmdProgramSignalTraceRestrict; CommandProc CmdCreateTraceRestrictSlot; CommandProc CmdAlterTraceRestrictSlot; @@ -503,6 +509,12 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT + DEF_CMD(CmdCreateLeagueTable, CMD_STR_SEP | CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_LEAGUE_TABLE + DEF_CMD(CmdCreateLeagueTableElement, CMD_STR_SEP | CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_LEAGUE_TABLE_ELEMENT + DEF_CMD(CmdUpdateLeagueTableElementData, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_UPDATE_LEAGUE_TABLE_ELEMENT_DATA + DEF_CMD(CmdUpdateLeagueTableElementScore, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_UPDATE_LEAGUE_TABLE_ELEMENT_SCORE + DEF_CMD(CmdRemoveLeagueTableElement, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_LEAGUE_TABLE_ELEMENT + DEF_CMD(CmdProgramSignalTraceRestrict, 0, CMDT_OTHER_MANAGEMENT ), // CMD_PROGRAM_TRACERESTRICT_SIGNAL DEF_CMD(CmdCreateTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_TRACERESTRICT_SLOT DEF_CMD(CmdAlterTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ALTER_TRACERESTRICT_SLOT diff --git a/src/command_type.h b/src/command_type.h index 0d06214586..e5619348ed 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -479,6 +479,12 @@ enum Commands { CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft + CMD_CREATE_LEAGUE_TABLE, ///< create a new league table + CMD_CREATE_LEAGUE_TABLE_ELEMENT, ///< create a new element in a league table + CMD_UPDATE_LEAGUE_TABLE_ELEMENT_DATA, ///< update the data fields of a league table element + CMD_UPDATE_LEAGUE_TABLE_ELEMENT_SCORE, ///< update the score of a league table element + CMD_REMOVE_LEAGUE_TABLE_ELEMENT, ///< remove a league table element + CMD_PROGRAM_TRACERESTRICT_SIGNAL, ///< modify a signal tracerestrict program CMD_CREATE_TRACERESTRICT_SLOT, ///< create a tracerestrict slot CMD_ALTER_TRACERESTRICT_SLOT, ///< alter a tracerestrict slot diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index a0e83913f8..e57c0f8a69 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -18,7 +18,6 @@ #include "window_func.h" #include "date_func.h" #include "gfx_func.h" -#include "sortlist_type.h" #include "core/geometry_func.hpp" #include "currency.h" #include "zoom_func.h" @@ -1160,192 +1159,6 @@ void ShowCargoPaymentRates() AllocateWindowDescFront(&_cargo_payment_rates_desc, 0); } -/************************/ -/* COMPANY LEAGUE TABLE */ -/************************/ - -static const StringID _performance_titles[] = { - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_PRESIDENT, - STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TYCOON, -}; - -static inline StringID GetPerformanceTitleFromValue(uint value) -{ - return _performance_titles[std::min(value, 1000u) >> 6]; -} - -class CompanyLeagueWindow : public Window { -private: - GUIList companies; - uint ordinal_width; ///< The width of the ordinal number - uint text_width; ///< The width of the actual text - uint icon_width; ///< The width of the company icon - int line_height; ///< Height of the text lines - - /** - * (Re)Build the company league list - */ - void BuildCompanyList() - { - if (!this->companies.NeedRebuild()) return; - - this->companies.clear(); - - for (const Company *c : Company::Iterate()) { - this->companies.push_back(c); - } - - this->companies.shrink_to_fit(); - this->companies.RebuildDone(); - } - - /** Sort the company league by performance history */ - static bool PerformanceSorter(const Company * const &c1, const Company * const &c2) - { - return c2->old_economy[0].performance_history < c1->old_economy[0].performance_history; - } - -public: - CompanyLeagueWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) - { - this->InitNested(window_number); - this->companies.ForceRebuild(); - this->companies.NeedResort(); - } - - void OnPaint() override - { - this->BuildCompanyList(); - this->companies.Sort(&PerformanceSorter); - - this->DrawWidgets(); - } - - void DrawWidget(const Rect &r, int widget) const override - { - if (widget != WID_CL_BACKGROUND) return; - - int icon_y_offset = 1 + (FONT_HEIGHT_NORMAL - this->line_height) / 2; - uint y = r.top + WD_FRAMERECT_TOP - icon_y_offset; - - bool rtl = _current_text_dir == TD_RTL; - uint ordinal_left = rtl ? r.right - WD_FRAMERECT_LEFT - this->ordinal_width : r.left + WD_FRAMERECT_LEFT; - uint ordinal_right = rtl ? r.right - WD_FRAMERECT_LEFT : r.left + WD_FRAMERECT_LEFT + this->ordinal_width; - uint icon_left = r.left + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT + (rtl ? this->text_width : this->ordinal_width); - uint text_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - WD_FRAMERECT_LEFT - this->text_width; - uint text_right = rtl ? r.left + WD_FRAMERECT_LEFT + this->text_width : r.right - WD_FRAMERECT_LEFT; - - for (uint i = 0; i != this->companies.size(); i++) { - const Company *c = this->companies[i]; - DrawString(ordinal_left, ordinal_right, y, i + STR_ORDINAL_NUMBER_1ST, i == 0 ? TC_WHITE : TC_YELLOW); - - DrawCompanyIcon(c->index, icon_left, y + icon_y_offset); - - SetDParam(0, c->index); - SetDParam(1, c->index); - SetDParam(2, GetPerformanceTitleFromValue(c->old_economy[0].performance_history)); - DrawString(text_left, text_right, y, STR_COMPANY_LEAGUE_COMPANY_NAME); - y += this->line_height; - } - } - - void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override - { - if (widget != WID_CL_BACKGROUND) return; - - this->ordinal_width = 0; - for (uint i = 0; i < MAX_COMPANIES; i++) { - this->ordinal_width = std::max(this->ordinal_width, GetStringBoundingBox(STR_ORDINAL_NUMBER_1ST + i).width); - } - this->ordinal_width += 5; // Keep some extra spacing - - uint widest_width = 0; - uint widest_title = 0; - for (uint i = 0; i < lengthof(_performance_titles); i++) { - uint width = GetStringBoundingBox(_performance_titles[i]).width; - if (width > widest_width) { - widest_title = i; - widest_width = width; - } - } - - Dimension d = GetSpriteSize(SPR_COMPANY_ICON); - this->icon_width = d.width + 2; - this->line_height = std::max(d.height + 2, FONT_HEIGHT_NORMAL); - - for (const Company *c : Company::Iterate()) { - SetDParam(0, c->index); - SetDParam(1, c->index); - SetDParam(2, _performance_titles[widest_title]); - widest_width = std::max(widest_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_NAME).width); - } - - this->text_width = widest_width + 30; // Keep some extra spacing - - size->width = WD_FRAMERECT_LEFT + this->ordinal_width + WD_FRAMERECT_RIGHT + this->icon_width + WD_FRAMERECT_LEFT + this->text_width + WD_FRAMERECT_RIGHT; - size->height = WD_FRAMERECT_TOP + this->line_height * MAX_COMPANIES + WD_FRAMERECT_BOTTOM; - } - - - void OnGameTick() override - { - if (this->companies.NeedResort()) { - this->SetDirty(); - } - } - - /** - * 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. - */ - void OnInvalidateData(int data = 0, bool gui_scope = true) override - { - if (data == 0) { - /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */ - this->companies.ForceRebuild(); - } else { - this->companies.ForceResort(); - } - } -}; - -static const NWidgetPart _nested_company_league_widgets[] = { - NWidget(NWID_HORIZONTAL), - NWidget(WWT_CLOSEBOX, COLOUR_BROWN), - NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_COMPANY_LEAGUE_TABLE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), - NWidget(WWT_SHADEBOX, COLOUR_BROWN), - NWidget(WWT_STICKYBOX, COLOUR_BROWN), - EndContainer(), - NWidget(WWT_PANEL, COLOUR_BROWN, WID_CL_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), -}; - -static WindowDesc _company_league_desc( - WDP_AUTO, "league", 0, 0, - WC_COMPANY_LEAGUE, WC_NONE, - 0, - _nested_company_league_widgets, lengthof(_nested_company_league_widgets) -); - -void ShowCompanyLeagueTable() -{ - AllocateWindowDescFront(&_company_league_desc, 0); -} - /*****************************/ /* PERFORMANCE RATING DETAIL */ /*****************************/ diff --git a/src/graph_gui.h b/src/graph_gui.h index b1d08b8e7b..9556ad36c7 100644 --- a/src/graph_gui.h +++ b/src/graph_gui.h @@ -20,7 +20,6 @@ void ShowDeliveredCargoGraph(); void ShowPerformanceHistoryGraph(); void ShowCompanyValueGraph(); void ShowCargoPaymentRates(); -void ShowCompanyLeagueTable(); void ShowPerformanceRatingDetail(); void ShowStationCargo(StationID); diff --git a/src/league_base.h b/src/league_base.h new file mode 100644 index 0000000000..8a70db4ca2 --- /dev/null +++ b/src/league_base.h @@ -0,0 +1,68 @@ +/* + * 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 league_base.h %LeagueTable base class. */ + +#ifndef LEAGUE_BASE_H +#define LEAGUE_BASE_H + +#include "company_type.h" +#include "goal_type.h" +#include "league_type.h" +#include "core/pool_type.hpp" + +bool IsValidLink(Link link); + +typedef Pool LeagueTableElementPool; +extern LeagueTableElementPool _league_table_element_pool; + +typedef Pool LeagueTablePool; +extern LeagueTablePool _league_table_pool; + + +/** + * Struct about league table elements. + * Each LeagueTable is composed of one or more elements. Elements are sorted by their rating (higher=better). + **/ +struct LeagueTableElement : LeagueTableElementPool::PoolItem<&_league_table_element_pool> { + LeagueTableID table; ///< Id of the table which this element belongs to + uint64 rating; ///< Value that determines ordering of elements in the table (higher=better) + CompanyID company; ///< Company Id to show the color blob for or INVALID_COMPANY + std::string text; ///< Text of the element + std::string score; ///< String representation of the score associated with the element + Link link; ///< What opens when element is clicked + + /** + * We need an (empty) constructor so struct isn't zeroed (as C++ standard states) + */ + LeagueTableElement() { } + + /** + * (Empty) destructor has to be defined else operator delete might be called with nullptr parameter + */ + ~LeagueTableElement() { } +}; + + +/** Struct about custom league tables */ +struct LeagueTable : LeagueTablePool::PoolItem<&_league_table_pool> { + std::string title; ///< Title of the table + std::string header; ///< Text to show above the table + std::string footer; ///< Text to show below the table + + /** + * We need an (empty) constructor so struct isn't zeroed (as C++ standard states) + */ + LeagueTable() { } + + /** + * (Empty) destructor has to be defined else operator delete might be called with nullptr parameter + */ + ~LeagueTable() { } +}; + +#endif /* LEAGUE_BASE_H */ diff --git a/src/league_cmd.cpp b/src/league_cmd.cpp new file mode 100644 index 0000000000..b201025e8e --- /dev/null +++ b/src/league_cmd.cpp @@ -0,0 +1,238 @@ +/* + * 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 league_cmd.cpp Handling of league tables. */ + +#include "stdafx.h" +#include "league_base.h" +#include "command_type.h" +#include "command_func.h" +#include "industry.h" +#include "story_base.h" +#include "town.h" +#include "window_func.h" +#include "core/pool_func.hpp" +#include "company_base.h" + +#include "safeguards.h" + +LeagueTableElementPool _league_table_element_pool("LeagueTableElement"); +INSTANTIATE_POOL_METHODS(LeagueTableElement) + +LeagueTablePool _league_table_pool("LeagueTable"); +INSTANTIATE_POOL_METHODS(LeagueTable) + +/** + * Checks whether a link is valid, i.e. has a valid target. + * @param link the link to check + * @return true iff the link is valid + */ +bool IsValidLink(Link link) +{ + switch (link.type) { + case LT_NONE: return (link.target == 0); + case LT_TILE: return IsValidTile(link.target); + case LT_INDUSTRY: return Industry::IsValidID(link.target); + case LT_TOWN: return Town::IsValidID(link.target); + case LT_COMPANY: return Company::IsValidID(link.target); + case LT_STORY_PAGE: return StoryPage::IsValidID(link.target); + default: return false; + } + return false; +} + +/** + * Create a new league table. + * @param flags type of operation + * @param title Title of the league table + * @param header Text to show above the table + * @param footer Text to show below the table + * @return the cost of this operation or an error + */ +std::tuple CmdCreateLeagueTable(DoCommandFlag flags, const std::string &title, const std::string &header, const std::string &footer) +{ + if (_current_company != OWNER_DEITY) return { CMD_ERROR, INVALID_LEAGUE_TABLE }; + if (!LeagueTable::CanAllocateItem()) return { CMD_ERROR, INVALID_LEAGUE_TABLE }; + if (title.empty()) return { CMD_ERROR, INVALID_LEAGUE_TABLE }; + + if (flags & DC_EXEC) { + LeagueTable *lt = new LeagueTable(); + lt->title = title; + lt->header = header; + lt->footer = footer; + return { CommandCost(), lt->index }; + } + + return { CommandCost(), INVALID_LEAGUE_TABLE }; +} + + +/** + * Create a new element in a league table. + * @param flags type of operation + * @param table Id of the league table this element belongs to + * @param rating Value that elements are ordered by + * @param company Company to show the color blob for or INVALID_COMPANY + * @param text Text of the element + * @param score String representation of the score associated with the element + * @param link_type Type of the referenced object + * @param link_target Id of the referenced object + * @return the cost of this operation or an error + */ +std::tuple CmdCreateLeagueTableElement(DoCommandFlag flags, LeagueTableID table, int64 rating, CompanyID company, const std::string &text, const std::string &score, LinkType link_type, LinkTargetID link_target) +{ + if (_current_company != OWNER_DEITY) return { CMD_ERROR, INVALID_LEAGUE_TABLE_ELEMENT }; + if (!LeagueTableElement::CanAllocateItem()) return { CMD_ERROR, INVALID_LEAGUE_TABLE_ELEMENT }; + Link link{link_type, link_target}; + if (!IsValidLink(link)) return { CMD_ERROR, INVALID_LEAGUE_TABLE_ELEMENT }; + if (company != INVALID_COMPANY && !Company::IsValidID(company)) return { CMD_ERROR, INVALID_LEAGUE_TABLE_ELEMENT }; + + if (flags & DC_EXEC) { + LeagueTableElement *lte = new LeagueTableElement(); + lte->table = table; + lte->rating = rating; + lte->company = company; + lte->text = text; + lte->score = score; + lte->link = link; + InvalidateWindowData(WC_COMPANY_LEAGUE, table); + return { CommandCost(), lte->index }; + } + return { CommandCost(), INVALID_LEAGUE_TABLE_ELEMENT }; +} + +/** + * Update the attributes of a league table element. + * @param flags type of operation + * @param element Id of the element to update + * @param company Company to show the color blob for or INVALID_COMPANY + * @param text Text of the element + * @param link_type Type of the referenced object + * @param link_target Id of the referenced object + * @return the cost of this operation or an error + */ +CommandCost CmdUpdateLeagueTableElementData(DoCommandFlag flags, LeagueTableElementID element, CompanyID company, const std::string &text, LinkType link_type, LinkTargetID link_target) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + auto lte = LeagueTableElement::GetIfValid(element); + if (lte == nullptr) return CMD_ERROR; + if (company != INVALID_COMPANY && !Company::IsValidID(company)) return CMD_ERROR; + Link link{link_type, link_target}; + if (!IsValidLink(link)) return CMD_ERROR; + + if (flags & DC_EXEC) { + lte->company = company; + lte->text = text; + lte->link = link; + InvalidateWindowData(WC_COMPANY_LEAGUE, lte->table); + } + return CommandCost(); +} + +/** + * Update the score of a league table element. + * @param flags type of operation + * @param element Id of the element to update + * @param rating Value that elements are ordered by + * @param score String representation of the score associated with the element + * @return the cost of this operation or an error + */ +CommandCost CmdUpdateLeagueTableElementScore(DoCommandFlag flags, LeagueTableElementID element, int64 rating, const std::string &score) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + auto lte = LeagueTableElement::GetIfValid(element); + if (lte == nullptr) return CMD_ERROR; + + if (flags & DC_EXEC) { + lte->rating = rating; + lte->score = score; + InvalidateWindowData(WC_COMPANY_LEAGUE, lte->table); + } + return CommandCost(); +} + +/** + * Remove a league table element. + * @param flags type of operation + * @param element Id of the element to update + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveLeagueTableElement(DoCommandFlag flags, LeagueTableElementID element) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + auto lte = LeagueTableElement::GetIfValid(element); + if (lte == nullptr) return CMD_ERROR; + + if (flags & DC_EXEC) { + auto table = lte->table; + delete lte; + InvalidateWindowData(WC_COMPANY_LEAGUE, table); + } + return CommandCost(); +} + +CommandCost CmdCreateLeagueTable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + std::string title, header, footer; + text = StrConsumeToSeparator(title, text); + if (text == nullptr) return CMD_ERROR; + text = StrConsumeToSeparator(header, text); + if (text == nullptr) return CMD_ERROR; + text = StrConsumeToSeparator(footer, text); + if (text != nullptr) return CMD_ERROR; + + auto [res, id] = CmdCreateLeagueTable(flags, title, header, footer); + res.SetResultData(id); + return res; +} + +CommandCost CmdCreateLeagueTableElement(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, uint64 p3, const char *text, uint32 binary_length) +{ + std::string text_str, score; + text = StrConsumeToSeparator(text_str, text); + if (text == nullptr) return CMD_ERROR; + text = StrConsumeToSeparator(score, text); + if (text != nullptr) return CMD_ERROR; + + LeagueTableID table = GB(p1, 0, 8); + int64 rating = p3; + CompanyID company = (CompanyID)GB(p1, 8, 8); + LinkType link_type = (LinkType)GB(p1, 16, 8); + LinkTargetID link_target = (LinkTargetID)p2; + + auto [res, id] = CmdCreateLeagueTableElement(flags, table, rating, company, text_str, score, link_type, link_target); + res.SetResultData(id); + return res; +} + +CommandCost CmdUpdateLeagueTableElementData(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + std::string title, header, footer; + text = StrConsumeToSeparator(title, text); + if (text == nullptr) return CMD_ERROR; + text = StrConsumeToSeparator(header, text); + if (text == nullptr) return CMD_ERROR; + text = StrConsumeToSeparator(footer, text); + if (text != nullptr) return CMD_ERROR; + + LeagueTableElementID element = GB(p1, 0, 16); + CompanyID company = (CompanyID)GB(p1, 16, 8); + LinkType link_type = (LinkType)GB(p1, 24, 8); + LinkTargetID link_target = (LinkTargetID)p2; + + return CmdUpdateLeagueTableElementData(flags, element, company, text, link_type, link_target); +} + +CommandCost CmdUpdateLeagueTableElementScore(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, uint64 p3, const char *text, uint32 binary_length) +{ + return CmdUpdateLeagueTableElementScore(flags, p1, p3, text); +} + +CommandCost CmdRemoveLeagueTableElement(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + return CmdRemoveLeagueTableElement(flags, p1); +} diff --git a/src/league_gui.cpp b/src/league_gui.cpp new file mode 100644 index 0000000000..a46cd2efa6 --- /dev/null +++ b/src/league_gui.cpp @@ -0,0 +1,452 @@ +/* + * 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 league_gui.cpp GUI for league tables. */ + +#include "stdafx.h" +#include "league_gui.h" + +#include "company_base.h" +#include "company_gui.h" +#include "gui.h" +#include "industry.h" +#include "league_base.h" +#include "sortlist_type.h" +#include "story_base.h" +#include "strings_func.h" +#include "tile_map.h" +#include "town.h" +#include "viewport_func.h" +#include "window_gui.h" +#include "widgets/league_widget.h" +#include "table/strings.h" +#include "table/sprites.h" + +#include "safeguards.h" + + +static const StringID _performance_titles[] = { + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ENGINEER, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRAFFIC_MANAGER, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TRANSPORT_COORDINATOR, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_ROUTE_SUPERVISOR, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_DIRECTOR, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHIEF_EXECUTIVE, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_CHAIRMAN, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_PRESIDENT, + STR_COMPANY_LEAGUE_PERFORMANCE_TITLE_TYCOON, +}; + +static inline StringID GetPerformanceTitleFromValue(uint value) +{ + return _performance_titles[std::min(value, 1000u) >> 6]; +} + +class PerformanceLeagueWindow : public Window { +private: + GUIList companies; + uint ordinal_width; ///< The width of the ordinal number + uint text_width; ///< The width of the actual text + int line_height; ///< Height of the text lines + Dimension icon; ///< Dimension of the company icon. + + /** + * (Re)Build the company league list + */ + void BuildCompanyList() + { + if (!this->companies.NeedRebuild()) return; + + this->companies.clear(); + + for (const Company *c : Company::Iterate()) { + this->companies.push_back(c); + } + + this->companies.shrink_to_fit(); + this->companies.RebuildDone(); + } + + /** Sort the company league by performance history */ + static bool PerformanceSorter(const Company * const &c1, const Company * const &c2) + { + return c2->old_economy[0].performance_history < c1->old_economy[0].performance_history; + } + +public: + PerformanceLeagueWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) + { + this->InitNested(window_number); + this->companies.ForceRebuild(); + this->companies.NeedResort(); + } + + void OnPaint() override + { + this->BuildCompanyList(); + this->companies.Sort(&PerformanceSorter); + + this->DrawWidgets(); + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget != WID_PLT_BACKGROUND) return; + + Rect ir = r.Shrink(WidgetDimensions::scaled.framerect); + int icon_y_offset = (this->line_height - this->icon.height) / 2; + int text_y_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2; + + bool rtl = _current_text_dir == TD_RTL; + Rect ordinal = ir.WithWidth(this->ordinal_width, rtl); + uint icon_left = ir.Indent(rtl ? this->text_width : this->ordinal_width, rtl).left; + Rect text = ir.WithWidth(this->text_width, !rtl); + + for (uint i = 0; i != this->companies.size(); i++) { + const Company *c = this->companies[i]; + DrawString(ordinal.left, ordinal.right, ir.top + text_y_offset, i + STR_ORDINAL_NUMBER_1ST, i == 0 ? TC_WHITE : TC_YELLOW); + + DrawCompanyIcon(c->index, icon_left, ir.top + icon_y_offset); + + SetDParam(0, c->index); + SetDParam(1, c->index); + SetDParam(2, GetPerformanceTitleFromValue(c->old_economy[0].performance_history)); + DrawString(text.left, text.right, ir.top + text_y_offset, STR_COMPANY_LEAGUE_COMPANY_NAME); + ir.top += this->line_height; + } + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget != WID_PLT_BACKGROUND) return; + + this->ordinal_width = 0; + for (uint i = 0; i < MAX_COMPANIES; i++) { + this->ordinal_width = std::max(this->ordinal_width, GetStringBoundingBox(STR_ORDINAL_NUMBER_1ST + i).width); + } + this->ordinal_width += WidgetDimensions::scaled.hsep_wide; // Keep some extra spacing + + uint widest_width = 0; + uint widest_title = 0; + for (uint i = 0; i < lengthof(_performance_titles); i++) { + uint width = GetStringBoundingBox(_performance_titles[i]).width; + if (width > widest_width) { + widest_title = i; + widest_width = width; + } + } + + this->icon = GetSpriteSize(SPR_COMPANY_ICON); + this->line_height = std::max(this->icon.height + WidgetDimensions::scaled.vsep_normal, FONT_HEIGHT_NORMAL); + + for (const Company *c : Company::Iterate()) { + SetDParam(0, c->index); + SetDParam(1, c->index); + SetDParam(2, _performance_titles[widest_title]); + widest_width = std::max(widest_width, GetStringBoundingBox(STR_COMPANY_LEAGUE_COMPANY_NAME).width); + } + + this->text_width = widest_width + WidgetDimensions::scaled.hsep_indent * 3; // Keep some extra spacing + + size->width = WidgetDimensions::scaled.framerect.Horizontal() + this->ordinal_width + this->icon.width + this->text_width + WidgetDimensions::scaled.hsep_wide; + size->height = this->line_height * MAX_COMPANIES + WidgetDimensions::scaled.framerect.Vertical(); + } + + void OnGameTick() override + { + if (this->companies.NeedResort()) { + this->SetDirty(); + } + } + + /** + * 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. + */ + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + if (data == 0) { + /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */ + this->companies.ForceRebuild(); + } else { + this->companies.ForceResort(); + } + } +}; + +static const NWidgetPart _nested_performance_league_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_BROWN), + NWidget(WWT_CAPTION, COLOUR_BROWN), SetDataTip(STR_COMPANY_LEAGUE_TABLE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_BROWN), + NWidget(WWT_STICKYBOX, COLOUR_BROWN), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_BROWN, WID_PLT_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WidgetDimensions::unscaled.framerect.Vertical()), +}; + +static WindowDesc _performance_league_desc( + WDP_AUTO, "league", 0, 0, + WC_COMPANY_LEAGUE, WC_NONE, + 0, + _nested_performance_league_widgets, lengthof(_nested_performance_league_widgets) +); + +void ShowPerformanceLeagueTable() +{ + AllocateWindowDescFront(&_performance_league_desc, 0); +} + +static void HandleLinkClick(Link link) +{ + TileIndex xy; + switch (link.type) { + case LT_NONE: return; + + case LT_TILE: + if (!IsValidTile(link.target)) return; + xy = link.target; + break; + + case LT_INDUSTRY: + if (!Industry::IsValidID(link.target)) return; + xy = Industry::Get(link.target)->location.tile; + break; + + case LT_TOWN: + if (!Town::IsValidID(link.target)) return; + xy = Town::Get(link.target)->xy; + break; + + case LT_COMPANY: + ShowCompany((CompanyID)link.target); + return; + + case LT_STORY_PAGE: { + if (!StoryPage::IsValidID(link.target)) return; + CompanyID story_company = StoryPage::Get(link.target)->company; + ShowStoryBook(story_company, link.target); + return; + } + + default: NOT_REACHED(); + } + + if (_ctrl_pressed) { + ShowExtraViewportWindow(xy); + } else { + ScrollMainWindowToTile(xy); + } +} + + +class ScriptLeagueWindow : public Window { +private: + LeagueTableID table; + std::vector> rows; + uint rank_width; ///< The width of the rank ordinal + uint text_width; ///< The width of the actual text + uint score_width; ///< The width of the score text + uint header_height; ///< Height of the table header + int line_height; ///< Height of the text lines + Dimension icon_size; ///< Dimenion of the company icon. + std::string title; + + /** + * Rebuild the company league list + */ + void BuildTable() + { + this->rows.clear(); + this->title = std::string{}; + auto lt = LeagueTable::GetIfValid(this->table); + if (lt == nullptr) return; + + /* We store title in the window class so we can safely reference the string later */ + this->title = lt->title; + + std::vector elements; + for(LeagueTableElement *lte : LeagueTableElement::Iterate()) { + if (lte->table == this->table) { + elements.push_back(lte); + } + } + std::sort(elements.begin(), elements.end(), [](auto a, auto b) { return a->rating > b->rating; }); + + /* Calculate rank, companies with the same rating share the ranks */ + uint rank = 0; + for (uint i = 0; i != elements.size(); i++) { + auto *lte = elements[i]; + if (i > 0 && elements[i - 1]->rating != lte->rating) rank = i; + this->rows.emplace_back(std::make_pair(rank, lte)); + } + } + +public: + ScriptLeagueWindow(WindowDesc *desc, LeagueTableID table) : Window(desc) + { + this->table = table; + this->BuildTable(); + this->InitNested(table); + } + + void SetStringParameters(int widget) const override + { + if (widget != WID_SLT_CAPTION) return; + SetDParamStr(0, this->title); + } + + void OnPaint() override + { + this->DrawWidgets(); + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget != WID_SLT_BACKGROUND) return; + + auto lt = LeagueTable::GetIfValid(this->table); + if (lt == nullptr) return; + + Rect ir = r.Shrink(WidgetDimensions::scaled.framerect); + + if (!lt->header.empty()) { + SetDParamStr(0, lt->header); + ir.top = DrawStringMultiLine(ir.left, ir.right, ir.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK) + WidgetDimensions::scaled.vsep_wide; + } + + int icon_y_offset = (this->line_height - this->icon_size.height) / 2; + int text_y_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2; + + /* Calculate positions.of the columns */ + bool rtl = _current_text_dir == TD_RTL; + int spacer = WidgetDimensions::scaled.hsep_wide; + Rect rank_rect = ir.WithWidth(this->rank_width, rtl); + Rect icon_rect = ir.Indent(this->rank_width + (rtl ? 0 : spacer), rtl).WithWidth(this->icon_size.width, rtl); + Rect text_rect = ir.Indent(this->rank_width + spacer + this->icon_size.width, rtl).WithWidth(this->text_width, rtl); + Rect score_rect = ir.Indent(this->rank_width + 2 * spacer + this->icon_size.width + this->text_width, rtl).WithWidth(this->score_width, rtl); + + for (auto [rank, lte] : this->rows) { + DrawString(rank_rect.left, rank_rect.right, ir.top + text_y_offset, rank + STR_ORDINAL_NUMBER_1ST, rank == 0 ? TC_WHITE : TC_YELLOW); + if (this->icon_size.width > 0 && lte->company != INVALID_COMPANY) DrawCompanyIcon(lte->company, icon_rect.left, ir.top + icon_y_offset); + SetDParamStr(0, lte->text); + DrawString(text_rect.left, text_rect.right, ir.top + text_y_offset, STR_JUST_RAW_STRING, TC_BLACK); + SetDParamStr(0, lte->score); + DrawString(score_rect.left, score_rect.right, ir.top + text_y_offset, STR_JUST_RAW_STRING, TC_BLACK, SA_RIGHT); + ir.top += this->line_height; + } + + if (!lt->footer.empty()) { + ir.top += WidgetDimensions::scaled.vsep_wide; + SetDParamStr(0, lt->footer); + ir.top = DrawStringMultiLine(ir.left, ir.right, ir.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK); + } + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget != WID_SLT_BACKGROUND) return; + + auto lt = LeagueTable::GetIfValid(this->table); + if (lt == nullptr) return; + + this->icon_size = GetSpriteSize(SPR_COMPANY_ICON); + this->line_height = std::max(this->icon_size.height + WidgetDimensions::scaled.fullbevel.Vertical(), FONT_HEIGHT_NORMAL); + + /* Calculate maximum width of every column */ + this->rank_width = this->text_width = this->score_width = 0; + bool show_icon_column = false; + for (auto [rank, lte] : this->rows) { + this->rank_width = std::max(this->rank_width, GetStringBoundingBox(STR_ORDINAL_NUMBER_1ST + rank).width); + SetDParamStr(0, lte->text); + this->text_width = std::max(this->text_width, GetStringBoundingBox(STR_JUST_RAW_STRING).width); + SetDParamStr(0, lte->score); + this->score_width = std::max(this->score_width, GetStringBoundingBox(STR_JUST_RAW_STRING).width); + if (lte->company != INVALID_COMPANY) show_icon_column = true; + } + + if (!show_icon_column) this->icon_size.width = 0; + else this->icon_size.width += WidgetDimensions::scaled.hsep_wide; + + size->width = this->rank_width + this->icon_size.width + this->text_width + this->score_width + WidgetDimensions::scaled.framerect.Horizontal() + WidgetDimensions::scaled.hsep_wide * 2; + size->height = this->line_height * std::max(3u, (unsigned)this->rows.size()) + WidgetDimensions::scaled.framerect.Vertical(); + + if (!lt->header.empty()) { + SetDParamStr(0, lt->header); + this->header_height = GetStringHeight(STR_JUST_RAW_STRING, size->width - WidgetDimensions::scaled.framerect.Horizontal()) + WidgetDimensions::scaled.vsep_wide; + size->height += header_height; + } else this->header_height = 0; + + if (!lt->footer.empty()) { + SetDParamStr(0, lt->footer); + size->height += GetStringHeight(STR_JUST_RAW_STRING, size->width - WidgetDimensions::scaled.framerect.Horizontal()) + WidgetDimensions::scaled.vsep_wide; + } + } + + void OnClick(Point pt, int widget, int click_count) override + { + if (widget != WID_SLT_BACKGROUND) return; + + auto *wid = this->GetWidget(WID_SLT_BACKGROUND); + int index = (pt.y - WidgetDimensions::scaled.framerect.top - wid->pos_y - this->header_height) / this->line_height; + if (index >= 0 && (uint)index < this->rows.size()) { + auto lte = this->rows[index].second; + HandleLinkClick(lte->link); + } + } + + /** + * 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. + */ + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + this->BuildTable(); + this->ReInit(); + } +}; + +static const NWidgetPart _nested_script_league_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_BROWN), + NWidget(WWT_CAPTION, COLOUR_BROWN, WID_SLT_CAPTION), SetDataTip(STR_BLACK_RAW_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_BROWN), + NWidget(WWT_STICKYBOX, COLOUR_BROWN), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_BROWN, WID_SLT_BACKGROUND), SetMinimalSize(400, 0), SetMinimalTextLines(15, WidgetDimensions::scaled.framerect.Vertical()), +}; + +static WindowDesc _script_league_desc( + WDP_AUTO, "league", 0, 0, + WC_COMPANY_LEAGUE, WC_NONE, + 0, + _nested_script_league_widgets, lengthof(_nested_script_league_widgets) +); + +void ShowScriptLeagueTable(LeagueTableID table) +{ + if (!LeagueTable::IsValidID(table)) return; + AllocateWindowDescFront(&_script_league_desc, table); +} + +void ShowFirstLeagueTable() +{ + auto it = LeagueTable::Iterate(); + if (!it.empty()) { + ShowScriptLeagueTable((*it.begin())->index); + } else { + ShowPerformanceLeagueTable(); + } +} diff --git a/src/league_gui.h b/src/league_gui.h new file mode 100644 index 0000000000..602979f1f6 --- /dev/null +++ b/src/league_gui.h @@ -0,0 +1,19 @@ +/* + * 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 league_gui.h League table GUI functions. */ + +#ifndef LEAGUE_GUI_H +#define LEAGUE_GUI_H + +#include "league_type.h" + +void ShowPerformanceLeagueTable(); +void ShowScriptLeagueTable(LeagueTableID table); +void ShowFirstLeagueTable(); + +#endif /* LEAGUE_GUI_H */ diff --git a/src/league_type.h b/src/league_type.h new file mode 100644 index 0000000000..3344bc206e --- /dev/null +++ b/src/league_type.h @@ -0,0 +1,40 @@ +/* + * 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 league_type.h basic types related to league tables */ + +#ifndef LEAGUE_TYPE_H +#define LEAGUE_TYPE_H + +/** Types of the possible link targets. */ +enum LinkType : byte { + LT_NONE = 0, ///< No link + LT_TILE = 1, ///< Link a tile + LT_INDUSTRY = 2, ///< Link an industry + LT_TOWN = 3, ///< Link a town + LT_COMPANY = 4, ///< Link a company + LT_STORY_PAGE = 5, ///< Link a story page +}; + +typedef uint32 LinkTargetID; ///< Contains either tile, industry ID, town ID, story page ID or company ID + +struct Link { + LinkType type; + LinkTargetID target; + Link(LinkType type, LinkTargetID target): type{type}, target{target} {} + Link(): Link(LT_NONE, 0) {} +}; + +typedef uint8 LeagueTableID; ///< ID of a league table +struct LeagueTable; +static const LeagueTableID INVALID_LEAGUE_TABLE = 0xFF; ///< Invalid/unknown index of LeagueTable + +typedef uint16 LeagueTableElementID; ///< ID of a league table element +struct LeagueTableElement; +static const LeagueTableElementID INVALID_LEAGUE_TABLE_ELEMENT = 0xFFFF; ///< Invalid/unknown index of LeagueTableElement + +#endif /* LEAGUE_TYPE_H */ diff --git a/src/saveload/CMakeLists.txt b/src/saveload/CMakeLists.txt index 88f7e1ee16..ce8b04f212 100644 --- a/src/saveload/CMakeLists.txt +++ b/src/saveload/CMakeLists.txt @@ -23,6 +23,7 @@ add_files( group_sl.cpp industry_sl.cpp labelmaps_sl.cpp + league_sl.cpp linkgraph_sl.cpp map_sl.cpp misc_sl.cpp diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index d0dda55f6f..e4028f18ff 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -180,6 +180,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_U64_TICK_COUNTER, XSCF_NULL, 1, 1, "u64_tick_counter", nullptr, nullptr, nullptr }, { XSLFI_LINKGRAPH_TRAVEL_TIME, XSCF_NULL, 1, 1, "linkgraph_travel_time", nullptr, nullptr, nullptr }, { XSLFI_LAST_LOADING_TICK, XSCF_NULL, 1, 1, "last_loading_tick", nullptr, nullptr, nullptr }, + { XSLFI_SCRIPT_LEAGUE_TABLES, XSCF_NULL, 1, 1, "script_league_tables", nullptr, nullptr, "LEAE,LEAT" }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index d50ff398d3..fcb0e97c43 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -134,6 +134,7 @@ enum SlXvFeatureIndex { XSLFI_U64_TICK_COUNTER, ///< See: SLV_U64_TICK_COUNTER XSLFI_LINKGRAPH_TRAVEL_TIME, ///< See: SLV_LINKGRAPH_TRAVEL_TIME XSLFI_LAST_LOADING_TICK, ///< See: SLV_LAST_LOADING_TICK + XSLFI_SCRIPT_LEAGUE_TABLES, ///< See: Scriptable league tables (PR #10001) XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk diff --git a/src/saveload/league_sl.cpp b/src/saveload/league_sl.cpp new file mode 100644 index 0000000000..d762e9daab --- /dev/null +++ b/src/saveload/league_sl.cpp @@ -0,0 +1,23 @@ +/* + * 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 league_sl.cpp Code handling saving and loading of league tables */ +#include "../stdafx.h" + +#include "saveload.h" + +struct GetLeagueChunkLoadInfo +{ + static SaveLoadVersion GetVersion() { return SLV_MULTITRACK_LEVEL_CROSSINGS; } +}; + +static const ChunkHandler league_chunk_handlers[] = { + MakeUpstreamChunkHandler<'LEAE', GetLeagueChunkLoadInfo>(), + MakeUpstreamChunkHandler<'LEAT', GetLeagueChunkLoadInfo>(), +}; + +extern const ChunkHandlerTable _league_chunk_handlers(league_chunk_handlers); diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 7d598ff8f5..4b0877d6ec 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -284,6 +284,7 @@ static const std::vector &ChunkHandlers() extern const ChunkHandlerTable _cargomonitor_chunk_handlers; extern const ChunkHandlerTable _goal_chunk_handlers; extern const ChunkHandlerTable _story_page_chunk_handlers; + extern const ChunkHandlerTable _league_chunk_handlers; extern const ChunkHandlerTable _ai_chunk_handlers; extern const ChunkHandlerTable _game_chunk_handlers; extern const ChunkHandlerTable _animated_tile_chunk_handlers; @@ -326,6 +327,7 @@ static const std::vector &ChunkHandlers() _cargomonitor_chunk_handlers, _goal_chunk_handlers, _story_page_chunk_handlers, + _league_chunk_handlers, _engine_chunk_handlers, _town_chunk_handlers, _sign_chunk_handlers, diff --git a/src/saveload/upstream/CMakeLists.txt b/src/saveload/upstream/CMakeLists.txt index b969180c19..665e2550a4 100644 --- a/src/saveload/upstream/CMakeLists.txt +++ b/src/saveload/upstream/CMakeLists.txt @@ -18,6 +18,7 @@ add_files( group_sl.cpp industry_sl.cpp labelmaps_sl.cpp + league_sl.cpp linkgraph_sl.cpp map_sl.cpp misc_sl.cpp diff --git a/src/saveload/upstream/league_sl.cpp b/src/saveload/upstream/league_sl.cpp new file mode 100644 index 0000000000..4eb953e5e3 --- /dev/null +++ b/src/saveload/upstream/league_sl.cpp @@ -0,0 +1,93 @@ +/* + * 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 league_sl.cpp Code handling saving and loading of league tables */ + +#include "../../stdafx.h" + +#include "saveload.h" + +#include "../../league_base.h" + +#include "../../safeguards.h" + +namespace upstream_sl { + +static const SaveLoad _league_table_elements_desc[] = { + SLE_VAR(LeagueTableElement, table, SLE_UINT8), + SLE_VAR(LeagueTableElement, rating, SLE_UINT64), + SLE_VAR(LeagueTableElement, company, SLE_UINT8), + SLE_SSTR(LeagueTableElement, text, SLE_STR | SLF_ALLOW_CONTROL), + SLE_SSTR(LeagueTableElement, score, SLE_STR | SLF_ALLOW_CONTROL), + SLE_VAR(LeagueTableElement, link.type, SLE_UINT8), + SLE_VAR(LeagueTableElement, link.target, SLE_UINT32), +}; + +struct LEAEChunkHandler : ChunkHandler { + LEAEChunkHandler() : ChunkHandler('LEAE', CH_TABLE) {} + + void Save() const override + { + SlTableHeader(_league_table_elements_desc); + + for (LeagueTableElement *lte : LeagueTableElement::Iterate()) { + SlSetArrayIndex(lte->index); + SlObject(lte, _league_table_elements_desc); + } + } + + void Load() const override + { + const std::vector slt = SlTableHeader(_league_table_elements_desc); + + int index; + while ((index = SlIterateArray()) != -1) { + LeagueTableElement *lte = new (index) LeagueTableElement(); + SlObject(lte, slt); + } + } +}; + +static const SaveLoad _league_tables_desc[] = { + SLE_SSTR(LeagueTable, title, SLE_STR | SLF_ALLOW_CONTROL), +}; + +struct LEATChunkHandler : ChunkHandler { + LEATChunkHandler() : ChunkHandler('LEAT', CH_TABLE) {} + + void Save() const override + { + SlTableHeader(_league_tables_desc); + + for (LeagueTable *lt : LeagueTable::Iterate()) { + SlSetArrayIndex(lt->index); + SlObject(lt, _league_tables_desc); + } + } + + void Load() const override + { + const std::vector slt = SlTableHeader(_league_tables_desc); + + int index; + while ((index = SlIterateArray()) != -1) { + LeagueTable *lt = new (index) LeagueTable(); + SlObject(lt, slt); + } + } +}; + +static const LEAEChunkHandler LEAE; +static const LEATChunkHandler LEAT; +static const ChunkHandlerRef league_chunk_handlers[] = { + LEAE, + LEAT, +}; + +extern const ChunkHandlerTable _league_chunk_handlers(league_chunk_handlers); + +} diff --git a/src/saveload/upstream/saveload.cpp b/src/saveload/upstream/saveload.cpp index b3124f6c00..0f8e4fc7d1 100644 --- a/src/saveload/upstream/saveload.cpp +++ b/src/saveload/upstream/saveload.cpp @@ -101,6 +101,7 @@ static const std::vector &ChunkHandlers() extern const ChunkHandlerTable _cargomonitor_chunk_handlers; extern const ChunkHandlerTable _goal_chunk_handlers; extern const ChunkHandlerTable _story_page_chunk_handlers; + extern const ChunkHandlerTable _league_chunk_handlers; extern const ChunkHandlerTable _ai_chunk_handlers; extern const ChunkHandlerTable _game_chunk_handlers; extern const ChunkHandlerTable _animated_tile_chunk_handlers; @@ -132,6 +133,7 @@ static const std::vector &ChunkHandlers() _cargomonitor_chunk_handlers, _goal_chunk_handlers, _story_page_chunk_handlers, + _league_chunk_handlers, _engine_chunk_handlers, _town_chunk_handlers, _sign_chunk_handlers, diff --git a/src/script/api/CMakeLists.txt b/src/script/api/CMakeLists.txt index 4ed00fb20e..7d5cd17cd7 100644 --- a/src/script/api/CMakeLists.txt +++ b/src/script/api/CMakeLists.txt @@ -178,6 +178,7 @@ add_files( script_inflation.hpp script_info_docs.hpp script_infrastructure.hpp + script_league.hpp script_list.hpp script_log.hpp script_map.hpp @@ -249,6 +250,7 @@ add_files( script_industrytypelist.cpp script_inflation.cpp script_infrastructure.cpp + script_league.cpp script_list.cpp script_log.cpp script_map.cpp diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 592550ec1b..69773edeeb 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -21,6 +21,7 @@ * \li GSCargo::GetWeight * \li GSIndustryType::ResolveNewGRFID * \li GSObjectType::ResolveNewGRFID + * \li GSLeagueTable * * Other changes: * \li GSRoad::HasRoadType now correctly checks RoadType against RoadType diff --git a/src/script/api/script_league.cpp b/src/script/api/script_league.cpp new file mode 100644 index 0000000000..fd3ac7233e --- /dev/null +++ b/src/script/api/script_league.cpp @@ -0,0 +1,128 @@ +/* + * 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 script_league.cpp Implementation of ScriptLeagueTable. */ + +#include "../../stdafx.h" + +#include "script_league.hpp" + +#include "../script_instance.hpp" +#include "script_error.hpp" +#include "../../league_base.h" +#include "../../string_func.h" + +#include "../../safeguards.h" + + +/* static */ bool ScriptLeagueTable::IsValidLeagueTable(LeagueTableID table_id) +{ + return ::LeagueTable::IsValidID(table_id); +} + +/* static */ ScriptLeagueTable::LeagueTableID ScriptLeagueTable::New(Text *title, Text *header, Text *footer) +{ + CCountedPtr title_counter(title); + CCountedPtr header_counter(header); + CCountedPtr footer_counter(footer); + + EnforcePrecondition(LEAGUE_TABLE_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); + EnforcePrecondition(LEAGUE_TABLE_INVALID, title != nullptr); + const char *encoded_title = title->GetEncodedText(); + EnforcePreconditionEncodedText(LEAGUE_TABLE_INVALID, encoded_title); + + std::string cmd_text = encoded_title; + cmd_text.push_back(0x1F); + if (header != nullptr) cmd_text += header->GetEncodedText(); + cmd_text.push_back(0x1F); + if (footer != nullptr) cmd_text += footer->GetEncodedText(); + + if (!ScriptObject::DoCommand(0, 0, 0, CMD_CREATE_LEAGUE_TABLE, cmd_text.c_str(), &ScriptInstance::DoCommandReturnLeagueTableID)) return LEAGUE_TABLE_INVALID; + + /* In case of test-mode, we return LeagueTableID 0 */ + return (ScriptLeagueTable::LeagueTableID)0; +} + +/* static */ bool ScriptLeagueTable::IsValidLeagueTableElement(LeagueTableElementID element_id) +{ + return ::LeagueTableElement::IsValidID(element_id); +} + +/* static */ ScriptLeagueTable::LeagueTableElementID ScriptLeagueTable::NewElement(ScriptLeagueTable::LeagueTableID table, int64 rating, ScriptCompany::CompanyID company, Text *text, Text *score, LinkType link_type, uint32 link_target) +{ + CCountedPtr text_counter(text); + CCountedPtr score_counter(score); + + EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); + + EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, IsValidLeagueTable(table)); + + EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, company == ScriptCompany::COMPANY_INVALID || ScriptCompany::ResolveCompanyID(company) != ScriptCompany::COMPANY_INVALID); + CompanyID c = (::CompanyID)company; + if (company == ScriptCompany::COMPANY_INVALID) c = INVALID_COMPANY; + + EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, text != nullptr); + const char *encoded_text_ptr = text->GetEncodedText(); + EnforcePreconditionEncodedText(LEAGUE_TABLE_ELEMENT_INVALID, encoded_text_ptr); + std::string encoded_text = encoded_text_ptr; // save into string so GetEncodedText can reuse the internal buffer + + EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, score != nullptr); + const char *encoded_score = score->GetEncodedText(); + EnforcePreconditionEncodedText(LEAGUE_TABLE_ELEMENT_INVALID, encoded_score); + + EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, IsValidLink(Link((::LinkType)link_type, link_target))); + + std::string cmd_text = std::move(encoded_text); + cmd_text.push_back(0x1F); + cmd_text += encoded_score; + if (!ScriptObject::DoCommandEx(0, table | (c << 8) | (link_type << 16), link_target, rating, CMD_CREATE_LEAGUE_TABLE_ELEMENT, cmd_text.c_str(), 0, &ScriptInstance::DoCommandReturnLeagueTableElementID)) return LEAGUE_TABLE_ELEMENT_INVALID; + + /* In case of test-mode, we return LeagueTableElementID 0 */ + return (ScriptLeagueTable::LeagueTableElementID)0; +} + +/* static */ bool ScriptLeagueTable::UpdateElementData(LeagueTableElementID element, ScriptCompany::CompanyID company, Text *text, LinkType link_type, LinkTargetID link_target) +{ + CCountedPtr text_counter(text); + + EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); + EnforcePrecondition(false, IsValidLeagueTableElement(element)); + + EnforcePrecondition(false, company == ScriptCompany::COMPANY_INVALID || ScriptCompany::ResolveCompanyID(company) != ScriptCompany::COMPANY_INVALID); + CompanyID c = (::CompanyID)company; + if (company == ScriptCompany::COMPANY_INVALID) c = INVALID_COMPANY; + + EnforcePrecondition(false, text != nullptr); + const char *encoded_text = text->GetEncodedText(); + EnforcePreconditionEncodedText(false, encoded_text); + + EnforcePrecondition(false, IsValidLink(Link((::LinkType)link_type, link_target))); + + return ScriptObject::DoCommand(0, element | (c << 16) | (link_type << 24), link_target, CMD_UPDATE_LEAGUE_TABLE_ELEMENT_DATA, encoded_text); +} + +/* static */ bool ScriptLeagueTable::UpdateElementScore(LeagueTableElementID element, int64 rating, Text *score) +{ + CCountedPtr score_counter(score); + + EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); + EnforcePrecondition(false, IsValidLeagueTableElement(element)); + + EnforcePrecondition(false, score != nullptr); + const char *encoded_score = score->GetEncodedText(); + EnforcePreconditionEncodedText(false, encoded_score); + + return ScriptObject::DoCommandEx(0, element, 0, rating, CMD_UPDATE_LEAGUE_TABLE_ELEMENT_SCORE, encoded_score); +} + +/* static */ bool ScriptLeagueTable::RemoveElement(LeagueTableElementID element) +{ + EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); + EnforcePrecondition(false, IsValidLeagueTableElement(element)); + + return ScriptObject::DoCommand(0, element, 0, CMD_REMOVE_LEAGUE_TABLE_ELEMENT); +} diff --git a/src/script/api/script_league.hpp b/src/script/api/script_league.hpp new file mode 100644 index 0000000000..d6fc2bbfea --- /dev/null +++ b/src/script/api/script_league.hpp @@ -0,0 +1,134 @@ +/* + * 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 script_league.hpp Everything to manipulate league tables. */ + +#ifndef SCRIPT_LEAGUE_HPP +#define SCRIPT_LEAGUE_HPP + +#include "script_company.hpp" +#include "script_text.hpp" +#include "../../league_type.h" + +/** + * Class that handles league table related functions. + * + * To create a league table: + * 1. Create the league table + * 2. Create league table elements that will be shown in the table in the order of their rating (higher=better). + * + * @api game + */ +class ScriptLeagueTable : public ScriptObject { +public: + /** + * The league table IDs. + */ + enum LeagueTableID { + LEAGUE_TABLE_INVALID = ::INVALID_LEAGUE_TABLE, ///< An invalid league table id. + }; + + /** + * The league table element IDs. + */ + enum LeagueTableElementID { + LEAGUE_TABLE_ELEMENT_INVALID = ::INVALID_LEAGUE_TABLE_ELEMENT, ///< An invalid league table element id. + }; + + /** + * The type of a link. + */ + enum LinkType : byte { + LINK_NONE = ::LT_NONE, ///< No link + LINK_TILE = ::LT_TILE, ///< Link a tile + LINK_INDUSTRY = ::LT_INDUSTRY, ///< Link an industry + LINK_TOWN = ::LT_TOWN, ///< Link a town + LINK_COMPANY = ::LT_COMPANY, ///< Link a company + LINK_STORY_PAGE = ::LT_STORY_PAGE, ///< Link a story page + }; + + /** + * Check whether this is a valid league table ID. + * @param table_id The LeagueTableID to check. + * @return true iff this league table is valid. + */ + static bool IsValidLeagueTable(LeagueTableID table_id); + + /** + * Check whether this is a valid league table element ID. + * @param element_id The LeagueTableElementID to check. + * @return true iff this league table element is valid. + */ + static bool IsValidLeagueTableElement(LeagueTableElementID element_id); + + /** + * Create a new league table. + * @param title League table title (can be either a raw string, or ScriptText object). + * @return The new LeagueTableID, or LEAGUE_TABLE_INVALID if it failed. + * @pre No ScriptCompanyMode may be in scope. + * @pre title != nullptr && len(title) != 0. + */ + static LeagueTableID New(Text *title, Text *header, Text *footer); + + /** + * Create a new league table element. + * @param table Id of the league table this element belongs to. + * @param rating Value that elements are ordered by. + * @param company Company to show the color blob for or INVALID_COMPANY. + * @param text Text of the element (can be either a raw string, or ScriptText object). + * @param score String representation of the score associated with the element (can be either a raw string, or ScriptText object). + * @param link_type Type of the referenced object. + * @param link_target Id of the referenced object. + * @return The new LeagueTableElementID, or LEAGUE_TABLE_ELEMENT_INVALID if it failed. + * @pre No ScriptCompanyMode may be in scope. + * @pre IsValidLeagueTable(table). + * @pre text != nullptr && len(text) != 0. + * @pre score != nullptr && len(score) != 0. + * @pre IsValidLink(Link(link_type, link_target)). + */ + static LeagueTableElementID NewElement(LeagueTableID table, int64 rating, ScriptCompany::CompanyID company, Text *text, Text *score, LinkType link_type, LinkTargetID link_target); + + /** + * Update the attributes of a league table element. + * @param element Id of the element to update + * @param company Company to show the color blob for or INVALID_COMPANY. + * @param text Text of the element (can be either a raw string, or ScriptText object). + * @param link_type Type of the referenced object. + * @param link_target Id of the referenced object. + * @return True if the action succeeded. + * @pre No ScriptCompanyMode may be in scope. + * @pre IsValidLeagueTableElement(element). + * @pre text != nullptr && len(text) != 0. + * @pre IsValidLink(Link(link_type, link_target)). + */ + static bool UpdateElementData(LeagueTableElementID element, ScriptCompany::CompanyID company, Text *text, LinkType link_type, LinkTargetID link_target); + + /** + * Create a new league table element. + * @param element Id of the element to update + * @param rating Value that elements are ordered by. + * @param score String representation of the score associated with the element (can be either a raw string, or ScriptText object). + * @return True if the action succeeded. + * @pre No ScriptCompanyMode may be in scope. + * @pre IsValidLeagueTableElement(element). + * @pre score != nullptr && len(score) != 0. + */ + static bool UpdateElementScore(LeagueTableElementID element, int64 rating, Text *score); + + + /** + * Remove a league table element. + * @param element Id of the element to update + * @return True if the action succeeded. + * @pre No ScriptCompanyMode may be in scope. + * @pre IsValidLeagueTableElement(element). + */ + static bool RemoveElement(LeagueTableElementID element); +}; + + +#endif /* SCRIPT_LEAGUE_HPP */ diff --git a/src/script/script_instance.cpp b/src/script/script_instance.cpp index 49861e1e36..ec3bc3ac39 100644 --- a/src/script/script_instance.cpp +++ b/src/script/script_instance.cpp @@ -26,6 +26,7 @@ #include "../company_base.h" #include "../company_func.h" #include "../fileio_func.h" +#include "../league_type.h" #include "../safeguards.h" @@ -321,6 +322,17 @@ void ScriptInstance::CollectGarbage() instance->engine->InsertResult(ScriptObject::GetNewStoryPageElementID()); } +/* static */ void ScriptInstance::DoCommandReturnLeagueTableElementID(ScriptInstance *instance) +{ + instance->engine->InsertResult(static_cast(ScriptObject::GetLastCommandResultData())); +} + +/* static */ void ScriptInstance::DoCommandReturnLeagueTableID(ScriptInstance *instance) +{ + instance->engine->InsertResult(static_cast(ScriptObject::GetLastCommandResultData())); +} + + ScriptStorage *ScriptInstance::GetStorage() { return this->storage; diff --git a/src/script/script_instance.hpp b/src/script/script_instance.hpp index 602beb6ad5..c43a9bd401 100644 --- a/src/script/script_instance.hpp +++ b/src/script/script_instance.hpp @@ -115,6 +115,16 @@ public: */ static void DoCommandReturnStoryPageElementID(ScriptInstance *instance); + /** + * Return a LeagueTableID reply for a DoCommand. + */ + static void DoCommandReturnLeagueTableID(ScriptInstance *instance); + + /** + * Return a LeagueTableElementID reply for a DoCommand. + */ + static void DoCommandReturnLeagueTableElementID(ScriptInstance *instance); + /** * Get the controller attached to the instance. */ diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index c90dccf4a1..28f56105e3 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -52,6 +52,8 @@ #include "zoning.h" #include "guitimer_func.h" #include "screenshot_gui.h" +#include "league_gui.h" +#include "league_base.h" #include "widgets/toolbar_widget.h" @@ -253,7 +255,6 @@ static void PopupMainCompanyToolbMenu(Window *w, int widget, int grey = 0) PopupMainToolbMenu(w, widget, std::move(list), _local_company == COMPANY_SPECTATOR ? (widget == WID_TN_COMPANIES ? CTMN_CLIENT_LIST : CTMN_SPECTATOR) : (int)_local_company); } - static ToolbarMode _toolbar_mode; static CallBackFunction SelectSignTool() @@ -689,59 +690,95 @@ static CallBackFunction MenuClickGoal(int index) return CBF_NONE; } -/* --- Graphs button menu --- */ +/* --- Graphs and League Table button menu --- */ + +/** + * Enum for the League Toolbar's and Graph Toolbar's related buttons. + * Use continuous numbering as League Toolbar can be combined into the Graph Toolbar. + */ +static const int GRMN_OPERATING_PROFIT_GRAPH = -1; ///< Show operating profit graph +static const int GRMN_INCOME_GRAPH = -2; ///< Show income graph +static const int GRMN_DELIVERED_CARGO_GRAPH = -3; ///< Show delivered cargo graph +static const int GRMN_PERFORMANCE_HISTORY_GRAPH = -4; ///< Show performance history graph +static const int GRMN_COMPANY_VALUE_GRAPH = -5; ///< Show company value graph +static const int GRMN_CARGO_PAYMENT_RATES = -6; ///< Show cargo payment rates graph +static const int LTMN_PERFORMANCE_LEAGUE = -7; ///< Show default league table +static const int LTMN_PERFORMANCE_RATING = -8; ///< Show detailed performance rating +static const int LTMN_HIGHSCORE = -9; ///< Show highscrore table + +static void AddDropDownLeagueTableOptions(DropDownList &list) { + if (LeagueTable::GetNumItems() > 0) { + for (LeagueTable *lt : LeagueTable::Iterate()) { + list.emplace_back(new DropDownListCharStringItem(lt->title, lt->index, false)); + } + } else { + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_COMPANY_LEAGUE_TABLE, LTMN_PERFORMANCE_LEAGUE, false)); + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_DETAILED_PERFORMANCE_RATING, LTMN_PERFORMANCE_RATING, false)); + if (!_networking) { + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_HIGHSCORE, LTMN_HIGHSCORE, false)); + } + } +} static CallBackFunction ToolbarGraphsClick(Window *w) { - PopupMainToolbMenu(w, WID_TN_GRAPHS, STR_GRAPH_MENU_OPERATING_PROFIT_GRAPH, (_toolbar_mode == TB_NORMAL) ? 6 : 8); + DropDownList list; + + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_OPERATING_PROFIT_GRAPH, GRMN_OPERATING_PROFIT_GRAPH, false)); + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_INCOME_GRAPH, GRMN_INCOME_GRAPH, false)); + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_DELIVERED_CARGO_GRAPH, GRMN_DELIVERED_CARGO_GRAPH, false)); + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_PERFORMANCE_HISTORY_GRAPH, GRMN_PERFORMANCE_HISTORY_GRAPH, false)); + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_COMPANY_VALUE_GRAPH, GRMN_COMPANY_VALUE_GRAPH, false)); + list.emplace_back(new DropDownListStringItem(STR_GRAPH_MENU_CARGO_PAYMENT_RATES, GRMN_CARGO_PAYMENT_RATES, false)); + + if (_toolbar_mode != TB_NORMAL) AddDropDownLeagueTableOptions(list); + + ShowDropDownList(w, std::move(list), 0, WID_TN_GRAPHS, 140, true, true); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + + return CBF_NONE; +} + +static CallBackFunction ToolbarLeagueClick(Window *w) +{ + DropDownList list; + + AddDropDownLeagueTableOptions(list); + + ShowDropDownList(w, std::move(list), 0, WID_TN_LEAGUE, 140, true, true); + if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + return CBF_NONE; } /** - * Handle click on the entry in the Graphs menu. + * Handle click on the entry in the Graphs or CompanyLeague. * * @param index Graph to show. * @return #CBF_NONE */ -static CallBackFunction MenuClickGraphs(int index) +static CallBackFunction MenuClickGraphsOrLeague(int index) { switch (index) { - case 0: ShowOperatingProfitGraph(); break; - case 1: ShowIncomeGraph(); break; - case 2: ShowDeliveredCargoGraph(); break; - case 3: ShowPerformanceHistoryGraph(); break; - case 4: ShowCompanyValueGraph(); break; - case 5: ShowCargoPaymentRates(); break; - /* functions for combined graphs/league button */ - case 6: ShowCompanyLeagueTable(); break; - case 7: ShowPerformanceRatingDetail(); break; + case GRMN_OPERATING_PROFIT_GRAPH: ShowOperatingProfitGraph(); break; + case GRMN_INCOME_GRAPH: ShowIncomeGraph(); break; + case GRMN_DELIVERED_CARGO_GRAPH: ShowDeliveredCargoGraph(); break; + case GRMN_PERFORMANCE_HISTORY_GRAPH: ShowPerformanceHistoryGraph(); break; + case GRMN_COMPANY_VALUE_GRAPH: ShowCompanyValueGraph(); break; + case GRMN_CARGO_PAYMENT_RATES: ShowCargoPaymentRates(); break; + case LTMN_PERFORMANCE_LEAGUE: ShowPerformanceLeagueTable(); break; + case LTMN_PERFORMANCE_RATING: ShowPerformanceRatingDetail(); break; + case LTMN_HIGHSCORE: ShowHighscoreTable(); break; + default: { + if (LeagueTable::IsValidID(index)) { + ShowScriptLeagueTable((LeagueTableID)index); + } + } } return CBF_NONE; } -/* --- League button menu --- */ -static CallBackFunction ToolbarLeagueClick(Window *w) -{ - PopupMainToolbMenu(w, WID_TN_LEAGUE, STR_GRAPH_MENU_COMPANY_LEAGUE_TABLE, _networking ? 2 : 3); - return CBF_NONE; -} - -/** - * Handle click on the entry in the CompanyLeague menu. - * - * @param index Menu entry number. - * @return #CBF_NONE - */ -static CallBackFunction MenuClickLeague(int index) -{ - switch (index) { - case 0: ShowCompanyLeagueTable(); break; - case 1: ShowPerformanceRatingDetail(); break; - case 2: ShowHighscoreTable(); break; - } - return CBF_NONE; -} /* --- Industries button menu --- */ @@ -1319,8 +1356,8 @@ static MenuClickedProc * const _menu_clicked_procs[] = { MenuClickCompany, // 9 MenuClickStory, // 10 MenuClickGoal, // 11 - MenuClickGraphs, // 12 - MenuClickLeague, // 13 + MenuClickGraphsOrLeague, // 12 + MenuClickGraphsOrLeague, // 13 MenuClickIndustry, // 14 MenuClickShowTrains, // 15 MenuClickShowRoad, // 16 @@ -2060,7 +2097,7 @@ struct MainToolbarWindow : Window { case MTHK_STORY: ShowStoryBook(_local_company); break; case MTHK_GOAL: ShowGoalsList(_local_company); break; case MTHK_GRAPHS: ShowOperatingProfitGraph(); break; - case MTHK_LEAGUE: ShowCompanyLeagueTable(); break; + case MTHK_LEAGUE: ShowFirstLeagueTable(); break; case MTHK_INDUSTRIES: ShowBuildIndustryWindow(); break; case MTHK_INDUSTRY_CHAINS: ShowIndustryCargoesWindow(); break; case MTHK_TRAIN_LIST: ShowVehicleListWindow(_local_company, VEH_TRAIN); break; diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 7b93ce8fde..41afbe8d9a 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -28,6 +28,7 @@ add_files( highscore_widget.h industry_widget.h intro_widget.h + league_widget.h link_graph_legend_widget.h main_widget.h misc_widget.h diff --git a/src/widgets/graph_widget.h b/src/widgets/graph_widget.h index 1ab0d81142..93c5566b0d 100644 --- a/src/widgets/graph_widget.h +++ b/src/widgets/graph_widget.h @@ -67,11 +67,6 @@ enum StationCargoWidgets { WID_SCG_MATRIX_SCROLLBAR, ///< Cargo list scrollbar. }; -/** Widget of the #CompanyLeagueWindow class. */ -enum CompanyLeagueWidgets { - WID_CL_BACKGROUND, ///< Background of the window. -}; - /** Widget of the #PerformanceRatingDetailWindow class. */ enum PerformanceRatingDetailsWidgets { WID_PRD_SCORE_FIRST, ///< First entry in the score list. diff --git a/src/widgets/league_widget.h b/src/widgets/league_widget.h new file mode 100644 index 0000000000..381d379a7e --- /dev/null +++ b/src/widgets/league_widget.h @@ -0,0 +1,24 @@ +/* + * 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 league_widget.h Types related to the graph widgets. */ + +#ifndef WIDGETS_LEAGUE_WIDGET_H +#define WIDGETS_LEAGUE_WIDGET_H + +/** Widget of the #PerformanceLeagueWindow class. */ +enum PerformanceLeagueWidgets { + WID_PLT_BACKGROUND, ///< Background of the window. +}; + +/** Widget of the #ScriptLeagueWindow class. */ +enum ScriptLeagueWidgets { + WID_SLT_CAPTION, ///< Caption of the window. + WID_SLT_BACKGROUND, ///< Background of the window. +}; + +#endif /* WIDGETS_LEAGUE_WIDGET_H */