From df2b66ad5d998a6d8f83e2ee14f50cb75f04560f Mon Sep 17 00:00:00 2001 From: translators Date: Tue, 7 Mar 2023 18:49:32 +0000 Subject: [PATCH 01/18] Update: Translations from eints english (au): 14 changes by krysclarke romanian: 3 changes by bnegrut finnish: 10 changes by hpiirai --- src/lang/english_AU.txt | 14 ++++++++++++++ src/lang/finnish.txt | 10 ++++++++++ src/lang/romanian.txt | 3 +++ 3 files changed, 27 insertions(+) diff --git a/src/lang/english_AU.txt b/src/lang/english_AU.txt index d67b6726ca..f2044c17e5 100644 --- a/src/lang/english_AU.txt +++ b/src/lang/english_AU.txt @@ -2358,6 +2358,8 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY :(New company) STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Create a new company and join it STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}This is you STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}This is the host of the game +STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} client{P "" s} - {NUM}/{NUM} compan{P y ies} +STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT_TOOLTIP :{BLACK}The number of currently connected clients, number of companies and maximum number of companies allowed by the server administrator # Matches ConnectionType ###length 5 @@ -5084,6 +5086,18 @@ STR_ERROR_TOO_FAR_FROM_PREVIOUS_DESTINATION :{WHITE}... too STR_ERROR_AIRCRAFT_NOT_ENOUGH_RANGE :{WHITE}... aircraft has not enough range # Extra messages which go on the third line of errors, explaining why orders failed +STR_ERROR_NO_RAIL_STATION :{WHITE}There is no railway station +STR_ERROR_NO_BUS_STATION :{WHITE}There is no bus station +STR_ERROR_NO_TRUCK_STATION :{WHITE}There is no lorry station +STR_ERROR_NO_DOCK :{WHITE}There is no dock +STR_ERROR_NO_AIRPORT :{WHITE}There is no airport/heliport +STR_ERROR_NO_STOP_COMPATIBLE_ROAD_TYPE :{WHITE}There are no stops with a compatible road type +STR_ERROR_NO_STOP_COMPATIBLE_TRAM_TYPE :{WHITE}There are no stops with a compatible tram type +STR_ERROR_NO_STOP_ARTICULATED_VEHICLE :{WHITE}There are no stops which are suitable for articulated road vehicles.{}Articulated road vehicles require a drive-through stop, not a bay stop +STR_ERROR_AIRPORT_NO_PLANES :{WHITE}This plane cannot land at this heliport +STR_ERROR_AIRPORT_NO_HELICOPTERS :{WHITE}This helicopter cannot land at this airport +STR_ERROR_NO_RAIL_WAYPOINT :{WHITE}There is no railway waypoint +STR_ERROR_NO_BUOY :{WHITE}There is no buoy # Timetable related errors STR_ERROR_CAN_T_TIMETABLE_VEHICLE :{WHITE}Can't timetable vehicle... diff --git a/src/lang/finnish.txt b/src/lang/finnish.txt index 02bf098e74..53bb64333d 100644 --- a/src/lang/finnish.txt +++ b/src/lang/finnish.txt @@ -5086,8 +5086,18 @@ STR_ERROR_TOO_FAR_FROM_PREVIOUS_DESTINATION :{WHITE}... liia STR_ERROR_AIRCRAFT_NOT_ENOUGH_RANGE :{WHITE}... ilma-aluksen toimintasäde ei riitä # Extra messages which go on the third line of errors, explaining why orders failed +STR_ERROR_NO_RAIL_STATION :{WHITE}Rautatieasemaa ei ole +STR_ERROR_NO_BUS_STATION :{WHITE}Linja-autopysäkkiä ei ole +STR_ERROR_NO_TRUCK_STATION :{WHITE}Lastauslaituria ei ole +STR_ERROR_NO_DOCK :{WHITE}Satamaa ei ole +STR_ERROR_NO_AIRPORT :{WHITE}Lento-/helikopterikenttää ei ole +STR_ERROR_NO_STOP_COMPATIBLE_ROAD_TYPE :{WHITE}Ei ole pysäkkejä sopivalla tien tyypillä +STR_ERROR_NO_STOP_COMPATIBLE_TRAM_TYPE :{WHITE}Ei ole pysäkkejä sopivalla raitiotien tyypillä +STR_ERROR_NO_STOP_ARTICULATED_VEHICLE :{WHITE}Ei ole nivelajoneuvoille sopivia pysäkkejä.{}Nivelletyt ajoneuvot vaativat pysäkin, joka ei ole kääntöpysäkki vaan läpiajettava STR_ERROR_AIRPORT_NO_PLANES :{WHITE}Tämä lentokone ei voi laskeutua tälle helikopterikentälle STR_ERROR_AIRPORT_NO_HELICOPTERS :{WHITE}Tämä helikopteri ei voi laskeutua tälle lentokentälle +STR_ERROR_NO_RAIL_WAYPOINT :{WHITE}Rautatien reittipistettä ei ole +STR_ERROR_NO_BUOY :{WHITE}Poijua ei ole # Timetable related errors STR_ERROR_CAN_T_TIMETABLE_VEHICLE :{WHITE}Ei voi asettaa aikataulua. diff --git a/src/lang/romanian.txt b/src/lang/romanian.txt index 74125a397b..36b2f63fa8 100644 --- a/src/lang/romanian.txt +++ b/src/lang/romanian.txt @@ -5084,6 +5084,9 @@ STR_ERROR_TOO_FAR_FROM_PREVIOUS_DESTINATION :{WHITE}... prea STR_ERROR_AIRCRAFT_NOT_ENOUGH_RANGE :{WHITE}... avionul nu are o rază de acțiune suficientă # Extra messages which go on the third line of errors, explaining why orders failed +STR_ERROR_NO_STOP_ARTICULATED_VEHICLE :{WHITE}Nu există opriri care să fie potrivite pentru vehiculele rutiere articulate.{}Vehiculele rutiere articulate necesită o oprire de drum, nu o oprire într-o rampă. +STR_ERROR_NO_RAIL_WAYPOINT :{WHITE}Nu există niciun punct intermediar feroviar +STR_ERROR_NO_BUOY :{WHITE}Nu există geamanduri # Timetable related errors STR_ERROR_CAN_T_TIMETABLE_VEHICLE :{WHITE}Nu pot programa vehiculul... From b6609d1833a4b85d5506b790964c712f7dc8e257 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 21 Jan 2023 18:02:39 +0000 Subject: [PATCH 02/18] Fix: Network server highlight invisible with RTL layout. --- src/network/network_gui.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index da89de8c25..40f1bb64e6 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -398,9 +398,8 @@ protected: /* show highlighted item with a different colour */ if (highlight) { - Rect r = {name.left, y, info.right, y + (int)this->resize.step_height - 1}; - Rect ir = r.Shrink(WidgetDimensions::scaled.bevel); - GfxFillRect(ir.left, ir.top, ir.right, ir.bottom, PC_GREY); + Rect r = {std::min(name.left, info.left), y, std::max(name.right, info.right), y + (int)this->resize.step_height - 1}; + GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), PC_GREY); } /* offsets to vertically centre text and icons */ From 3287acd11496dea1dcd653c3d561852d3830e21f Mon Sep 17 00:00:00 2001 From: Rubidium Date: Tue, 7 Mar 2023 22:56:20 +0100 Subject: [PATCH 03/18] Fix 13528bfcd0: bank balance command allows int64, GS was limited to int32 --- src/script/api/script_company.cpp | 2 -- src/script/api/script_company.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/script/api/script_company.cpp b/src/script/api/script_company.cpp index 895c0b6eae..92c8755a69 100644 --- a/src/script/api/script_company.cpp +++ b/src/script/api/script_company.cpp @@ -241,8 +241,6 @@ { EnforceDeityMode(false); EnforcePrecondition(false, expenses_type < (ExpensesType)::EXPENSES_END); - EnforcePrecondition(false, (int64)delta >= INT32_MIN); - EnforcePrecondition(false, (int64)delta <= INT32_MAX); EnforcePrecondition(false, tile == INVALID_TILE || ::IsValidTile(tile)); company = ResolveCompanyID(company); diff --git a/src/script/api/script_company.hpp b/src/script/api/script_company.hpp index fffd93bff0..690277600b 100644 --- a/src/script/api/script_company.hpp +++ b/src/script/api/script_company.hpp @@ -246,8 +246,6 @@ public: * @return True, if the bank balance was changed. * @game @pre ScriptCompanyMode::IsDeity(). * @pre ResolveCompanyID(company) != COMPANY_INVALID. - * @pre delta >= -2**31 - * @pre delta < 2**31 * @note You need to create your own news message to inform about costs/gifts that you create using this command. * @api -ai */ From c74dfe393f02d1b0d96ad014e888fcebb57e2c9c Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 8 Mar 2023 18:38:19 +0000 Subject: [PATCH 04/18] Fix #10556: Duplication of road infrastructure count updates (#10557) When building a road stop --- src/station_cmd.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 53b3ef168e..e9f96f0ce0 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2058,9 +2058,6 @@ CommandCost CmdBuildRoadStop(DoCommandFlag flags, TileIndex tile, uint8 width, u UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); Company::Get(st->owner)->infrastructure.station++; - UpdateCompanyRoadInfrastructure(road_rt, road_owner, ROAD_STOP_TRACKBIT_FACTOR); - UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR); - SetCustomRoadStopSpecIndex(cur_tile, specindex); if (roadstopspec != nullptr) { st->SetRoadStopRandomBits(cur_tile, GB(Random(), 0, 8)); From 4af7f3e6454c7feeaef70229a3ed28c154209e44 Mon Sep 17 00:00:00 2001 From: translators Date: Wed, 8 Mar 2023 18:48:34 +0000 Subject: [PATCH 05/18] Update: Translations from eints catalan: 13 changes by J0anJosep --- src/lang/catalan.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lang/catalan.txt b/src/lang/catalan.txt index a0a7acea88..18ccbb8167 100644 --- a/src/lang/catalan.txt +++ b/src/lang/catalan.txt @@ -2997,7 +2997,7 @@ STR_LAI_TREE_NAME_CACTUS_PLANTS :Plantes de Cact STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION :Estació de Ferrocarril STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR :Hangar d'Avions STR_LAI_STATION_DESCRIPTION_AIRPORT :Aeroport -STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA :Àrea de càrrega de camions +STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA :Moll de càrrega de camions STR_LAI_STATION_DESCRIPTION_BUS_STATION :Parada d'autobús STR_LAI_STATION_DESCRIPTION_SHIP_DOCK :Moll per vaixells STR_LAI_STATION_DESCRIPTION_BUOY :Boia @@ -5087,6 +5087,18 @@ STR_ERROR_TOO_FAR_FROM_PREVIOUS_DESTINATION :{WHITE}... mass STR_ERROR_AIRCRAFT_NOT_ENOUGH_RANGE :{WHITE}... l'avió no té prou autonomia # Extra messages which go on the third line of errors, explaining why orders failed +STR_ERROR_NO_RAIL_STATION :{WHITE}No hi ha cap estació ferroviària +STR_ERROR_NO_BUS_STATION :{WHITE}No hi ha cap estació d'autobusos +STR_ERROR_NO_TRUCK_STATION :{WHITE}No hi ha cap estació de camions +STR_ERROR_NO_DOCK :{WHITE}No hi ha cap port +STR_ERROR_NO_AIRPORT :{WHITE}No hi ha cap aeroport/heliport +STR_ERROR_NO_STOP_COMPATIBLE_ROAD_TYPE :{WHITE}No hi ha parades amb un tipus de carretera compatible +STR_ERROR_NO_STOP_COMPATIBLE_TRAM_TYPE :{WHITE}No hi ha parades amb un tipus de tramvia compatible +STR_ERROR_NO_STOP_ARTICULATED_VEHICLE :{WHITE}No hi ha parades que siguin adequades per a vehicles de carretera articulats.{}Aquest tipus de vehicles necessiten una estació de pas, no pas una estació final +STR_ERROR_AIRPORT_NO_PLANES :{WHITE}L'avió no pot aterrar en aquest aeroport +STR_ERROR_AIRPORT_NO_HELICOPTERS :{WHITE}L'helicòpter no pot aterrar en aquest aeroport +STR_ERROR_NO_RAIL_WAYPOINT :{WHITE}No hi ha cap punt de pas ferroviari +STR_ERROR_NO_BUOY :{WHITE}No hi ha cap boia # Timetable related errors STR_ERROR_CAN_T_TIMETABLE_VEHICLE :{WHITE}Impossible establir l'horari del vehicle... From d80f193e74a304ffbd7e2d5743599216970e7cbc Mon Sep 17 00:00:00 2001 From: Joan Josep Date: Wed, 8 Mar 2023 21:59:01 +0100 Subject: [PATCH 06/18] Fix: Improve grfmessage for ShipVehicleChangeInfo. (#10558) --- src/newgrf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 045e2e9940..cda3f4d5d3 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -1621,7 +1621,7 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint engine, int numinfo, int prop ei->cargo_type = ctype; } else { ei->cargo_type = CT_INVALID; - grfmsg(2, "RailVehicleChangeInfo: Invalid cargo type %d, using first refittable", ctype); + grfmsg(2, "ShipVehicleChangeInfo: Invalid cargo type %d, using first refittable", ctype); } break; } From a79f97c0228d2ce3f196c7b49b5fa80b540e30cb Mon Sep 17 00:00:00 2001 From: Rubidium Date: Wed, 1 Mar 2023 21:18:55 +0100 Subject: [PATCH 07/18] Change: upon an invalid company mode, set ERR_PRECONDITION_INVALID_COMPANY instead of ERR_PRECONDITION_INVALID --- src/script/api/script_error.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/api/script_error.hpp b/src/script/api/script_error.hpp index e053350188..3b2ecc0ccf 100644 --- a/src/script/api/script_error.hpp +++ b/src/script/api/script_error.hpp @@ -53,14 +53,14 @@ * @param returnval The value to return on failure. */ #define EnforceCompanyModeValid(returnval) \ - EnforcePrecondition(returnval, ScriptCompanyMode::IsValid()) + EnforcePreconditionCustomError(returnval, ScriptCompanyMode::IsValid(), ScriptError::ERR_PRECONDITION_INVALID_COMPANY) /** * Helper to enforce the precondition that we are in a deity mode. * @param returnval The value to return on failure. */ #define EnforceDeityMode(returnval) \ - EnforcePrecondition(returnval, ScriptCompanyMode::IsDeity()) + EnforcePreconditionCustomError(returnval, ScriptCompanyMode::IsDeity(), ScriptError::ERR_PRECONDITION_INVALID_COMPANY) /** * Class that handles all error related functions. From 2fffde08916a0b1ca74075ab0d482fafcf83245f Mon Sep 17 00:00:00 2001 From: Rubidium Date: Thu, 2 Mar 2023 22:02:37 +0100 Subject: [PATCH 08/18] Add: workflow and script for checking missing mode enforcements --- .github/script-missing-mode-enforcement.py | 71 +++++++++++++++++++ .github/unused-strings.py | 7 +- .../script-missing-mode-enforcement.yml | 22 ++++++ 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 .github/script-missing-mode-enforcement.py create mode 100644 .github/workflows/script-missing-mode-enforcement.yml diff --git a/.github/script-missing-mode-enforcement.py b/.github/script-missing-mode-enforcement.py new file mode 100644 index 0000000000..5903b5f0a0 --- /dev/null +++ b/.github/script-missing-mode-enforcement.py @@ -0,0 +1,71 @@ +""" +Script to scan the OpenTTD's script API for functions that miss checks for the +function being called from the right mode (deity or company mode). + +When a function calls either ScriptObject::Command or ScriptObject::GetCompany +then the function is considered dangerous. When one of the mode enforcement +macros from script_error.hpp, i.e. EnforceDeityMode, EnforceCompanyModeValid or +EnforceDeityOrCompanyModeValid, are called in the function, then we consider +that the function has mode enforcement. + +Any dangerous function for which no enforcement is found are emitted as errors. +""" + +import glob +import re +import sys + + +def check_mode_enforcement(path): + errors = [] + with open(path, "r") as reader: + mode_enforcement_found = False + dangerous_function = False + for line in reader: + # Line does not start with a tab and have ::. That looks like the begin of a function, so reset the state. + if re.match(r"^[^\t].*\w::\w", line): + mode_enforcement_found = False + dangerous_function = False + currentFunction = line + continue + + if re.match( + r"\t(EnforceDeityMode|EnforceCompanyModeValid|EnforceDeityOrCompanyModeValid|EnforceDeityOrCompanyModeValid_Void)\(", + line, + ): + # Mode enforcement macro found + mode_enforcement_found = True + continue + + if re.match(r".*(ScriptObject::Command|ScriptObject::GetCompany).*", line): + # Dangerous function found + dangerous_function = True + continue + + # Line with only a closing bracket. That looks like the end of a function, so check for the dangerous function without mode enforcement + if re.match(r"^}$", line) and dangerous_function and not mode_enforcement_found: + function_name = currentFunction.rstrip("\n").replace("/* static */ ", "") + errors.append(f"{path}: {function_name}") + + return errors + + +def main(): + errors = [] + for path in sorted(glob.glob("src/script/api/*.cpp")): + # Skip a number of files that yield only false positives + if path.endswith(("script_object.cpp", "script_companymode.cpp", "script_controller.cpp", "script_game.cpp")): + continue + + errors.extend(check_mode_enforcement(path)) + + if errors: + print("Mode enforcement was expected in the following files/functions:") + print("\n".join(errors)) + sys.exit(1) + + print("OK") + + +if __name__ == "__main__": + main() diff --git a/.github/unused-strings.py b/.github/unused-strings.py index 210212c397..527a938dc5 100644 --- a/.github/unused-strings.py +++ b/.github/unused-strings.py @@ -210,11 +210,10 @@ def main(): errors.append(f"ERROR: {string} is (possibly) no longer needed.") if errors: - for error in errors: - print(error) + print("\n".join(errors)) sys.exit(1) - else: - print("OK") + + print("OK") if __name__ == "__main__": diff --git a/.github/workflows/script-missing-mode-enforcement.yml b/.github/workflows/script-missing-mode-enforcement.yml new file mode 100644 index 0000000000..9b9e8e7efa --- /dev/null +++ b/.github/workflows/script-missing-mode-enforcement.yml @@ -0,0 +1,22 @@ +name: Script missing mode enforcement + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + script-missing-mode-enforcement: + name: Script missing mode enforcement + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check for finding script functions that require company/deity mode enforcement/checks + run: | + set -ex + python3 .github/script-missing-mode-enforcement.py From 534f2419add0018a72eeb9da49012ac6735aab79 Mon Sep 17 00:00:00 2001 From: Rubidium Date: Thu, 2 Mar 2023 21:22:37 +0100 Subject: [PATCH 09/18] Add: precondition checks to functions that work with both valid company and deity These are functions that either use ScriptObject::Command or ScriptObject::GetCompany. This is a bit over-protective, but having the check everywhere makes it easier to validate that no check is missing automatically instead of by review. At this moment these checks will not do anything useful, as either IsValid or IsDeity from ScriptCompanyMode returns true, but that will change later. --- src/script/api/script_airport.cpp | 2 ++ src/script/api/script_basestation.cpp | 1 + src/script/api/script_bridge.cpp | 5 +++++ src/script/api/script_depotlist.cpp | 1 + src/script/api/script_engine.cpp | 2 ++ src/script/api/script_enginelist.cpp | 1 + src/script/api/script_error.hpp | 17 +++++++++++++++++ src/script/api/script_game.cpp | 1 + src/script/api/script_gamesettings.cpp | 1 + src/script/api/script_group.cpp | 1 + src/script/api/script_grouplist.cpp | 2 ++ src/script/api/script_industrytype.cpp | 2 ++ src/script/api/script_objecttype.cpp | 1 + src/script/api/script_rail.cpp | 1 + src/script/api/script_railtypelist.cpp | 3 ++- src/script/api/script_road.cpp | 2 ++ src/script/api/script_roadtypelist.cpp | 1 + src/script/api/script_sign.cpp | 4 ++++ src/script/api/script_station.cpp | 1 + src/script/api/script_stationlist.cpp | 1 + src/script/api/script_tile.cpp | 2 ++ src/script/api/script_town.cpp | 1 + src/script/api/script_tunnel.cpp | 5 +++++ src/script/api/script_vehicle.cpp | 1 + src/script/api/script_vehiclelist.cpp | 5 +++++ src/script/api/script_waypoint.cpp | 1 + src/script/api/script_waypointlist.cpp | 1 + 27 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/script/api/script_airport.cpp b/src/script/api/script_airport.cpp index 5ecaa390ee..6513a1d205 100644 --- a/src/script/api/script_airport.cpp +++ b/src/script/api/script_airport.cpp @@ -91,6 +91,7 @@ /* static */ SQInteger ScriptAirport::GetNumHangars(TileIndex tile) { + EnforceDeityOrCompanyModeValid(-1); if (!::IsValidTile(tile)) return -1; if (!::IsTileType(tile, MP_STATION)) return -1; @@ -103,6 +104,7 @@ /* static */ TileIndex ScriptAirport::GetHangarOfAirport(TileIndex tile) { + EnforceDeityOrCompanyModeValid(INVALID_TILE); if (!::IsValidTile(tile)) return INVALID_TILE; if (!::IsTileType(tile, MP_STATION)) return INVALID_TILE; if (GetNumHangars(tile) < 1) return INVALID_TILE; diff --git a/src/script/api/script_basestation.cpp b/src/script/api/script_basestation.cpp index fe5dc5d3d2..408543ae8a 100644 --- a/src/script/api/script_basestation.cpp +++ b/src/script/api/script_basestation.cpp @@ -21,6 +21,7 @@ /* static */ bool ScriptBaseStation::IsValidBaseStation(StationID station_id) { + EnforceDeityOrCompanyModeValid(false); const BaseStation *st = ::BaseStation::GetIfValid(station_id); return st != nullptr && (st->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity() || st->owner == OWNER_NONE); } diff --git a/src/script/api/script_bridge.cpp b/src/script/api/script_bridge.cpp index e34d14d18f..1ec873204e 100644 --- a/src/script/api/script_bridge.cpp +++ b/src/script/api/script_bridge.cpp @@ -72,6 +72,7 @@ static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance) /* static */ bool ScriptBridge::BuildBridge(ScriptVehicle::VehicleType vehicle_type, BridgeID bridge_id, TileIndex start, TileIndex end) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, start != end); EnforcePrecondition(false, ::IsValidTile(start) && ::IsValidTile(end)); EnforcePrecondition(false, TileX(start) == TileX(end) || TileY(start) == TileY(end)); @@ -95,6 +96,8 @@ static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance) /* static */ bool ScriptBridge::_BuildBridgeRoad1() { + EnforceDeityOrCompanyModeValid(false); + /* Build the piece of road on the 'start' side of the bridge */ TileIndex end = ScriptObject::GetCallbackVariable(0); TileIndex start = ScriptObject::GetCallbackVariable(1); @@ -107,6 +110,8 @@ static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance) /* static */ bool ScriptBridge::_BuildBridgeRoad2() { + EnforceDeityOrCompanyModeValid(false); + /* Build the piece of road on the 'end' side of the bridge */ TileIndex end = ScriptObject::GetCallbackVariable(0); TileIndex start = ScriptObject::GetCallbackVariable(1); diff --git a/src/script/api/script_depotlist.cpp b/src/script/api/script_depotlist.cpp index 6f86ade5f4..2e1dc6fc98 100644 --- a/src/script/api/script_depotlist.cpp +++ b/src/script/api/script_depotlist.cpp @@ -16,6 +16,7 @@ ScriptDepotList::ScriptDepotList(ScriptTile::TransportType transport_type) { + EnforceDeityOrCompanyModeValid_Void(); ::TileType tile_type; switch (transport_type) { default: return; diff --git a/src/script/api/script_engine.cpp b/src/script/api/script_engine.cpp index 2d59df5006..17845ae4fe 100644 --- a/src/script/api/script_engine.cpp +++ b/src/script/api/script_engine.cpp @@ -24,6 +24,7 @@ /* static */ bool ScriptEngine::IsValidEngine(EngineID engine_id) { + EnforceDeityOrCompanyModeValid(false); const Engine *e = ::Engine::GetIfValid(engine_id); if (e == nullptr || !e->IsEnabled()) return false; @@ -35,6 +36,7 @@ /* static */ bool ScriptEngine::IsBuildable(EngineID engine_id) { + EnforceDeityOrCompanyModeValid(false); const Engine *e = ::Engine::GetIfValid(engine_id); return e != nullptr && ::IsEngineBuildable(engine_id, e->type, ScriptObject::GetCompany()); } diff --git a/src/script/api/script_enginelist.cpp b/src/script/api/script_enginelist.cpp index 9a98d9cb9b..9ba725dfd0 100644 --- a/src/script/api/script_enginelist.cpp +++ b/src/script/api/script_enginelist.cpp @@ -15,6 +15,7 @@ ScriptEngineList::ScriptEngineList(ScriptVehicle::VehicleType vehicle_type) { + EnforceDeityOrCompanyModeValid_Void(); for (const Engine *e : Engine::IterateType((::VehicleType)vehicle_type)) { if (ScriptCompanyMode::IsDeity() || HasBit(e->company_avail, ScriptObject::GetCompany())) this->AddItem(e->index); } diff --git a/src/script/api/script_error.hpp b/src/script/api/script_error.hpp index 3b2ecc0ccf..f4adb84cec 100644 --- a/src/script/api/script_error.hpp +++ b/src/script/api/script_error.hpp @@ -62,6 +62,23 @@ #define EnforceDeityMode(returnval) \ EnforcePreconditionCustomError(returnval, ScriptCompanyMode::IsDeity(), ScriptError::ERR_PRECONDITION_INVALID_COMPANY) +/** + * Helper to enforce the precondition that the company mode is valid or that we are a deity. + * @param returnval The value to return on failure. + */ +#define EnforceDeityOrCompanyModeValid(returnval) \ + EnforcePreconditionCustomError(returnval, ScriptCompanyMode::IsDeity() || ScriptCompanyMode::IsValid(), ScriptError::ERR_PRECONDITION_INVALID_COMPANY) + +/** + * Helper to enforce the precondition that the company mode is valid or that we are a deity. + */ +#define EnforceDeityOrCompanyModeValid_Void() \ + if (!(ScriptCompanyMode::IsDeity() || ScriptCompanyMode::IsValid())) { \ + ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_INVALID_COMPANY); \ + return; \ + } + + /** * Class that handles all error related functions. * @api ai game diff --git a/src/script/api/script_game.cpp b/src/script/api/script_game.cpp index 3df6aef154..d338837f14 100644 --- a/src/script/api/script_game.cpp +++ b/src/script/api/script_game.cpp @@ -9,6 +9,7 @@ #include "../../stdafx.h" #include "script_game.hpp" +#include "script_error.hpp" #include "../../command_type.h" #include "../../settings_type.h" #include "../../network/network.h" diff --git a/src/script/api/script_gamesettings.cpp b/src/script/api/script_gamesettings.cpp index 00e88e8351..c8d99c36e6 100644 --- a/src/script/api/script_gamesettings.cpp +++ b/src/script/api/script_gamesettings.cpp @@ -33,6 +33,7 @@ /* static */ bool ScriptGameSettings::SetValue(const char *setting, SQInteger value) { + EnforceDeityOrCompanyModeValid(false); if (!IsValid(setting)) return false; const SettingDesc *sd = GetSettingFromName(setting); diff --git a/src/script/api/script_group.cpp b/src/script/api/script_group.cpp index a7f0417695..7f1542e37d 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -25,6 +25,7 @@ /* static */ bool ScriptGroup::IsValidGroup(GroupID group_id) { + EnforceDeityOrCompanyModeValid(false); const Group *g = ::Group::GetIfValid(group_id); return g != nullptr && g->owner == ScriptObject::GetCompany(); } diff --git a/src/script/api/script_grouplist.cpp b/src/script/api/script_grouplist.cpp index 569b44634c..11099ca67e 100644 --- a/src/script/api/script_grouplist.cpp +++ b/src/script/api/script_grouplist.cpp @@ -9,12 +9,14 @@ #include "../../stdafx.h" #include "script_grouplist.hpp" +#include "script_error.hpp" #include "../../group.h" #include "../../safeguards.h" ScriptGroupList::ScriptGroupList() { + EnforceDeityOrCompanyModeValid_Void(); for (const Group *g : Group::Iterate()) { if (g->owner == ScriptObject::GetCompany()) this->AddItem(g->index); } diff --git a/src/script/api/script_industrytype.cpp b/src/script/api/script_industrytype.cpp index 24289b9cbb..3d7073e40b 100644 --- a/src/script/api/script_industrytype.cpp +++ b/src/script/api/script_industrytype.cpp @@ -119,6 +119,7 @@ /* static */ bool ScriptIndustryType::BuildIndustry(IndustryType industry_type, TileIndex tile) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, CanBuildIndustry(industry_type)); EnforcePrecondition(false, ScriptMap::IsValidTile(tile)); @@ -129,6 +130,7 @@ /* static */ bool ScriptIndustryType::ProspectIndustry(IndustryType industry_type) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, CanProspectIndustry(industry_type)); uint32 seed = ScriptBase::Rand(); diff --git a/src/script/api/script_objecttype.cpp b/src/script/api/script_objecttype.cpp index 71d09a4097..7d74d9b8b8 100644 --- a/src/script/api/script_objecttype.cpp +++ b/src/script/api/script_objecttype.cpp @@ -39,6 +39,7 @@ /* static */ bool ScriptObjectType::BuildObject(ObjectType object_type, SQInteger view, TileIndex tile) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, IsValidObjectType(object_type)); EnforcePrecondition(false, view >= 0 && view < GetViews(object_type)); EnforcePrecondition(false, ScriptMap::IsValidTile(tile)); diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index 6643ce2781..e9bdbd0658 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -69,6 +69,7 @@ /* static */ bool ScriptRail::IsRailTypeAvailable(RailType rail_type) { + EnforceDeityOrCompanyModeValid(false); if ((::RailType)rail_type >= RAILTYPE_END) return false; return ScriptCompanyMode::IsDeity() || ::HasRailtypeAvail(ScriptObject::GetCompany(), (::RailType)rail_type); diff --git a/src/script/api/script_railtypelist.cpp b/src/script/api/script_railtypelist.cpp index a7ddf9b20c..cfc0b3b550 100644 --- a/src/script/api/script_railtypelist.cpp +++ b/src/script/api/script_railtypelist.cpp @@ -9,13 +9,14 @@ #include "../../stdafx.h" #include "script_railtypelist.hpp" -#include "script_companymode.hpp" +#include "script_error.hpp" #include "../../rail.h" #include "../../safeguards.h" ScriptRailTypeList::ScriptRailTypeList() { + EnforceDeityOrCompanyModeValid_Void(); for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) { if (ScriptCompanyMode::IsDeity() || ::HasRailtypeAvail(ScriptObject::GetCompany(), rt)) this->AddItem(rt); } diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index ea9a90e75b..2326247753 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -67,6 +67,7 @@ /* static */ bool ScriptRoad::IsRoadTypeAvailable(RoadType road_type) { + EnforceDeityOrCompanyModeValid(false); return (::RoadType)road_type < ROADTYPE_END && ::HasRoadTypeAvail(ScriptObject::GetCompany(), (::RoadType)road_type); } @@ -489,6 +490,7 @@ static bool NeighbourHasReachableRoad(::RoadType rt, TileIndex start_tile, DiagD /* static */ bool ScriptRoad::_BuildRoadInternal(TileIndex start, TileIndex end, bool one_way, bool full) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, start != end); EnforcePrecondition(false, ::IsValidTile(start)); EnforcePrecondition(false, ::IsValidTile(end)); diff --git a/src/script/api/script_roadtypelist.cpp b/src/script/api/script_roadtypelist.cpp index 7b54d8c99a..5d2ad8ae6a 100644 --- a/src/script/api/script_roadtypelist.cpp +++ b/src/script/api/script_roadtypelist.cpp @@ -15,6 +15,7 @@ ScriptRoadTypeList::ScriptRoadTypeList(ScriptRoad::RoadTramTypes rtts) { + EnforceDeityOrCompanyModeValid_Void(); for (RoadType rt = ROADTYPE_BEGIN; rt != ROADTYPE_END; rt++) { if (!HasBit(rtts, GetRoadTramType(rt))) continue; if (ScriptCompanyMode::IsDeity() || ::HasRoadTypeAvail(ScriptObject::GetCompany(), rt)) this->AddItem(rt); diff --git a/src/script/api/script_sign.cpp b/src/script/api/script_sign.cpp index 269d3e69dc..01488b8953 100644 --- a/src/script/api/script_sign.cpp +++ b/src/script/api/script_sign.cpp @@ -21,6 +21,7 @@ /* static */ bool ScriptSign::IsValidSign(SignID sign_id) { + EnforceDeityOrCompanyModeValid(false); const Sign *si = ::Sign::GetIfValid(sign_id); return si != nullptr && (si->owner == ScriptObject::GetCompany() || si->owner == OWNER_DEITY); } @@ -36,6 +37,7 @@ { CCountedPtr counter(name); + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, IsValidSign(sign_id)); EnforcePrecondition(false, name != nullptr); const std::string &text = name->GetDecodedText(); @@ -63,6 +65,7 @@ /* static */ bool ScriptSign::RemoveSign(SignID sign_id) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, IsValidSign(sign_id)); return ScriptObject::Command::Do(sign_id, ""); } @@ -71,6 +74,7 @@ { CCountedPtr counter(name); + EnforceDeityOrCompanyModeValid(INVALID_SIGN); EnforcePrecondition(INVALID_SIGN, ::IsValidTile(location)); EnforcePrecondition(INVALID_SIGN, name != nullptr); const std::string &text = name->GetDecodedText(); diff --git a/src/script/api/script_station.cpp b/src/script/api/script_station.cpp index d7c3353647..a545b949ab 100644 --- a/src/script/api/script_station.cpp +++ b/src/script/api/script_station.cpp @@ -21,6 +21,7 @@ /* static */ bool ScriptStation::IsValidStation(StationID station_id) { + EnforceDeityOrCompanyModeValid(false); const Station *st = ::Station::GetIfValid(station_id); return st != nullptr && (st->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity() || st->owner == OWNER_NONE); } diff --git a/src/script/api/script_stationlist.cpp b/src/script/api/script_stationlist.cpp index 7b692483f1..5ccb6a0609 100644 --- a/src/script/api/script_stationlist.cpp +++ b/src/script/api/script_stationlist.cpp @@ -18,6 +18,7 @@ ScriptStationList::ScriptStationList(ScriptStation::StationType station_type) { + EnforceDeityOrCompanyModeValid_Void(); for (Station *st : Station::Iterate()) { if ((st->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity()) && (st->facilities & station_type) != 0) this->AddItem(st->index); } diff --git a/src/script/api/script_tile.cpp b/src/script/api/script_tile.cpp index 8dc1c65fcf..f292bfd647 100644 --- a/src/script/api/script_tile.cpp +++ b/src/script/api/script_tile.cpp @@ -25,6 +25,7 @@ /* static */ bool ScriptTile::IsBuildable(TileIndex tile) { + EnforceDeityOrCompanyModeValid(false); if (!::IsValidTile(tile)) return false; switch (::GetTileType(tile)) { @@ -277,6 +278,7 @@ /* static */ bool ScriptTile::DemolishTile(TileIndex tile) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, ::IsValidTile(tile)); return ScriptObject::Command::Do(tile); diff --git a/src/script/api/script_town.cpp b/src/script/api/script_town.cpp index 855dba6302..6bdd2283ff 100644 --- a/src/script/api/script_town.cpp +++ b/src/script/api/script_town.cpp @@ -281,6 +281,7 @@ { CCountedPtr counter(name); + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, ScriptCompanyMode::IsDeity() || _settings_game.economy.found_town != TF_FORBIDDEN); EnforcePrecondition(false, ::IsValidTile(tile)); EnforcePrecondition(false, size == TOWN_SIZE_SMALL || size == TOWN_SIZE_MEDIUM || size == TOWN_SIZE_LARGE) diff --git a/src/script/api/script_tunnel.cpp b/src/script/api/script_tunnel.cpp index cdbcb6df8b..0e1286c334 100644 --- a/src/script/api/script_tunnel.cpp +++ b/src/script/api/script_tunnel.cpp @@ -82,6 +82,7 @@ static void _DoCommandReturnBuildTunnel1(class ScriptInstance *instance) /* static */ bool ScriptTunnel::BuildTunnel(ScriptVehicle::VehicleType vehicle_type, TileIndex start) { + EnforceDeityOrCompanyModeValid(false); EnforcePrecondition(false, ::IsValidTile(start)); EnforcePrecondition(false, vehicle_type == ScriptVehicle::VT_RAIL || vehicle_type == ScriptVehicle::VT_ROAD); EnforcePrecondition(false, vehicle_type != ScriptVehicle::VT_RAIL || ScriptRail::IsRailTypeAvailable(ScriptRail::GetCurrentRailType())); @@ -99,6 +100,8 @@ static void _DoCommandReturnBuildTunnel1(class ScriptInstance *instance) /* static */ bool ScriptTunnel::_BuildTunnelRoad1() { + EnforceDeityOrCompanyModeValid(false); + /* Build the piece of road on the 'start' side of the tunnel */ TileIndex end = ScriptObject::GetCallbackVariable(0); TileIndex start = ScriptTunnel::GetOtherTunnelEnd(end); @@ -111,6 +114,8 @@ static void _DoCommandReturnBuildTunnel1(class ScriptInstance *instance) /* static */ bool ScriptTunnel::_BuildTunnelRoad2() { + EnforceDeityOrCompanyModeValid(false); + /* Build the piece of road on the 'end' side of the tunnel */ TileIndex end = ScriptObject::GetCallbackVariable(0); TileIndex start = ScriptTunnel::GetOtherTunnelEnd(end); diff --git a/src/script/api/script_vehicle.cpp b/src/script/api/script_vehicle.cpp index f308b6e064..144e5e3e29 100644 --- a/src/script/api/script_vehicle.cpp +++ b/src/script/api/script_vehicle.cpp @@ -30,6 +30,7 @@ /* static */ bool ScriptVehicle::IsValidVehicle(VehicleID vehicle_id) { + EnforceDeityOrCompanyModeValid(false); const Vehicle *v = ::Vehicle::GetIfValid(vehicle_id); return v != nullptr && (v->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity()) && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())); } diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp index d849ea186e..0687ac366c 100644 --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -20,6 +20,7 @@ ScriptVehicleList::ScriptVehicleList() { + EnforceDeityOrCompanyModeValid_Void(); for (const Vehicle *v : Vehicle::Iterate()) { if ((v->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity()) && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon()))) this->AddItem(v->index); } @@ -27,6 +28,7 @@ ScriptVehicleList::ScriptVehicleList() ScriptVehicleList_Station::ScriptVehicleList_Station(StationID station_id) { + EnforceDeityOrCompanyModeValid_Void(); if (!ScriptBaseStation::IsValidBaseStation(station_id)) return; for (const Vehicle *v : Vehicle::Iterate()) { @@ -43,6 +45,7 @@ ScriptVehicleList_Station::ScriptVehicleList_Station(StationID station_id) ScriptVehicleList_Depot::ScriptVehicleList_Depot(TileIndex tile) { + EnforceDeityOrCompanyModeValid_Void(); if (!ScriptMap::IsValidTile(tile)) return; DestinationID dest; @@ -100,6 +103,7 @@ ScriptVehicleList_SharedOrders::ScriptVehicleList_SharedOrders(VehicleID vehicle ScriptVehicleList_Group::ScriptVehicleList_Group(GroupID group_id) { + EnforceDeityOrCompanyModeValid_Void(); if (!ScriptGroup::IsValidGroup((ScriptGroup::GroupID)group_id)) return; for (const Vehicle *v : Vehicle::Iterate()) { @@ -111,6 +115,7 @@ ScriptVehicleList_Group::ScriptVehicleList_Group(GroupID group_id) ScriptVehicleList_DefaultGroup::ScriptVehicleList_DefaultGroup(ScriptVehicle::VehicleType vehicle_type) { + EnforceDeityOrCompanyModeValid_Void(); if (vehicle_type < ScriptVehicle::VT_RAIL || vehicle_type > ScriptVehicle::VT_AIR) return; for (const Vehicle *v : Vehicle::Iterate()) { diff --git a/src/script/api/script_waypoint.cpp b/src/script/api/script_waypoint.cpp index 9b4f2de5a0..5f7bceaa61 100644 --- a/src/script/api/script_waypoint.cpp +++ b/src/script/api/script_waypoint.cpp @@ -17,6 +17,7 @@ /* static */ bool ScriptWaypoint::IsValidWaypoint(StationID waypoint_id) { + EnforceDeityOrCompanyModeValid(false); const Waypoint *wp = ::Waypoint::GetIfValid(waypoint_id); return wp != nullptr && (wp->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity() || wp->owner == OWNER_NONE); } diff --git a/src/script/api/script_waypointlist.cpp b/src/script/api/script_waypointlist.cpp index 92d684d450..5e1e1ca079 100644 --- a/src/script/api/script_waypointlist.cpp +++ b/src/script/api/script_waypointlist.cpp @@ -17,6 +17,7 @@ ScriptWaypointList::ScriptWaypointList(ScriptWaypoint::WaypointType waypoint_type) { + EnforceDeityOrCompanyModeValid_Void(); for (const Waypoint *wp : Waypoint::Iterate()) { if ((wp->facilities & waypoint_type) && (wp->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity() || wp->owner == OWNER_NONE)) this->AddItem(wp->index); From 8d443d137988f8a16b4f700c32ca360660fcff25 Mon Sep 17 00:00:00 2001 From: Rubidium Date: Thu, 2 Mar 2023 21:22:37 +0100 Subject: [PATCH 10/18] Add: company mode enforcement checks to functions with command or company access Command functions are those that call ScriptObject::Command, and functions with company access are any that call ScriptObject::GetCompany. This is a bit over-protective, but having the check everywhere makes it easier to validate that no check is missing automatically instead of by review. --- src/script/api/script_company.cpp | 9 +++++++++ src/script/api/script_company.hpp | 8 ++++++++ src/script/api/script_event_types.cpp | 2 ++ src/script/api/script_event_types.hpp | 2 ++ src/script/api/script_group.cpp | 15 +++++++++++++++ src/script/api/script_group.hpp | 15 +++++++++++++++ src/script/api/script_order.cpp | 17 +++++++++++++++++ src/script/api/script_order.hpp | 17 +++++++++++++++++ src/script/api/script_station.cpp | 1 + src/script/api/script_station.hpp | 1 + 10 files changed, 87 insertions(+) diff --git a/src/script/api/script_company.cpp b/src/script/api/script_company.cpp index 92c8755a69..6ee7bcc5d3 100644 --- a/src/script/api/script_company.cpp +++ b/src/script/api/script_company.cpp @@ -47,6 +47,7 @@ { CCountedPtr counter(name); + EnforceCompanyModeValid(false); EnforcePrecondition(false, name != nullptr); const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); @@ -68,6 +69,7 @@ { CCountedPtr counter(name); + EnforceCompanyModeValid(false); EnforcePrecondition(false, name != nullptr); const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); @@ -94,6 +96,7 @@ /* static */ bool ScriptCompany::SetPresidentGender(Gender gender) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, gender == GENDER_MALE || gender == GENDER_FEMALE); EnforcePrecondition(false, GetPresidentGender(ScriptCompany::COMPANY_SELF) != gender); @@ -269,6 +272,7 @@ /* static */ bool ScriptCompany::SetAutoRenewStatus(bool autorenew) { + EnforceCompanyModeValid(false); return ScriptObject::Command::Do("company.engine_renew", autorenew ? 1 : 0); } @@ -282,7 +286,9 @@ /* static */ bool ScriptCompany::SetAutoRenewMonths(SQInteger months) { + EnforceCompanyModeValid(false); months = Clamp(months, INT16_MIN, INT16_MAX); + return ScriptObject::Command::Do("company.engine_renew_months", months); } @@ -296,6 +302,7 @@ /* static */ bool ScriptCompany::SetAutoRenewMoney(Money money) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, money >= 0); EnforcePrecondition(false, (int64)money <= UINT32_MAX); return ScriptObject::Command::Do("company.engine_renew_money", money); @@ -311,11 +318,13 @@ /* static */ bool ScriptCompany::SetPrimaryLiveryColour(LiveryScheme scheme, Colours colour) { + EnforceCompanyModeValid(false); return ScriptObject::Command::Do((::LiveryScheme)scheme, true, (::Colours)colour); } /* static */ bool ScriptCompany::SetSecondaryLiveryColour(LiveryScheme scheme, Colours colour) { + EnforceCompanyModeValid(false); return ScriptObject::Command::Do((::LiveryScheme)scheme, false, (::Colours)colour); } diff --git a/src/script/api/script_company.hpp b/src/script/api/script_company.hpp index 690277600b..c96e7216ca 100644 --- a/src/script/api/script_company.hpp +++ b/src/script/api/script_company.hpp @@ -138,6 +138,7 @@ public: * Set the name of your company. * @param name The new name of the company (can be either a raw string, or a ScriptText object). * @pre name != null && len(name) != 0. + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_NAME_IS_NOT_UNIQUE * @return True if the name was changed. */ @@ -155,6 +156,7 @@ public: * Set the name of your president. * @param name The new name of the president (can be either a raw string, or a ScriptText object). * @pre name != null && len(name) != 0. + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_NAME_IS_NOT_UNIQUE * @return True if the name was changed. */ @@ -172,6 +174,7 @@ public: * Set the gender of the president of your company. * @param gender The new gender for your president. * @pre GetPresidentGender(ScriptCompany.COMPANY_SELF) != gender. + * @game @pre ScriptCompanyMode::IsValid(). * @return True if the gender was changed. * @note When successful a random face will be created. * @api -game @@ -333,6 +336,7 @@ public: /** * Set whether autorenew is enabled for your company. * @param autorenew The new autorenew status. + * @game @pre ScriptCompanyMode::IsValid(). * @return True if autorenew status has been modified. * @api -game */ @@ -350,6 +354,7 @@ public: * Set the number of months before/after max age to autorenew an engine for your company. * @param months The new months between autorenew. * The value will be clamped to MIN(int16) .. MAX(int16). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if autorenew months has been modified. * @api -game */ @@ -366,6 +371,7 @@ public: /** * Set the minimum money needed to autorenew an engine for your company. * @param money The new minimum required money for autorenew to work. + * @game @pre ScriptCompanyMode::IsValid(). * @return True if autorenew money has been modified. * @pre money >= 0 * @pre money < 2**32 @@ -385,6 +391,7 @@ public: * Set primary colour for your company. * @param scheme Livery scheme to set. * @param colour Colour to set. + * @game @pre ScriptCompanyMode::IsValid(). * @return False if unable to set primary colour of the livery scheme (e.g. colour in use). */ static bool SetPrimaryLiveryColour(LiveryScheme scheme, Colours colour); @@ -393,6 +400,7 @@ public: * Set secondary colour for your company. * @param scheme Livery scheme to set. * @param colour Colour to set. + * @game @pre ScriptCompanyMode::IsValid(). * @return False if unable to set secondary colour of the livery scheme. */ static bool SetSecondaryLiveryColour(LiveryScheme scheme, Colours colour); diff --git a/src/script/api/script_event_types.cpp b/src/script/api/script_event_types.cpp index e9349a035f..9eb01a3682 100644 --- a/src/script/api/script_event_types.cpp +++ b/src/script/api/script_event_types.cpp @@ -111,12 +111,14 @@ int32 ScriptEventEnginePreview::GetVehicleType() bool ScriptEventEnginePreview::AcceptPreview() { + EnforceCompanyModeValid(false); if (!this->IsEngineValid()) return false; return ScriptObject::Command::Do(this->engine); } bool ScriptEventCompanyAskMerger::AcceptMerger() { + EnforceCompanyModeValid(false); return ScriptObject::Command::Do((::CompanyID)this->owner); } diff --git a/src/script/api/script_event_types.hpp b/src/script/api/script_event_types.hpp index 161ad4d8ae..24e50ae4b0 100644 --- a/src/script/api/script_event_types.hpp +++ b/src/script/api/script_event_types.hpp @@ -289,6 +289,7 @@ public: /** * Accept the engine preview. + * @game @pre ScriptCompanyMode::IsValid(). * @return True when the accepting succeeded. */ bool AcceptPreview(); @@ -410,6 +411,7 @@ public: /** * Take over the company for this merger. + * @game @pre ScriptCompanyMode::IsValid(). * @return true if the merger was a success. */ bool AcceptMerger(); diff --git a/src/script/api/script_group.cpp b/src/script/api/script_group.cpp index 7f1542e37d..1f83c55d0c 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -32,6 +32,7 @@ /* static */ ScriptGroup::GroupID ScriptGroup::CreateGroup(ScriptVehicle::VehicleType vehicle_type, GroupID parent_group_id) { + EnforceCompanyModeValid(GROUP_INVALID); if (!ScriptObject::Command::Do(&ScriptInstance::DoCommandReturnGroupID, (::VehicleType)vehicle_type, parent_group_id)) return GROUP_INVALID; /* In case of test-mode, we return GroupID 0 */ @@ -40,6 +41,7 @@ /* static */ bool ScriptGroup::DeleteGroup(GroupID group_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id)); return ScriptObject::Command::Do(group_id); @@ -56,6 +58,7 @@ { CCountedPtr counter(name); + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id)); EnforcePrecondition(false, name != nullptr); const std::string &text = name->GetDecodedText(); @@ -75,6 +78,7 @@ /* static */ bool ScriptGroup::SetParent(GroupID group_id, GroupID parent_group_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id)); EnforcePrecondition(false, IsValidGroup(parent_group_id)); @@ -91,6 +95,7 @@ /* static */ bool ScriptGroup::EnableAutoReplaceProtection(GroupID group_id, bool enable) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id)); return ScriptObject::Command::Do(group_id, GroupFlags::GF_REPLACE_PROTECTION, enable, false); @@ -105,6 +110,7 @@ /* static */ SQInteger ScriptGroup::GetNumEngines(GroupID group_id, EngineID engine_id) { + EnforceCompanyModeValid(-1); if (!IsValidGroup(group_id) && group_id != GROUP_DEFAULT && group_id != GROUP_ALL) return -1; return GetGroupNumEngines(ScriptObject::GetCompany(), group_id, engine_id); @@ -112,6 +118,7 @@ /* static */ SQInteger ScriptGroup::GetNumVehicles(GroupID group_id, ScriptVehicle::VehicleType vehicle_type) { + EnforceCompanyModeValid(-1); bool valid_group = IsValidGroup(group_id); if (!valid_group && group_id != GROUP_DEFAULT && group_id != GROUP_ALL) return -1; if (!valid_group && (vehicle_type < ScriptVehicle::VT_RAIL || vehicle_type > ScriptVehicle::VT_AIR)) return -1; @@ -121,6 +128,7 @@ /* static */ bool ScriptGroup::MoveVehicle(GroupID group_id, VehicleID vehicle_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id) || group_id == GROUP_DEFAULT); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); @@ -129,6 +137,7 @@ /* static */ bool ScriptGroup::EnableWagonRemoval(bool enable_removal) { + EnforceCompanyModeValid(false); if (HasWagonRemoval() == enable_removal) return true; return ScriptObject::Command::Do("company.renew_keep_length", enable_removal ? 1 : 0); @@ -136,11 +145,13 @@ /* static */ bool ScriptGroup::HasWagonRemoval() { + EnforceCompanyModeValid(false); return ::Company::Get(ScriptObject::GetCompany())->settings.renew_keep_length; } /* static */ bool ScriptGroup::SetAutoReplace(GroupID group_id, EngineID engine_id_old, EngineID engine_id_new) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL); EnforcePrecondition(false, ScriptEngine::IsBuildable(engine_id_new)); @@ -149,6 +160,7 @@ /* static */ EngineID ScriptGroup::GetEngineReplacement(GroupID group_id, EngineID engine_id) { + EnforceCompanyModeValid(::INVALID_ENGINE); if (!IsValidGroup(group_id) && group_id != GROUP_DEFAULT && group_id != GROUP_ALL) return ::INVALID_ENGINE; return ::EngineReplacementForCompany(Company::Get(ScriptObject::GetCompany()), engine_id, group_id); @@ -156,6 +168,7 @@ /* static */ bool ScriptGroup::StopAutoReplace(GroupID group_id, EngineID engine_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL); return ScriptObject::Command::Do(group_id, engine_id, ::INVALID_ENGINE, false); @@ -206,6 +219,7 @@ /* static */ bool ScriptGroup::SetPrimaryColour(GroupID group_id, ScriptCompany::Colours colour) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id)); return ScriptObject::Command::Do(group_id, true, (::Colours)colour); @@ -213,6 +227,7 @@ /* static */ bool ScriptGroup::SetSecondaryColour(GroupID group_id, ScriptCompany::Colours colour) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidGroup(group_id)); return ScriptObject::Command::Do(group_id, false, (::Colours)colour); diff --git a/src/script/api/script_group.hpp b/src/script/api/script_group.hpp index f74b87d5c8..c685813a88 100644 --- a/src/script/api/script_group.hpp +++ b/src/script/api/script_group.hpp @@ -41,6 +41,7 @@ public: * Create a new group. * @param vehicle_type The type of vehicle to create a group for. * @param parent_group_id The parent group id to create this group under, INVALID_GROUP for top-level. + * @game @pre ScriptCompanyMode::IsValid(). * @return The GroupID of the new group, or an invalid GroupID when * it failed. Check the return value using IsValidGroup(). In test-mode * 0 is returned if it was successful; any other value indicates failure. @@ -52,6 +53,7 @@ public: * given group will move to the GROUP_DEFAULT. * @param group_id The group to delete. * @pre IsValidGroup(group_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and only if the group was successfully deleted. */ static bool DeleteGroup(GroupID group_id); @@ -70,6 +72,7 @@ public: * @param name The name for the group (can be either a raw string, or a ScriptText object). * @pre IsValidGroup(group_id). * @pre name != null && len(name) != 0 + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_NAME_IS_NOT_UNIQUE * @return True if and only if the name was changed. */ @@ -89,6 +92,7 @@ public: * @param parent_group_id The parent group to set. * @pre IsValidGroup(group_id). * @pre IsValidGroup(parent_group_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and only if the parent group was changed. */ static bool SetParent(GroupID group_id, GroupID parent_group_id); @@ -107,6 +111,7 @@ public: * @param group_id The group to change the protection for. * @param enable True if protection should be enabled. * @pre IsValidGroup(group_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and only if the protection was successfully changed. */ static bool EnableAutoReplaceProtection(GroupID group_id, bool enable); @@ -124,6 +129,7 @@ public: * @param group_id The group to get the number of engines in. * @param engine_id The engine id to count. * @pre IsValidGroup(group_id) || group_id == GROUP_ALL || group_id == GROUP_DEFAULT. + * @game @pre ScriptCompanyMode::IsValid(). * @return The number of engines with id engine_id in the group with id group_id. */ static SQInteger GetNumEngines(GroupID group_id, EngineID engine_id); @@ -135,6 +141,7 @@ public: * @pre IsValidGroup(group_id) || group_id == GROUP_ALL || group_id == GROUP_DEFAULT. * @pre IsValidGroup(group_id) || vehicle_type == ScriptVehicle::VT_ROAD || vehicle_type == ScriptVehicle::VT_RAIL || * vehicle_type == ScriptVehicle::VT_WATER || vehicle_type == ScriptVehicle::VT_AIR + * @game @pre ScriptCompanyMode::IsValid(). * @return The total number of vehicles in the group with id group_id and it's sub-groups. * @note If the group is valid (neither GROUP_ALL nor GROUP_DEFAULT), the value of * vehicle_type is retrieved from the group itself and not from the input value. @@ -148,6 +155,7 @@ public: * @param vehicle_id The vehicle to move to the group. * @pre IsValidGroup(group_id) || group_id == GROUP_DEFAULT. * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and only if the vehicle was successfully moved to the group. * @note A vehicle can be in only one group at the same time. To remove it from * a group, move it to another or to GROUP_DEFAULT. Moving the vehicle to the @@ -161,12 +169,14 @@ public: * If enabled, wagons are removed from the end of the vehicle until it * fits in the same number of tiles as it did before. * @param keep_length If true, wagons will be removed if the new engine is longer. + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and only if the value was successfully changed. */ static bool EnableWagonRemoval(bool keep_length); /** * Get the current status of wagon removal. + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether or not wagon removal is enabled. */ static bool HasWagonRemoval(); @@ -179,6 +189,7 @@ public: * @param engine_id_new The engine id to replace with. * @pre IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL. * @pre ScriptEngine.IsBuildable(engine_id_new). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and if the replacing was successfully started. * @note To stop autoreplacing engine_id_old, call StopAutoReplace(group_id, engine_id_old). */ @@ -189,6 +200,7 @@ public: * @param group_id The group to get the replacement from. * @param engine_id The engine that is being replaced. * @pre IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL. + * @game @pre ScriptCompanyMode::IsValid(). * @return The EngineID that is replacing engine_id or an invalid EngineID * in case engine_id is not begin replaced. */ @@ -199,6 +211,7 @@ public: * @param group_id The group to stop replacing the engine in. * @param engine_id The engine id to stop replacing with another engine. * @pre IsValidGroup(group_id) || group_id == GROUP_DEFAULT || group_id == GROUP_ALL. + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and if the replacing was successfully stopped. */ static bool StopAutoReplace(GroupID group_id, EngineID engine_id); @@ -232,6 +245,7 @@ public: * @param group_id The group id to set the colour of. * @param colour Colour to set. * @pre IsValidGroup(group_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True iff the colour was set successfully. */ static bool SetPrimaryColour(GroupID group_id, ScriptCompany::Colours colour); @@ -241,6 +255,7 @@ public: * @param group_id The group id to set the colour of. * @param colour Colour to set. * @pre IsValidGroup(group_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True iff the colour was set successfully. */ static bool SetSecondaryColour(GroupID group_id, ScriptCompany::Colours colour); diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 8209221b9a..307b4b851a 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -381,6 +381,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::SetOrderJumpTo(VehicleID vehicle_id, OrderPosition order_position, OrderPosition jump_to) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position)); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, jump_to) && jump_to != ORDER_CURRENT); @@ -390,6 +391,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::SetOrderCondition(VehicleID vehicle_id, OrderPosition order_position, OrderCondition condition) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position)); EnforcePrecondition(false, condition >= OC_LOAD_PERCENTAGE && condition <= OC_REMAINING_LIFETIME); @@ -400,6 +402,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::SetOrderCompareFunction(VehicleID vehicle_id, OrderPosition order_position, CompareFunction compare) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position)); EnforcePrecondition(false, compare >= CF_EQUALS && compare <= CF_IS_FALSE); @@ -410,6 +413,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::SetOrderCompareValue(VehicleID vehicle_id, OrderPosition order_position, SQInteger value) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position)); EnforcePrecondition(false, value >= 0 && value < 2048); @@ -421,6 +425,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::SetStopLocation(VehicleID vehicle_id, OrderPosition order_position, StopLocation stop_location) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, ScriptVehicle::GetVehicleType(vehicle_id) == ScriptVehicle::VT_RAIL); EnforcePrecondition(false, IsGotoStationOrder(vehicle_id, order_position)); @@ -434,6 +439,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::SetOrderRefit(VehicleID vehicle_id, OrderPosition order_position, CargoID refit_cargo) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, IsGotoStationOrder(vehicle_id, order_position) || (IsGotoDepotOrder(vehicle_id, order_position) && refit_cargo != CT_AUTO_REFIT)); EnforcePrecondition(false, ScriptCargo::IsValidCargo(refit_cargo) || refit_cargo == CT_AUTO_REFIT || refit_cargo == CT_NO_REFIT); @@ -443,6 +449,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::AppendOrder(VehicleID vehicle_id, TileIndex destination, ScriptOrderFlags order_flags) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, AreOrderFlagsValid(destination, order_flags)); @@ -451,6 +458,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::AppendConditionalOrder(VehicleID vehicle_id, OrderPosition jump_to) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, jump_to)); @@ -462,6 +470,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* IsValidVehicleOrder is not good enough because it does not allow appending. */ if (order_position == ORDER_CURRENT) order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, order_position >= 0 && order_position <= ::Vehicle::Get(vehicle_id)->GetNumManualOrders()); EnforcePrecondition(false, AreOrderFlagsValid(destination, order_flags)); @@ -516,6 +525,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* IsValidVehicleOrder is not good enough because it does not allow appending. */ if (order_position == ORDER_CURRENT) order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, order_position >= 0 && order_position <= ::Vehicle::Get(vehicle_id)->GetNumManualOrders()); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, jump_to) && jump_to != ORDER_CURRENT); @@ -531,6 +541,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr { order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); int order_pos = ScriptOrderPositionToRealOrderPosition(vehicle_id, order_position); @@ -541,6 +552,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr { next_order = ScriptOrder::ResolveOrderPosition(vehicle_id, next_order); + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, next_order)); int order_pos = ScriptOrderPositionToRealOrderPosition(vehicle_id, next_order); @@ -577,6 +589,7 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position)); EnforcePrecondition(false, AreOrderFlagsValid(GetOrderDestination(vehicle_id, order_position), order_flags)); @@ -634,6 +647,7 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) order_position_move = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position_move); order_position_target = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position_target); + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position_move)); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, order_position_target)); EnforcePrecondition(false, order_position_move != order_position_target); @@ -645,6 +659,7 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) /* static */ bool ScriptOrder::CopyOrders(VehicleID vehicle_id, VehicleID main_vehicle_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(main_vehicle_id)); @@ -653,6 +668,7 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) /* static */ bool ScriptOrder::ShareOrders(VehicleID vehicle_id, VehicleID main_vehicle_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(main_vehicle_id)); @@ -661,6 +677,7 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) /* static */ bool ScriptOrder::UnshareOrders(VehicleID vehicle_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); return ScriptObject::Command::Do(0, CO_UNSHARE, vehicle_id, 0); diff --git a/src/script/api/script_order.hpp b/src/script/api/script_order.hpp index d779ac336b..fd0f76496d 100644 --- a/src/script/api/script_order.hpp +++ b/src/script/api/script_order.hpp @@ -356,6 +356,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre IsValidVehicleOrder(vehicle_id, jump_to). * @pre order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position). + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether the order has been/can be changed. * @api -game */ @@ -369,6 +370,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position). * @pre condition >= OC_LOAD_PERCENTAGE && condition <= OC_UNCONDITIONALLY. + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether the order has been/can be changed. * @api -game */ @@ -382,6 +384,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position). * @pre compare >= CF_EQUALS && compare <= CF_IS_FALSE. + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether the order has been/can be changed. * @api -game */ @@ -395,6 +398,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre order_position != ORDER_CURRENT && IsConditionalOrder(vehicle_id, order_position). * @pre value >= 0 && value < 2048. + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether the order has been/can be changed. * @api -game */ @@ -409,6 +413,7 @@ public: * @pre ScriptVehicle::GetVehicleType(vehicle_id) == ScriptVehicle::VT_RAIL. * @pre IsGotoStationOrder(vehicle_id, order_position). * @pre stop_location >= STOPLOCATION_NEAR && stop_location <= STOPLOCATION_FAR + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether the order has been/can be changed. * @api -game */ @@ -422,6 +427,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre IsGotoStationOrder(vehicle_id, order_position) || (IsGotoDepotOrder(vehicle_id, order_position) && refit_cargo != CT_AUTO_REFIT). * @pre ScriptCargo::IsValidCargo(refit_cargo) || refit_cargo == CT_AUTO_REFIT || refit_cargo == CT_NO_REFIT + * @game @pre ScriptCompanyMode::IsValid(). * @return Whether the order has been/can be changed. * @api -game */ @@ -434,6 +440,7 @@ public: * @param order_flags The flags given to the order. * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre AreOrderFlagsValid(destination, order_flags). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY * @exception ScriptOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION @@ -448,6 +455,7 @@ public: * @param jump_to The OrderPosition to jump to if the condition is true. * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre IsValidVehicleOrder(vehicle_id, jump_to). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY * @return True if and only if the order was appended. @@ -464,6 +472,7 @@ public: * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id) * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre AreOrderFlagsValid(destination, order_flags). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY * @exception ScriptOrder::ERR_ORDER_TOO_FAR_AWAY_FROM_PREVIOUS_DESTINATION @@ -480,6 +489,7 @@ public: * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre IsValidVehicleOrder(vehicle_id, jump_to). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY * @return True if and only if the order was inserted. @@ -492,6 +502,7 @@ public: * @param vehicle_id The vehicle to remove the order from. * @param order_position The order to remove from the order list. * @pre IsValidVehicleOrder(vehicle_id, order_position). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @return True if and only if the order was removed. * @api -game @@ -512,6 +523,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre AreOrderFlagsValid(GetOrderDestination(vehicle_id, order_position), order_flags). * @pre (order_flags & OF_GOTO_NEAREST_DEPOT) == (GetOrderFlags(vehicle_id, order_position) & OF_GOTO_NEAREST_DEPOT). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @return True if and only if the order was changed. * @api -game @@ -526,6 +538,7 @@ public: * @pre IsValidVehicleOrder(vehicle_id, order_position_move). * @pre IsValidVehicleOrder(vehicle_id, order_position_target). * @pre order_position_move != order_position_target. + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @return True if and only if the order was moved. * @note If the order is moved to a lower place (e.g. from 7 to 2) @@ -541,6 +554,7 @@ public: * @param vehicle_id The vehicle that should skip some orders. * @param next_order The order the vehicle should skip to. * @pre IsValidVehicleOrder(vehicle_id, next_order). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @return True if and only the current order was changed. * @api -game @@ -554,6 +568,7 @@ public: * @param main_vehicle_id The vehicle to copy the orders from. * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre ScriptVehicle::IsPrimaryVehicle(main_vehicle_id). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY * @exception ScriptOrder::ERR_ORDER_AIRCRAFT_NOT_ENOUGH_RANGE @@ -569,6 +584,7 @@ public: * @param main_vehicle_id The vehicle to share the orders with. * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre ScriptVehicle::IsPrimaryVehicle(main_vehicle_id). + * @game @pre ScriptCompanyMode::IsValid(). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_AIRCRAFT_NOT_ENOUGH_RANGE * @return True if and only if the sharing succeeded. @@ -581,6 +597,7 @@ public: * After unsharing orders, the orders list of the vehicle is empty. * @param vehicle_id The vehicle to remove from the shared order list. * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). + * @game @pre ScriptCompanyMode::IsValid(). * @return True if and only if the unsharing succeeded. * @api -game */ diff --git a/src/script/api/script_station.cpp b/src/script/api/script_station.cpp index a545b949ab..fd837bb717 100644 --- a/src/script/api/script_station.cpp +++ b/src/script/api/script_station.cpp @@ -238,6 +238,7 @@ template /* static */ bool ScriptStation::OpenCloseAirport(StationID station_id) { + EnforceCompanyModeValid(false); EnforcePrecondition(false, IsValidStation(station_id)); EnforcePrecondition(false, HasStationType(station_id, STATION_AIRPORT)); diff --git a/src/script/api/script_station.hpp b/src/script/api/script_station.hpp index 4f8be172cc..4f705a518a 100644 --- a/src/script/api/script_station.hpp +++ b/src/script/api/script_station.hpp @@ -287,6 +287,7 @@ public: /** * Toggle the open/closed state of an airport. * @param station_id The airport to modify. + * @game @pre ScriptCompanyMode::IsValid(). * @pre IsValidStation(station_id). * @pre HasStationType(station_id, STATION_AIRPORT). * @return True if the state was toggled successfully. From 717f79ff22586810318648c4fc979dbf7511a3a3 Mon Sep 17 00:00:00 2001 From: Rubidium Date: Thu, 2 Mar 2023 21:22:37 +0100 Subject: [PATCH 11/18] Add: deity mode enforcement checks to functions with command or company access Command functions are those that call ScriptObject::Command, and functions with company access are any that call ScriptObject::GetCompany. This is a bit over-protective, but having the check everywhere makes it easier to validate that no check is missing automatically instead of by review. --- src/script/api/script_industry.cpp | 3 +++ src/script/api/script_industry.hpp | 3 +++ src/script/api/script_news.cpp | 1 + src/script/api/script_news.hpp | 1 + src/script/api/script_subsidy.cpp | 1 + src/script/api/script_subsidy.hpp | 1 + src/script/api/script_town.cpp | 4 ++++ src/script/api/script_town.hpp | 4 ++++ 8 files changed, 18 insertions(+) diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index 93047868e8..1f114c1c45 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -53,6 +53,7 @@ { CCountedPtr counter(text); + EnforceDeityMode(false); EnforcePrecondition(false, IsValidIndustry(industry_id)); return ScriptObject::Command::Do(industry_id, text != nullptr ? text->GetEncodedText() : std::string{}); @@ -268,6 +269,7 @@ /* static */ bool ScriptIndustry::SetExclusiveSupplier(IndustryID industry_id, ScriptCompany::CompanyID company_id) { + EnforceDeityMode(false); EnforcePrecondition(false, IsValidIndustry(industry_id)); auto company = ScriptCompany::ResolveCompanyID(company_id); @@ -287,6 +289,7 @@ /* static */ bool ScriptIndustry::SetExclusiveConsumer(IndustryID industry_id, ScriptCompany::CompanyID company_id) { + EnforceDeityMode(false); EnforcePrecondition(false, IsValidIndustry(industry_id)); auto company = ScriptCompany::ResolveCompanyID(company_id); diff --git a/src/script/api/script_industry.hpp b/src/script/api/script_industry.hpp index 46acb1d958..d7fca868d0 100644 --- a/src/script/api/script_industry.hpp +++ b/src/script/api/script_industry.hpp @@ -85,6 +85,7 @@ public: * Set the custom text of an industry, shown in the GUI. * @param industry_id The industry to set the custom text of. * @param text The text to set it to (can be either a raw string, or a ScriptText object). If null, or an empty string, is passed, the text will be removed. + * @pre ScriptCompanyMode::IsDeity(). * @pre IsValidIndustry(industry_id). * @return True if the action succeeded. * @api -ai @@ -286,6 +287,7 @@ public: * @param industry_id The index of the industry. * @param company_id The company to set (ScriptCompany::COMPANY_INVALID to reset). * @pre IsValidIndustry(industry_id). + * @pre ScriptCompanyMode::IsDeity(). * @return True if the action succeeded. * @api -ai */ @@ -306,6 +308,7 @@ public: * @param industry_id The index of the industry. * @param company_id The company to set (ScriptCompany::COMPANY_INVALID to reset). * @pre IsValidIndustry(industry_id). + * @pre ScriptCompanyMode::IsDeity(). * @return True if the action succeeded. * @api -ai */ diff --git a/src/script/api/script_news.cpp b/src/script/api/script_news.cpp index 290810eb69..6d668fea05 100644 --- a/src/script/api/script_news.cpp +++ b/src/script/api/script_news.cpp @@ -24,6 +24,7 @@ { CCountedPtr counter(text); + EnforceDeityMode(false); EnforcePrecondition(false, text != nullptr); const std::string &encoded = text->GetEncodedText(); EnforcePreconditionEncodedText(false, encoded); diff --git a/src/script/api/script_news.hpp b/src/script/api/script_news.hpp index 4518a56047..808964f7ba 100644 --- a/src/script/api/script_news.hpp +++ b/src/script/api/script_news.hpp @@ -62,6 +62,7 @@ public: * @pre text != null. * @pre company == COMPANY_INVALID || ResolveCompanyID(company) != COMPANY_INVALID. * @pre The \a reference condition must be fulfilled. + * @pre ScriptCompanyMode::IsDeity(). */ static bool Create(NewsType type, Text *text, ScriptCompany::CompanyID company, NewsReferenceType ref_type, SQInteger reference); }; diff --git a/src/script/api/script_subsidy.cpp b/src/script/api/script_subsidy.cpp index 0372f315cb..afc6eef14b 100644 --- a/src/script/api/script_subsidy.cpp +++ b/src/script/api/script_subsidy.cpp @@ -33,6 +33,7 @@ /* static */ bool ScriptSubsidy::Create(CargoID cargo_type, SubsidyParticipantType from_type, SQInteger from_id, SubsidyParticipantType to_type, SQInteger to_id) { + EnforceDeityMode(false); EnforcePrecondition(false, ScriptCargo::IsValidCargo(cargo_type)); EnforcePrecondition(false, from_type == SPT_INDUSTRY || from_type == SPT_TOWN); EnforcePrecondition(false, to_type == SPT_INDUSTRY || to_type == SPT_TOWN); diff --git a/src/script/api/script_subsidy.hpp b/src/script/api/script_subsidy.hpp index b59d8bb591..dd71ab036b 100644 --- a/src/script/api/script_subsidy.hpp +++ b/src/script/api/script_subsidy.hpp @@ -55,6 +55,7 @@ public: * @param to_type The type of the subsidy on the 'to' side. * @param to_id The ID of the 'to' side. * @return True if the action succeeded. + * @pre ScriptCompanyMode::IsDeity(). * @pre ScriptCargo::IsValidCargo(cargo_type) * @pre from_type == SPT_INDUSTRY || from_type == SPT_TOWN. * @pre to_type == SPT_INDUSTRY || to_type == SPT_TOWN. diff --git a/src/script/api/script_town.cpp b/src/script/api/script_town.cpp index 6bdd2283ff..c39b9e1185 100644 --- a/src/script/api/script_town.cpp +++ b/src/script/api/script_town.cpp @@ -44,6 +44,7 @@ { CCountedPtr counter(name); + EnforceDeityMode(false); EnforcePrecondition(false, IsValidTown(town_id)); std::string text; if (name != nullptr) { @@ -58,6 +59,7 @@ { CCountedPtr counter(text); + EnforceDeityMode(false); EnforcePrecondition(false, IsValidTown(town_id)); return ScriptObject::Command::Do(town_id, text != nullptr ? text->GetEncodedText() : std::string{}); @@ -125,6 +127,7 @@ /* static */ bool ScriptTown::SetCargoGoal(TownID town_id, ScriptCargo::TownEffect towneffect_id, SQInteger goal) { + EnforceDeityMode(false); EnforcePrecondition(false, IsValidTown(town_id)); EnforcePrecondition(false, ScriptCargo::IsValidTownEffect(towneffect_id)); @@ -155,6 +158,7 @@ /* static */ bool ScriptTown::SetGrowthRate(TownID town_id, SQInteger days_between_town_growth) { + EnforceDeityMode(false); EnforcePrecondition(false, IsValidTown(town_id)); uint16 growth_rate; switch (days_between_town_growth) { diff --git a/src/script/api/script_town.hpp b/src/script/api/script_town.hpp index a4b5117b35..bd2167f0f7 100644 --- a/src/script/api/script_town.hpp +++ b/src/script/api/script_town.hpp @@ -149,6 +149,7 @@ public: * @param town_id The town to rename * @param name The new name of the town. If null, or an empty string, is passed, the town name will be reset to the default name. * @pre IsValidTown(town_id). + * @pre ScriptCompanyMode::IsDeity(). * @return True if the action succeeded. * @api -ai */ @@ -159,6 +160,7 @@ public: * @param town_id The town to set the custom text of. * @param text The text to set it to (can be either a raw string, or a ScriptText object). If null, or an empty string, is passed, the text will be removed. * @pre IsValidTown(town_id). + * @pre ScriptCompanyMode::IsDeity(). * @return True if the action succeeded. * @api -ai */ @@ -236,6 +238,7 @@ public: * The value will be clamped to 0 .. MAX(uint32). * @pre IsValidTown(town_id). * @pre ScriptCargo::IsValidTownEffect(towneffect_id). + * @pre ScriptCompanyMode::IsDeity(). * @return True if the action succeeded. * @api -ai */ @@ -392,6 +395,7 @@ public: * The value will be clamped to 0 .. MAX(uint32). * @pre IsValidTown(town_id). * @pre houses > 0. + * @pre ScriptCompanyMode::IsDeity(). * @return True if the action succeeded. * @api -ai */ From 1e4a89177e88d44a4ac82acd7197a6ee79d46e66 Mon Sep 17 00:00:00 2001 From: Rubidium Date: Thu, 2 Mar 2023 21:27:30 +0100 Subject: [PATCH 12/18] Fix: crashes to desktop from game scripts when companies disappear When a game script is in company mode, it pretends to be another company. When that company disappear (bankruptcy/merger), the game script still uses that company and it keeps calling functions as if it is that company. For example, ScriptEngine::IsBuildable internally dereferences Company without checks, causing a null dereference for any ScriptEngine function when called from a company scope of a company that has disappeared. Guard against this by extending the ScriptCompanyScope::IsValid check to also check for the company still being active. --- src/script/api/script_companymode.cpp | 2 +- src/script/api/script_companymode.hpp | 2 +- src/script/api/script_object.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/script/api/script_companymode.cpp b/src/script/api/script_companymode.cpp index ef738dc4ad..3a5c5f48f7 100644 --- a/src/script/api/script_companymode.cpp +++ b/src/script/api/script_companymode.cpp @@ -29,7 +29,7 @@ ScriptCompanyMode::~ScriptCompanyMode() /* static */ bool ScriptCompanyMode::IsValid() { - return ScriptObject::GetCompany() != OWNER_DEITY; + return ::Company::IsValidID(ScriptObject::GetCompany()); } /* static */ bool ScriptCompanyMode::IsDeity() diff --git a/src/script/api/script_companymode.hpp b/src/script/api/script_companymode.hpp index 6c2e13530f..8378e11c4c 100644 --- a/src/script/api/script_companymode.hpp +++ b/src/script/api/script_companymode.hpp @@ -50,7 +50,7 @@ public: /** * Check whether a company mode is valid. In other words, are commands - * being executed under some company. + * being executed under some company and does the company still exist? * @return true When a company mode is valid. * @post !ScriptCompanyMode::IsDeity(). */ diff --git a/src/script/api/script_object.cpp b/src/script/api/script_object.cpp index 2c5156f0a6..f15b9d56ec 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -253,7 +253,7 @@ std::tuple ScriptObject::DoCommandPrep() bool networking = _networking && !_generating_world; - if (ScriptCompanyMode::IsValid() && !::Company::IsValidID(ScriptObject::GetCompany())) { + if (!ScriptCompanyMode::IsDeity() && !ScriptCompanyMode::IsValid()) { ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_INVALID_COMPANY); return { true, estimate_only, networking }; } From 2d3250923c3c86937d398d1fc7197bde7efc4028 Mon Sep 17 00:00:00 2001 From: PeterN Date: Thu, 9 Mar 2023 15:19:58 +0000 Subject: [PATCH 13/18] Fix #10554: Let Scrollbar::SetPosition clamp instead of assert. (#10555) --- src/company_gui.cpp | 2 +- src/script/script_gui.cpp | 4 +--- src/widget_type.h | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 79cede9573..ea0c9df394 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -795,7 +795,7 @@ public: /* Position scrollbar to selected group */ for (uint i = 0; i < this->rows; i++) { if (this->groups[i]->index == sel) { - this->vscroll->SetPosition(Clamp(i - this->vscroll->GetCapacity() / 2, 0, std::max(this->vscroll->GetCount() - this->vscroll->GetCapacity(), 0))); + this->vscroll->SetPosition(i - this->vscroll->GetCapacity() / 2); break; } } diff --git a/src/script/script_gui.cpp b/src/script/script_gui.cpp index 8c9a1abcc7..1a1bca2822 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -866,9 +866,7 @@ struct ScriptDebugWindow : public Window { } if (this->autoscroll) { int scroll_pos = std::max(0, log->used - this->vscroll->GetCapacity()); - if (scroll_pos != this->vscroll->GetPosition()) { - this->vscroll->SetPosition(scroll_pos); - + if (this->vscroll->SetPosition(scroll_pos)) { /* We need a repaint */ this->SetWidgetDirty(WID_SCRD_SCROLLBAR); this->SetWidgetDirty(WID_SCRD_LOG_PANEL); diff --git a/src/widget_type.h b/src/widget_type.h index 8c05a321f7..078aa49b10 100644 --- a/src/widget_type.h +++ b/src/widget_type.h @@ -746,10 +746,8 @@ public: */ bool SetPosition(int position) { - assert(position >= 0); - assert(this->count <= this->cap ? (position == 0) : (position + this->cap <= this->count)); uint16 old_pos = this->pos; - this->pos = position; + this->pos = Clamp(position, 0, std::max(this->count - this->cap, 0)); return this->pos != old_pos; } @@ -768,7 +766,7 @@ public: case SS_BIG: difference *= this->cap; break; default: break; } - return this->SetPosition(Clamp(this->pos + difference, 0, std::max(this->count - this->cap, 0))); + return this->SetPosition(this->pos + difference); } /** From 1ec34acb51530c2ca904273f065f09e7912b0ad8 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Thu, 26 Jan 2023 14:58:51 +0000 Subject: [PATCH 14/18] Fix: Truncated music-set song names cause warning log. The music-set does not need to be selected for this to occur. Resolved by using std::string instead of fixed buffer for song names, which avoids manual string copying and removes the length limit. --- src/base_media_base.h | 2 +- src/music.cpp | 6 ++---- src/music_gui.cpp | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/base_media_base.h b/src/base_media_base.h index 790bb9d38c..9a9d6ff48a 100644 --- a/src/base_media_base.h +++ b/src/base_media_base.h @@ -289,7 +289,7 @@ enum MusicTrackType { /** Metadata about a music track. */ struct MusicSongInfo { - char songname[32]; ///< name of song displayed in UI + std::string songname; ///< name of song displayed in UI byte tracknr; ///< track number of song displayed in UI const char *filename; ///< file on disk containing song (when used in MusicSet class, this pointer is owned by MD5File object for the file) MusicTrackType filetype; ///< decoder required for song file diff --git a/src/music.cpp b/src/music.cpp index fdeb2d3df8..d7fbd4b6b0 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -128,7 +128,6 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f for (uint i = 0; i < lengthof(this->songinfo); i++) { const char *filename = this->files[i].filename; if (names == nullptr || StrEmpty(filename) || this->files[i].check_result == MD5File::CR_NO_FILE) { - this->songinfo[i].songname[0] = '\0'; continue; } @@ -142,10 +141,9 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f char *songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index); if (songname == nullptr) { Debug(grf, 0, "Base music set song missing from CAT file: {}/{}", filename, this->songinfo[i].cat_index); - this->songinfo[i].songname[0] = '\0'; continue; } - strecpy(this->songinfo[i].songname, songname, lastof(this->songinfo[i].songname)); + this->songinfo[i].songname = songname; free(songname); } else { this->songinfo[i].filetype = MTT_STANDARDMIDI; @@ -166,7 +164,7 @@ bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_f if (this->songinfo[i].filetype == MTT_STANDARDMIDI) { if (item != nullptr && item->value.has_value() && !item->value->empty()) { - strecpy(this->songinfo[i].songname, item->value->c_str(), lastof(this->songinfo[i].songname)); + this->songinfo[i].songname = item->value.value(); } else { Debug(grf, 0, "Base music set song name missing: {}", filename); return false; diff --git a/src/music_gui.cpp b/src/music_gui.cpp index a2bd44add7..4b17b83371 100644 --- a/src/music_gui.cpp +++ b/src/music_gui.cpp @@ -43,7 +43,7 @@ struct MusicSystem { uint set_index; ///< index of song in set PlaylistEntry(const MusicSet *set, uint set_index) : MusicSongInfo(set->songinfo[set_index]), set(set), set_index(set_index) { } - bool IsValid() const { return !StrEmpty(this->songname); } + bool IsValid() const { return !this->songname.empty(); } }; typedef std::vector Playlist; From 0c4e2ce1149587e47ec998f3d90579fadfdf43e8 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 7 Mar 2023 22:43:22 +0000 Subject: [PATCH 15/18] Change: Resize jukebox when changing music set. --- src/music_gui.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/music_gui.cpp b/src/music_gui.cpp index 4b17b83371..85d33e3f0f 100644 --- a/src/music_gui.cpp +++ b/src/music_gui.cpp @@ -178,6 +178,8 @@ void MusicSystem::ChangeMusicSet(const std::string &set_name) this->ChangePlaylist(this->selected_playlist); InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS, 0, true); + InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0, 1, true); + InvalidateWindowData(WC_MUSIC_WINDOW, 0, 1, true); } /** Enable shuffle mode and restart playback */ @@ -480,7 +482,12 @@ struct MusicTrackSelectionWindow : public Window { this->SetWidgetLoweredState(WID_MTS_ALL + i, i == _settings_client.music.playlist); } this->SetWidgetDisabledState(WID_MTS_CLEAR, _settings_client.music.playlist <= 3); - this->SetDirty(); + + if (data == 1) { + this->ReInit(); + } else { + this->SetDirty(); + } } void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override @@ -764,7 +771,11 @@ struct MusicWindow : public Window { UpdateDisabledButtons(); - this->SetDirty(); + if (data == 1) { + this->ReInit(); + } else { + this->SetDirty(); + } } void OnClick(Point pt, int widget, int click_count) override From 3b90b85439b71ce1da045519dc34e265c849ecd0 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 7 Mar 2023 22:50:59 +0000 Subject: [PATCH 16/18] Change: Allow jukebox lists to fill window width. --- src/music_gui.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/music_gui.cpp b/src/music_gui.cpp index 85d33e3f0f..9aa90be195 100644 --- a/src/music_gui.cpp +++ b/src/music_gui.cpp @@ -612,9 +612,9 @@ static const NWidgetPart _nested_music_track_selection_widgets[] = { NWidget(NWID_HORIZONTAL), SetPIP(2, 4, 2), /* Left panel. */ NWidget(NWID_VERTICAL), - NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_PLAYLIST_TRACK_INDEX, STR_NULL), - NWidget(WWT_PANEL, COLOUR_GREY, WID_MTS_LIST_LEFT), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK), EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 2), + NWidget(WWT_LABEL, COLOUR_GREY), SetFill(1, 0), SetDataTip(STR_PLAYLIST_TRACK_INDEX, STR_NULL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_MTS_LIST_LEFT), SetFill(1, 1), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK), EndContainer(), + NWidget(NWID_SPACER), SetFill(1, 0), SetMinimalSize(0, 2), EndContainer(), /* Middle buttons. */ NWidget(NWID_VERTICAL), @@ -631,9 +631,9 @@ static const NWidgetPart _nested_music_track_selection_widgets[] = { EndContainer(), /* Right panel. */ NWidget(NWID_VERTICAL), - NWidget(WWT_LABEL, COLOUR_GREY, WID_MTS_PLAYLIST), SetDataTip(STR_PLAYLIST_PROGRAM, STR_NULL), - NWidget(WWT_PANEL, COLOUR_GREY, WID_MTS_LIST_RIGHT), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK), EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 2), + NWidget(WWT_LABEL, COLOUR_GREY, WID_MTS_PLAYLIST), SetFill(1, 0), SetDataTip(STR_PLAYLIST_PROGRAM, STR_NULL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_MTS_LIST_RIGHT), SetFill(1, 1), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK), EndContainer(), + NWidget(NWID_SPACER), SetFill(1, 0), SetMinimalSize(0, 2), EndContainer(), EndContainer(), EndContainer(), From e999aa2923d391c60bbb96c23db60bb9d7338be4 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 8 Mar 2023 11:20:50 +0000 Subject: [PATCH 17/18] Fix: Don't (briefly) switch from title-only playlist on menu screen. Switching playlist while on the menu screen now only changes the playlist that will be used when out of the menu screen. --- src/music_gui.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/music_gui.cpp b/src/music_gui.cpp index 9aa90be195..7841bbf0d2 100644 --- a/src/music_gui.cpp +++ b/src/music_gui.cpp @@ -147,18 +147,20 @@ void MusicSystem::ChangePlaylist(PlaylistChoices pl) { assert(pl < PLCH_MAX && pl >= PLCH_ALLMUSIC); - this->displayed_playlist = this->standard_playlists[pl]; - this->active_playlist = this->displayed_playlist; - this->selected_playlist = pl; - this->playlist_position = 0; + if (pl != PLCH_THEMEONLY) _settings_client.music.playlist = pl; - if (this->selected_playlist != PLCH_THEMEONLY) _settings_client.music.playlist = this->selected_playlist; + if (_game_mode != GM_MENU || pl == PLCH_THEMEONLY) { + this->displayed_playlist = this->standard_playlists[pl]; + this->active_playlist = this->displayed_playlist; + this->selected_playlist = pl; + this->playlist_position = 0; - if (_settings_client.music.shuffle) { - this->Shuffle(); - /* Shuffle() will also Play() if necessary, only start once */ - } else if (_settings_client.music.playing) { - this->Play(); + if (_settings_client.music.shuffle) { + this->Shuffle(); + /* Shuffle() will also Play() if necessary, only start once */ + } else if (_settings_client.music.playing) { + this->Play(); + } } InvalidateWindowData(WC_MUSIC_TRACK_SELECTION, 0); From 64719f0e1c302ae6f1134d8d3f91f96348efb23c Mon Sep 17 00:00:00 2001 From: Limyx826 <38696851+Limyx826@users.noreply.github.com> Date: Sat, 11 Mar 2023 02:40:12 +0800 Subject: [PATCH 18/18] Doc: Add Coding Style into the repo for easier access (#10512) --- CODINGSTYLE.md | 490 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 CODINGSTYLE.md diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md new file mode 100644 index 0000000000..2c204446c8 --- /dev/null +++ b/CODINGSTYLE.md @@ -0,0 +1,490 @@ +**Why a set coding style is important**
+This project is an open source project. To get open source working as intended, many people should be able to comprehend the code. This implies that there is a well documented and uniform flow of code. +That does not necessarily mean that a contributor should not write optimised yet cryptic code - one should always care about performance. However, other developers need to understand the code which means complicated parts of code **must** have good documentation. + +**Why we require that EVERYTHING is documented**
+What is simple to some might appear very complicated to others. Documentation helps these others. Anyone should be able to improve the code. But the main reason is, that when patch contributors want their patches added to the common codebase, the code needs to be reviewed by one or more developers. Documentation makes reviewing much easier. + +## Coding style for OpenTTD +### Functions +* Function names use [CamelCase](http://www.wikipedia.org/wiki/Camelcase) without underscores. +* Opening curly bracket **{** for a function starts on the next line. +* Use Foo() instead of Foo(void). +```c++ +void ThisIsAFunction() +{ +} +``` + +### Variables +* Variable names are all lowercase, and use "_" as separator. +* Global variables are preceded by an underscore. ("_") Use descriptive names because leading underscores are often used for system / compiler variables. +* Own members of classes should always be referenced using "this->" +* Pointers and references should have their reference symbol next to the name (compatibility with current code). +* Variables that are declared below one another should have their type, name or reference operator, and assignment operator aligned by spaces. +* There are set names for many variables. Those are (but not limited to): Vehicle *u, *v, *w; Station *st; Town *t; Window *w; Engine *e. +* For multiple instances, use numbers "*t1, *t2" or postfixes "*st_from, *st_to". +* Declare variables upon first usage. +* Declare iterators in their loop. +* There is a space between '*' and 'const' in "const pointers" +```c++ +int number = 10; +Vehicle *u_first_wagon = v->next; +int value = v->value; + +uint32 _global_variable = 3750; + +static const char * const _global_array[] = { + "first string", + "second string", + "another string", + "last string followed by comma", +}; + +protected: + char protected_array[10]; + +for (int i = 0;;); +``` + +* Give the variables expedient names, this increases the readability of the code +```c++ +bool is_maglev_train = true; +if (!is_maglev_train) DoSomething(); +``` + +* Some people like to introduce copies of variables to increase readability, which can waste memory. However, in some cases, especially in code pieces which are often called, it makes sense to cache some calculated variables. +```c++ +/* Unoptimized code: + * foo is not touched inside the loop! + */ +for (uint8 i = 0; i < 100000; i++) { + /* Do something */ + if (value_to_check == (foo * 4) % 5 + 6) DoSomething(); + /* Do something else */ +} + +/* Better: + * The used value of foo is calculated outside the loop. + */ +const uint32 bar = (foo * 4) % 5 + 6; +for (uint8 i = 0; i < 100000; i++) { + /* Do something */ + if (value_to_check == bar) DoSomething(); + /* Do something else */ +} +``` + +### Enumerations / static consts +* Enumerations store integers that belong logically together (railtypes, string IDs, etc.). +* Enumeration names also use CamelCase. +* Unscoped enumerators are all caps with "_" between the words. +* Scoped enumerators are use CamelCase. +* Enums are not used to store single numbers. +* Enums have consecutive numbers OR +* Enums have consecutive powers of two. Powers of two (bits) are written in hex or with the shift operator. +* Enums may have special enumerators: "_BEGIN", "\_END", and "INVALID\_"). See example. +* The invalid always has 0xFF, 0xFFFF, 0xFFFFFFFF as a value. +* Other special values are consecutively less than the invalid. +* Variables that hold enumerators should have the type of the enumeration. +```c++ +enum DiagDirection { + DIAGDIR_BEGIN = 0, + DIAGDIR_NE = 0, + DIAGDIR_SE = 1, + DIAGDIR_SW = 2, + DIAGDIR_NW = 3, + DIAGDIR_END, + INVALID_DIAGDIR = 0xFF, + BROKEN_DIAGDIR = 0xFE, +}; + +enum { + DEPOT_SERVICE = (1 << 0), + DEPOT_MASS_SEND = (1 << 1), + DEPOT_DONT_CANCEL = (1 << 2), + DEPOT_LOCATE_HANGAR = (1 << 3), +}; + +DiagDirection enterdir = DIAGDIR_NE; +``` + +* Numbers that store single or uncorrelated data are static consts. Those may use the naming conventions of enums. +Example: +```c++ +static const int MAXIMUM_STATIONS = 42; +``` + +* Enums are useful in GUI code: When widgets are enumerated, they are easier to access during many operations. Additionally changes caused by modified widget sequences require less code adapting. If a widget is used like this, its enum name should be present in a comment behind the corresponding widget definition. +```c++ +/** Enum referring to the widgets of the build rail depot window */ +enum BuildRailDepotWidgets { + BRDW_CLOSEBOX = 0, + BRDW_CAPTION, + BRDW_BACKGROUND, + BRDW_DEPOT_NE, + BRDW_DEPOT_SE, + BRDW_DEPOT_SW, + BRDW_DEPOT_NW, +}; +/* ... */ +/** Widget definition of the build rail depot window */ +static const Widget _build_depot_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_..}, // BRDW_CLOSEBOX +{ WWT_CAPTION, RESIZE_NONE, 7, 11, 139, 0, 13, STR_..., STR_...}, // BRDW_CAPTION +{ WWT_PANEL, RESIZE_NONE, 7, 0, 139, 14, 121, 0x0, STR_NULL}, // BRDW_BACKGROUND +{ WWT_PANEL, RESIZE_NONE, 14, 71, 136, 17, 66, 0x0, STR_..}, // BRDW_DEPOT_NE +{ WWT_PANEL, RESIZE_NONE, 14, 71, 136, 69, 118, 0x0, STR_..}, // BRDW_DEPOT_SE +{ WWT_PANEL, RESIZE_NONE, 14, 3, 68, 69, 118, 0x0, STR_..}, // BRDW_DEPOT_SW +{ WWT_PANEL, RESIZE_NONE, 14, 3, 68, 17, 66, 0x0, STR_..}, // BRDW_DEPOT_NW +{ WIDGETS_END}, +}; +``` + +* Comments on the enum values should start with "///<" to enable doxygen documentation +```c++ +enum SomeEnumeration { + SE_BEGIN = 0, ///< Begin of the enumeration, used for iterations + SE_FOO = 0, ///< Used for xy + SE_BAR, ///< Another value of the enumeration + SE_SUB, ///< Special case for z + SE_END, ///< End of the enumeration, used for iterations +}; +``` + +### Control flow +* Put a space before the parentheses in **if**, **switch**, **for** and **while** statements. +* Use curly braces and put the contained statements on their own lines (e.g., don't put them directly after the **if**). +* Opening curly bracket **{** stays on the first line, closing curly bracket **}** gets a line to itself (except for the **}** preceding an **else**, which should be on the same line as the **else**). +* When only a single statement is contained, the brackets can be omitted. In this case, put the single statement on the same line as the preceding keyword (**if**, **while**, etc.). Note that this is only allowed for if statements without an **else** clause. +* All fall throughs must be documented, using a **FALLTHROUGH** define/macro. +* The NOT_REACHED() macro can be used in default constructs that should never be reached. +* Unconditional loops are written with **`for (;;) {`** + +```c++ +if (a == b) { + Foo(); +} else { + Bar(); +} + +if (very_large_checks && + spread_over_two_lines) { + Foo(); +} + +if (a == b) Foo(); + +switch (a) { + case 0: DoSomethingShort(); break; + + case 1: + DoSomething(); + /* FALL THROUGH */ + + case 2: + DoMore(); + b = a; + break; + + case 3: { + int r = 2; + + DoEvenMore(a); + /* FALL THROUGH */ + } + + case 4: { + int q = 345; + + DoIt(); + break; + } + + default: + NOT_REACHED(); +} + +for (int i = 0; i < 10; i++) { + Foo(); + Bar(); +} +``` + +### Classes +* Classes names also use CamelCase. +* Classes should have "public", "protected", and "private" sections. +* Within these section the order is: types, static const members, static members, members, constructors / destructors, static methods, methods. +* Deviations from above order are allowed when the code dictates it (e.g. a static const is needed for a typedef) +* Methods and members ought to be grouped logically. +* All those sorting rules sometimes conflict which one another. Please use common sense what increases legibility of the code in such a case. +* The method implementation should indicate if it is virtual or similar by using a comment. +* Very short methods can have one-line definition (if defined in the class scope). +```c++ +class ThisIsAClass { +public: + typedef Titem_ *ItemPtr; +private: + static const int MAX_SIZE = 500; + int size; + ItemPtr *items; + +public: + explicit ThisIsAClass(); + ~ThisIsAClass(); + + int GetSize() { return this->size; } + virtual void Method(); +}; + +/*virtual*/ void Class::Method() +{ + this->size *= 2; +} +``` + +### Templates +Templates are a very powerful C++ tool, but they can easily confuse beginners. Thus: +* Templates are to be documented in a very clear and verbose manner. Never assume anything in the documentation. +* the template keyword and the template layout get a separate line. typenames are either "T" or preceded by a "T", integers get a single capital letter or a descriptive name preceded by "T". +```c++ +template +int Func(); +``` + +* If you are writing one or more template class in the dedicated header file, use file.hpp for its name instead of file.h. This will let others know that it is template library (includes also implementation), not just header with declarations. + +### Other important rules +* Put a space before and after binary operators: "a + b", "a == b", "a & b", "a <<= b", etc.. Exceptions are ".", "->" and "[]" (no spaces) and "," (just space after it). +* Put parenthesis where it improves readability: "*(b++)" instead of "*b++", and "if ((a & b) && c == 2)" instead of "if (a & b && c == 2)". +* Do not put external declarations in implementation (i.e. cpp) files. +* Use const where possible. +* Do not typedef enums and structs. +* Don't treat non-flags as flags: use "if (char_pointer != nullptr && *char_pointer != '\0')", not "if (char_pointer && *char_pointer)". +* Use "free(p)" instead of "if (p != nullptr) free(p)". "free(nullptr)" doesn't hurt anyone. +* No trailing whitespace. The Github workflows will not allow tabs or space on the end of lines. +* Only use tabs to indent from the start of the line. +* Line length is unlimited. In practice it may be useful to split a long line. When splitting, add two tabs in front of the second part. +* The '#' of preprocessor instructions goes into the first column of a line. Indenting is done after the '#' (using tabs again). +* Use /* */ for single line comments. +* Use // at the end of a command line to indicate comments. +** However, use /* */ after # preprocessor statements as // causes warnings in some compilers and/or might have unwanted side effects. +* C++ is defined using the ASCII character set. Do not use other character sets, not even in comments. +* OpenTTD includes some Objective-C sources (*.mm, used by OSX), which has a special object method invocation syntax: "[ obj doStuff:foo ]". Please use spaces on the inside of the brackets to differentiate from the C array syntax. + +## Documentation +We use [Doxygen](http://doxygen.org/) to automatically generate documentation from the source code. It scans the source files for *recognizable* comments. +* **Make your comments recognizable.** +Doxygen only comments starting with the following style: +```c++ +/** +///< +``` + +Use /** for multi-line comment blocks. Use ///< for single line comments for variables. Use //!< for single-line comments in the NoAI .hpp files. +For comments blocks inside a function always use /* */ or //. Use // only if the comment is on the same line as an instruction, otherwise use /* */. + +### Files +* Put a @file command in a JavaDoc style comment at the start of the file, followed by a description. +```c++ +/** + * @file + * This is the brief description. + * This is the detailed description here and on the following lines. + */ +``` +> ### Warning +> If a file lacks a **file comment block** then NO entities in that file will be documented by Doxygen! + +### Functions +* The documentation should be as close to the actual code as possible to maximise the chance of staying in sync. + * Comments for functions go in the .cpp file. + * Comments for inlines go in the .h/.hpp file. +* Small inlines can have a short 3 or 4 line JavaDoc style comment. +* Completely document larger functions. +* Document obvious parameters and return values too! + +```c++ +/** + * A brief explanation of what the function does and/or what purpose it serves. + * Then follows a more detailed explanation of the function that can span multiple lines. + * + * @param foo Explanation of the foo parameter + * @param bar Explanation of the bar parameter + * @return The sum of foo and bar (@return can be omitted if the return type is void) + * + * @see SomeOtherFunc() + * @see SOME_ENUM + * + * @bug Some bug description + * @bug Another bug description which continues in the next line + * and ends with the following blank line + * + * @todo Some to-do entry + */ +static int FooBar(int foo, int bar) +{ + return foo + bar; +} +``` + +### Classes +* Document structs similarly to functions: +```c++ +/** + * A short description of the struct. + * More detailed description of the its usage. + * + * @see [link to anything of interest] + */ +struct foo { +} +``` + +### JavaDoc structural commands + +This table shows the commands you should use with OpenTTD. The full list is [here](http://www.stack.nl/~dimitri/doxygen/commands.html). + +| Command | Action | Example | +| ------- | -------- | ------- | +| **@attention** | Starts a paragraph where a message that needs attention may be entered. The paragraph will be indented. | @attention Whales crossing! | +| **@brief** | Starts a paragraph that serves as a brief description. For classes and files the brief description will be used in lists and at the start of the documentation page. For class and file members, the brief description will be placed at the declaration of the member and prepended to the detailed description. A brief description may span several lines (although it is advised to keep it brief!). | @brief This is the brief description. | +| **@bug** | Starts a paragraph where one or more bugs may be reported. The paragraph will be indented. Multiple adjacent @bug commands will be joined into a single paragraph. Each bug description will start on a new line. Alternatively, one @bug command may mention several bugs. | @bug Memory leak in here? | +| **@note** | Starts a paragraph where a note can be entered. The paragraph will be indented. | @note Might be slow | +| **@todo** | Starts a paragraph where a TODO item is described. The description will also add an item to a separate TODO list. The two instances of the description will be cross-referenced. Each item in the TODO list will be preceded by a header that indicates the origin of the item. | @todo Better error checking | +| **@warning** | Starts a paragraph where one or more warning messages may be entered. The paragraph will be indented. | @warning Not thread safe! | +| | **Function related commands** | | +| **@return** | Starts a return value description for a function. | @return a character pointer | +| **@param** | Starts a parameter description for a function parameter with name . Followed by a description of the parameter. The existence of the parameter is checked and a warning is given if the documentation of this (or any other) parameter is missing or not present in the function declaration or definition.

The @param command has an optional attribute specifying the direction of the attribute. Possible values are "in" and "out". | @param n The number of bytes to copy
@param[out] dest The memory area to copy to.
@param[in] src The memory area to copy from. | +| **@see** | Starts a paragraph where one or more cross-references to classes, functions, methods, variables, files or URL may be specified. Two names joined by either :: or # are understood as referring to a class and one of its members. One of several overloaded methods or constructors may be selected by including a parenthesized list of argument types after the method name. [Here](http://www.stack.nl/~dimitri/doxygen/autolink.html) you can find detailed information about this feature. | @see OtherFunc() | +| **@b** | Displays the following word using a bold font. Equivalent to <b>word</b>. To put multiple words in bold use <b>multiple words</b>.| ...@b this and @b that... | +| **@c / @p** | Displays the parameter using a typewriter font. You can use this command to refer to member function parameters in the running text. To have multiple words in typewriter font use <tt>multiple words</tt>. | ... the @p x and @p y coordinates are used to... | +| **@arg / @li** | This command has one argument that continues until the first blank line or until another @arg / @li is encountered. The command can be used to generate a simple, not nested list of arguments. Each argument should start with an @arg / @li command. | @arg @c AlignLeft left alignment.
@arg @c AlignCenter center alignment.
@arg @c AlignRight right alignment | +| **@n** | Forces a new line. Equivalent to and inspired by the printf function. |@n | + +### More on Doxygen and JavaDoc + +Doxygen knows two different kinds of comments: +* *Brief descriptions*: one-liners that describe the function roughly ([example](http://docs.openttd.org/annotated.html)) +* *Detailed descriptions*: as the name suggests, these contain the detailed function/purpose of the entity they describe ([example](http://docs.openttd.org/structBridge.html)) + +You can omit either one or put them into different places but there's only one brief and one detailed description allowed for the same entity. + +Doxygen knows three modes for documenting an entity: +* Before the entity +* After the entity +* In a different file + +The latter is a little harder to maintain since the prototype of the entity it describes then is stored in several places (e.g. the .h file and the file with the descriptions). Also while it makes the code easier to read it also makes it easier to omit the important step of updating the description of an entity if it was changed - and we all know why that shouldn't happen ;)
+Because of those reasons, we will only use the first two documentation schemes. + + +Doxygen supports both Qt and JavaDoc comment styles: +* Qt style example: **int i; //!< The counter for the main loop** +* JavaDoc style example: **int i; /*\*< The counter for the main loop \*/** + +It also supports more comment styles but those two are the ones which are standardized. For OTTD we'll be using the JavaDoc style. One of the reasons is that it has a feature that the Qt style doesn't offer: JavaDoc style comment blocks will automatically start a brief description which ends at the first dot followed by a space or new line. Everything after that will also be part of the detailed description. + +The general structure of a JavaDoc style comment is +```c++ +/** + * This is the brief description. And this sentence contains some further explanations that will appear in the detailed description only. + */ +``` + +and the resulting descriptions of that block would be: +* *Brief description*: This is the brief description. +* *Detailed description*: This is the brief description. And this sentence contains some further explanations that will appear in the detailed description only. + +The distinction between the brief and detailed descriptions is made by the dot followed by a space/newline, so if you want to use that inside the brief description you need to escape the space/newline: +```c++ +/** + * This is a brief description (e.g.\ using only a few words). Details go here. + */ +``` + +If you're doing a one-line comment, use: +```c++ +int i; ///< This is the description. +``` + +Also in the comment block you can include so-called structural commands which tell Doxygen what follows. In general, their area of effect begins after the command word and ends when a blank line or some other command is encountered. Also, multiple occurrences of the same structural command within a comment block or the referring entity will be joined in the documentation output usually. + +## Commit message +To achieve a coherent whole and to make changelog writing easier, here are some guidelines for commit messages. +There is a check-script on the git server (also available for clients, see below) to make sure we all follow those rules. + +The first line of a message must match: +``` +
+( #| (, ( #|))*)?: ([
+
+``` +Keywords are: +* Add, Feature: Adding new stuff. Difference between "Feature" and "Add" is somewhat subjective. "Feature" for user-point-of-view stuff, "Add" for other. +* Change: Changing behaviour from user-point-of-view. +* Remove: Removing something from user-point-of-view. +* Codechange, Cleanup: Changes without intentional change of behaviour from user-point-of-view. Difference between "Codechange" and "Cleanup" is somewhat subjective. +* Fix, Revert: Fixing stuff. +* Doc, Update: Documentation changes, version increments, translator commits. +* Prepare: Preparation for bigger changes. Rarely used. + +If you commit a fix for an [issue](https://github.com/OpenTTD/OpenTTD/issues), add the corresponding issue number in the form of #NNNN. Do it as well if you implement a feature with a matching entry. + +In the case of bugfixes, if you know what revision the bug was introduced (eg regression), please mention that revision as well just after the prefix. Finding the trouble-causing revision is highly encouraged as it makes backporting/branching/releases that much easier. + +To further structure the changelog, you can add sections. Example are: +* "Network" for network specific changes +* "NewGRF" for NewGRF additions +* "YAPP", "NPF", for changes in these features +* "OSX", "Debian", "win32", for OS-specific packaging changes + +Further explanations, general bitching, etc. don't go into the first line. Use a new line for those. + +Complete examples: +* Fix: [YAPF] Infinite loop in pathfinder. +* Fix #5926: [YAPF] Infinite loop in pathfinder. +* Fix 80dffae130: Warning about unsigned unary minus. +* Fix #6673, 99bb3a95b4: Store the map variety setting in the samegame. +* Revert d9065fbfbe, Fix #5922: ClientSizeChanged is only called via WndProcGdi which already has the mutex. +* Fix #1264, Fix #2037, Fix #2038, Fix #2110: Rewrite the autoreplace kernel. + + +## Other tips +### Remove trailing whitespace +To find out if/where you have trailing whitespace, you can use the following (unix/bash) command: +``` +grep -n -R --include "*.[ch]" '[ ]$' * | grep --invert-match ".diff" | grep --invert-match ".patch" +``` +Automatically removing whitespace is also possible with the following shell script (Note that it only checks .c, .cpp, .h, .hpp and .mm files): +``` +#!/bin/sh +IFS=' +' +for i in Makefile `find . -name \*.c -o -name \*.cpp -o -name \*.h -o -name \*.hpp -o -name \*.mm` +do + ( + echo '%s/[ ]\{1,\}$/' + echo w + echo q + ) | ed $i 2> /dev/null > /dev/null +done +``` + +### Install the client-side git commit hooks + +The client-side hooks perform various checks when you commit changes locally. +* Whitespace and indentation checks. +* **Coding style** checks. + +Get the hooks: +``` +git clone https://github.com/OpenTTD/OpenTTD-git-hooks.git openttd_hooks +``` + +Install the hooks, assuming "openttd" is your work tree folder: +``` +cd openttd/.git/hooks +ln -s -t . ../../../openttd_hooks/hooks/* +``` +