diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..e5bd27659c --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,10 @@ +name: openttd +queries: +- uses: security-and-quality +query-filters: +- exclude: + id: + # Only feasible way is to move away from fopen; fopen_s is optional C11 and not implemented on most platforms. + - cpp/world-writable-file-creation + # Basically OpenTTD's coding style for adding things like ..._INVALID to enumerations + - cpp/irregular-enum-init diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..d30e9b3755 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,78 @@ +name: CodeQL + +on: + push: + branches: + - master + pull_request: + # The branches below must be a subset of the branches above + branches: + - master + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::Update apt" + sudo apt-get update + echo "::endgroup::" + + echo "::group::Install dependencies" + sudo apt-get install -y --no-install-recommends \ + liballegro4-dev \ + libfontconfig-dev \ + libicu-dev \ + liblzma-dev \ + liblzo2-dev \ + libsdl2-dev \ + zlib1g-dev \ + # EOF + echo "::endgroup::" + env: + DEBIAN_FRONTEND: noninteractive + + - name: Set number of make jobs + run: | + echo "MAKEFLAGS=-j$(nproc)" >> $GITHUB_ENV + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: cpp + config-file: ./.github/codeql/codeql-config.yml + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: /language:cpp + upload: False + output: sarif-results + + - name: Filter out table & generated code + uses: advanced-security/filter-sarif@v1 + with: + patterns: | + +**/*.* + -**/table/*.* + -**/generated/**/*.* + input: sarif-results/cpp.sarif + output: sarif-results/cpp.sarif + + - name: Upload results + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: sarif-results/cpp.sarif diff --git a/regression/regression/result.txt b/regression/regression/result.txt index dc3e294844..7ce9550599 100644 --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -88,24 +88,24 @@ abs( 21): 21 --AIBase-- - Rand(): -54346916 - Rand(): -937374575 - Rand(): 823953997 + Rand(): -2062310602 + Rand(): -1780331126 + Rand(): -397928569 RandRange(0): 0 RandRange(0): 0 RandRange(0): 0 RandRange(1): 0 RandRange(1): 0 RandRange(1): 0 - RandRange(2): 1 - RandRange(2): 1 - RandRange(2): 1 - RandRange(1000000): 966676 - RandRange(1000000): 289525 - RandRange(1000000): 170283 - Chance(1, 2): false + RandRange(2): 0 + RandRange(2): 0 + RandRange(2): 0 + RandRange(1000000): 666804 + RandRange(1000000): 624059 + RandRange(1000000): 697029 Chance(1, 2): true Chance(1, 2): false + Chance(1, 2): true --List-- IsEmpty(): true @@ -420,144 +420,144 @@ 1098 => 46116 1099 => 46158 Randomize ListDump: - 1 => -200078348 - 2 => -29799264 - 1000 => 1630721656 - 1001 => 959306175 - 1002 => 1527421791 - 1003 => 1259692483 - 1004 => -1289244298 - 1005 => -1572996668 - 1006 => -2069479746 - 1007 => -1819131606 - 1008 => -1007163964 - 1009 => -1185394870 - 1010 => -1471365065 - 1011 => 364354366 - 1012 => -1478084253 - 1013 => 405281367 - 1014 => -11170062 - 1015 => 156767750 - 1016 => 1288924796 - 1017 => 1796884876 - 1018 => -1947073702 - 1019 => -1999614238 - 1020 => -231292809 - 1021 => 966621566 - 1022 => -606766557 - 1023 => -1138727825 - 1024 => -749544262 - 1025 => 2004771271 - 1026 => 686734186 - 1027 => 923274744 - 1028 => -1672035149 - 1029 => -1642064950 - 1030 => 1363389551 - 1031 => -559500928 - 1032 => 1656196991 - 1033 => 1655354425 - 1034 => -1027156689 - 1035 => 1952644328 - 1036 => 1217870217 - 1037 => 242274100 - 1038 => 201816080 - 1039 => 2127464758 - 1040 => 446043650 - 1041 => -319728455 - 1042 => 204701002 - 1043 => -571265398 - 1044 => -1422217131 - 1045 => -391208397 - 1046 => -1822628371 - 1047 => -1499755350 - 1048 => -1422137641 - 1049 => 1621693134 - 1051 => -1428728134 - 1052 => -147587573 - 1053 => 681719500 - 1054 => 1172011190 - 1055 => -1834344882 - 1056 => 1157634586 - 1057 => 1902133676 - 1058 => -1967780161 - 1059 => -1618025531 - 1060 => -810220453 - 1061 => 1582854921 - 1062 => -410004643 - 1063 => 1159917159 - 1064 => -1377804984 - 1065 => -738843914 - 1066 => -1578756103 - 1067 => -464090986 - 1068 => 1711504679 - 1069 => 545330655 - 1070 => 379462570 - 1071 => 514511099 - 1072 => -1813251176 - 1073 => 1424958266 - 1074 => -825255131 - 1075 => 539054595 - 1076 => -1764192010 - 1077 => -1243277769 - 1078 => 2017874281 - 1079 => -1972353607 - 1080 => 1879761467 - 1081 => 1638986560 - 1082 => -1832287507 - 1083 => -492411882 - 1084 => 658940812 - 1085 => -1044199400 - 1086 => 1586504918 - 1087 => -125492611 - 1088 => -1562883174 - 1089 => -1013778441 - 1090 => 1560228607 - 1091 => -550265689 - 1092 => 524767105 - 1093 => -713387661 - 1094 => 1425927738 - 1095 => 942653932 - 1096 => 1233220698 - 1097 => 1313602368 - 1098 => -140318584 - 1099 => 1199179892 + 1 => 688298322 + 2 => -1709546982 + 1000 => 1701392078 + 1001 => -1630848421 + 1002 => -886500935 + 1003 => -196324972 + 1004 => -436037402 + 1005 => -520341784 + 1006 => -1485224804 + 1007 => -311036236 + 1008 => -1503442439 + 1009 => -110945695 + 1010 => -82825175 + 1011 => 46859773 + 1012 => -1199223018 + 1013 => -1190555925 + 1014 => 326384434 + 1015 => 1486817960 + 1016 => -1411425597 + 1017 => -508426854 + 1018 => 820019294 + 1019 => 710762995 + 1020 => -760867032 + 1021 => -709611146 + 1022 => 732190215 + 1023 => 236336673 + 1024 => 740596257 + 1025 => 1135321785 + 1026 => 2067474156 + 1027 => -1395683607 + 1028 => -240528699 + 1029 => 928616892 + 1030 => 1712486685 + 1031 => 1994118287 + 1032 => 1333321243 + 1033 => 194124284 + 1034 => 615083294 + 1035 => 628086450 + 1036 => 498957825 + 1037 => 1359697121 + 1038 => 1888433963 + 1039 => 941623020 + 1040 => -1925663292 + 1041 => -771540264 + 1042 => -1058341359 + 1043 => 182127597 + 1044 => 646955927 + 1045 => -1424621714 + 1046 => 623062612 + 1047 => -1986955586 + 1048 => -1268826980 + 1049 => -456776220 + 1051 => -1112555329 + 1052 => -1532134052 + 1053 => 1960404034 + 1054 => 1573325453 + 1055 => -316619303 + 1056 => 699712177 + 1057 => 863274966 + 1058 => 1728276475 + 1059 => -246695889 + 1060 => 1919485436 + 1061 => 111273464 + 1062 => 125435213 + 1063 => 155132602 + 1064 => -171674076 + 1065 => 655046914 + 1066 => 1577399562 + 1067 => 1028818150 + 1068 => 447058239 + 1069 => -1057920269 + 1070 => -1326215323 + 1071 => -198688588 + 1072 => 1523643051 + 1073 => 231373233 + 1074 => 1121759962 + 1075 => 1449439846 + 1076 => -1615270753 + 1077 => -1509293864 + 1078 => 2116903943 + 1079 => 672822173 + 1080 => -969573911 + 1081 => 1589904755 + 1082 => 1148782015 + 1083 => 663503316 + 1084 => 933352745 + 1085 => 577717039 + 1086 => 402172048 + 1087 => 1812250453 + 1088 => 667300501 + 1089 => -1838825777 + 1090 => -856474776 + 1091 => 420696035 + 1092 => 2131427774 + 1093 => -435303548 + 1094 => -160883878 + 1095 => 1969629634 + 1096 => -555794155 + 1097 => -835119691 + 1098 => -1460907909 + 1099 => -1146924084 KeepTop(10): - 1 => -200078348 - 2 => -29799264 - 1000 => 1630721656 - 1001 => 959306175 - 1002 => 1527421791 - 1003 => 1259692483 - 1004 => -1289244298 - 1005 => -1572996668 - 1006 => -2069479746 - 1007 => -1819131606 + 1 => 688298322 + 2 => -1709546982 + 1000 => 1701392078 + 1001 => -1630848421 + 1002 => -886500935 + 1003 => -196324972 + 1004 => -436037402 + 1005 => -520341784 + 1006 => -1485224804 + 1007 => -311036236 KeepBottom(8): - 1000 => 1630721656 - 1001 => 959306175 - 1002 => 1527421791 - 1003 => 1259692483 - 1004 => -1289244298 - 1005 => -1572996668 - 1006 => -2069479746 - 1007 => -1819131606 + 1000 => 1701392078 + 1001 => -1630848421 + 1002 => -886500935 + 1003 => -196324972 + 1004 => -436037402 + 1005 => -520341784 + 1006 => -1485224804 + 1007 => -311036236 RemoveBottom(2): - 1000 => 1630721656 - 1001 => 959306175 - 1002 => 1527421791 - 1003 => 1259692483 - 1004 => -1289244298 - 1005 => -1572996668 + 1000 => 1701392078 + 1001 => -1630848421 + 1002 => -886500935 + 1003 => -196324972 + 1004 => -436037402 + 1005 => -520341784 RemoveTop(2): - 1002 => 1527421791 - 1003 => 1259692483 - 1004 => -1289244298 - 1005 => -1572996668 + 1002 => -886500935 + 1003 => -196324972 + 1004 => -436037402 + 1005 => -520341784 RemoveList({1003, 1004}): - 1002 => 1527421791 - 1005 => -1572996668 + 1002 => -886500935 + 1005 => -520341784 KeepList({1003, 1004, 1005}): - 1005 => -1572996668 + 1005 => -520341784 AddList({1005, 4000, 4001, 4002}): 1005 => 1005 4000 => 8000 diff --git a/src/3rdparty/squirrel/squirrel/sqclass.h b/src/3rdparty/squirrel/squirrel/sqclass.h index 7a16fa9439..c59750d91e 100644 --- a/src/3rdparty/squirrel/squirrel/sqclass.h +++ b/src/3rdparty/squirrel/squirrel/sqclass.h @@ -10,6 +10,7 @@ struct SQClassMember { val = o.val; attrs = o.attrs; } + SQClassMember& operator=(SQClassMember &o) = delete; SQObjectPtr val; SQObjectPtr attrs; }; diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp index a230b3f55c..e9ebe72bd0 100644 --- a/src/ai/ai_scanner.cpp +++ b/src/ai/ai_scanner.cpp @@ -14,6 +14,7 @@ #include "../core/random_func.hpp" #include "../script/squirrel_class.hpp" +#include "../script/api/script_object.hpp" #include "ai_info.hpp" #include "ai_scanner.hpp" @@ -77,12 +78,7 @@ AIInfo *AIScannerInfo::SelectRandomAI() const } /* Find a random AI */ - uint pos; - if (_networking) { - pos = InteractiveRandomRange(num_random_ais); - } else { - pos = RandomRange(num_random_ais); - } + uint pos = ScriptObject::GetRandomizer(OWNER_NONE).Next(num_random_ais); /* Find the Nth item from the array */ ScriptInfoList::const_iterator it = this->info_single_list.begin(); diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 3cb651c459..deeb9b67c0 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1068,6 +1068,7 @@ static uint ShowAdditionalText(int left, int right, int y, EngineID engine) uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr); if (callback == CALLBACK_FAILED || callback == 0x400) return y; const GRFFile *grffile = Engine::Get(engine)->GetGRF(); + assert(grffile != nullptr); if (callback > 0x400) { ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback); return y; diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 68388aec62..62a756266c 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -596,7 +596,7 @@ Company *DoStartupNewCompany(DoStartupNewCompanyFlag flags, CompanyID company) if (_company_manager_face != 0 && !is_ai && !_networking) { c->face = _company_manager_face; } else { - RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, false); + RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, _random); } SetDefaultCompanySettings(c->index); diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 4966d71569..25128563da 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -1751,7 +1751,7 @@ public: /* Randomize face button */ case WID_SCMF_RANDOM_NEW_FACE: - RandomCompanyManagerFaceBits(this->face, this->ge, this->advanced); + RandomCompanyManagerFaceBits(this->face, this->ge, this->advanced, _interactive_random); this->UpdateData(); this->SetDirty(); break; diff --git a/src/company_manager_face.h b/src/company_manager_face.h index fe6365a121..83bfd29e11 100644 --- a/src/company_manager_face.h +++ b/src/company_manager_face.h @@ -199,15 +199,13 @@ static inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf) * @param cmf the company manager's face to write the bits to * @param ge the gender and ethnicity of the old company manager's face * @param adv if it for the advanced company manager's face window - * @param interactive is the call from within the user interface? + * @param randomizer the source of random to use for creating the manager face * * @pre scale 'ge' to a valid gender/ethnicity combination */ -static inline void RandomCompanyManagerFaceBits(CompanyManagerFace &cmf, GenderEthnicity ge, bool adv, bool interactive = true) +static inline void RandomCompanyManagerFaceBits(CompanyManagerFace &cmf, GenderEthnicity ge, bool adv, Randomizer &randomizer) { - /* This method is called from a command when not interactive and - * then we must use Random to get the same result on all clients. */ - cmf = interactive ? InteractiveRandom() : Random(); // random all company manager's face bits + cmf = randomizer.Next(); // random all company manager's face bits /* scale ge: 0 == GE_WM, 1 == GE_WF, 2 == GE_BM, 3 == GE_BF (and maybe in future: ...) */ ge = (GenderEthnicity)((uint)ge % GE_END); diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 731069bd3e..56e07ece4b 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -313,7 +313,11 @@ DEF_CONSOLE_CMD(ConZoomToLevel) case 2: { uint32 level; if (GetArgumentInteger(&level, argv[1])) { - if (level < ZOOM_LVL_MIN) { + /* In case ZOOM_LVL_MIN is more than 0, the next if statement needs to be amended. + * A simple check for less than ZOOM_LVL_MIN does not work here because we are + * reading an unsigned integer from the console, so just check for a '-' char. */ + static_assert(ZOOM_LVL_MIN == 0); + if (argv[1][0] == '-') { IConsolePrintF(CC_ERROR, "Zoom-in levels below %u are not supported.", ZOOM_LVL_MIN); } else if (level < _settings_client.gui.zoom_min) { IConsolePrintF(CC_ERROR, "Current client settings do not allow zooming in below level %u.", _settings_client.gui.zoom_min); @@ -322,7 +326,7 @@ DEF_CONSOLE_CMD(ConZoomToLevel) } else if (level > _settings_client.gui.zoom_max) { IConsolePrintF(CC_ERROR, "Current client settings do not allow zooming out beyond level %u.", _settings_client.gui.zoom_max); } else { - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); Viewport *vp = w->viewport; while (vp->zoom > level) DoZoomInOutWindow(ZOOM_IN, w); while (vp->zoom < level) DoZoomInOutWindow(ZOOM_OUT, w); @@ -529,7 +533,7 @@ DEF_CONSOLE_CMD(ConRemove) _console_file_list.ValidateFileList(); const FiosItem *item = _console_file_list.FindItem(file); if (item != nullptr) { - if (!FiosDelete(item->name)) { + if (unlink(item->name) != 0) { IConsolePrintF(CC_ERROR, "%s: Failed to delete file", file); } } else { @@ -1031,6 +1035,7 @@ DEF_CONSOLE_CMD(ConResetCompany) return false; } const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER); + assert(ci != nullptr); if (ci->client_playas == index) { IConsoleError("Cannot remove company: the server is connected to that company."); return true; @@ -2240,18 +2245,15 @@ DEF_CONSOLE_CMD(ConFont) bool aa = setting->aa; byte arg_index = 2; - - if (argc > arg_index) { - /* We may encounter "aa" or "noaa" but it must be the last argument. */ - if (strcasecmp(argv[arg_index], "aa") == 0 || strcasecmp(argv[arg_index], "noaa") == 0) { - aa = strncasecmp(argv[arg_index++], "no", 2) != 0; - if (argc > arg_index) return false; - } else { - /* For we want a string. */ - uint v; - if (!GetArgumentInteger(&v, argv[arg_index])) { - font = argv[arg_index++]; - } + /* We may encounter "aa" or "noaa" but it must be the last argument. */ + if (strcasecmp(argv[arg_index], "aa") == 0 || strcasecmp(argv[arg_index], "noaa") == 0) { + aa = strncasecmp(argv[arg_index++], "no", 2) != 0; + if (argc > arg_index) return false; + } else { + /* For we want a string. */ + uint v; + if (!GetArgumentInteger(&v, argv[arg_index])) { + font = argv[arg_index++]; } } @@ -2282,7 +2284,7 @@ DEF_CONSOLE_CMD(ConFont) InitFontCache(fs == FS_MONO); fc = FontCache::Get(fs); } - IConsolePrintF(CC_DEFAULT, "%s: \"%s\" %d %s [\"%s\" %d %s]", FontSizeToName(fs), fc->GetFontName(), fc->GetFontSize(), GetFontAAState(fs) ? "true" : "false", setting->font.c_str(), setting->size, setting->aa ? "true" : "false"); + IConsolePrintF(CC_DEFAULT, "%s: \"%s\" %d %s [\"%s\" %d %s]", FontSizeToName(fs), fc->GetFontName(), fc->GetFontSize(), GetFontAAState(fs) ? "aa" : "noaa", setting->font.c_str(), setting->size, setting->aa ? "aa" : "noaa"); } return true; diff --git a/src/core/overflowsafe_type.hpp b/src/core/overflowsafe_type.hpp index e23a478731..066261a859 100644 --- a/src/core/overflowsafe_type.hpp +++ b/src/core/overflowsafe_type.hpp @@ -172,7 +172,10 @@ public: inline constexpr bool operator < (const int other) const { return !(*this >= other); } inline constexpr bool operator <= (const int other) const { return !(*this > other); } - inline constexpr operator int64 () const { return this->m_value; } + inline constexpr operator T () const { return this->m_value; } + + static inline constexpr OverflowSafeInt max() { return T_MAX; } + static inline constexpr OverflowSafeInt min() { return T_MIN; } }; diff --git a/src/error_gui.cpp b/src/error_gui.cpp index ca345098d1..d765358fa4 100644 --- a/src/error_gui.cpp +++ b/src/error_gui.cpp @@ -239,7 +239,7 @@ public: int scr_bot = GetMainViewBottom() - 20; Point pt = RemapCoords(this->position.x, this->position.y, GetSlopePixelZOutsideMap(this->position.x, this->position.y)); - const Viewport *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport; + const Viewport *vp = GetMainWindow()->viewport; if (this->face == INVALID_COMPANY) { /* move x pos to opposite corner */ pt.x = UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left; diff --git a/src/game/game_text.cpp b/src/game/game_text.cpp index ce20c1ee70..ad509c04e7 100644 --- a/src/game/game_text.cpp +++ b/src/game/game_text.cpp @@ -225,6 +225,7 @@ public: GameStrings *LoadTranslations() { const GameInfo *info = Game::GetInfo(); + assert(info != nullptr); std::string basename(info->GetMainScript()); auto e = basename.rfind(PATHSEPCHAR); if (e == std::string::npos) return nullptr; diff --git a/src/genworld.cpp b/src/genworld.cpp index 45a4fd46c1..a876c31de6 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -104,6 +104,7 @@ static void _GenerateWorld() SetGeneratingWorldProgress(GWP_MAP_INIT, 2); SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); + ScriptObject::InitializeRandomizers(); BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); @@ -337,9 +338,7 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti ShowGenerateWorldProgress(); /* Centre the view on the map */ - if (FindWindowById(WC_MAIN_WINDOW, 0) != nullptr) { - ScrollMainWindowToTile(TileXY(MapSizeX() / 2, MapSizeY() / 2), true); - } + ScrollMainWindowToTile(TileXY(MapSizeX() / 2, MapSizeY() / 2), true); _GenerateWorld(); } diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index bf8a17d984..e07027a00e 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -552,8 +552,6 @@ std::unique_ptr FallbackParagraphLayout::NextLine next_run = this->buffer_begin + iter->first; begin = this->buffer; - - last_space = nullptr; } if (IsWhitespace(c)) last_space = this->buffer; @@ -591,7 +589,7 @@ std::unique_ptr FallbackParagraphLayout::NextLine this->buffer++; } - if (l->size() == 0 || last_char - begin != 0) { + if (l->size() == 0 || last_char - begin > 0) { int w = l->GetWidth(); l->emplace_back(iter->second, begin, last_char - begin, w); } diff --git a/src/intro_gui.cpp b/src/intro_gui.cpp index 7e86c1e53c..1d84732bf7 100644 --- a/src/intro_gui.cpp +++ b/src/intro_gui.cpp @@ -222,7 +222,7 @@ struct SelectGameWindow : public Window { } IntroGameViewportCommand &vc = intro_viewport_commands[this->cur_viewport_command_index]; - Window *mw = FindWindowByClass(WC_MAIN_WINDOW); + Window *mw = GetMainWindow(); Viewport *vp = mw->viewport; /* Early exit if the current command hasn't elapsed and isn't animated. */ diff --git a/src/lang/norwegian_bokmal.txt b/src/lang/norwegian_bokmal.txt index 57c870efef..434d1b0ac0 100644 --- a/src/lang/norwegian_bokmal.txt +++ b/src/lang/norwegian_bokmal.txt @@ -2076,6 +2076,7 @@ STR_INTRO_TOOLTIP_HIGHSCORE :{BLACK}Vise tav STR_INTRO_TOOLTIP_CONFIG_SETTINGS_TREE :{BLACK}Vis innstillinger STR_INTRO_TOOLTIP_NEWGRF_SETTINGS :{BLACK}Vis NewGRF-instillinger STR_INTRO_TOOLTIP_ONLINE_CONTENT :{BLACK}Se etter nytt og oppdatert innhold for nedlasting +STR_INTRO_TOOLTIP_AI_SETTINGS :{BLACK}Vis KI innstillinger STR_INTRO_TOOLTIP_QUIT :{BLACK}Avslutt 'OpenTTD' STR_INTRO_BASESET :{BLACK}Det valgte innebygde grafikksettet mangler {NUM} sprite{P "" r}. Se etter oppdateringer for settet. diff --git a/src/linkgraph/linkgraph_gui.cpp b/src/linkgraph/linkgraph_gui.cpp index a304e197b3..1420b52e14 100644 --- a/src/linkgraph/linkgraph_gui.cpp +++ b/src/linkgraph/linkgraph_gui.cpp @@ -721,7 +721,7 @@ LinkGraphLegendWindow::LinkGraphLegendWindow(WindowDesc *desc, int window_number { this->InitNested(window_number); this->InvalidateData(0); - this->SetOverlay(FindWindowById(WC_MAIN_WINDOW, 0)->viewport->overlay); + this->SetOverlay(GetMainWindow()->viewport->overlay); } /** diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 1b88cc39ed..17639e7082 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -182,7 +182,7 @@ void FixTitleGameZoom(int zoom_adjust) { if (_game_mode != GM_MENU) return; - Viewport *vp = FindWindowByClass(WC_MAIN_WINDOW)->viewport; + Viewport *vp = GetMainWindow()->viewport; /* Adjust the zoom in/out. * Can't simply add, since operator+ is not defined on the ZoomLevel type. */ diff --git a/src/misc_cmd.cpp b/src/misc_cmd.cpp index 775809f4f8..07c52542c0 100644 --- a/src/misc_cmd.cpp +++ b/src/misc_cmd.cpp @@ -67,8 +67,10 @@ CommandCost CmdIncreaseLoan(TileIndex tile, DoCommandFlag flags, uint32 p1, uint break; } - /* Overflow protection */ - if (c->money + c->current_loan + loan < c->money) return CMD_ERROR; + /* In case adding the loan triggers the overflow protection of Money, + * we would essentially be losing money as taking and repaying the loan + * immediately would not get us back to the same bank balance anymore. */ + if (c->money > Money::max() - loan) return CMD_ERROR; if (flags & DC_EXEC) { c->money += loan; diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 8b1c3a18e7..58666280bc 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -1340,7 +1340,7 @@ static WindowDesc _query_desc( */ void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) { - if (parent == nullptr) parent = FindWindowById(WC_MAIN_WINDOW, 0); + if (parent == nullptr) parent = GetMainWindow(); for (const Window *w : Window::IterateFromBack()) { if (w->window_class != WC_CONFIRM_POPUP_QUERY) continue; diff --git a/src/network/core/game_info.cpp b/src/network/core/game_info.cpp index 7fc63a698d..4717e090d4 100644 --- a/src/network/core/game_info.cpp +++ b/src/network/core/game_info.cpp @@ -358,14 +358,14 @@ void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfo } case 4: { - GRFConfig **dst = &info->grfconfig; - uint i; + /* Ensure that the maximum number of NewGRFs and the field in the network + * protocol are matched to eachother. If that is not the case anymore a + * check must be added to ensure the received data is still valid. */ + static_assert(std::numeric_limits::max() == NETWORK_MAX_GRF_COUNT); uint num_grfs = p->Recv_uint8(); - /* Broken/bad data. It cannot have that many NewGRFs. */ - if (num_grfs > NETWORK_MAX_GRF_COUNT) return; - - for (i = 0; i < num_grfs; i++) { + GRFConfig **dst = &info->grfconfig; + for (uint i = 0; i < num_grfs; i++) { NamedGRFIdentifier grf; switch (newgrf_serialisation) { case NST_GRFID_MD5: diff --git a/src/network/network.cpp b/src/network/network.cpp index 33a2ac962e..5ea1c1338d 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -649,6 +649,7 @@ void NetworkClose(bool close_admins) delete[] _network_company_states; _network_company_states = nullptr; _network_company_server_id.clear(); + _network_company_passworded = 0; InitializeNetworkPools(close_admins); diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 905573ee83..d4426ef584 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -2531,6 +2531,6 @@ void ShowNetworkAskRelay(const std::string &server_connection_string, const std: { DeleteWindowByClass(WC_NETWORK_ASK_RELAY); - Window *parent = FindWindowById(WC_MAIN_WINDOW, 0); + Window *parent = GetMainWindow(); new NetworkAskRelayWindow(&_network_ask_relay_desc, parent, server_connection_string, relay_connection_string, token); } diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 207c52bec9..f29213e6fe 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -1744,6 +1744,7 @@ static void NetworkAutoCleanCompanies() if (!_network_dedicated) { const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER); + assert(ci != nullptr); if (Company::IsValidID(ci->client_playas)) clients_in_company[ci->client_playas] = true; } @@ -2135,6 +2136,7 @@ void NetworkServerDoMove(ClientID client_id, CompanyID company_id) if (client_id == CLIENT_ID_SERVER && _network_dedicated) return; NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id); + assert(ci != nullptr); /* No need to waste network resources if the client is in the company already! */ if (ci->client_playas == company_id) return; diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 69d2363c93..8a3ae5a227 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -4306,7 +4306,6 @@ static ChangeInfoResult ObjectChangeInfo(uint id, int numinfo, int prop, const G /* Swap classid because we read it in BE. */ uint32 classid = buf->ReadDWord(); (*ospec)->cls_id = ObjectClass::Allocate(BSWAP32(classid)); - (*ospec)->enabled = true; break; } @@ -10857,7 +10856,7 @@ static void FinaliseObjectsArray() ObjectSpec **&objectspec = file->objectspec; if (objectspec != nullptr) { for (int i = 0; i < NUM_OBJECTS_PER_GRF; i++) { - if (objectspec[i] != nullptr && objectspec[i]->grf_prop.grffile != nullptr && objectspec[i]->enabled) { + if (objectspec[i] != nullptr && objectspec[i]->grf_prop.grffile != nullptr && objectspec[i]->IsEnabled()) { _object_mngr.SetEntitySpec(objectspec[i]); } } diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index 3d5606a757..b587ae2d33 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -658,6 +658,7 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object, { const Vehicle *w = v->Next(); + assert(w != nullptr); uint16 altitude = ClampToU16(v->z_pos - w->z_pos); // Aircraft height - shadow height byte airporttype = ATP_TTDP_LARGE; diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp index 86d7e1bdb4..9d854076ee 100644 --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -1468,6 +1468,9 @@ private: this->avails.push_back(c); } else { const GRFConfig *best = FindGRFConfig(c->ident.grfid, HasBit(c->flags, GCF_INVALID) ? FGCM_NEWEST : FGCM_NEWEST_VALID); + /* Never triggers; FindGRFConfig returns either c, or a newer version of c. */ + assert(best != nullptr); + /* * If the best version is 0, then all NewGRF with this GRF ID * have version 0, so for backward compatibility reasons we diff --git a/src/newgrf_object.cpp b/src/newgrf_object.cpp index 8a8de90792..6bdcea5dd7 100644 --- a/src/newgrf_object.cpp +++ b/src/newgrf_object.cpp @@ -60,7 +60,7 @@ ObjectSpec _object_specs[NUM_OBJECTS]; */ bool ObjectSpec::IsEverAvailable() const { - return this->enabled && HasBit(this->climate, _settings_game.game_creation.landscape) && + return this->IsEnabled() && HasBit(this->climate, _settings_game.game_creation.landscape) && (this->flags & ((_game_mode != GM_EDITOR && !_generating_world) ? OBJECT_FLAG_ONLY_IN_SCENEDIT : OBJECT_FLAG_ONLY_IN_GAME)) == 0; } diff --git a/src/newgrf_object.h b/src/newgrf_object.h index b44e8e3825..e6579119a4 100644 --- a/src/newgrf_object.h +++ b/src/newgrf_object.h @@ -21,7 +21,7 @@ #include "newgrf_commons.h" /** Various object behaviours. */ -enum ObjectFlags { +enum ObjectFlags : uint16 { OBJECT_FLAG_NONE = 0, ///< Just nothing. OBJECT_FLAG_ONLY_IN_SCENEDIT = 1 << 0, ///< Object can only be constructed in the scenario editor. OBJECT_FLAG_CANNOT_REMOVE = 1 << 1, ///< Object can not be removed. @@ -62,7 +62,7 @@ static const uint8 OBJECT_SIZE_1X1 = 0x11; ///< The value of a NewGRF's size pro void ResetObjects(); /** Class IDs for objects. */ -enum ObjectClassID { +enum ObjectClassID : uint16 { OBJECT_CLASS_BEGIN = 0, ///< The lowest valid value OBJECT_CLASS_MAX = 0xFFFF, ///< Maximum number of classes. INVALID_OBJECT_CLASS = 0xFFFF, ///< Class for the less fortunate. @@ -91,6 +91,7 @@ enum ObjectViewportMapType { struct ObjectSpec { /* 2 because of the "normal" and "buy" sprite stacks. */ GRFFilePropsBase<2> grf_prop; ///< Properties related the the grf file + AnimationInfo animation; ///< Information about the animation. ObjectClassID cls_id; ///< The class to which this spec belongs. StringID name; ///< The name for this object. @@ -103,15 +104,19 @@ struct ObjectSpec { ObjectFlags flags; ///< Flags/settings related to the object. ObjectCtrlFlags ctrl_flags; ///< Extra control flags. uint8 edge_foundation[4]; ///< Edge foundation flags - AnimationInfo animation; ///< Information about the animation. uint16 callback_mask; ///< Bitmask of requested/allowed callbacks. uint8 height; ///< The height of this structure, in heightlevels; max MAX_TILE_HEIGHT. uint8 views; ///< The number of views. uint8 generate_amount; ///< Number of objects which are attempted to be generated per 256^2 map during world generation. - bool enabled; ///< Is this spec enabled? ObjectViewportMapType vport_map_type; ///< Viewport map type uint16 vport_map_subtype; ///< Viewport map subtype + /** + * Test if this object is enabled. + * @return True iif this object is enabled. + */ + bool IsEnabled() const { return this->views > 0; } + /** * Get the cost for building a structure of this type. * @return The cost for building. diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 288e086afb..42e7184840 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -578,7 +578,7 @@ static void DrawTile_Object(TileInfo *ti, DrawTileProcParams params) int building_z_offset = 0; /* Fall back for when the object doesn't exist anymore. */ - if (!spec->enabled) { + if (!spec->IsEnabled()) { type = OBJECT_TRANSMITTER; } else if ((spec->flags & OBJECT_FLAG_HAS_NO_FOUNDATION) == 0) { if (spec->ctrl_flags & OBJECT_CTRL_FLAG_EDGE_FOUNDATION) { @@ -1222,7 +1222,7 @@ static CommandCost TerraformTile_Object(TileIndex tile, DoCommandFlag flags, int pre_success_checks(); return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); } - } else if (spec->enabled) { + } else if (spec->IsEnabled()) { /* allow autoslope */ pre_success_checks(); return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index b8d8676059..016f937e9b 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1778,6 +1778,7 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 if (sel_ord >= v->GetNumOrders()) return CMD_ERROR; Order *order = v->GetOrder(sel_ord); + assert(order != nullptr); switch (order->GetType()) { case OT_GOTO_STATION: if (mof != MOF_NON_STOP && mof != MOF_STOP_LOCATION && mof != MOF_UNLOAD && mof != MOF_LOAD && mof != MOF_CARGO_TYPE_UNLOAD && mof != MOF_CARGO_TYPE_LOAD && mof != MOF_RV_TRAVEL_DIR) return CMD_ERROR; diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index bfe0929c6f..fa8624f545 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -233,46 +233,21 @@ err1: } #endif /* WITH_FREETYPE */ -class FontList { -protected: - wchar_t **fonts; - uint items; - uint capacity; - -public: - FontList() : fonts(nullptr), items(0), capacity(0) { }; - - ~FontList() { - if (this->fonts == nullptr) return; - - for (uint i = 0; i < this->items; i++) { - free(this->fonts[i]); - } - - free(this->fonts); - } - - bool Add(const wchar_t *font) { - for (uint i = 0; i < this->items; i++) { - if (wcscmp(this->fonts[i], font) == 0) return false; - } - - if (this->items == this->capacity) { - this->capacity += 10; - this->fonts = ReallocT(this->fonts, this->capacity); - } - - this->fonts[this->items++] = wcsdup(font); - - return true; - } -}; - struct EFCParam { FontCacheSettings *settings; LOCALESIGNATURE locale; MissingGlyphSearcher *callback; - FontList fonts; + std::vector fonts; + + bool Add(const std::wstring_view &font) { + for (const auto &entry : this->fonts) { + if (font.compare(entry) == 0) return false; + } + + this->fonts.emplace_back(font); + + return true; + } }; static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam) @@ -280,7 +255,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT EFCParam *info = (EFCParam *)lParam; /* Skip duplicates */ - if (!info->fonts.Add((const wchar_t *)logfont->elfFullName)) return 1; + if (!info->Add(logfont->elfFullName)) return 1; /* Only use TrueType fonts */ if (!(type & TRUETYPE_FONTTYPE)) return 1; /* Don't use SYMBOL fonts */ @@ -463,22 +438,9 @@ void Win32FontCache::ClearFontCache() GLYPHMETRICS gm; MAT2 mat = { {0, 1}, {0, 0}, {0, 0}, {0, 1} }; - /* Make a guess for the needed memory size. */ - DWORD size = this->glyph_size.cy * Align(aa ? this->glyph_size.cx : std::max(this->glyph_size.cx / 8l, 1l), 4); // Bitmap data is DWORD-aligned rows. - byte *bmp = AllocaM(byte, size); - size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); - - if (size == GDI_ERROR) { - /* No dice with the guess. First query size of needed glyph memory, then allocate the - * memory and query again. This dance is necessary as some glyphs will only render with - * the exact matching size; e.g. the space glyph has no pixels and must be requested - * with size == 0, anything else fails. Unfortunately, a failed call doesn't return any - * info about the size and thus the triple GetGlyphOutline()-call. */ - size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat); - if (size == GDI_ERROR) usererror("Unable to render font glyph"); - bmp = AllocaM(byte, size); - GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); - } + /* Call GetGlyphOutline with zero size initially to get required memory size. */ + DWORD size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat); + if (size == GDI_ERROR) usererror("Unable to render font glyph"); /* Add 1 scaled pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */ uint shadow = (this->fs == FS_NORMAL) ? ScaleGUITrad(1) : 0; @@ -488,6 +450,10 @@ void Win32FontCache::ClearFontCache() /* Limit glyph size to prevent overflows later on. */ if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large"); + /* Call GetGlyphOutline again with size to actually render the glyph. */ + byte *bmp = AllocaM(byte, size); + GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat); + /* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */ SpriteLoader::Sprite sprite; sprite.AllocateData(ZOOM_LVL_NORMAL, width * height); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 8f31391b66..a78e3c74d2 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -266,6 +266,8 @@ static void InitializeWindowsAndCaches() UpdateAllVirtCoords(); ResetViewportAfterLoadGame(); + ScriptObject::InitializeRandomizers(); + for (Company *c : Company::Iterate()) { /* For each company, verify (while loading a scenario) that the inauguration date is the current year and set it * accordingly if it is not the case. No need to set it on companies that are not been used already, diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index 6ebe238edf..345ca29677 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -36,18 +36,16 @@ ZoomLevel _saved_scrollpos_zoom; void SaveViewportBeforeSaveGame() { - const Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + const Window *w = GetMainWindow(); - if (w != nullptr) { - _saved_scrollpos_x = w->viewport->scrollpos_x; - _saved_scrollpos_y = w->viewport->scrollpos_y; - _saved_scrollpos_zoom = w->viewport->zoom; - } + _saved_scrollpos_x = w->viewport->scrollpos_x; + _saved_scrollpos_y = w->viewport->scrollpos_y; + _saved_scrollpos_zoom = w->viewport->zoom; } void ResetViewportAfterLoadGame() { - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); w->viewport->scrollpos_x = _saved_scrollpos_x; w->viewport->scrollpos_y = _saved_scrollpos_y; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 232543a8ac..5e543473ae 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -497,14 +497,16 @@ void AfterLoadVehicles(bool part_of_load) v->GetImage(v->direction, EIT_ON_MAP, &v->sprite_seq); v->UpdateSpriteSeqBound(); - /* The plane's shadow will have the same image as the plane, but no colour */ + /* The aircraft's shadow will have the same image as the aircraft, but no colour */ Vehicle *shadow = v->Next(); + if (shadow == nullptr) SlErrorCorrupt("Missing shadow for aircraft"); shadow->sprite_seq.CopyWithoutPalette(v->sprite_seq); shadow->sprite_seq_bounds = v->sprite_seq_bounds; /* In the case of a helicopter we will update the rotor sprites */ if (v->subtype == AIR_HELICOPTER) { Vehicle *rotor = shadow->Next(); + if (rotor == nullptr) SlErrorCorrupt("Missing rotor for helicopter"); GetRotorImage(Aircraft::From(v), EIT_ON_MAP, &rotor->sprite_seq); rotor->UpdateSpriteSeqBound(); } diff --git a/src/screenshot.cpp b/src/screenshot.cpp index 1f519877e8..c3c29cdd53 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -759,7 +759,7 @@ void SetupScreenshotViewport(ScreenshotType t, Viewport *vp, uint32 width, uint3 case SC_CRASHLOG: { assert(width == 0 && height == 0); - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); vp->virtual_left = w->viewport->virtual_left; vp->virtual_top = w->viewport->virtual_top; vp->virtual_width = w->viewport->virtual_width; @@ -810,7 +810,7 @@ void SetupScreenshotViewport(ScreenshotType t, Viewport *vp, uint32 width, uint3 default: { vp->zoom = (t == SC_ZOOMEDIN) ? _settings_client.gui.zoom_min : ZOOM_LVL_VIEWPORT; - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); vp->virtual_left = w->viewport->virtual_left; vp->virtual_top = w->viewport->virtual_top; diff --git a/src/script/api/script_base.cpp b/src/script/api/script_base.cpp index 996d4d488f..52946520a2 100644 --- a/src/script/api/script_base.cpp +++ b/src/script/api/script_base.cpp @@ -10,17 +10,12 @@ #include "../../stdafx.h" #include "script_base.hpp" #include "script_error.hpp" -#include "../../network/network.h" -#include "../../core/random_func.hpp" #include "../../safeguards.h" /* static */ uint32 ScriptBase::Rand() { - /* We pick RandomRange if we are in SP (so when saved, we do the same over and over) - * but we pick InteractiveRandomRange if we are a network_server or network-client. */ - if (_networking) return ::InteractiveRandom(); - return ::Random(); + return ScriptObject::GetRandomizer().Next(); } /* static */ uint32 ScriptBase::RandItem(int unused_param) @@ -30,10 +25,7 @@ /* static */ uint ScriptBase::RandRange(uint max) { - /* We pick RandomRange if we are in SP (so when saved, we do the same over and over) - * but we pick InteractiveRandomRange if we are a network_server or network-client. */ - if (_networking) return ::InteractiveRandomRange(max); - return ::RandomRange(max); + return ScriptObject::GetRandomizer().Next(max); } /* static */ uint32 ScriptBase::RandRangeItem(int unused_param, uint max) diff --git a/src/script/api/script_base.hpp b/src/script/api/script_base.hpp index b8eebdf1b0..4184e79d69 100644 --- a/src/script/api/script_base.hpp +++ b/src/script/api/script_base.hpp @@ -18,9 +18,6 @@ * * @note The random functions are not called Random and RandomRange, because * RANDOM_DEBUG does some tricky stuff, which messes with those names. - * @note In MP we cannot use Random because that will cause desyncs (scripts are - * ran on the server only, not on all clients). This means that - * we use InteractiveRandom in MP. Rand() takes care of this for you. */ class ScriptBase : public ScriptObject { public: diff --git a/src/script/api/script_company.cpp b/src/script/api/script_company.cpp index 79d076321e..36c8e7ca6d 100644 --- a/src/script/api/script_company.cpp +++ b/src/script/api/script_company.cpp @@ -93,9 +93,10 @@ EnforcePrecondition(false, gender == GENDER_MALE || gender == GENDER_FEMALE); EnforcePrecondition(false, GetPresidentGender(ScriptCompany::COMPANY_SELF) != gender); + Randomizer &randomizer = ScriptObject::GetRandomizer(); CompanyManagerFace cmf; - GenderEthnicity ge = (GenderEthnicity)((gender == GENDER_FEMALE ? (1 << ::GENDER_FEMALE) : 0) | (::InteractiveRandom() & (1 << ETHNICITY_BLACK))); - RandomCompanyManagerFaceBits(cmf, ge, false); + GenderEthnicity ge = (GenderEthnicity)((gender == GENDER_FEMALE ? (1 << ::GENDER_FEMALE) : 0) | (randomizer.Next() & (1 << ETHNICITY_BLACK))); + RandomCompanyManagerFaceBits(cmf, ge, false, randomizer); return ScriptObject::DoCommand(0, 0, cmf, CMD_SET_COMPANY_MANAGER_FACE); } diff --git a/src/script/api/script_gamesettings.cpp b/src/script/api/script_gamesettings.cpp index 458545226f..0961cd681a 100644 --- a/src/script/api/script_gamesettings.cpp +++ b/src/script/api/script_gamesettings.cpp @@ -26,6 +26,7 @@ if (!IsValid(setting)) return -1; const SettingDesc *sd = GetSettingFromName(setting); + assert(sd != nullptr); return sd->AsIntSetting()->Read(&_settings_game); } @@ -34,6 +35,7 @@ if (!IsValid(setting)) return false; const SettingDesc *sd = GetSettingFromName(setting); + assert(sd != nullptr); if ((sd->flags & SF_NO_NETWORK_SYNC) != 0) return false; diff --git a/src/script/api/script_industrytype.cpp b/src/script/api/script_industrytype.cpp index ea0a82f591..1d5fb955a2 100644 --- a/src/script/api/script_industrytype.cpp +++ b/src/script/api/script_industrytype.cpp @@ -9,6 +9,7 @@ #include "../../stdafx.h" #include "script_industrytype.hpp" +#include "script_base.hpp" #include "script_map.hpp" #include "script_error.hpp" #include "../../strings_func.h" @@ -120,8 +121,8 @@ EnforcePrecondition(false, CanBuildIndustry(industry_type)); EnforcePrecondition(false, ScriptMap::IsValidTile(tile)); - uint32 seed = ::InteractiveRandom(); - uint32 layout_index = ::InteractiveRandomRange((uint32)::GetIndustrySpec(industry_type)->layouts.size()); + uint32 seed = ScriptBase::Rand(); + uint32 layout_index = ScriptBase::RandRange((uint32)::GetIndustrySpec(industry_type)->layouts.size()); return ScriptObject::DoCommand(tile, (1 << 16) | (layout_index << 8) | industry_type, seed, CMD_BUILD_INDUSTRY); } @@ -129,7 +130,7 @@ { EnforcePrecondition(false, CanProspectIndustry(industry_type)); - uint32 seed = ::InteractiveRandom(); + uint32 seed = ScriptBase::Rand(); return ScriptObject::DoCommand(0, industry_type, seed, CMD_BUILD_INDUSTRY); } diff --git a/src/script/api/script_object.cpp b/src/script/api/script_object.cpp index 53125dcd86..f1d753e44c 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -395,3 +395,19 @@ ScriptObject::ActiveInstance::~ActiveInstance() NOT_REACHED(); } + + +/* static */ Randomizer ScriptObject::random_states[OWNER_END]; + +Randomizer &ScriptObject::GetRandomizer(Owner owner) +{ + return ScriptObject::random_states[owner]; +} + +void ScriptObject::InitializeRandomizers() +{ + Randomizer random = _random; + for (Owner owner = OWNER_BEGIN; owner < OWNER_END; owner++) { + ScriptObject::GetRandomizer(owner).SetSeed(random.Next()); + } +} diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index f0e921dcee..9630df42af 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -13,6 +13,7 @@ #include "../../misc/countedptr.hpp" #include "../../road_type.h" #include "../../rail_type.h" +#include "../../core/random_func.hpp" #include "script_types.hpp" #include "../script_suspend.hpp" @@ -67,6 +68,18 @@ public: */ static class ScriptInstance *GetActiveInstance(); + /** + * Get a reference of the randomizer that brings this script random values. + * @param owner The owner/script to get the randomizer for. This defaults to ScriptObject::GetRootCompany() + */ + static Randomizer &GetRandomizer(Owner owner = ScriptObject::GetRootCompany()); + + /** + * Initialize/reset the script random states. The state of the scripts are + * based on the current _random seed, but _random does not get changed. + */ + static void InitializeRandomizers(); + protected: /** * Executes a raw DoCommand for the script. @@ -315,6 +328,8 @@ private: * @param story_page_id The new StoryPageID. */ static void SetNewStoryPageElementID(StoryPageElementID story_page_element_id); + + static Randomizer random_states[OWNER_END]; ///< Random states for each of the scripts (game script uses OWNER_DEITY) }; #endif /* SCRIPT_OBJECT_HPP */ diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 832fa5a9f8..0533642b1b 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -67,6 +67,7 @@ static const Order *ResolveOrder(VehicleID vehicle_id, ScriptOrder::OrderPositio if (order_position == ScriptOrder::ORDER_INVALID) return nullptr; } const Order *order = v->GetFirstOrder(); + assert(order != nullptr); while (order->GetType() == OT_IMPLICIT) order = order->next; while (order_position > 0) { order_position = (ScriptOrder::OrderPosition)(order_position - 1); @@ -91,6 +92,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr int res = (int)order_position; const Order *order = v->orders->GetFirstOrder(); + assert(order != nullptr); for (; order->GetType() == OT_IMPLICIT; order = order->next) res++; while (order_position > 0) { order_position = (ScriptOrder::OrderPosition)(order_position - 1); @@ -136,6 +138,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr if (!IsValidVehicleOrder(vehicle_id, order_position)) return false; const Order *order = ::Vehicle::Get(vehicle_id)->GetOrder(ScriptOrderPositionToRealOrderPosition(vehicle_id, order_position)); + assert(order != nullptr); return order->GetType() == OT_CONDITIONAL; } @@ -145,6 +148,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr if (!IsValidVehicleOrder(vehicle_id, order_position)) return false; const Order *order = ::ResolveOrder(vehicle_id, order_position); + assert(order != nullptr); return order->GetType() == OT_DUMMY; } @@ -176,6 +180,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr if (order_position == ORDER_CURRENT) { int cur_order_pos = ::Vehicle::Get(vehicle_id)->cur_real_order_index; const Order *order = ::Vehicle::Get(vehicle_id)->GetFirstOrder(); + assert(order != nullptr); int num_implicit_orders = 0; for (int i = 0; i < cur_order_pos; i++) { if (order->GetType() == OT_IMPLICIT) num_implicit_orders++; diff --git a/src/script/api/script_town.cpp b/src/script/api/script_town.cpp index 3eeb709823..005b524568 100644 --- a/src/script/api/script_town.cpp +++ b/src/script/api/script_town.cpp @@ -303,7 +303,7 @@ EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_TOWN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); } uint32 townnameparts; - if (!GenerateTownName(&townnameparts)) { + if (!GenerateTownName(ScriptObject::GetRandomizer(), &townnameparts)) { ScriptController::DecreaseOps(50000); ScriptObject::SetLastError(ScriptError::ERR_NAME_IS_NOT_UNIQUE); return false; diff --git a/src/script/api/script_window.cpp b/src/script/api/script_window.cpp index f54b4fd601..2950b99a03 100644 --- a/src/script/api/script_window.cpp +++ b/src/script/api/script_window.cpp @@ -46,6 +46,7 @@ if (colour != TC_INVALID && (::TextColour)colour >= ::TC_END) return; Window *w = FindWindowById((::WindowClass)window, number); + assert(w != nullptr); if (widget == WIDGET_ALL) { if (colour != TC_INVALID) return; diff --git a/src/script/script_config.cpp b/src/script/script_config.cpp index 07be966052..738a898a79 100644 --- a/src/script/script_config.cpp +++ b/src/script/script_config.cpp @@ -11,6 +11,7 @@ #include "../settings_type.h" #include "../core/random_func.hpp" #include "script_info.hpp" +#include "api/script_object.hpp" #include "../textfile_gui.h" #include "../string_func.h" @@ -35,7 +36,7 @@ void ScriptConfig::Change(const char *name, int version, bool force_exact_match, * for the Script that have the random flag to a random value. */ for (const auto &item : *this->info->GetConfigList()) { if (item.flags & SCRIPTCONFIG_RANDOM) { - this->SetSetting(item.name, InteractiveRandomRange(item.max_value + 1 - item.min_value) + item.min_value); + this->SetSetting(item.name, ScriptObject::GetRandomizer(OWNER_NONE).Next(item.max_value + 1 - item.min_value) + item.min_value); } } @@ -157,7 +158,7 @@ void ScriptConfig::AddRandomDeviation() { for (const auto &item : *this->GetConfigList()) { if (item.random_deviation != 0) { - this->SetSetting(item.name, InteractiveRandomRange(item.random_deviation * 2 + 1) - item.random_deviation + this->GetSetting(item.name)); + this->SetSetting(item.name, ScriptObject::GetRandomizer(OWNER_NONE).Next(item.random_deviation * 2 + 1) - item.random_deviation + this->GetSetting(item.name)); } } } diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index fc268c53af..4eceba868a 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -969,7 +969,7 @@ void SmallMapWindow::DrawTowns(const DrawPixelInfo *dpi) const void SmallMapWindow::DrawMapIndicators() const { /* Find main viewport. */ - const Viewport *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport; + const Viewport *vp = GetMainWindow()->viewport; Point upper_left_smallmap_coord = InverseRemapCoords2(vp->virtual_left, vp->virtual_top); Point lower_right_smallmap_coord = InverseRemapCoords2(vp->virtual_left + vp->virtual_width - 1, vp->virtual_top + vp->virtual_height - 1); @@ -1461,7 +1461,7 @@ int SmallMapWindow::GetPositionOnLegend(Point pt) if (click_count > 0) this->mouse_capture_widget = widget; const NWidgetBase *wid = this->GetWidget(WID_SM_MAP); - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); int sub; pt = this->PixelToTile(pt.x - wid->pos_x, pt.y - wid->pos_y, &sub); ScrollWindowTo(this->scroll_x + pt.x * TILE_SIZE, this->scroll_y + pt.y * TILE_SIZE, -1, w); @@ -1732,7 +1732,7 @@ void SmallMapWindow::SetNewScroll(int sx, int sy, int sub) */ void SmallMapWindow::SmallMapCenterOnCurrentPos() { - const Viewport *vp = FindWindowById(WC_MAIN_WINDOW, 0)->viewport; + const Viewport *vp = GetMainWindow()->viewport; Point viewport_center = InverseRemapCoords2(vp->virtual_left + vp->virtual_width / 2, vp->virtual_top + vp->virtual_height / 2); int sub; @@ -2024,7 +2024,7 @@ void ShowSmallMap() */ bool ScrollMainWindowTo(int x, int y, int z, bool instant) { - bool res = ScrollWindowTo(x, y, z, FindWindowById(WC_MAIN_WINDOW, 0), instant); + bool res = ScrollWindowTo(x, y, z, GetMainWindow(), instant); /* If a user scrolls to a tile (via what way what so ever) and already is on * that tile (e.g.: pressed twice), move the smallmap to that location, diff --git a/src/table/object_land.h b/src/table/object_land.h index 1c90a459e8..08639fc403 100644 --- a/src/table/object_land.h +++ b/src/table/object_land.h @@ -121,7 +121,7 @@ static const DrawTileSprites _object_hq[] = { #undef TILE_SPRITE_LINE -#define M(name, size, build_cost_multiplier, clear_cost_multiplier, height, climate, gen_amount, flags) { GRFFilePropsBase<2>(), INVALID_OBJECT_CLASS, name, climate, size, build_cost_multiplier, clear_cost_multiplier, 0, MAX_DAY + 1, flags, OBJECT_CTRL_FLAG_NONE, {0, 0, 0, 0}, {0, 0, 0, 0}, 0, height, 1, gen_amount, true, OVMT_DEFAULT, 0 } +#define M(name, size, build_cost_multiplier, clear_cost_multiplier, height, climate, gen_amount, flags) { GRFFilePropsBase<2>(), {0, 0, 0, 0}, INVALID_OBJECT_CLASS, name, climate, size, build_cost_multiplier, clear_cost_multiplier, 0, MAX_DAY + 1, flags, OBJECT_CTRL_FLAG_NONE, {0, 0, 0, 0}, 0, height, 1, gen_amount, OVMT_DEFAULT, 0 } /* Climates * T = Temperate diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp index b5de96f4a7..29f0279bcc 100644 --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -36,6 +36,7 @@ static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint32 val, ModifyTimetableFlags mtf, bool timetabled, bool ignore_lock = false) { Order *order = v->GetOrder(order_number); + assert(order != nullptr); int total_delta = 0; int timetable_delta = 0; diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index b44b76866a..a6dd59296c 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -904,7 +904,7 @@ static CallBackFunction MenuClickShowAir(int index) static CallBackFunction ToolbarZoomInClick(Window *w) { - if (DoZoomInOutWindow(ZOOM_IN, FindWindowById(WC_MAIN_WINDOW, 0))) { + if (DoZoomInOutWindow(ZOOM_IN, GetMainWindow())) { w->HandleButtonClick((_game_mode == GM_EDITOR) ? (byte)WID_TE_ZOOM_IN : (byte)WID_TN_ZOOM_IN); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); } @@ -915,7 +915,7 @@ static CallBackFunction ToolbarZoomInClick(Window *w) static CallBackFunction ToolbarZoomOutClick(Window *w) { - if (DoZoomInOutWindow(ZOOM_OUT, FindWindowById(WC_MAIN_WINDOW, 0))) { + if (DoZoomInOutWindow(ZOOM_OUT, GetMainWindow())) { w->HandleButtonClick((_game_mode == GM_EDITOR) ? (byte)WID_TE_ZOOM_OUT : (byte)WID_TN_ZOOM_OUT); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); } @@ -2196,7 +2196,7 @@ struct MainToolbarWindow : Window { void OnInvalidateData(int data = 0, bool gui_scope = true) override { if (!gui_scope) return; - if (FindWindowById(WC_MAIN_WINDOW, 0) != nullptr) HandleZoomMessage(this, FindWindowById(WC_MAIN_WINDOW, 0)->viewport, WID_TN_ZOOM_IN, WID_TN_ZOOM_OUT); + HandleZoomMessage(this, GetMainWindow()->viewport, WID_TN_ZOOM_IN, WID_TN_ZOOM_OUT); } static HotkeyList hotkeys; @@ -2577,7 +2577,7 @@ struct ScenarioEditorToolbarWindow : Window { void OnInvalidateData(int data = 0, bool gui_scope = true) override { if (!gui_scope) return; - if (FindWindowById(WC_MAIN_WINDOW, 0) != nullptr) HandleZoomMessage(this, FindWindowById(WC_MAIN_WINDOW, 0)->viewport, WID_TE_ZOOM_IN, WID_TE_ZOOM_OUT); + HandleZoomMessage(this, GetMainWindow()->viewport, WID_TE_ZOOM_IN, WID_TE_ZOOM_OUT); } void OnQueryTextFinished(char *str) override diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 12a1d59d43..6dd356b734 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -2546,7 +2546,7 @@ bool GenerateTowns(TownLayout layout) bool city = (_settings_game.economy.larger_towns != 0 && Chance16(1, _settings_game.economy.larger_towns)); IncreaseGeneratingWorldProgress(GWP_TOWN); /* Get a unique name for the town. */ - if (!GenerateTownName(&townnameparts, &town_names)) continue; + if (!GenerateTownName(_random, &townnameparts, &town_names)) continue; /* try 20 times to create a random-sized town for the first loop. */ if (CreateRandomTown(20, townnameparts, TSZ_RANDOM, city, layout) != nullptr) current_number++; // If creation was successful, raise a flag. } while (--total); @@ -2560,7 +2560,7 @@ bool GenerateTowns(TownLayout layout) /* If current_number is still zero at this point, it means that not a single town has been created. * So give it a last try, but now more aggressive */ - if (GenerateTownName(&townnameparts) && + if (GenerateTownName(_random, &townnameparts) && CreateRandomTown(10000, townnameparts, TSZ_RANDOM, _settings_game.economy.larger_towns != 0, layout) != nullptr) { return true; } diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 7921f37fc5..690fde2de2 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -1323,7 +1323,7 @@ public: void RandomTownName() { - this->townnamevalid = GenerateTownName(&this->townnameparts); + this->townnamevalid = GenerateTownName(_interactive_random, &this->townnameparts); if (!this->townnamevalid) { this->townname_editbox.text.DeleteAll(); diff --git a/src/townname.cpp b/src/townname.cpp index de0435da8b..7789bf4c22 100644 --- a/src/townname.cpp +++ b/src/townname.cpp @@ -112,11 +112,12 @@ bool VerifyTownName(uint32 r, const TownNameParams *par, TownNames *town_names) /** * Generates valid town name. + * @param randomizer the source of random data for generating the name * @param townnameparts if a name is generated, it's stored there * @param town_names if a name is generated, check its uniqueness with the set * @return true iff a name was generated */ -bool GenerateTownName(uint32 *townnameparts, TownNames *town_names) +bool GenerateTownName(Randomizer &randomizer, uint32 *townnameparts, TownNames *town_names) { TownNameParams par(_settings_game.game_creation.town_name); @@ -130,7 +131,7 @@ bool GenerateTownName(uint32 *townnameparts, TownNames *town_names) * the other towns may take considerable amount of time (10000 is * too much). */ for (int i = 1000; i != 0; i--) { - uint32 r = _generating_world ? Random() : InteractiveRandom(); + uint32 r = randomizer.Next(); if (!VerifyTownName(r, &par, town_names)) continue; *townnameparts = r; diff --git a/src/townname_func.h b/src/townname_func.h index 6438d2b283..a3d29e467d 100644 --- a/src/townname_func.h +++ b/src/townname_func.h @@ -10,12 +10,13 @@ #ifndef TOWNNAME_FUNC_H #define TOWNNAME_FUNC_H +#include "core/random_func.hpp" #include "townname_type.h" char *GenerateTownNameString(char *buf, const char *last, size_t lang, uint32 seed); char *GetTownName(char *buff, const TownNameParams *par, uint32 townnameparts, const char *last); char *GetTownName(char *buff, const Town *t, const char *last); bool VerifyTownName(uint32 r, const TownNameParams *par, TownNames *town_names = nullptr); -bool GenerateTownName(uint32 *townnameparts, TownNames *town_names = nullptr); +bool GenerateTownName(Randomizer &randomizer, uint32 *townnameparts, TownNames *town_names = nullptr); #endif /* TOWNNAME_FUNC_H */ diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp index 2247397ec9..93d93de572 100644 --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -457,6 +457,7 @@ void RemoveAllTrees() */ uint PlaceTreeGroupAroundTile(TileIndex tile, TreeType treetype, uint radius, uint count, bool set_zone) { + dbg_assert(_game_mode == GM_EDITOR); // Due to InteractiveRandom being used in this function dbg_assert(treetype < TREE_TOYLAND + TREE_COUNT_TOYLAND); const bool allow_desert = treetype == TREE_CACTUS; uint planted = 0; diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 7279e969c1..f1d060c868 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -479,6 +479,7 @@ static CommandCost RefitVehicle(Vehicle *v, bool only_this, uint8 num_vehicles, u->cargo_subtype = result.subtype; if (u->type == VEH_AIRCRAFT) { Vehicle *w = u->Next(); + assert(w != nullptr); w->refit_cap = std::min(w->refit_cap, result.mail_capacity); w->cargo_cap = result.mail_capacity; if (w->cargo.TotalCount() > w->refit_cap) w->cargo.Truncate(w->cargo.TotalCount() - w->refit_cap); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 17aa5fef2d..8c2f6a346c 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -3878,7 +3878,7 @@ public: if (_ctrl_pressed) { ShowExtraViewportWindow(TileVirtXY(v->x_pos, v->y_pos)); } else { - const Window *mainwindow = FindWindowById(WC_MAIN_WINDOW, 0); + const Window *mainwindow = GetMainWindow(); if (click_count > 1 && mainwindow->viewport->zoom < ZOOM_LVL_DRAW_MAP) { /* main window 'follows' vehicle */ mainwindow->viewport->follow_vehicle = v->index; @@ -3949,8 +3949,8 @@ public: { /* If the hotkey is not for any widget in the UI (i.e. for honking) */ if (hotkey == WID_VV_HONK_HORN) { - const Window* mainwindow = FindWindowById(WC_MAIN_WINDOW, 0); - const Vehicle* v = Vehicle::Get(window_number); + const Window *mainwindow = GetMainWindow(); + const Vehicle *v = Vehicle::Get(window_number); /* Only play the sound if we're following this vehicle */ if (mainwindow->viewport->follow_vehicle == v->index) { v->PlayLeaveStationSound(true); @@ -4185,8 +4185,8 @@ bool VehicleClicked(const GUIVehicleGroup &vehgroup) void StopGlobalFollowVehicle(const Vehicle *v) { - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); - if (w != nullptr && w->viewport->follow_vehicle == v->index) { + Window *w = GetMainWindow(); + if (w->viewport->follow_vehicle == v->index) { ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos, true); // lock the main view on the vehicle's last position w->viewport->follow_vehicle = INVALID_VEHICLE; } diff --git a/src/viewport_gui.cpp b/src/viewport_gui.cpp index fd3d864c67..a4c7e04bcd 100644 --- a/src/viewport_gui.cpp +++ b/src/viewport_gui.cpp @@ -68,7 +68,7 @@ public: Point pt; if (tile == INVALID_TILE) { /* No tile? Use center of main viewport. */ - const Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + const Window *w = GetMainWindow(); /* center on same place as main window (zoom is maximum, no adjustment needed) */ pt.x = w->viewport->scrollpos_x + w->viewport->virtual_width / 2; @@ -101,7 +101,7 @@ public: case WID_EV_ZOOM_OUT: DoZoomInOutWindow(ZOOM_OUT, this); break; case WID_EV_MAIN_TO_VIEW: { // location button (move main view to same spot as this view) 'Paste Location' - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); int x = this->viewport->scrollpos_x; // Where is the main looking at int y = this->viewport->scrollpos_y; @@ -113,7 +113,7 @@ public: } case WID_EV_VIEW_TO_MAIN: { // inverse location button (move this view to same spot as main view) 'Copy Location' - const Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + const Window *w = GetMainWindow(); int x = w->viewport->scrollpos_x; int y = w->viewport->scrollpos_y; diff --git a/src/water.h b/src/water.h index 6d044cdaec..be17882187 100644 --- a/src/water.h +++ b/src/water.h @@ -40,6 +40,7 @@ void MakeWaterKeepingClass(TileIndex tile, Owner o); void CheckForDockingTile(TileIndex t); bool RiverModifyDesertZone(TileIndex tile, void *data); +void MakeRiverAndModifyDesertZoneAround(TileIndex tile); static const uint RIVER_OFFSET_DESERT_DISTANCE = 5; ///< Circular tile search radius to create non-desert around a river tile. bool IsWateredTile(TileIndex tile, Direction from); diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp index e30bc4fce5..61f0f303fe 100644 --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -446,6 +446,18 @@ bool RiverModifyDesertZone(TileIndex tile, void *) return false; } +/** + * Make a river tile and remove desert directly around it. + * @param tile The tile to change into river and create non-desert around + */ +void MakeRiverAndModifyDesertZoneAround(TileIndex tile) { + MakeRiver(tile, Random()); + MarkTileDirtyByTile(tile); + + /* Remove desert directly around the river tile. */ + CircularTileSearch(&tile, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr); +} + /** * Build a piece of canal. * @param tile end tile of stretch-dragging diff --git a/src/window.cpp b/src/window.cpp index fe7377b20a..a5b9408927 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1191,6 +1191,18 @@ Window *FindWindowByClass(WindowClass cls) return nullptr; } +/** + * Get the main window, i.e. FindWindowById(WC_MAIN_WINDOW, 0). + * If the main window is not available, this function will trigger an assert. + * @return Pointer to the main window. + */ +Window *GetMainWindow() +{ + Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + assert(w != nullptr); + return w; +} + /** * Delete a window by its class and window number (if it is open). * @param cls Window class @@ -2550,7 +2562,7 @@ static EventState HandleViewportScroll() return ES_NOT_HANDLED; } - if (_last_scroll_window == FindWindowById(WC_MAIN_WINDOW, 0) && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) { + if (_last_scroll_window == GetMainWindow() && _last_scroll_window->viewport->follow_vehicle != INVALID_VEHICLE) { /* If the main window is following a vehicle, then first let go of it! */ const Vehicle *veh = Vehicle::Get(_last_scroll_window->viewport->follow_vehicle); ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle @@ -2922,7 +2934,7 @@ const std::chrono::milliseconds TIME_BETWEEN_DOUBLE_CLICK(500); ///< Time betwee static void ScrollMainViewport(int x, int y) { if (_game_mode != GM_MENU && _game_mode != GM_BOOTSTRAP) { - Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + Window *w = GetMainWindow(); assert(w); w->viewport->dest_scrollpos_x += ScaleByZoom(x, w->viewport->zoom); diff --git a/src/window_func.h b/src/window_func.h index 9dccbd56ed..586f96aa8d 100644 --- a/src/window_func.h +++ b/src/window_func.h @@ -16,6 +16,7 @@ Window *FindWindowById(WindowClass cls, WindowNumber number); Window *FindWindowByClass(WindowClass cls); +Window *GetMainWindow(); void ChangeWindowOwner(Owner old_owner, Owner new_owner); void ResizeWindow(Window *w, int x, int y, bool clamp_to_screen = true);