diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 599a8c2045..8e103ff7af 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -81,24 +81,30 @@ jobs: echo "::endgroup::" linux: - name: Linux strategy: fail-fast: false matrix: include: - - compiler: clang + - name: Clang + compiler: clang cxxcompiler: clang++ - libsdl: libsdl2-dev - - compiler: gcc + libraries: libsdl2-dev nlohmann-json3-dev + - name: GCC - SDL2 + compiler: gcc cxxcompiler: g++ - libsdl: libsdl2-dev - - compiler: gcc + libraries: libsdl2-dev nlohmann-json3-dev + - name: GCC - SDL1.2 + compiler: gcc cxxcompiler: g++ - libsdl: libsdl1.2-dev - - compiler: gcc + libraries: libsdl1.2-dev nlohmann-json3-dev + - name: GCC - Dedicated + compiler: gcc cxxcompiler: g++ extra-cmake-parameters: -DOPTION_DEDICATED=ON -DCMAKE_CXX_FLAGS_INIT="-DRANDOM_DEBUG" + # Compile without SDL / SDL2 / nlohmann-json, as that should compile fine too. + + name: Linux (${{ matrix.name }}) runs-on: ubuntu-20.04 env: @@ -132,7 +138,7 @@ jobs: liblzma-dev \ libzstd-dev \ liblzo2-dev \ - ${{ matrix.libsdl }} \ + ${{ matrix.libraries }} \ zlib1g-dev \ # EOF echo "::endgroup::" @@ -172,8 +178,6 @@ jobs: echo "::endgroup::" macos: - name: Mac OS - strategy: fail-fast: false matrix: @@ -181,6 +185,8 @@ jobs: - arch: x64 full_arch: x86_64 + name: Mac OS (${{ matrix.arch }}) + runs-on: macos-latest env: MACOSX_DEPLOYMENT_TARGET: 10.13 @@ -214,7 +220,7 @@ jobs: uses: actions/cache@v3 with: path: /usr/local/share/vcpkg/installed - key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-0 # Increase the number whenever dependencies are modified + key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-1 # Increase the number whenever dependencies are modified restore-keys: | ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }} @@ -225,6 +231,7 @@ jobs: liblzma \ libpng \ lzo \ + nlohmann-json \ zlib \ zstd \ # EOF @@ -232,7 +239,7 @@ jobs: - name: Install OpenGFX run: | mkdir -p ~/Documents/OpenTTD/baseset - cd ~/Documents//OpenTTD/baseset + cd ~/Documents/OpenTTD/baseset echo "::group::Download OpenGFX" curl -L https://cdn.openttd.org/opengfx-releases/0.6.0/opengfx-0.6.0-all.zip -o opengfx-all.zip @@ -253,7 +260,7 @@ jobs: cd build echo "::group::CMake" - cmake ${GITHUB_WORKSPACE} \ + cmake .. \ -DCMAKE_OSX_ARCHITECTURES=${{ matrix.full_arch }} \ -DVCPKG_TARGET_TRIPLET=${{ matrix.arch }}-osx \ -DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake \ @@ -266,14 +273,14 @@ jobs: echo "::endgroup::" windows: - name: Windows - strategy: fail-fast: false matrix: os: [windows-latest, windows-2019] arch: [x86, x64] + name: Windows (${{ matrix.os }} / ${{ matrix.arch }}) + runs-on: ${{ matrix.os }} steps: @@ -300,7 +307,7 @@ jobs: uses: actions/cache@v3 with: path: vcpkg/installed - key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-0 # Increase the number whenever dependencies are modified + key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-1 # Increase the number whenever dependencies are modified restore-keys: | ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }} @@ -311,6 +318,7 @@ jobs: liblzma \ libpng \ lzo \ + nlohmann-json \ zlib \ zstd \ # EOF @@ -358,8 +366,6 @@ jobs: echo "::endgroup::" msys2: - name: msys2 - strategy: fail-fast: false matrix: @@ -369,6 +375,8 @@ jobs: - msystem: MINGW32 arch: i686 + name: MinGW (${{ matrix.arch }}) + runs-on: windows-latest steps: @@ -394,6 +402,7 @@ jobs: mingw-w64-${{ matrix.arch }}-gcc mingw-w64-${{ matrix.arch }}-lzo2 mingw-w64-${{ matrix.arch }}-libpng + mingw-w64-${{ matrix.arch }}-nlohmann-json - name: Install OpenGFX shell: bash diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b05300775a..47dcafc882 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,10 +49,12 @@ jobs: liballegro4-dev \ libcurl4-openssl-dev \ libfontconfig-dev \ + libharfbuzz-dev \ libicu-dev \ liblzma-dev \ liblzo2-dev \ libsdl2-dev \ + nlohmann-json3-dev \ zlib1g-dev \ # EOF echo "::endgroup::" diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d6636f3c1..d5b40a9aa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,7 @@ find_package(LibLZMA) find_package(LZO) find_package(ZSTD 1.4) find_package(PNG) +find_package(nlohmann_json) if(WIN32 OR EMSCRIPTEN) # Windows uses WinHttp for HTTP requests. @@ -345,6 +346,7 @@ link_package(ZLIB TARGET ZLIB::ZLIB ENCOURAGED) link_package(LIBLZMA TARGET LibLZMA::LibLZMA ENCOURAGED) link_package(LZO) link_package(ZSTD TARGET ZSTD::ZSTD RECOMMENDED) +link_package(nlohmann_json ENCOURAGED) if(NOT WIN32 AND NOT EMSCRIPTEN) link_package(CURL ENCOURAGED) diff --git a/COMPILING.md b/COMPILING.md index 68fbbb73c7..df2e961c71 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -4,6 +4,7 @@ OpenTTD makes use of the following external libraries: +- (encouraged) nlohmann-json: JSON handling - (encouraged) zlib: (de)compressing of old (0.3.0-1.0.5) savegames, content downloads, heightmaps - (encouraged) liblzma: (de)compressing of savegames (1.1.0 and later) @@ -54,13 +55,14 @@ the `static` versions, and OpenTTD currently needs the following dependencies: - libzstd - libpng - lzo +- nlohmann-json - zlib To install both the x64 (64bit) and x86 (32bit) variants (though only one is necessary), you can use: ```ps -.\vcpkg install liblzma:x64-windows-static zstd:x64-windows-static libpng:x64-windows-static lzo:x64-windows-static zlib:x64-windows-static -.\vcpkg install liblzma:x86-windows-static zstd:x86-windows-static libpng:x86-windows-static lzo:x86-windows-static zlib:x86-windows-static +.\vcpkg install liblzma:x64-windows-static zstd:x64-windows-static libpng:x64-windows-static lzo:x64-windows-static nlohmann-json:x64-windows-static zlib:x64-windows-static +.\vcpkg install liblzma:x86-windows-static zstd:x86-windows-static libpng:x86-windows-static lzo:x86-windows-static nlohmann-json:x86-windows-static zlib:x86-windows-static ``` You can open the folder (as a CMake project). CMake will be detected, and you can compile from there. diff --git a/cmake/Options.cmake b/cmake/Options.cmake index 42d1127906..371b03d841 100644 --- a/cmake/Options.cmake +++ b/cmake/Options.cmake @@ -71,6 +71,8 @@ function(set_options) if (OPTION_DOCS_ONLY) set(OPTION_TOOLS_ONLY ON PARENT_SCOPE) endif() + + option(OPTION_SURVEY_KEY "Survey-key to use for the opt-in survey (empty if you have none)" "") endfunction() # Show the values of the generic options. @@ -84,6 +86,12 @@ function(show_options) message(STATUS "Option Use assert - ${OPTION_USE_ASSERTS}") message(STATUS "Option Use threads - ${OPTION_USE_THREADS}") message(STATUS "Option Use NSIS - ${OPTION_USE_NSIS}") + + if(OPTION_SURVEY_KEY) + message(STATUS "Option Survey Key - USED") + else() + message(STATUS "Option Survey Key - NOT USED") + endif() endfunction() # Add the definitions for the options that are selected. @@ -104,4 +112,8 @@ function(add_definitions_based_on_options) else() add_definitions(-DNDEBUG) endif() + + if(OPTION_SURVEY_KEY) + add_definitions(-DSURVEY_KEY="${OPTION_SURVEY_KEY}") + endif() endfunction() diff --git a/cmake/scripts/SquirrelExport.cmake b/cmake/scripts/SquirrelExport.cmake index 6fc6f094e6..e4096bc9de 100644 --- a/cmake/scripts/SquirrelExport.cmake +++ b/cmake/scripts/SquirrelExport.cmake @@ -28,21 +28,21 @@ endmacro() macro(dump_class_templates NAME) string(REGEX REPLACE "^Script" "" REALNAME ${NAME}) - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} *> { static inline ${NAME} *Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return (${NAME} *)instance; } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} &> { static inline ${NAME} &Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return *(${NAME} *)instance; } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} *Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return (${NAME} *)instance; } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} &Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return *(${NAME} *)instance; } };") + string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} *> { static inline ${NAME} *Get(HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return (${NAME} *)instance; } };") + string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} &> { static inline ${NAME} &Get(HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return *(${NAME} *)instance; } };") + string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} *Get(HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return (${NAME} *)instance; } };") + string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} &Get(HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, nullptr); return *(${NAME} *)instance; } };") if("${NAME}" STREQUAL "ScriptEvent") string(APPEND SQUIRREL_EXPORT "\n template <> struct Return<${NAME} *> { static inline int Set(HSQUIRRELVM vm, ${NAME} *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") elseif("${NAME}" STREQUAL "ScriptText") string(APPEND SQUIRREL_EXPORT "\n") string(APPEND SQUIRREL_EXPORT "\n template <> struct Param {") - string(APPEND SQUIRREL_EXPORT "\n static inline Text *Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) {") + string(APPEND SQUIRREL_EXPORT "\n static inline Text *Get(HSQUIRRELVM vm, int index) {") string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_INSTANCE) {") - string(APPEND SQUIRREL_EXPORT "\n return Param::Get(vm, index, ptr);") + string(APPEND SQUIRREL_EXPORT "\n return Param::Get(vm, index);") string(APPEND SQUIRREL_EXPORT "\n }") string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_STRING) {") - string(APPEND SQUIRREL_EXPORT "\n return new RawText(Param::Get(vm, index, ptr));") + string(APPEND SQUIRREL_EXPORT "\n return new RawText(Param::Get(vm, index));") string(APPEND SQUIRREL_EXPORT "\n }") string(APPEND SQUIRREL_EXPORT "\n return nullptr;") string(APPEND SQUIRREL_EXPORT "\n }") @@ -299,7 +299,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() string(APPEND SQUIRREL_EXPORT "\n /* Allow enums to be used as Squirrel parameters */") foreach(ENUM IN LISTS ENUMS) - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${ENUM}> { static inline ${ENUM} Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (${ENUM})tmp; } };") + string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${ENUM}> { static inline ${ENUM} Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (${ENUM})tmp; } };") string(APPEND SQUIRREL_EXPORT "\n template <> struct Return<${ENUM}> { static inline int Set(HSQUIRRELVM vm, ${ENUM} res) { sq_pushinteger(vm, res); return 1; } };") endforeach() endif() @@ -631,6 +631,8 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND TYPES "a") elseif("${PARAM}" MATCHES "^Text") string(APPEND TYPES ".") + elseif("${PARAM}" MATCHES "^std::string") + string(APPEND TYPES ".") else() string(APPEND TYPES "x") endif() diff --git a/os/emscripten/Dockerfile b/os/emscripten/Dockerfile index e92858df87..4ded7fe57c 100644 --- a/os/emscripten/Dockerfile +++ b/os/emscripten/Dockerfile @@ -2,3 +2,6 @@ FROM emscripten/emsdk:3.1.37 COPY emsdk-liblzma.patch / RUN cd /emsdk/upstream/emscripten && patch -p1 < /emsdk-liblzma.patch + +COPY emsdk-nlohmann-json.patch / +RUN cd /emsdk/upstream/emscripten && patch -p1 < /emsdk-nlohmann-json.patch diff --git a/os/emscripten/README.md b/os/emscripten/README.md index cf8e3ed692..c16eea2416 100644 --- a/os/emscripten/README.md +++ b/os/emscripten/README.md @@ -4,10 +4,11 @@ Please use docker with the supplied `Dockerfile` to build for emscripten. It takes care of a few things: - Use a version of emscripten we know works - Patch in LibLZMA support (as this is not supported by upstream) +- Patch in nlohmann-json support (as this is not supported by upstream) First, build the docker image by navigating in the folder this `README.md` is in, and executing: ``` - docker build -t emsdk-lzma . + docker build -t emsdk-openttd . ``` Next, navigate back to the root folder of this project. @@ -15,15 +16,15 @@ Next, navigate back to the root folder of this project. Now we build the host tools first: ``` mkdir build-host - docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-lzma cmake .. -DOPTION_TOOLS_ONLY=ON - docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-lzma make -j$(nproc) tools + docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-openttd cmake .. -DOPTION_TOOLS_ONLY=ON + docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-openttd make -j$(nproc) tools ``` Finally, we build the actual game: ``` mkdir build - docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-lzma emcmake cmake .. -DHOST_BINARY_DIR=../build-host -DCMAKE_BUILD_TYPE=Release -DOPTION_USE_ASSERTS=OFF - docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-lzma emmake make -j$(nproc) + docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-openttd emcmake cmake .. -DHOST_BINARY_DIR=../build-host -DCMAKE_BUILD_TYPE=Release -DOPTION_USE_ASSERTS=OFF + docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-openttd emmake make -j$(nproc) ``` In the `build` folder you will now see `openttd.html`. diff --git a/os/emscripten/cmake/FindLibLZMA.cmake b/os/emscripten/cmake/FindLibLZMA.cmake index e8a024c4ee..cd6b44ad3d 100644 --- a/os/emscripten/cmake/FindLibLZMA.cmake +++ b/os/emscripten/cmake/FindLibLZMA.cmake @@ -1,5 +1,5 @@ -# LibLZMA is a recent addition to the emscripten SDK, so it is possible -# someone hasn't updated their SDK yet. Test out if the SDK supports LibLZMA. +# LibLZMA is a custom addition to the emscripten SDK, so it is possible +# someone patched their SDK. Test out if the SDK supports LibLZMA. include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_FLAGS "-sUSE_LIBLZMA=1") diff --git a/os/emscripten/cmake/Findnlohmann_json.cmake b/os/emscripten/cmake/Findnlohmann_json.cmake new file mode 100644 index 0000000000..6ff84544da --- /dev/null +++ b/os/emscripten/cmake/Findnlohmann_json.cmake @@ -0,0 +1,20 @@ +# nlohmann-json is a custom addition to the emscripten SDK, so it is possible +# someone patched their SDK. Test out if the SDK supports nlohmann-json. +include(CheckCXXSourceCompiles) +set(CMAKE_REQUIRED_FLAGS "-sUSE_NLOHMANN_JSON=1") + +check_cxx_source_compiles(" + #include + int main() { return 0; }" + NLOHMANN_JSON_FOUND +) + +if (NLOHMANN_JSON_FOUND) + add_library(nlohmann_json INTERFACE IMPORTED) + set_target_properties(nlohmann_json PROPERTIES + INTERFACE_COMPILE_OPTIONS "-sUSE_NLOHMANN_JSON=1" + INTERFACE_LINK_LIBRARIES "-sUSE_NLOHMANN_JSON=1" + ) +else() + message(WARNING "You are using an emscripten SDK without nlohmann-json support. Please apply 'emsdk-nlohmann_json.patch' to your local emsdk installation.") +endif() diff --git a/os/emscripten/emsdk-nlohmann-json.patch b/os/emscripten/emsdk-nlohmann-json.patch new file mode 100644 index 0000000000..37d052a2a7 --- /dev/null +++ b/os/emscripten/emsdk-nlohmann-json.patch @@ -0,0 +1,93 @@ +From 0edcedbea375e59f41df10acaee0c483d245751f Mon Sep 17 00:00:00 2001 +From: Patric Stout +Date: Tue, 2 May 2023 21:48:08 +0200 +Subject: [PATCH] Add nlohmmann-json port + +--- + src/settings.js | 4 ++++ + tools/ports/nlohmann_json.py | 46 ++++++++++++++++++++++++++++++++++++ + tools/settings.py | 1 + + 3 files changed, 51 insertions(+) + create mode 100644 tools/ports/nlohmann_json.py + +diff --git a/src/settings.js b/src/settings.js +index f93140d..39f4366 100644 +--- a/src/settings.js ++++ b/src/settings.js +@@ -1483,6 +1483,10 @@ var USE_MPG123 = false; + // [compile+link] + var USE_FREETYPE = false; + ++// 1 = use nlohmann-json from emscripten-ports ++// [compile+link] ++var USE_NLOHMANN_JSON = false; ++ + // Specify the SDL_mixer version that is being linked against. + // Doesn't *have* to match USE_SDL, but a good idea. + // [compile+link] +diff --git a/tools/ports/nlohmann_json.py b/tools/ports/nlohmann_json.py +new file mode 100644 +index 0000000..9e44297 +--- /dev/null ++++ b/tools/ports/nlohmann_json.py +@@ -0,0 +1,46 @@ ++# Copyright 2023 The Emscripten Authors. All rights reserved. ++# Emscripten is available under two separate licenses, the MIT license and the ++# University of Illinois/NCSA Open Source License. Both these licenses can be ++# found in the LICENSE file. ++ ++import os ++ ++TAG = '3.11.2' ++HASH = '99d9e6d588cabe8913a37437f86acb5d4b8b98bce12423e633c11c13b61e6c7f92ef8f9a4e991baa590329ee2b5c09ca9db9894bee1e54bdd68e8d09d83cc245' ++ ++ ++def needed(settings): ++ return settings.USE_NLOHMANN_JSON ++ ++ ++def get(ports, settings, shared): ++ ports.fetch_project('nlohmann_json', ++ f'https://github.com/nlohmann/json/releases/download/v{TAG}/include.zip', ++ sha512hash=HASH) ++ ++ def create(final): ++ source_path = os.path.join(ports.get_dir(), 'nlohmann_json') ++ source_path_include = os.path.join(source_path, 'include', 'nlohmann') ++ ports.install_header_dir(source_path_include, 'nlohmann') ++ ++ # write out a dummy cpp file, to create an empty library ++ # this is needed as emscripten ports expect this, even if it is not used ++ dummy_file = os.path.join(source_path, 'dummy.cpp') ++ shared.safe_ensure_dirs(os.path.dirname(dummy_file)) ++ ports.write_file(dummy_file, 'static void dummy() {}') ++ ++ ports.build_port(source_path, final, 'nlohmann_json', srcs=['dummy.cpp']) ++ ++ return [shared.cache.get_lib('libnlohmann_json.a', create, what='port')] ++ ++ ++def clear(ports, settings, shared): ++ shared.cache.erase_lib('libnlohmann_json.a') ++ ++ ++def process_args(ports): ++ return [] ++ ++ ++def show(): ++ return 'nlohmann-json' +diff --git a/tools/settings.py b/tools/settings.py +index 10d6ca0..8536092 100644 +--- a/tools/settings.py ++++ b/tools/settings.py +@@ -47,6 +47,7 @@ PORTS_SETTINGS = { + 'USE_MPG123', + 'USE_GIFLIB', + 'USE_FREETYPE', ++ 'USE_NLOHMANN_JSON', + 'SDL2_MIXER_FORMATS', + 'SDL2_IMAGE_FORMATS', + 'USE_SQLITE3', +-- +2.34.1 diff --git a/src/3rdparty/squirrel/include/squirrel.h b/src/3rdparty/squirrel/include/squirrel.h index c28ab403d3..0287cc3a8b 100644 --- a/src/3rdparty/squirrel/include/squirrel.h +++ b/src/3rdparty/squirrel/include/squirrel.h @@ -307,7 +307,8 @@ SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror, SQRESULT sq_resume(HSQUIRRELVM v,SQBool retval,SQBool raiseerror); const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx); const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval); -SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err); +SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err, SQInteger len = -1); +static inline SQRESULT sq_throwerror(HSQUIRRELVM v, const std::string_view err) { return sq_throwerror(v, err.data(), err.size()); } void sq_reseterror(HSQUIRRELVM v); void sq_getlasterror(HSQUIRRELVM v); diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp index 56f3ef7b47..f7ddf18a4b 100644 --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -930,9 +930,9 @@ void sq_resetobject(HSQOBJECT *po) po->_unVal.pUserPointer=nullptr;po->_type=OT_NULL; } -SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err) +SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err, SQInteger len) { - v->_lasterror=SQString::Create(_ss(v),err); + v->_lasterror=SQString::Create(_ss(v),err, len); return -1; } diff --git a/src/ai/ai.hpp b/src/ai/ai.hpp index fce9c2059c..ae19439754 100644 --- a/src/ai/ai.hpp +++ b/src/ai/ai.hpp @@ -120,9 +120,9 @@ public: /** Wrapper function for AIScanner::GetUniqueAIInfoList */ static const ScriptInfoList *GetUniqueInfoList(); /** Wrapper function for AIScanner::FindInfo */ - static class AIInfo *FindInfo(const char *name, int version, bool force_exact_match); + static class AIInfo *FindInfo(const std::string &name, int version, bool force_exact_match); /** Wrapper function for AIScanner::FindLibrary */ - static class AILibrary *FindLibrary(const char *library, int version); + static class AILibrary *FindLibrary(const std::string &library, int version); /** * Rescans all searchpaths for available AIs. If a used AI is no longer diff --git a/src/ai/ai_config.cpp b/src/ai/ai_config.cpp index a1c6479952..7493556736 100644 --- a/src/ai/ai_config.cpp +++ b/src/ai/ai_config.cpp @@ -33,7 +33,7 @@ class AIInfo *AIConfig::GetInfo() const return static_cast(ScriptConfig::GetInfo()); } -ScriptInfo *AIConfig::FindInfo(const char *name, int version, bool force_exact_match) +ScriptInfo *AIConfig::FindInfo(const std::string &name, int version, bool force_exact_match) { return static_cast(AI::FindInfo(name, version, force_exact_match)); } diff --git a/src/ai/ai_config.hpp b/src/ai/ai_config.hpp index a0f98d2187..b64020b4f0 100644 --- a/src/ai/ai_config.hpp +++ b/src/ai/ai_config.hpp @@ -41,7 +41,7 @@ public: bool ResetInfo(bool force_exact_match); protected: - ScriptInfo *FindInfo(const char *name, int version, bool force_exact_match) override; + ScriptInfo *FindInfo(const std::string &name, int version, bool force_exact_match) override; }; #endif /* AI_CONFIG_HPP */ diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp index 5f3469e890..fb0123bf0a 100644 --- a/src/ai/ai_core.cpp +++ b/src/ai/ai_core.cpp @@ -81,7 +81,7 @@ Backup cur_company(_current_company, FILE_LINE); for (const Company *c : Company::Iterate()) { if (c->is_ai) { - SCOPE_INFO_FMT([&], "AI::GameLoop: %i: %s (v%d)\n", (int)c->index, c->ai_info->GetName(), c->ai_info->GetVersion()); + SCOPE_INFO_FMT([&], "AI::GameLoop: %i: %s (v%d)\n", (int)c->index, c->ai_info->GetName().c_str(), c->ai_info->GetVersion()); PerformanceMeasurer framerate((PerformanceElement)(PFE_AI0 + c->index)); cur_company.Change(c->index); c->ai_instance->GameLoop(); @@ -205,8 +205,8 @@ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { if (_settings_game.ai_config[c] != nullptr && _settings_game.ai_config[c]->HasScript()) { if (!_settings_game.ai_config[c]->ResetInfo(true)) { - DEBUG(script, 0, "After a reload, the AI by the name '%s' was no longer found, and removed from the list.", _settings_game.ai_config[c]->GetName()); - _settings_game.ai_config[c]->Change(nullptr); + DEBUG(script, 0, "After a reload, the AI by the name '%s' was no longer found, and removed from the list.", _settings_game.ai_config[c]->GetName().c_str()); + _settings_game.ai_config[c]->Change(std::nullopt); if (Company::IsValidAiID(c)) { /* The code belonging to an already running AI was deleted. We can only do * one thing here to keep everything sane and that is kill the AI. After @@ -222,8 +222,8 @@ } if (_settings_newgame.ai_config[c] != nullptr && _settings_newgame.ai_config[c]->HasScript()) { if (!_settings_newgame.ai_config[c]->ResetInfo(false)) { - DEBUG(script, 0, "After a reload, the AI by the name '%s' was no longer found, and removed from the list.", _settings_newgame.ai_config[c]->GetName()); - _settings_newgame.ai_config[c]->Change(nullptr); + DEBUG(script, 0, "After a reload, the AI by the name '%s' was no longer found, and removed from the list.", _settings_newgame.ai_config[c]->GetName().c_str()); + _settings_newgame.ai_config[c]->Change(std::nullopt); } } } @@ -307,12 +307,12 @@ return AI::scanner_info->GetUniqueInfoList(); } -/* static */ AIInfo *AI::FindInfo(const char *name, int version, bool force_exact_match) +/* static */ AIInfo *AI::FindInfo(const std::string &name, int version, bool force_exact_match) { return AI::scanner_info->FindInfo(name, version, force_exact_match); } -/* static */ AILibrary *AI::FindLibrary(const char *library, int version) +/* static */ AILibrary *AI::FindLibrary(const std::string &library, int version) { return AI::scanner_library->FindLibrary(library, version); } diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp index 6324cb0186..02743dec9f 100644 --- a/src/ai/ai_gui.cpp +++ b/src/ai/ai_gui.cpp @@ -181,7 +181,7 @@ struct AIConfigWindow : public Window { void OnClick(Point pt, int widget, int click_count) override { - if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_END) { + if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_CONTENT_END) { if (this->selected_slot == INVALID_COMPANY || AIConfig::GetConfig(this->selected_slot) == nullptr) return; ShowScriptTextfileWindow((TextfileType)(widget - WID_AIC_TEXTFILE), this->selected_slot); @@ -282,7 +282,7 @@ struct AIConfigWindow : public Window { this->SetWidgetDisabledState(WID_AIC_MOVE_UP, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot - 1))); this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot + 1))); - for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) { + for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { this->SetWidgetDisabledState(WID_AIC_TEXTFILE + tft, this->selected_slot == INVALID_COMPANY || (AIConfig::GetConfig(this->selected_slot)->GetTextfile(tft, this->selected_slot) == nullptr)); } } diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp index 7cc82346d9..98e3d793f9 100644 --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -15,6 +15,7 @@ #include "../debug.h" #include "../string_func.h" #include "../rev.h" +#include "../3rdparty/fmt/format.h" #include #include "../safeguards.h" @@ -23,7 +24,7 @@ * Check if the API version provided by the AI is supported. * @param api_version The API version as provided by the AI. */ -static bool CheckAPIVersion(const char *api_version) +static bool CheckAPIVersion(const std::string &api_version) { static const std::set versions = { "0.7", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12", "13", "14" }; return versions.find(api_version) != versions.end(); @@ -69,26 +70,26 @@ template <> const char *GetClassName() { return "AIInfo" SQInteger res = ScriptInfo::Constructor(vm, info); if (res != 0) return res; - if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) { - if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_OPS)) return SQ_ERROR; + if (info->engine->MethodExists(info->SQ_instance, "MinVersionToLoad")) { + if (!info->engine->CallIntegerMethod(info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_OPS)) return SQ_ERROR; } else { info->min_loadable_version = info->GetVersion(); } /* When there is an UseAsRandomAI function, call it. */ - if (info->engine->MethodExists(*info->SQ_instance, "UseAsRandomAI")) { - if (!info->engine->CallBoolMethod(*info->SQ_instance, "UseAsRandomAI", &info->use_as_random, MAX_GET_OPS)) return SQ_ERROR; + if (info->engine->MethodExists(info->SQ_instance, "UseAsRandomAI")) { + if (!info->engine->CallBoolMethod(info->SQ_instance, "UseAsRandomAI", &info->use_as_random, MAX_GET_OPS)) return SQ_ERROR; } else { info->use_as_random = true; } /* Try to get the API version the AI is written for. */ - if (info->engine->MethodExists(*info->SQ_instance, "GetAPIVersion")) { - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAPIVersion", &info->api_version, MAX_GET_OPS)) return SQ_ERROR; + if (info->engine->MethodExists(info->SQ_instance, "GetAPIVersion")) { + if (!info->engine->CallStringMethod(info->SQ_instance, "GetAPIVersion", &info->api_version, MAX_GET_OPS)) return SQ_ERROR; if (!CheckAPIVersion(info->api_version)) { - DEBUG(script, 1, "Loading info.nut from (%s.%d): GetAPIVersion returned invalid version", info->GetName(), info->GetVersion()); + DEBUG(script, 1, "Loading info.nut from (%s.%d): GetAPIVersion returned invalid version", info->GetName().c_str(), info->GetVersion()); return SQ_ERROR; } } else { - info->api_version = stredup("0.7"); + info->api_version = "0.7"; } /* Remove the link to the real instance, else it might get deleted by RegisterAI() */ @@ -104,15 +105,11 @@ template <> const char *GetClassName() { return "AIInfo" SQUserPointer instance; sq_getinstanceup(vm, 2, &instance, nullptr); AIInfo *info = (AIInfo *)instance; - info->api_version = nullptr; + info->api_version = fmt::format("{}.{}", GB(_openttd_newgrf_version, 28, 4), GB(_openttd_newgrf_version, 24, 4)); SQInteger res = ScriptInfo::Constructor(vm, info); if (res != 0) return res; - char buf[8]; - seprintf(buf, lastof(buf), "%d.%d", GB(_openttd_newgrf_version, 28, 4), GB(_openttd_newgrf_version, 24, 4)); - info->api_version = stredup(buf); - /* Remove the link to the real instance, else it might get deleted by RegisterAI() */ sq_setinstanceup(vm, 2, nullptr); /* Register the AI to the base system */ @@ -122,14 +119,8 @@ template <> const char *GetClassName() { return "AIInfo" AIInfo::AIInfo() : min_loadable_version(0), - use_as_random(false), - api_version(nullptr) -{ -} - -AIInfo::~AIInfo() + use_as_random(false) { - free(this->api_version); } bool AIInfo::CanLoadFromVersion(int version) const @@ -139,11 +130,6 @@ bool AIInfo::CanLoadFromVersion(int version) const } -AILibrary::~AILibrary() -{ - free(this->category); -} - /* static */ void AILibrary::RegisterAPI(Squirrel *engine) { /* Create the AILibrary class, and add the RegisterLibrary function */ @@ -164,7 +150,7 @@ AILibrary::~AILibrary() } /* Cache the category */ - if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethodStrdup(*library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { + if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { delete library; return SQ_ERROR; } diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp index 8f236e09ab..df8e6b65d7 100644 --- a/src/ai/ai_info.hpp +++ b/src/ai/ai_info.hpp @@ -16,7 +16,6 @@ class AIInfo : public ScriptInfo { public: AIInfo(); - ~AIInfo(); /** * Register the functions of this class. @@ -46,19 +45,18 @@ public: /** * Get the API version this AI is written for. */ - const char *GetAPIVersion() const { return this->api_version; } + const std::string &GetAPIVersion() const { return this->api_version; } private: int min_loadable_version; ///< The AI can load savegame data if the version is equal or greater than this. bool use_as_random; ///< Should this AI be used when the user wants a "random AI"? - const char *api_version; ///< API version used by this AI. + std::string api_version; ///< API version used by this AI. }; /** All static information from an AI library like name, version, etc. */ class AILibrary : public ScriptInfo { public: - AILibrary() : ScriptInfo(), category(nullptr) {}; - ~AILibrary(); + AILibrary() : ScriptInfo() {}; /** * Register the functions of this class. @@ -73,10 +71,10 @@ public: /** * Get the category this library is in. */ - const char *GetCategory() const { return this->category; } + const std::string &GetCategory() const { return this->category; } private: - const char *category; ///< The category this library is in. + std::string category; ///< The category this library is in. }; #endif /* AI_INFO_HPP */ diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 4489c9114d..a3b6166884 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -69,7 +69,7 @@ void AIInstance::Died() if (info != nullptr) { ShowErrorMessage(STR_ERROR_AI_PLEASE_REPORT_CRASH, INVALID_STRING_ID, WL_WARNING); - if (info->GetURL() != nullptr) { + if (!info->GetURL().empty()) { ScriptLog::Info("Please report the error to the following URL:"); ScriptLog::Info(info->GetURL()); } @@ -82,12 +82,12 @@ void AIInstance::LoadDummyScript() Script_CreateDummy(this->engine->GetVM(), STR_ERROR_AI_NO_AI_FOUND, "AI"); } -int AIInstance::GetSetting(const char *name) +int AIInstance::GetSetting(const std::string &name) { return AIConfig::GetConfig(_current_company)->GetSetting(name); } -ScriptInfo *AIInstance::FindLibrary(const char *library, int version) +ScriptInfo *AIInstance::FindLibrary(const std::string &library, int version) { return (ScriptInfo *)AI::FindLibrary(library, version); } diff --git a/src/ai/ai_instance.hpp b/src/ai/ai_instance.hpp index f8d2100b1c..fab9956922 100644 --- a/src/ai/ai_instance.hpp +++ b/src/ai/ai_instance.hpp @@ -23,8 +23,8 @@ public: */ void Initialize(class AIInfo *info); - int GetSetting(const char *name) override; - ScriptInfo *FindLibrary(const char *library, int version) override; + int GetSetting(const std::string &name) override; + ScriptInfo *FindLibrary(const std::string &library, int version) override; private: void RegisterAPI() override; diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp index cda6b3c28d..cbc4cc933c 100644 --- a/src/ai/ai_scanner.cpp +++ b/src/ai/ai_scanner.cpp @@ -93,10 +93,10 @@ AIInfo *AIScannerInfo::SelectRandomAI() const #undef GetAIInfo } -AIInfo *AIScannerInfo::FindInfo(const char *name, int version, bool force_exact_match) +AIInfo *AIScannerInfo::FindInfo(const std::string &name, int version, bool force_exact_match) { if (this->info_list.size() == 0) return nullptr; - if (name == nullptr) return nullptr; + if (name.empty()) return nullptr; if (version == -1) { /* We want to load the latest version of this AI; so find it */ @@ -146,7 +146,7 @@ void AIScannerLibrary::RegisterAPI(class Squirrel *engine) AILibrary::RegisterAPI(engine); } -AILibrary *AIScannerLibrary::FindLibrary(const char *library, int version) +AILibrary *AIScannerLibrary::FindLibrary(const std::string &library, int version) { /* Internally we store libraries as 'library.version' */ std::string library_name = fmt::format("{}.{}", library, version); diff --git a/src/ai/ai_scanner.hpp b/src/ai/ai_scanner.hpp index 4a6a065142..a2c8a7280e 100644 --- a/src/ai/ai_scanner.hpp +++ b/src/ai/ai_scanner.hpp @@ -32,7 +32,7 @@ public: * @param force_exact_match Only match name+version, never latest. * @return nullptr if no match found, otherwise the AI that matched. */ - class AIInfo *FindInfo(const char *name, int version, bool force_exact_match); + class AIInfo *FindInfo(const std::string &name, int version, bool force_exact_match); /** * Set the Dummy AI. @@ -60,7 +60,7 @@ public: * @param version The version the library should have. * @return The library if found, nullptr otherwise. */ - class AILibrary *FindLibrary(const char *library, int version); + class AILibrary *FindLibrary(const std::string &library, int version); protected: std::string GetScriptName(ScriptInfo *info) override; diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index a64630f880..cee8d5d30c 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -487,8 +487,8 @@ public: break; case WID_AP_AIRPORT_LIST: { - int num_clicked = this->vscroll->GetPosition() + (pt.y - this->GetWidget(widget)->pos_y) / this->line_height; - if (num_clicked >= this->vscroll->GetCount()) break; + int num_clicked = this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget, 0, this->line_height); + if (num_clicked == INT_MAX) break; const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(num_clicked); if (as->IsAvailable()) this->SelectOtherAirport(num_clicked); break; diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 52dccf1d0c..d52b7bbf34 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -193,10 +193,10 @@ class ReplaceVehicleWindow : public Window { this->sel_engine[side] = selected_engine; // update which engine we selected (the same or none, if it's not in the list anymore) if (draw_left) { - EngList_Sort(&list, &EngineNumberSorter); + EngList_Sort(list, &EngineNumberSorter); } else { _engine_sort_direction = this->descending_sort_order; - EngList_Sort(&list, _engine_sort_functions[this->window_number][this->sort_criteria]); + EngList_Sort(list, _engine_sort_functions[this->window_number][this->sort_criteria]); } this->engines[side].clear(); @@ -617,12 +617,11 @@ public: } else { click_side = 1; } - uint i = this->vscroll[click_side]->GetScrolledRowFromWidget(pt.y, this, widget); - size_t engine_count = this->engines[click_side].size(); EngineID e = INVALID_ENGINE; - if (i < engine_count) { - const auto &item = this->engines[click_side][i]; + const auto it = this->vscroll[click_side]->GetScrolledItemFromWidget(this->engines[click_side], pt.y, this, widget); + if (it != this->engines[click_side].end()) { + const auto &item = *it; const Rect r = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix).WithWidth(WidgetDimensions::scaled.hsep_indent * (item.indent + 1), _current_text_dir == TD_RTL); if ((item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None && IsInsideMM(r.left, r.right, pt.x)) { /* toggle folded flag on engine */ diff --git a/src/base_consist.h b/src/base_consist.h index e4fbb79544..29691cdc03 100644 --- a/src/base_consist.h +++ b/src/base_consist.h @@ -33,7 +33,7 @@ struct BaseConsist { uint32 vehicle_flags; ///< Used for gradual loading and other miscellaneous things (@see VehicleFlags enum) - virtual ~BaseConsist() {} + virtual ~BaseConsist() = default; void CopyConsistPropertiesFrom(const BaseConsist *src); }; diff --git a/src/base_media_base.h b/src/base_media_base.h index 84c12d0858..5213aa1ff7 100644 --- a/src/base_media_base.h +++ b/src/base_media_base.h @@ -11,7 +11,6 @@ #define BASE_MEDIA_BASE_H #include "fileio_func.h" -#include "core/smallmap_type.hpp" #include "gfx_type.h" #include "textfile_type.h" #include "textfile_gui.h" diff --git a/src/blitter/32bpp_sse2.hpp b/src/blitter/32bpp_sse2.hpp index 8cb73c47ab..7be9baf4fc 100644 --- a/src/blitter/32bpp_sse2.hpp +++ b/src/blitter/32bpp_sse2.hpp @@ -29,7 +29,7 @@ /** Base methods for 32bpp SSE blitters. */ class Blitter_32bppSSE_Base { public: - virtual ~Blitter_32bppSSE_Base() {} + virtual ~Blitter_32bppSSE_Base() = default; struct MapValue { uint8 m; diff --git a/src/blitter/base.hpp b/src/blitter/base.hpp index fd640a28df..65bd989389 100644 --- a/src/blitter/base.hpp +++ b/src/blitter/base.hpp @@ -273,7 +273,7 @@ public: */ virtual void PostResize() { }; - virtual ~Blitter() { } + virtual ~Blitter() = default; template void DrawLineGeneric(int x, int y, int x2, int y2, int screen_width, int screen_height, int width, int dash, SetPixelT set_pixel); }; diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index 442c2fb213..434fc45b45 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -100,7 +100,8 @@ public: { if (widget == WID_BEM_MESSAGE) { *size = GetStringBoundingBox(STR_MISSING_GRAPHICS_ERROR); - size->height = GetStringHeight(STR_MISSING_GRAPHICS_ERROR, size->width - WidgetDimensions::scaled.frametext.Horizontal()) + WidgetDimensions::scaled.frametext.Vertical(); + size->width += WidgetDimensions::scaled.frametext.Horizontal(); + size->height += WidgetDimensions::scaled.frametext.Vertical(); } } diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index 3dc859aa09..6e426c53c6 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -277,9 +277,9 @@ public: switch (widget) { default: break; case WID_BBS_BRIDGE_LIST: { - uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BBS_BRIDGE_LIST); - if (i < this->bridges->size()) { - this->BuildBridge(i); + auto it = this->vscroll->GetScrolledItemFromWidget(*this->bridges, pt.y, this, WID_BBS_BRIDGE_LIST); + if (it != this->bridges->end()) { + this->BuildBridge(it - this->bridges->begin()); delete this; } break; diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 0c29d4c33b..a7f0015002 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -41,6 +41,8 @@ #include "table/strings.h" +#include + #include "safeguards.h" /** @@ -1702,14 +1704,14 @@ struct BuildVehicleWindow : BuildVehicleWindowBase { /* make engines first, and then wagons, sorted by selected sort_criteria */ _engine_sort_direction = false; - EngList_Sort(&list, TrainEnginesThenWagonsSorter); + EngList_Sort(list, TrainEnginesThenWagonsSorter); /* and then sort engines */ _engine_sort_direction = this->descending_sort_order; - EngList_SortPartial(&list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines); + EngList_SortPartial(list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines); /* and finally sort wagons */ - EngList_SortPartial(&list, _engine_sort_functions[0][this->sort_criteria], num_engines, list.size() - num_engines); + EngList_SortPartial(list, _engine_sort_functions[0][this->sort_criteria], num_engines, list.size() - num_engines); } /* Figure out what road vehicle EngineIDs to put in the list */ @@ -1833,7 +1835,7 @@ struct BuildVehicleWindow : BuildVehicleWindowBase { } _engine_sort_direction = this->descending_sort_order; - EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]); + EngList_Sort(this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]); this->eng_list.swap(list); AddChildren(this->eng_list, list, INVALID_ENGINE, 0); @@ -1860,11 +1862,10 @@ struct BuildVehicleWindow : BuildVehicleWindowBase { break; case WID_BV_LIST: { - uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST); - size_t num_items = this->eng_list.size(); EngineID e = INVALID_ENGINE; - if (i < num_items) { - const auto &item = this->eng_list[i]; + const auto it = this->vscroll->GetScrolledItemFromWidget(this->eng_list, pt.y, this, WID_BV_LIST); + if (it != this->eng_list.end()) { + const auto &item = *it; const Rect r = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix).WithWidth(WidgetDimensions::scaled.hsep_indent * (item.indent + 1), _current_text_dir == TD_RTL); if ((item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None && IsInsideMM(r.left, r.right, pt.x)) { /* toggle folded flag on engine */ @@ -2637,7 +2638,7 @@ struct BuildVehicleWindowTrainAdvanced final : BuildVehicleWindowBase { /* Sort */ _engine_sort_direction = state.descending_sort_order; - EngList_Sort(&list, sorters[state.sort_criteria]); + EngList_Sort(list, sorters[state.sort_criteria]); } /* Generate the list of vehicles */ diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 9e75c3146b..f31463b841 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -759,9 +759,9 @@ private: return r < 0; } - void AddChildren(GUIGroupList *source, GroupID parent, int indent) + void AddChildren(GUIGroupList &source, GroupID parent, int indent) { - for (const Group *g : *source) { + for (const Group *g : source) { if (g->parent != parent) continue; this->groups.push_back(g); this->indents.push_back(indent); @@ -793,7 +793,7 @@ private: list.Sort(&GroupNameSorter); - AddChildren(&list, INVALID_GROUP, 0); + AddChildren(list, INVALID_GROUP, 0); } this->groups.shrink_to_fit(); diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index ea0089ef07..5fa8f2d5b8 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -3351,8 +3351,8 @@ DEF_CONSOLE_CMD(ConNewGRFProfile) IConsoleHelp(" Select one or more GRFs for profiling."); IConsoleHelp("Usage: newgrf_profile unselect ..."); IConsoleHelp(" Unselect one or more GRFs from profiling. Use the keyword \"all\" instead of a GRF number to unselect all. Removing an active profiler aborts data collection."); - IConsoleHelp("Usage: newgrf_profile start []"); - IConsoleHelp(" Begin profiling all selected GRFs. If a number of days is provided, profiling stops after that many in-game days."); + IConsoleHelp("Usage: 'newgrf_profile start []':"); + IConsoleHelp(" Begin profiling all selected GRFs. If a number of ticks is provided, profiling stops after that many game ticks. There are 74 ticks in a calendar day."); IConsoleHelp("Usage: newgrf_profile stop"); IConsoleHelp(" End profiling and write the collected data to CSV files."); IConsoleHelp("Usage: newgrf_profile abort"); @@ -3433,15 +3433,9 @@ DEF_CONSOLE_CMD(ConNewGRFProfile) if (started > 0) { IConsolePrintF(CC_DEBUG, "Started profiling for GRFID%s %s", (started > 1) ? "s" : "", grfids.c_str()); if (argc >= 3) { - int days = std::max(atoi(argv[2]), 1); - _newgrf_profile_end_date = _date + days; - - char datestrbuf[32]{ 0 }; - SetDParam(0, _newgrf_profile_end_date); - GetString(datestrbuf, STR_JUST_DATE_ISO, lastof(datestrbuf)); - IConsolePrintF(CC_DEBUG, "Profiling will automatically stop on game date %s", datestrbuf); - } else { - _newgrf_profile_end_date = MAX_DAY; + uint64 ticks = std::max(atoi(argv[2]), 1); + NewGRFProfiler::StartTimer(ticks); + IConsolePrintF(CC_DEBUG, "Profiling will automatically stop after %u ticks.", (uint)ticks); } } else if (_newgrf_profilers.empty()) { IConsolePrintF(CC_WARNING, "No GRFs selected for profiling, did not start."); @@ -3462,7 +3456,7 @@ DEF_CONSOLE_CMD(ConNewGRFProfile) for (NewGRFProfiler &pr : _newgrf_profilers) { pr.Abort(); } - _newgrf_profile_end_date = MAX_DAY; + NewGRFProfiler::AbortTimer(); return true; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ab769a34b1..ac54e02fc0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -29,7 +29,6 @@ add_files( random_func.hpp serialisation.cpp serialisation.hpp - smallmap_type.hpp smallstack_type.hpp smallvec_type.hpp tinystring_type.hpp diff --git a/src/core/alloc_type.hpp b/src/core/alloc_type.hpp index 561f849ed3..ae396eb8bd 100644 --- a/src/core/alloc_type.hpp +++ b/src/core/alloc_type.hpp @@ -86,7 +86,7 @@ class ZeroedMemoryAllocator { public: ZeroedMemoryAllocator() {} - virtual ~ZeroedMemoryAllocator() {} + virtual ~ZeroedMemoryAllocator() = default; /** * Memory allocator for a single class instance. diff --git a/src/core/smallmap_type.hpp b/src/core/smallmap_type.hpp deleted file mode 100644 index d34635a536..0000000000 --- a/src/core/smallmap_type.hpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . - */ - -/** @file smallmap_type.hpp Simple mapping class targeted for small sets of data. Stored data shall be POD ("Plain Old Data")! */ - -#ifndef SMALLMAP_TYPE_HPP -#define SMALLMAP_TYPE_HPP - -#include "smallvec_type.hpp" -#include - -/** - * Implementation of simple mapping class. - * It has inherited accessors from std::vector(). - * @tparam T Key type. - * @tparam U Value type. - * @tparam S Unit of allocation. - * - * @see std::vector - */ -template -struct SmallMap : std::vector > { - typedef std::pair Pair; - typedef Pair *iterator; - typedef const Pair *const_iterator; - - /** Creates new SmallMap. Data are initialized in std::vector constructor */ - inline SmallMap() { } - /** Data are freed in std::vector destructor */ - inline ~SmallMap() { } - - /** - * Finds given key in this map - * @param key key to find - * @return &Pair(key, data) if found, this->End() if not - */ - inline typename std::vector::const_iterator Find(const T &key) const - { - return std::find_if(std::vector::begin(), std::vector::end(), [&key](const Pair &pair) { return key == pair.first; }); - } - - /** - * Finds given key in this map - * @param key key to find - * @return &Pair(key, data) if found, this->End() if not - */ - inline Pair *Find(const T &key) - { - for (uint i = 0; i < std::vector::size(); i++) { - if (key == std::vector::operator[](i).first) return &std::vector::operator[](i); - } - return this->End(); - } - - inline const Pair *End() const - { - return std::vector::data() + std::vector::size(); - } - - inline Pair *End() - { - return std::vector::data() + std::vector::size(); - } - - - /** - * Tests whether a key is assigned in this map. - * @param key key to test - * @return true iff the item is present - */ - inline bool Contains(const T &key) const - { - return this->Find(key) != std::vector::end(); - } - - /** - * Tests whether a key is assigned in this map. - * @param key key to test - * @return true iff the item is present - */ - inline bool Contains(const T &key) - { - return this->Find(key) != this->End(); - } - - /** - * Removes given pair from this map - * @param pair pair to remove - * @note it has to be pointer to pair in this map. It is overwritten by the last item. - */ - inline void Erase(Pair *pair) - { - assert(pair >= std::vector::data() && pair < this->End()); - auto distance = pair - std::vector::data(); - std::vector::erase(std::vector::begin() + distance); - } - - /** - * Removes given key from this map - * @param key key to remove - * @return true iff the key was found - * @note last item is moved to its place, so don't increase your iterator if true is returned! - */ - inline bool Erase(const T &key) - { - Pair *pair = this->Find(key); - if (pair == this->End()) return false; - - this->Erase(pair); - return true; - } - - /** - * Adds new item to this map. - * @param key key - * @param data data - * @return true iff the key wasn't already present - */ - inline bool Insert(const T &key, const U &data) - { - if (this->Contains(key)) return false; - std::vector::emplace_back(key, data); - return true; - } - - /** - * Returns data belonging to this key - * @param key key - * @return data belonging to this key - * @note if this key wasn't present, new entry is created - */ - inline U &operator[](const T &key) - { - for (uint i = 0; i < std::vector::size(); i++) { - if (key == std::vector::operator[](i).first) return std::vector::operator[](i).second; - } - Pair &n = std::vector::emplace_back(); - n.first = key; - return n.second; - } -}; - -#endif /* SMALLMAP_TYPE_HPP */ diff --git a/src/crashlog.cpp b/src/crashlog.cpp index 0fbec485a2..e3811f42b3 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -24,6 +24,7 @@ #include "screenshot.h" #include "gfx_func.h" #include "network/network.h" +#include "network/network_survey.h" #include "language.h" #include "fontcache.h" #include "news_gui.h" @@ -308,12 +309,12 @@ char *CrashLog::LogConfiguration(char *buffer, const char *last) const if (c->ai_info == nullptr) { buffer += seprintf(buffer, last, " %2i: Human\n", (int)c->index); } else { - buffer += seprintf(buffer, last, " %2i: %s (v%d)\n", (int)c->index, c->ai_info->GetName(), c->ai_info->GetVersion()); + buffer += seprintf(buffer, last, " %2i: %s (v%d)\n", (int)c->index, c->ai_info->GetName().c_str(), c->ai_info->GetVersion()); } } if (Game::GetInfo() != nullptr) { - buffer += seprintf(buffer, last, " GS: %s (v%d)\n", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion()); + buffer += seprintf(buffer, last, " GS: %s (v%d)\n", Game::GetInfo()->GetName().c_str(), Game::GetInfo()->GetVersion()); } buffer += seprintf(buffer, last, "\n"); @@ -1173,6 +1174,10 @@ bool CrashLog::MakeCrashSavegameAndScreenshot() const printf("Writing crash screenshot failed.\n\n"); } + if (_game_mode == GM_NORMAL) { + _survey.Transmit(NetworkSurveyHandler::Reason::CRASH, true); + } + return ret; } diff --git a/src/crashlog.h b/src/crashlog.h index 7af22007ba..029e2a8a63 100644 --- a/src/crashlog.h +++ b/src/crashlog.h @@ -161,7 +161,7 @@ public: const char *crash_buffer_write = nullptr; /** Stub destructor to silence some compilers. */ - virtual ~CrashLog() {} + virtual ~CrashLog() = default; char *FillCrashLog(char *buffer, const char *last); void FlushCrashLogBuffer(); diff --git a/src/date.cpp b/src/date.cpp index 79c3f46e6f..3a953fdaa4 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -322,10 +322,6 @@ static void OnNewDay() SetWindowDirty(WC_STATUS_BAR, 0); } - if (!_newgrf_profilers.empty() && _newgrf_profile_end_date <= _date) { - NewGRFProfiler::FinishAll(); - } - if (_network_server) NetworkServerDailyLoop(); DisasterDailyLoop(); diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index 96dd9d65a4..9dd50d8b73 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -445,6 +445,8 @@ struct DepotWindow : Window { DepotGUIAction GetVehicleFromDepotWndPt(int x, int y, const Vehicle **veh, GetDepotVehiclePtData *d) const { const NWidgetCore *matrix_widget = this->GetWidget(WID_D_MATRIX); + /* Make X relative to widget. Y is left alone for GetScrolledRowFromWidget(). */ + x -= matrix_widget->pos_x; /* In case of RTL the widgets are swapped as a whole */ if (_current_text_dir == TD_RTL) x = matrix_widget->current_x - x; @@ -456,12 +458,12 @@ struct DepotWindow : Window { xm = x % this->resize.step_width; if (xt >= this->num_columns) return MODE_ERROR; } - ym = y % this->resize.step_height; + ym = (y - matrix_widget->pos_y) % this->resize.step_height; - uint row = y / this->resize.step_height; - if (row >= this->vscroll->GetCapacity()) return MODE_ERROR; + int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_D_MATRIX); + if (row == INT_MAX) return MODE_ERROR; - uint pos = ((row + this->vscroll->GetPosition()) * this->num_columns) + xt; + uint pos = (row * this->num_columns) + xt; if (this->vehicle_list.size() + this->wagon_list.size() <= pos) { /* Clicking on 'line' / 'block' without a vehicle */ @@ -762,11 +764,9 @@ struct DepotWindow : Window { void OnClick(Point pt, int widget, int click_count) override { switch (widget) { - case WID_D_MATRIX: { // List - NWidgetBase *nwi = this->GetWidget(WID_D_MATRIX); - this->DepotClick(pt.x - nwi->pos_x, pt.y - nwi->pos_y); + case WID_D_MATRIX: // List + this->DepotClick(pt.x, pt.y); break; - } case WID_D_BUILD: // Build vehicle ResetObjectToPlace(); @@ -849,8 +849,7 @@ struct DepotWindow : Window { GetDepotVehiclePtData gdvp = { nullptr, nullptr }; const Vehicle *v = nullptr; - NWidgetBase *nwi = this->GetWidget(WID_D_MATRIX); - DepotGUIAction mode = this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp); + DepotGUIAction mode = this->GetVehicleFromDepotWndPt(pt.x, pt.y, &v, &gdvp); if (this->type == VEH_TRAIN) v = gdvp.wagon; @@ -1021,11 +1020,10 @@ struct DepotWindow : Window { return; } - NWidgetBase *matrix = this->GetWidget(widget); const Vehicle *v = nullptr; GetDepotVehiclePtData gdvp = {nullptr, nullptr}; - if (this->GetVehicleFromDepotWndPt(pt.x - matrix->pos_x, pt.y - matrix->pos_y, &v, &gdvp) != MODE_DRAG_VEHICLE) return; + if (this->GetVehicleFromDepotWndPt(pt.x, pt.y, &v, &gdvp) != MODE_DRAG_VEHICLE) return; VehicleID new_vehicle_over = INVALID_VEHICLE; if (gdvp.head != nullptr) { @@ -1058,11 +1056,10 @@ struct DepotWindow : Window { this->sel = INVALID_VEHICLE; this->SetDirty(); - NWidgetBase *nwi = this->GetWidget(WID_D_MATRIX); if (this->type == VEH_TRAIN) { GetDepotVehiclePtData gdvp = { nullptr, nullptr }; - if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, &gdvp) == MODE_DRAG_VEHICLE && sel != INVALID_VEHICLE) { + if (this->GetVehicleFromDepotWndPt(pt.x, pt.y, &v, &gdvp) == MODE_DRAG_VEHICLE && sel != INVALID_VEHICLE) { if (gdvp.wagon != nullptr && gdvp.wagon->index == sel && _ctrl_pressed) { DoCommandP(Vehicle::Get(sel)->tile, Vehicle::Get(sel)->index, true, CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE)); @@ -1073,7 +1070,7 @@ struct DepotWindow : Window { ShowVehicleViewWindow(gdvp.head); } } - } else if (this->GetVehicleFromDepotWndPt(pt.x - nwi->pos_x, pt.y - nwi->pos_y, &v, nullptr) == MODE_DRAG_VEHICLE && v != nullptr && sel == v->index) { + } else if (this->GetVehicleFromDepotWndPt(pt.x, pt.y, &v, nullptr) == MODE_DRAG_VEHICLE && v != nullptr && sel == v->index) { ShowVehicleViewWindow(v); } break; diff --git a/src/driver.h b/src/driver.h index 56cccef22d..4caf825c88 100644 --- a/src/driver.h +++ b/src/driver.h @@ -33,7 +33,7 @@ public: */ virtual void Stop() = 0; - virtual ~Driver() { } + virtual ~Driver() = default; /** The type of driver */ enum Type { diff --git a/src/engine_gui.cpp b/src/engine_gui.cpp index e2d8d9980d..f19a369a44 100644 --- a/src/engine_gui.cpp +++ b/src/engine_gui.cpp @@ -323,10 +323,10 @@ void DrawVehicleEngine(int left, int right, int preferred_x, int y, EngineID eng * @param el list to be sorted * @param compare function for evaluation of the quicksort */ -void EngList_Sort(GUIEngineList *el, EngList_SortTypeFunction compare) +void EngList_Sort(GUIEngineList &el, EngList_SortTypeFunction compare) { - if (el->size() < 2) return; - std::sort(el->begin(), el->end(), compare); + if (el.size() < 2) return; + std::sort(el.begin(), el.end(), compare); } /** @@ -336,11 +336,11 @@ void EngList_Sort(GUIEngineList *el, EngList_SortTypeFunction compare) * @param begin start of sorting * @param num_items count of items to be sorted */ -void EngList_SortPartial(GUIEngineList *el, EngList_SortTypeFunction compare, size_t begin, size_t num_items) +void EngList_SortPartial(GUIEngineList &el, EngList_SortTypeFunction compare, size_t begin, size_t num_items) { if (num_items < 2) return; - assert(begin < el->size()); - assert(begin + num_items <= el->size()); - std::sort(el->begin() + begin, el->begin() + begin + num_items, compare); + assert(begin < el.size()); + assert(begin + num_items <= el.size()); + std::sort(el.begin() + begin, el.begin() + begin + num_items, compare); } diff --git a/src/engine_gui.h b/src/engine_gui.h index 9667e884d5..b89a3f7404 100644 --- a/src/engine_gui.h +++ b/src/engine_gui.h @@ -32,8 +32,8 @@ struct GUIEngineListItem { typedef GUIList GUIEngineList; typedef bool EngList_SortTypeFunction(const GUIEngineListItem&, const GUIEngineListItem&); ///< argument type for #EngList_Sort. -void EngList_Sort(GUIEngineList *el, EngList_SortTypeFunction compare); -void EngList_SortPartial(GUIEngineList *el, EngList_SortTypeFunction compare, size_t begin, size_t num_items); +void EngList_Sort(GUIEngineList &el, EngList_SortTypeFunction compare); +void EngList_SortPartial(GUIEngineList &el, EngList_SortTypeFunction compare, size_t begin, size_t num_items); StringID GetEngineCategoryName(EngineID engine); StringID GetEngineInfoString(EngineID engine); diff --git a/src/fileio.cpp b/src/fileio.cpp index c21af0d614..3d2e6dc11a 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -1259,7 +1259,7 @@ uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool r * @return the number of found files, i.e. the number of times that * AddFile returned true. */ -uint FileScanner::Scan(const char *extension, const char *directory, bool recursive) +uint FileScanner::Scan(const char *extension, const std::string &directory, bool recursive) { std::string path(directory); AppendPathSeparator(path); diff --git a/src/fileio_func.h b/src/fileio_func.h index ffce5b7a3d..9684650dcf 100644 --- a/src/fileio_func.h +++ b/src/fileio_func.h @@ -41,10 +41,10 @@ protected: Subdirectory subdir; ///< The current sub directory we are searching through public: /** Destruct the proper one... */ - virtual ~FileScanner() {} + virtual ~FileScanner() = default; uint Scan(const char *extension, Subdirectory sd, bool tars = true, bool recursive = true); - uint Scan(const char *extension, const char *directory, bool recursive = true); + uint Scan(const char *extension, const std::string &directory, bool recursive = true); /** * Add a file with the given filename. diff --git a/src/fios.cpp b/src/fios.cpp index a55df13bfe..1a03be0f93 100644 --- a/src/fios.cpp +++ b/src/fios.cpp @@ -412,7 +412,7 @@ static void FiosGetFileList(SaveLoadOperation fop, fios_getlist_callback_proc *c /* Show files */ FiosFileScanner scanner(fop, callback_proc, file_list); if (subdir == NO_DIRECTORY) { - scanner.Scan(nullptr, _fios_path->c_str(), false); + scanner.Scan(nullptr, *_fios_path, false); } else { scanner.Scan(nullptr, subdir, true, true); } diff --git a/src/fios.h b/src/fios.h index cf75dd812d..2742549b71 100644 --- a/src/fios.h +++ b/src/fios.h @@ -23,7 +23,7 @@ enum SaveLoadInvalidateWindowData { SLIWD_FILTER_CHANGES, ///< The filename filter has changed (via the editbox) }; -typedef SmallMap CompanyPropertiesMap; +using CompanyPropertiesMap = std::map>; /** * Container for loading in mode SL_LOAD_CHECK. diff --git a/src/fios_gui.cpp b/src/fios_gui.cpp index b9ede19dac..f78543b785 100644 --- a/src/fios_gui.cpp +++ b/src/fios_gui.cpp @@ -55,9 +55,6 @@ void LoadCheckData::Clear() this->current_date = 0; this->settings = {}; - for (auto &pair : this->companies) { - delete pair.second; - } companies.clear(); GamelogFree(this->gamelog_action, this->gamelog_actions); diff --git a/src/fontcache/truetypefontcache.cpp b/src/fontcache/truetypefontcache.cpp index f6f389b7fc..330bdb09d6 100644 --- a/src/fontcache/truetypefontcache.cpp +++ b/src/fontcache/truetypefontcache.cpp @@ -167,14 +167,14 @@ const Sprite *TrueTypeFontCache::GetGlyph(GlyphID key) const void *TrueTypeFontCache::GetFontTable(uint32 tag, size_t &length) { - const FontTable::iterator iter = this->font_tables.Find(tag); - if (iter != this->font_tables.data() + this->font_tables.size()) { + const auto iter = this->font_tables.find(tag); + if (iter != this->font_tables.end()) { length = iter->second.first; return iter->second.second; } const void *result = this->InternalGetFontTable(tag, length); - this->font_tables.Insert(tag, std::pair(length, result)); + this->font_tables[tag] = std::pair(length, result); return result; } diff --git a/src/fontcache/truetypefontcache.h b/src/fontcache/truetypefontcache.h index 8f44ee9534..6bb2e46a90 100644 --- a/src/fontcache/truetypefontcache.h +++ b/src/fontcache/truetypefontcache.h @@ -10,8 +10,8 @@ #ifndef TRUETYPEFONTCACHE_H #define TRUETYPEFONTCACHE_H -#include "../core/smallmap_type.hpp" #include "../fontcache.h" +#include "../3rdparty/cpp-btree/btree_map.h" static const int MAX_FONT_SIZE = 72; ///< Maximum font size. @@ -28,7 +28,7 @@ protected: int req_size; ///< Requested font size. int used_size; ///< Used font size. - typedef SmallMap > FontTable; ///< Table with font table cache + using FontTable = btree::btree_map>; ///< Table with font table cache FontTable font_tables; ///< Cached font tables. /** Container for information about a glyph. */ diff --git a/src/framerate_gui.cpp b/src/framerate_gui.cpp index 7bd32a533a..02245c9c64 100644 --- a/src/framerate_gui.cpp +++ b/src/framerate_gui.cpp @@ -362,7 +362,7 @@ static const PerformanceElement DISPLAY_ORDER_PFE[PFE_MAX] = { static const char * GetAIName(int ai_index) { if (!Company::IsValidAiID(ai_index)) return ""; - return Company::Get(ai_index)->ai_info->GetName(); + return Company::Get(ai_index)->ai_info->GetName().c_str(); } /** @hideinitializer */ diff --git a/src/game/game.hpp b/src/game/game.hpp index 9e92130f10..3c8fcdad92 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -91,9 +91,9 @@ public: /** Wrapper function for GameScanner::GetUniqueInfoList */ static const ScriptInfoList *GetUniqueInfoList(); /** Wrapper function for GameScannerInfo::FindInfo */ - static class GameInfo *FindInfo(const char *name, int version, bool force_exact_match); + static class GameInfo *FindInfo(const std::string &name, int version, bool force_exact_match); /** Wrapper function for GameScanner::FindLibrary */ - static class GameLibrary *FindLibrary(const char *library, int version); + static class GameLibrary *FindLibrary(const std::string &library, int version); /** * Get the current active instance. diff --git a/src/game/game_config.cpp b/src/game/game_config.cpp index 89283129e7..28b6d281b0 100644 --- a/src/game/game_config.cpp +++ b/src/game/game_config.cpp @@ -32,7 +32,7 @@ class GameInfo *GameConfig::GetInfo() const return static_cast(ScriptConfig::GetInfo()); } -ScriptInfo *GameConfig::FindInfo(const char *name, int version, bool force_exact_match) +ScriptInfo *GameConfig::FindInfo(const std::string &name, int version, bool force_exact_match) { return static_cast(Game::FindInfo(name, version, force_exact_match)); } diff --git a/src/game/game_config.hpp b/src/game/game_config.hpp index a01128e671..41385035d2 100644 --- a/src/game/game_config.hpp +++ b/src/game/game_config.hpp @@ -40,7 +40,7 @@ public: bool ResetInfo(bool force_exact_match); protected: - ScriptInfo *FindInfo(const char *name, int version, bool force_exact_match) override; + ScriptInfo *FindInfo(const std::string &name, int version, bool force_exact_match) override; }; #endif /* GAME_CONFIG_HPP */ diff --git a/src/game/game_core.cpp b/src/game/game_core.cpp index 6467309963..764657f50d 100644 --- a/src/game/game_core.cpp +++ b/src/game/game_core.cpp @@ -171,8 +171,8 @@ * the GameConfig. If not, remove the Game from the list. */ if (_settings_game.game_config != nullptr && _settings_game.game_config->HasScript()) { if (!_settings_game.game_config->ResetInfo(true)) { - DEBUG(script, 0, "After a reload, the GameScript by the name '%s' was no longer found, and removed from the list.", _settings_game.game_config->GetName()); - _settings_game.game_config->Change(nullptr); + DEBUG(script, 0, "After a reload, the GameScript by the name '%s' was no longer found, and removed from the list.", _settings_game.game_config->GetName().c_str()); + _settings_game.game_config->Change(std::nullopt); if (Game::instance != nullptr) { delete Game::instance; Game::instance = nullptr; @@ -184,8 +184,8 @@ } if (_settings_newgame.game_config != nullptr && _settings_newgame.game_config->HasScript()) { if (!_settings_newgame.game_config->ResetInfo(false)) { - DEBUG(script, 0, "After a reload, the GameScript by the name '%s' was no longer found, and removed from the list.", _settings_newgame.game_config->GetName()); - _settings_newgame.game_config->Change(nullptr); + DEBUG(script, 0, "After a reload, the GameScript by the name '%s' was no longer found, and removed from the list.", _settings_newgame.game_config->GetName().c_str()); + _settings_newgame.game_config->Change(std::nullopt); } } } @@ -236,12 +236,12 @@ return Game::scanner_info->GetUniqueInfoList(); } -/* static */ GameInfo *Game::FindInfo(const char *name, int version, bool force_exact_match) +/* static */ GameInfo *Game::FindInfo(const std::string &name, int version, bool force_exact_match) { return Game::scanner_info->FindInfo(name, version, force_exact_match); } -/* static */ GameLibrary *Game::FindLibrary(const char *library, int version) +/* static */ GameLibrary *Game::FindLibrary(const std::string &library, int version) { return Game::scanner_library->FindLibrary(library, version); } diff --git a/src/game/game_gui.cpp b/src/game/game_gui.cpp index 3226ddf36a..1ea72afd56 100644 --- a/src/game/game_gui.cpp +++ b/src/game/game_gui.cpp @@ -246,7 +246,7 @@ struct GSConfigWindow : public Window { void OnClick(Point pt, int widget, int click_count) override { - if (widget >= WID_GSC_TEXTFILE && widget < WID_GSC_TEXTFILE + TFT_END) { + if (widget >= WID_GSC_TEXTFILE && widget < WID_GSC_TEXTFILE + TFT_CONTENT_END) { if (GameConfig::GetConfig() == nullptr) return; ShowScriptTextfileWindow((TextfileType)(widget - WID_GSC_TEXTFILE), (CompanyID)OWNER_DEITY); @@ -273,13 +273,13 @@ struct GSConfigWindow : public Window { break; case WID_GSC_SETTINGS: { - Rect r = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero); - int num = (pt.y - r.top) / this->line_height + this->vscroll->GetPosition(); - if (num >= (int)this->visible_settings.size()) break; + auto it = this->vscroll->GetScrolledItemFromWidget(this->visible_settings, pt.y, this, widget); + if (it == this->visible_settings.end()) break; - const ScriptConfigItem &config_item = *this->visible_settings[num]; + const ScriptConfigItem &config_item = **it; if (!this->IsEditableItem(config_item)) return; + int num = it - this->visible_settings.begin(); if (this->clicked_row != num) { this->DeleteChildWindows(WC_QUERY_STRING); HideDropDownMenu(this); @@ -289,6 +289,7 @@ struct GSConfigWindow : public Window { bool bool_item = (config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0; + Rect r = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero); int x = pt.x - r.left; if (_current_text_dir == TD_RTL) x = r.Width() - 1 - x; @@ -413,7 +414,7 @@ struct GSConfigWindow : public Window { this->SetWidgetDisabledState(WID_GSC_CHANGE, !UserIsAllowedToChangeGameScript() || !IsEditable()); - for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) { + for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { this->SetWidgetDisabledState(WID_GSC_TEXTFILE + tft, GameConfig::GetConfig()->GetTextfile(tft, (CompanyID)OWNER_DEITY) == nullptr); } this->RebuildVisibleSettings(); diff --git a/src/game/game_info.cpp b/src/game/game_info.cpp index 96e1284c09..cd07b8b028 100644 --- a/src/game/game_info.cpp +++ b/src/game/game_info.cpp @@ -21,7 +21,7 @@ * Check if the API version provided by the Game is supported. * @param api_version The API version as provided by the Game. */ -static bool CheckAPIVersion(const char *api_version) +static bool CheckAPIVersion(const std::string &api_version) { static const std::set versions = { "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12", "13", "14" }; return versions.find(api_version) != versions.end(); @@ -60,22 +60,22 @@ template <> const char *GetClassName() { return "GSInf SQInteger res = ScriptInfo::Constructor(vm, info); if (res != 0) return res; - if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) { - if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_OPS)) return SQ_ERROR; + if (info->engine->MethodExists(info->SQ_instance, "MinVersionToLoad")) { + if (!info->engine->CallIntegerMethod(info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_OPS)) return SQ_ERROR; } else { info->min_loadable_version = info->GetVersion(); } /* When there is an IsSelectable function, call it. */ - if (info->engine->MethodExists(*info->SQ_instance, "IsDeveloperOnly")) { - if (!info->engine->CallBoolMethod(*info->SQ_instance, "IsDeveloperOnly", &info->is_developer_only, MAX_GET_OPS)) return SQ_ERROR; + if (info->engine->MethodExists(info->SQ_instance, "IsDeveloperOnly")) { + if (!info->engine->CallBoolMethod(info->SQ_instance, "IsDeveloperOnly", &info->is_developer_only, MAX_GET_OPS)) return SQ_ERROR; } else { info->is_developer_only = false; } /* Try to get the API version the AI is written for. */ if (!info->CheckMethod("GetAPIVersion")) return SQ_ERROR; - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAPIVersion", &info->api_version, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "GetAPIVersion", &info->api_version, MAX_GET_OPS)) return SQ_ERROR; if (!CheckAPIVersion(info->api_version)) { - DEBUG(script, 1, "Loading info.nut from (%s.%d): GetAPIVersion returned invalid version", info->GetName(), info->GetVersion()); + DEBUG(script, 1, "Loading info.nut from (%s.%d): GetAPIVersion returned invalid version", info->GetName().c_str(), info->GetVersion()); return SQ_ERROR; } @@ -88,16 +88,10 @@ template <> const char *GetClassName() { return "GSInf GameInfo::GameInfo() : min_loadable_version(0), - is_developer_only(false), - api_version(nullptr) + is_developer_only(false) { } -GameInfo::~GameInfo() -{ - free(this->api_version); -} - bool GameInfo::CanLoadFromVersion(int version) const { if (version == -1) return true; @@ -105,11 +99,6 @@ bool GameInfo::CanLoadFromVersion(int version) const } -GameLibrary::~GameLibrary() -{ - free(this->category); -} - /* static */ void GameLibrary::RegisterAPI(Squirrel *engine) { /* Create the GameLibrary class, and add the RegisterLibrary function */ @@ -130,7 +119,7 @@ GameLibrary::~GameLibrary() } /* Cache the category */ - if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethodStrdup(*library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { + if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { delete library; return SQ_ERROR; } diff --git a/src/game/game_info.hpp b/src/game/game_info.hpp index cfa900767c..d5dd48701d 100644 --- a/src/game/game_info.hpp +++ b/src/game/game_info.hpp @@ -16,7 +16,6 @@ class GameInfo : public ScriptInfo { public: GameInfo(); - ~GameInfo(); /** * Register the functions of this class. @@ -36,21 +35,20 @@ public: /** * Get the API version this Game is written for. */ - const char *GetAPIVersion() const { return this->api_version; } + const std::string &GetAPIVersion() const { return this->api_version; } bool IsDeveloperOnly() const override { return this->is_developer_only; } private: int min_loadable_version; ///< The Game can load savegame data if the version is equal or greater than this. bool is_developer_only; ///< Is the script selectable by non-developers? - const char *api_version; ///< API version used by this Game. + std::string api_version; ///< API version used by this Game. }; /** All static information from an Game library like name, version, etc. */ class GameLibrary : public ScriptInfo { public: - GameLibrary() : ScriptInfo(), category(nullptr) {}; - ~GameLibrary(); + GameLibrary() : ScriptInfo() {}; /** * Register the functions of this class. @@ -65,10 +63,10 @@ public: /** * Get the category this library is in. */ - const char *GetCategory() const { return this->category; } + const std::string &GetCategory() const { return this->category; } private: - const char *category; ///< The category this library is in. + std::string category; ///< The category this library is in. }; #endif /* GAME_INFO_HPP */ diff --git a/src/game/game_instance.cpp b/src/game/game_instance.cpp index 82cbc0cdcb..985b50fc75 100644 --- a/src/game/game_instance.cpp +++ b/src/game/game_instance.cpp @@ -52,12 +52,12 @@ void GameInstance::RegisterAPI() if (!this->LoadCompatibilityScripts(this->versionAPI, GAME_DIR)) this->Died(); } -int GameInstance::GetSetting(const char *name) +int GameInstance::GetSetting(const std::string &name) { return GameConfig::GetConfig()->GetSetting(name); } -ScriptInfo *GameInstance::FindLibrary(const char *library, int version) +ScriptInfo *GameInstance::FindLibrary(const std::string &library, int version) { return (ScriptInfo *)Game::FindLibrary(library, version); } @@ -72,7 +72,7 @@ void GameInstance::Died() if (info != nullptr) { ShowErrorMessage(STR_ERROR_AI_PLEASE_REPORT_CRASH, INVALID_STRING_ID, WL_WARNING); - if (info->GetURL() != nullptr) { + if (!info->GetURL().empty()) { ScriptLog::Info("Please report the error to the following URL:"); ScriptLog::Info(info->GetURL()); } diff --git a/src/game/game_instance.hpp b/src/game/game_instance.hpp index 7b3b12b379..238ddad9e9 100644 --- a/src/game/game_instance.hpp +++ b/src/game/game_instance.hpp @@ -23,8 +23,8 @@ public: */ void Initialize(class GameInfo *info); - int GetSetting(const char *name) override; - ScriptInfo *FindLibrary(const char *library, int version) override; + int GetSetting(const std::string &name) override; + ScriptInfo *FindLibrary(const std::string &library, int version) override; private: void RegisterAPI() override; diff --git a/src/game/game_scanner.cpp b/src/game/game_scanner.cpp index 85b2a77ea5..465392e698 100644 --- a/src/game/game_scanner.cpp +++ b/src/game/game_scanner.cpp @@ -33,10 +33,10 @@ void GameScannerInfo::RegisterAPI(class Squirrel *engine) GameInfo::RegisterAPI(engine); } -GameInfo *GameScannerInfo::FindInfo(const char *name, int version, bool force_exact_match) +GameInfo *GameScannerInfo::FindInfo(const std::string &name, int version, bool force_exact_match) { if (this->info_list.size() == 0) return nullptr; - if (name == nullptr) return nullptr; + if (name.empty()) return nullptr; if (version == -1) { /* We want to load the latest version of this Game script; so find it */ @@ -86,7 +86,7 @@ void GameScannerLibrary::RegisterAPI(class Squirrel *engine) GameLibrary::RegisterAPI(engine); } -GameLibrary *GameScannerLibrary::FindLibrary(const char *library, int version) +GameLibrary *GameScannerLibrary::FindLibrary(const std::string &library, int version) { /* Internally we store libraries as 'library.version' */ std::string library_name = fmt::format("{}.{}", library, version); diff --git a/src/game/game_scanner.hpp b/src/game/game_scanner.hpp index c0628fc6a7..027649b1ad 100644 --- a/src/game/game_scanner.hpp +++ b/src/game/game_scanner.hpp @@ -23,7 +23,7 @@ public: * @param force_exact_match Only match name+version, never latest. * @return nullptr if no match found, otherwise the game script that matched. */ - class GameInfo *FindInfo(const char *name, int version, bool force_exact_match); + class GameInfo *FindInfo(const std::string &name, int version, bool force_exact_match); protected: std::string GetScriptName(ScriptInfo *info) override; @@ -44,7 +44,7 @@ public: * @param version The version the library should have. * @return The library if found, nullptr otherwise. */ - class GameLibrary *FindLibrary(const char *library, int version); + class GameLibrary *FindLibrary(const std::string &library, int version); protected: std::string GetScriptName(ScriptInfo *info) override; diff --git a/src/gamelog.cpp b/src/gamelog.cpp index 1ce896e2d4..a13ba8553e 100644 --- a/src/gamelog.cpp +++ b/src/gamelog.cpp @@ -16,6 +16,7 @@ #include "debug.h" #include "date_func.h" #include "rev.h" +#include "3rdparty/cpp-btree/btree_map.h" #include @@ -160,7 +161,7 @@ struct GRFPresence{ GRFPresence(const GRFConfig *gc) : gc(gc), was_missing(false) {} GRFPresence() = default; }; -typedef SmallMap GrfIDMapping; +typedef btree::btree_map GrfIDMapping; /** * Prints active gamelog @@ -249,25 +250,25 @@ void GamelogPrint(GamelogPrintProc *proc) const GRFConfig *gc = FindGRFConfig(lc->grfadd.grfid, FGCM_EXACT, lc->grfadd.md5sum); buf += seprintf(buf, lastof(buffer), "Added NewGRF: "); buf = PrintGrfInfo(buf, lastof(buffer), lc->grfadd.grfid, lc->grfadd.md5sum, gc); - GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid); - if (gm != grf_names.End() && !gm->second.was_missing) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was already added!"); + auto gm = grf_names.find(lc->grfrem.grfid); + if (gm != grf_names.end() && !gm->second.was_missing) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was already added!"); grf_names[lc->grfadd.grfid] = gc; break; } case GLCT_GRFREM: { /* A NewGRF got removed from the game, either manually or by it missing when loading the game. */ - GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid); + auto gm = grf_names.find(lc->grfrem.grfid); buf += seprintf(buf, lastof(buffer), la->at == GLAT_LOAD ? "Missing NewGRF: " : "Removed NewGRF: "); - buf = PrintGrfInfo(buf, lastof(buffer), lc->grfrem.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr); - if (gm == grf_names.End()) { + buf = PrintGrfInfo(buf, lastof(buffer), lc->grfrem.grfid, nullptr, gm != grf_names.end() ? gm->second.gc : nullptr); + if (gm == grf_names.end()) { buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); } else { if (la->at == GLAT_LOAD) { /* Missing grfs on load are not removed from the configuration */ gm->second.was_missing = true; } else { - grf_names.Erase(gm); + grf_names.erase(gm); } } break; @@ -278,38 +279,38 @@ void GamelogPrint(GamelogPrintProc *proc) const GRFConfig *gc = FindGRFConfig(lc->grfadd.grfid, FGCM_EXACT, lc->grfadd.md5sum); buf += seprintf(buf, lastof(buffer), "Compatible NewGRF loaded: "); buf = PrintGrfInfo(buf, lastof(buffer), lc->grfcompat.grfid, lc->grfcompat.md5sum, gc); - if (!grf_names.Contains(lc->grfcompat.grfid)) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); + if (grf_names.find(lc->grfcompat.grfid) == grf_names.end()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); grf_names[lc->grfcompat.grfid] = gc; break; } case GLCT_GRFPARAM: { /* A parameter of a NewGRF got changed after the game was started. */ - GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid); + auto gm = grf_names.find(lc->grfrem.grfid); buf += seprintf(buf, lastof(buffer), "GRF parameter changed: "); - buf = PrintGrfInfo(buf, lastof(buffer), lc->grfparam.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr); - if (gm == grf_names.End()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); + buf = PrintGrfInfo(buf, lastof(buffer), lc->grfparam.grfid, nullptr, gm != grf_names.end() ? gm->second.gc : nullptr); + if (gm == grf_names.end()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); break; } case GLCT_GRFMOVE: { /* The order of NewGRFs got changed, which might cause some other NewGRFs to behave differently. */ - GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid); + auto gm = grf_names.find(lc->grfrem.grfid); buf += seprintf(buf, lastof(buffer), "GRF order changed: %08X moved %d places %s", BSWAP32(lc->grfmove.grfid), abs(lc->grfmove.offset), lc->grfmove.offset >= 0 ? "down" : "up" ); - buf = PrintGrfInfo(buf, lastof(buffer), lc->grfmove.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr); - if (gm == grf_names.End()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); + buf = PrintGrfInfo(buf, lastof(buffer), lc->grfmove.grfid, nullptr, gm != grf_names.end() ? gm->second.gc : nullptr); + if (gm == grf_names.end()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); break; } case GLCT_GRFBUG: { /* A specific bug in a NewGRF, that could cause wide spread problems, has been noted during the execution of the game. */ - GrfIDMapping::Pair *gm = grf_names.Find(lc->grfrem.grfid); + auto gm = grf_names.find(lc->grfrem.grfid); assert (lc->grfbug.bug == GBUG_VEH_LENGTH); buf += seprintf(buf, lastof(buffer), "Rail vehicle changes length outside a depot: GRF ID %08X, internal ID 0x%X", BSWAP32(lc->grfbug.grfid), (uint)lc->grfbug.data); - buf = PrintGrfInfo(buf, lastof(buffer), lc->grfbug.grfid, nullptr, gm != grf_names.End() ? gm->second.gc : nullptr); - if (gm == grf_names.End()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); + buf = PrintGrfInfo(buf, lastof(buffer), lc->grfbug.grfid, nullptr, gm != grf_names.end() ? gm->second.gc : nullptr); + if (gm == grf_names.end()) buf += seprintf(buf, lastof(buffer), ". Gamelog inconsistency: GrfID was never added!"); break; } diff --git a/src/gfx.cpp b/src/gfx.cpp index 496077eb9c..1ec50ed891 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -58,6 +58,7 @@ bool _check_special_modes; std::atomic _exit_game; GameMode _game_mode; SwitchMode _switch_mode; ///< The next mainloop command. +std::chrono::steady_clock::time_point _switch_mode_time; ///< The time when the switch mode was requested. PauseMode _pause_mode; uint32 _pause_countdown; Palette _cur_palette; diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 2c4e0d3ed4..73420b0add 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -103,8 +103,8 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s continue; } - if (!fontMapping.Contains(buff - buff_begin)) { - fontMapping.Insert(buff - buff_begin, f); + if (fontMapping.count(buff - buff_begin) == 0) { + fontMapping[buff - buff_begin] = f; } f = Layouter::GetFont(state.fontsize, state.cur_colour); } @@ -112,8 +112,8 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s /* Better safe than sorry. */ *buff = '\0'; - if (!fontMapping.Contains(buff - buff_begin)) { - fontMapping.Insert(buff - buff_begin, f); + if (fontMapping.count(buff - buff_begin) == 0) { + fontMapping[buff - buff_begin] = f; } line.layout = T::GetParagraphLayout(buff_begin, buff, fontMapping); line.state_after = state; @@ -296,12 +296,11 @@ ptrdiff_t Layouter::GetCharAtPosition(int x) const */ Font *Layouter::GetFont(FontSize size, TextColour colour) { - FontColourMap::iterator it = fonts[size].Find(colour); - if (it != fonts[size].End()) return it->second; + FontColourMap::iterator it = fonts[size].find(colour); + if (it != fonts[size].end()) return it->second.get(); - Font *f = new Font(size, colour); - fonts[size].emplace_back(colour, f); - return f; + fonts[size][colour] = std::make_unique(size, colour); + return fonts[size][colour].get(); } /** @@ -310,9 +309,6 @@ Font *Layouter::GetFont(FontSize size, TextColour colour) */ void Layouter::ResetFontCache(FontSize size) { - for (auto &pair : fonts[size]) { - delete pair.second; - } fonts[size].clear(); /* We must reset the linecache since it references the just freed fonts */ diff --git a/src/gfx_layout.h b/src/gfx_layout.h index a227cd1db4..0f6a0ea655 100644 --- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -12,7 +12,7 @@ #include "fontcache.h" #include "gfx_func.h" -#include "core/smallmap_type.hpp" +#include "core/math_func.hpp" #include #include @@ -84,20 +84,20 @@ public: Font(FontSize size, TextColour colour); }; -/** Mapping from index to font. */ -typedef SmallMap FontMap; +/** Mapping from index to font. The pointer is owned by FontColourMap. */ +using FontMap = std::map; /** * Interface to glue fallback and normal layouter into one. */ class ParagraphLayouter { public: - virtual ~ParagraphLayouter() {} + virtual ~ParagraphLayouter() = default; /** Visual run contains data about the bit of text with the same font. */ class VisualRun { public: - virtual ~VisualRun() {} + virtual ~VisualRun() = default; virtual const Font *GetFont() const = 0; virtual int GetGlyphCount() const = 0; virtual const GlyphID *GetGlyphs() const = 0; @@ -109,7 +109,7 @@ public: /** A single line worth of VisualRuns. */ class Line { public: - virtual ~Line() {} + virtual ~Line() = default; virtual int GetLeading() const = 0; virtual int GetWidth() const = 0; virtual int CountRuns() const = 0; @@ -173,7 +173,7 @@ private: static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state); - typedef SmallMap FontColourMap; + using FontColourMap = std::map>; static FontColourMap fonts[FS_END]; public: static Font *GetFont(FontSize size, TextColour colour); diff --git a/src/gfx_layout_fallback.cpp b/src/gfx_layout_fallback.cpp index dbb2f0cc28..f16c8551ef 100644 --- a/src/gfx_layout_fallback.cpp +++ b/src/gfx_layout_fallback.cpp @@ -266,7 +266,7 @@ const ParagraphLayouter::VisualRun &FallbackParagraphLayout::FallbackLine::GetVi */ FallbackParagraphLayout::FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs) : buffer_begin(buffer), buffer(buffer), runs(runs) { - assert(runs.End()[-1].first == length); + assert(runs.rbegin()->first == length); } /** @@ -295,15 +295,15 @@ std::unique_ptr FallbackParagraphLayout::NextLine if (*this->buffer == '\0') { /* Only a newline. */ this->buffer = nullptr; - l->emplace_back(this->runs.front().second, this->buffer, 0, 0); + l->emplace_back(this->runs.begin()->second, this->buffer, 0, 0); return l; } int offset = this->buffer - this->buffer_begin; - FontMap::iterator iter = this->runs.data(); + FontMap::iterator iter = this->runs.begin(); while (iter->first <= offset) { - iter++; - assert(iter != this->runs.End()); + ++iter; + assert(iter != this->runs.end()); } const FontCache *fc = iter->second->fc; @@ -325,8 +325,8 @@ std::unique_ptr FallbackParagraphLayout::NextLine if (this->buffer == next_run) { int w = l->GetWidth(); l->emplace_back(iter->second, begin, this->buffer - begin, w); - iter++; - assert(iter != this->runs.End()); + ++iter; + assert(iter != this->runs.end()); next_run = this->buffer_begin + iter->first; begin = this->buffer; diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index 0bfe56e874..051e9f234b 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -1349,13 +1349,9 @@ struct PaymentRatesGraphWindow : BaseGraphWindow { } case WID_CPR_MATRIX: { - uint row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_CPR_MATRIX); - if (row >= this->vscroll->GetCount()) return; - - for (const CargoSpec *cs : _sorted_standard_cargo_specs) { - if (row-- > 0) continue; - - ToggleBit(_legend_excluded_cargo, cs->Index()); + auto it = this->vscroll->GetScrolledItemFromWidget(_sorted_standard_cargo_specs, pt.y, this, WID_CPR_MATRIX); + if (it != _sorted_standard_cargo_specs.end()) { + ToggleBit(_legend_excluded_cargo, (*it)->Index()); this->UpdateExcludedData(); this->UpdateCargoExcludingGraphs(); break; diff --git a/src/group_gui.cpp b/src/group_gui.cpp index b3e2d34c32..f6325681a2 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -172,17 +172,15 @@ private: Money money_last_year; uint32 occupancy_ratio; - /** return true if group has children */ - void AddChildren(GUIGroupList *source, GroupID parent, int indent) + void AddChildren(GUIGroupList &source, GroupID parent, int indent) { - for (const Group *g : *source) { + for (const Group *g : source) { if (g->parent != parent) continue; this->groups.push_back(g); this->indents.push_back(indent); if (g->folded) { /* Test if this group has children at all. If not, the folded flag should be cleared to avoid lingering unfold buttons in the list. */ - auto child = std::find_if(source->begin(), source->end(), [g](const Group *child){ return child->parent == g->index; }); - bool has_children = child != source->end(); + bool has_children = std::any_of(source.begin(), source.end(), [g](const Group *child){ return child->parent == g->index; }); Group::Get(g->index)->folded = has_children; } else { AddChildren(source, g->index, indent + 1); @@ -230,7 +228,7 @@ private: list.Sort(&GroupNameSorter); - AddChildren(&list, INVALID_GROUP, 0); + AddChildren(list, INVALID_GROUP, 0); this->groups.shrink_to_fit(); this->groups.RebuildDone(); @@ -761,10 +759,11 @@ public: break; case WID_GL_LIST_GROUP: { // Matrix Group - uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP); - if (id_g >= this->groups.size()) return; + auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); + if (it == this->groups.end()) return; - if (groups[id_g]->folded || (id_g + 1 < this->groups.size() && this->indents[id_g + 1] > this->indents[id_g])) { + size_t id_g = it - this->groups.begin(); + if ((*it)->folded || (id_g + 1 < this->groups.size() && this->indents[id_g + 1] > this->indents[id_g])) { /* The group has children, check if the user clicked the fold / unfold button. */ NWidgetCore *group_display = this->GetWidget(widget); int x = _current_text_dir == TD_RTL ? @@ -801,10 +800,10 @@ public: } case WID_GL_LIST_VEHICLE: { // Matrix Vehicle - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE); - if (id_v >= this->vehgroups.size()) return; // click out of list bound + auto it = this->vscroll->GetScrolledItemFromWidget(this->vehgroups, pt.y, this, WID_GL_LIST_VEHICLE); + if (it == this->vehgroups.end()) return; // click out of list bound - const GUIVehicleGroup &vehgroup = this->vehgroups[id_v]; + const GUIVehicleGroup &vehgroup = *it; const Vehicle *v = nullptr; @@ -937,8 +936,8 @@ public: break; case WID_GL_LIST_GROUP: { // Matrix group - uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP); - GroupID new_g = id_g >= this->groups.size() ? INVALID_GROUP : this->groups[id_g]->index; + auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); + GroupID new_g = it == this->groups.end() ? INVALID_GROUP : (*it)->index; if (this->group_sel != new_g && g->parent != new_g) { DoCommandP(0, this->group_sel | (1 << 16), new_g, CMD_ALTER_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_SET_PARENT)); @@ -970,8 +969,8 @@ public: this->group_over = INVALID_GROUP; this->SetDirty(); - uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP); - GroupID new_g = id_g >= this->groups.size() ? NEW_GROUP : this->groups[id_g]->index; + auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); + GroupID new_g = it == this->groups.end() ? NEW_GROUP : (*it)->index; DoCommandP(0, new_g, vindex | (_ctrl_pressed || this->grouping == GB_SHARED_ORDERS ? 1 << 31 : 0), CMD_ADD_VEHICLE_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_ADD_VEHICLE), new_g == NEW_GROUP ? CcAddVehicleNewGroup : nullptr); break; @@ -983,10 +982,10 @@ public: this->group_over = INVALID_GROUP; this->SetDirty(); - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_VEHICLE); - if (id_v >= this->vehgroups.size()) return; // click out of list bound + auto it = this->vscroll->GetScrolledItemFromWidget(this->vehgroups, pt.y, this, WID_GL_LIST_VEHICLE); + if (it == this->vehgroups.end()) return; // click out of list bound - const GUIVehicleGroup &vehgroup = this->vehgroups[id_v]; + const GUIVehicleGroup &vehgroup = *it; switch (this->grouping) { case GB_NONE: { const Vehicle *v = vehgroup.GetSingleVehicle(); @@ -1171,8 +1170,8 @@ public: break; case WID_GL_LIST_GROUP: { // ... the list of custom groups. - uint id_g = this->group_sb->GetScrolledRowFromWidget(pt.y, this, WID_GL_LIST_GROUP); - new_group_over = id_g >= this->groups.size() ? NEW_GROUP : this->groups[id_g]->index; + auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); + new_group_over = it == this->groups.end() ? NEW_GROUP : (*it)->index; break; } diff --git a/src/hotkeys.cpp b/src/hotkeys.cpp index 25a887f378..8cce27c1f8 100644 --- a/src/hotkeys.cpp +++ b/src/hotkeys.cpp @@ -170,53 +170,41 @@ static void ParseHotkeys(Hotkey *hotkey, const char *value) * by a '+'. * @param keycode The keycode to convert to a string. * @return A string representation of this keycode. - * @note The return value is a static buffer, stredup the result before calling - * this function again. */ -static const char *KeycodeToString(uint16 keycode) +static std::string KeycodeToString(uint16 keycode) { - static char buf[32]; - buf[0] = '\0'; - bool first = true; + std::string str; if (keycode & WKC_GLOBAL_HOTKEY) { - strecat(buf, "GLOBAL", lastof(buf)); - first = false; + str += "GLOBAL"; } if (keycode & WKC_SHIFT) { - if (!first) strecat(buf, "+", lastof(buf)); - strecat(buf, "SHIFT", lastof(buf)); - first = false; + if (!str.empty()) str += "+"; + str += "SHIFT"; } if (keycode & WKC_CTRL) { - if (!first) strecat(buf, "+", lastof(buf)); - strecat(buf, "CTRL", lastof(buf)); - first = false; + if (!str.empty()) str += "+"; + str += "CTRL"; } if (keycode & WKC_ALT) { - if (!first) strecat(buf, "+", lastof(buf)); - strecat(buf, "ALT", lastof(buf)); - first = false; + if (!str.empty()) str += "+"; + str += "ALT"; } if (keycode & WKC_META) { - if (!first) strecat(buf, "+", lastof(buf)); - strecat(buf, "META", lastof(buf)); - first = false; + if (!str.empty()) str += "+"; + str += "META"; } - if (!first) strecat(buf, "+", lastof(buf)); + if (!str.empty()) str += "+"; keycode = keycode & ~WKC_SPECIAL_KEYS; for (uint i = 0; i < lengthof(_keycode_to_name); i++) { if (_keycode_to_name[i].keycode == keycode) { - strecat(buf, _keycode_to_name[i].name, lastof(buf)); - return buf; + str += _keycode_to_name[i].name; + return str; } } assert(keycode < 128); - char key[2]; - key[0] = keycode; - key[1] = '\0'; - strecat(buf, key, lastof(buf)); - return buf; + str.push_back(keycode); + return str; } /** @@ -224,19 +212,15 @@ static const char *KeycodeToString(uint16 keycode) * keycodes are attached to the hotkey they are split by a comma. * @param hotkey The keycodes of this hotkey need to be converted to a string. * @return A string representation of all keycodes. - * @note The return value is a static buffer, stredup the result before calling - * this function again. */ -const char *SaveKeycodes(const Hotkey *hotkey) +std::string SaveKeycodes(const Hotkey *hotkey) { - static char buf[128]; - buf[0] = '\0'; + std::string str; for (uint i = 0; i < hotkey->keycodes.size(); i++) { - const char *str = KeycodeToString(hotkey->keycodes[i]); - if (i > 0) strecat(buf, ",", lastof(buf)); - strecat(buf, str, lastof(buf)); + if (i > 0) str += ","; + str += KeycodeToString(hotkey->keycodes[i]); } - return buf; + return str; } /** diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index 9593234a9e..df14fc7edf 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -652,9 +652,9 @@ public: } case WID_DPI_MATRIX_WIDGET: { - int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_DPI_MATRIX_WIDGET); - if (y != INT_MAX) { // Is it within the boundaries of available data? - this->selected_type = this->list[y]; + auto it = this->vscroll->GetScrolledItemFromWidget(this->list, pt.y, this, WID_DPI_MATRIX_WIDGET); + if (it != this->list.end()) { // Is it within the boundaries of available data? + this->selected_type = *it; this->UpdateAvailability(); const IndustrySpec *indsp = GetIndustrySpec(this->selected_type); @@ -1764,12 +1764,12 @@ public: break; case WID_ID_INDUSTRY_LIST: { - uint p = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.top); - if (p < this->industries.size()) { + auto it = this->vscroll->GetScrolledItemFromWidget(this->industries, pt.y, this, WID_ID_INDUSTRY_LIST, WidgetDimensions::scaled.framerect.top); + if (it != this->industries.end()) { if (_ctrl_pressed) { - ShowExtraViewportWindow(this->industries[p]->location.tile); + ShowExtraViewportWindow((*it)->location.tile); } else { - ScrollMainWindowToTile(this->industries[p]->location.tile); + ScrollMainWindowToTile((*it)->location.tile); } } break; diff --git a/src/ini_type.h b/src/ini_type.h index 8f76f6aa72..172679c532 100644 --- a/src/ini_type.h +++ b/src/ini_type.h @@ -14,8 +14,6 @@ #include #include -#include - /** Types of groups */ enum IniGroupType { IGT_VARIABLES = 0, ///< Values of the form "landscape = hilly". diff --git a/src/intro_gui.cpp b/src/intro_gui.cpp index 1d84732bf7..f445b0d62d 100644 --- a/src/intro_gui.cpp +++ b/src/intro_gui.cpp @@ -17,6 +17,7 @@ #include "genworld.h" #include "network/network_gui.h" #include "network/network_content.h" +#include "network/network_survey.h" #include "landscape_type.h" #include "landscape.h" #include "strings_func.h" @@ -504,7 +505,10 @@ void ShowSelectGameWindow() static void AskExitGameCallback(Window *w, bool confirmed) { - if (confirmed) _exit_game = true; + if (confirmed) { + _survey.Transmit(NetworkSurveyHandler::Reason::EXIT, true); + _exit_game = true; + } } void AskExitGame() diff --git a/src/linkgraph/demands.h b/src/linkgraph/demands.h index c230fc17bc..b759c64941 100644 --- a/src/linkgraph/demands.h +++ b/src/linkgraph/demands.h @@ -40,7 +40,7 @@ public: /** * Virtual destructor has to be defined because of virtual Run(). */ - virtual ~DemandHandler() {} + virtual ~DemandHandler() = default; }; #endif /* DEMANDS_H */ diff --git a/src/linkgraph/flowmapper.h b/src/linkgraph/flowmapper.h index 90ccd9fe52..da7375b8a6 100644 --- a/src/linkgraph/flowmapper.h +++ b/src/linkgraph/flowmapper.h @@ -30,10 +30,6 @@ public: FlowMapper(bool scale) : scale(scale) {} virtual void Run(LinkGraphJob &job) const; - /** - * Virtual destructor has to be defined because of virtual Run(). - */ - virtual ~FlowMapper() {} private: /** diff --git a/src/linkgraph/init.h b/src/linkgraph/init.h index 377c886311..a627005162 100644 --- a/src/linkgraph/init.h +++ b/src/linkgraph/init.h @@ -17,11 +17,6 @@ public: * @param job Job to be initialized. */ virtual void Run(LinkGraphJob &job) const { job.Init(); } - - /** - * Virtual destructor has to be defined because of virtual Run(). - */ - virtual ~InitHandler() {} }; #endif /* INIT_H */ diff --git a/src/linkgraph/linkgraph.h b/src/linkgraph/linkgraph.h index 40bab9dd8b..5117061051 100644 --- a/src/linkgraph/linkgraph.h +++ b/src/linkgraph/linkgraph.h @@ -11,7 +11,6 @@ #define LINKGRAPH_H #include "../core/pool_type.hpp" -#include "../core/smallmap_type.hpp" #include "../core/bitmath_func.hpp" #include "../station_base.h" #include "../cargotype.h" diff --git a/src/linkgraph/linkgraphschedule.h b/src/linkgraph/linkgraphschedule.h index e67a978fc5..af54751afc 100644 --- a/src/linkgraph/linkgraphschedule.h +++ b/src/linkgraph/linkgraphschedule.h @@ -29,7 +29,7 @@ public: /** * Destroy the handler. Must be given due to virtual Run. */ - virtual ~ComponentHandler() {} + virtual ~ComponentHandler() = default; /** * Run the handler. A link graph handler must not read or write any data diff --git a/src/linkgraph/mcf.h b/src/linkgraph/mcf.h index 17ab216f31..996e6698e7 100644 --- a/src/linkgraph/mcf.h +++ b/src/linkgraph/mcf.h @@ -82,11 +82,6 @@ public: * @param graph Component to be calculated. */ virtual void Run(LinkGraphJob &job) const { Tpass pass(job); } - - /** - * Destructor. Has to be given because of virtual Run(). - */ - virtual ~MCFHandler() {} }; #endif /* MCF_H */ diff --git a/src/misc.cpp b/src/misc.cpp index 2cb03e573a..065dad99fa 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -39,9 +39,11 @@ #include "cargopacket.h" #include "tbtr_template_vehicle_func.h" #include "event_logs.h" +#include "3rdparty/monocypher/monocypher.h" #include "safeguards.h" +std::string _savegame_id; ///< Unique ID of the current savegame. extern TileIndex _cur_tileloop_tile; extern TileIndex _aux_tileloop_tile; @@ -66,6 +68,36 @@ void InitializeCheats(); void InitializeNPF(); void InitializeOldNames(); +/** + * Generate a unique ID. + */ +std::string GenerateUid(std::string_view subject) +{ + extern void NetworkRandomBytesWithFallback(void *buf, size_t n); + extern std::string BytesToHexString(const byte *data, uint length); + + uint8 random_bytes[32]; + NetworkRandomBytesWithFallback(random_bytes, lengthof(random_bytes)); + + uint8 digest[16]; + + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, lengthof(digest)); + crypto_blake2b_update(&ctx, random_bytes, lengthof(random_bytes)); + crypto_blake2b_update(&ctx, (const byte *)subject.data(), subject.size()); + crypto_blake2b_final (&ctx, digest); + + return BytesToHexString(digest, lengthof(digest)); +} + +/** + * Generate a unique savegame ID. + */ +void GenerateSavegameId() +{ + _savegame_id = GenerateUid("OpenTTD Savegame ID"); +} + void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings) { /* Make sure there isn't any window that can influence anything diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 07a4dfd97b..5b97de5cd2 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -28,6 +28,8 @@ add_files( network_server.h network_stun.cpp network_stun.h + network_survey.cpp + network_survey.h network_turn.cpp network_turn.h network_type.h diff --git a/src/network/core/address.cpp b/src/network/core/address.cpp index 0a2066b7dd..6e658af250 100644 --- a/src/network/core/address.cpp +++ b/src/network/core/address.cpp @@ -282,7 +282,7 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList * * of course totally unneeded ;) */ if (sockets != nullptr) { NetworkAddress address(runp->ai_addr, (int)runp->ai_addrlen); - if (sockets->Contains(address)) continue; + if (std::any_of(sockets->begin(), sockets->end(), [&address](const auto &p) { return p.second == address; })) continue; } sock = func(runp); if (sock == INVALID_SOCKET) continue; @@ -307,7 +307,7 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList * } NetworkAddress addr(runp->ai_addr, (int)runp->ai_addrlen); - (*sockets)[addr] = sock; + (*sockets)[sock] = addr; sock = INVALID_SOCKET; } freeaddrinfo (ai); diff --git a/src/network/core/address.h b/src/network/core/address.h index bd1deb737d..65adb9f3fc 100644 --- a/src/network/core/address.h +++ b/src/network/core/address.h @@ -14,13 +14,13 @@ #include "config.h" #include "../../company_type.h" #include "../../string_func.h" -#include "../../core/smallmap_type.hpp" +#include #include class NetworkAddress; typedef std::vector NetworkAddressList; ///< Type for a list of addresses. -typedef SmallMap SocketList; ///< Type for a mapping between address and socket. +using SocketList = std::map; ///< Type for a mapping between address and socket. /** * Wrapper for (un)resolved network addresses; there's no reason to transform diff --git a/src/network/core/config.cpp b/src/network/core/config.cpp index c2f4467d8e..62a87b9450 100644 --- a/src/network/core/config.cpp +++ b/src/network/core/config.cpp @@ -67,3 +67,13 @@ const char *NetworkContentMirrorUriString() { return GetEnv("OTTD_CONTENT_MIRROR_URI", "https://binaries.openttd.org/bananas"); } + +/** + * Get the URI string for the survey from the environment variable OTTD_SURVEY_URI, + * or when it has not been set a hard coded URI of the production server. + * @return The survey's URI string. + */ +const char *NetworkSurveyUriString() +{ + return GetEnv("OTTD_SURVEY_URI", "https://survey-participate.openttd.org/"); +} diff --git a/src/network/core/config.h b/src/network/core/config.h index 4bfde0824f..1a6d482bf2 100644 --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -16,6 +16,7 @@ const char *NetworkCoordinatorConnectionString(); const char *NetworkStunConnectionString(); const char *NetworkContentServerConnectionString(); const char *NetworkContentMirrorUriString(); +const char *NetworkSurveyUriString(); static const uint16 NETWORK_COORDINATOR_SERVER_PORT = 3976; ///< The default port of the Game Coordinator server (TCP) static const uint16 NETWORK_STUN_SERVER_PORT = 3975; ///< The default port of the STUN server (TCP) @@ -27,6 +28,8 @@ static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The d static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet static const uint16 UDP_MTU_SHORT = 1400; ///< Number of bytes we can pack in a single UDP packet (conservative) + +static const std::string NETWORK_SURVEY_DETAILS_LINK = "https://survey.openttd.org/participate"; ///< Link with more details & privacy statement of the survey. /* * Technically a TCP packet could become 64kiB, however the high bit is kept so it becomes possible in the future * to go to (significantly) larger packets if needed. This would entail a strategy such as employed for UTF-8. @@ -47,6 +50,7 @@ static const uint16 COMPAT_MTU = 1460; ///< Numbe static const byte NETWORK_GAME_ADMIN_VERSION = 3; ///< What version of the admin network do we use? static const byte NETWORK_GAME_INFO_VERSION = 6; ///< What version of game-info do we use? static const byte NETWORK_COORDINATOR_VERSION = 6; ///< What version of game-coordinator-protocol do we use? +static const byte NETWORK_SURVEY_VERSION = 1; ///< What version of the survey do we use? static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' diff --git a/src/network/core/core.h b/src/network/core/core.h index 84d5b62013..712687022a 100644 --- a/src/network/core/core.h +++ b/src/network/core/core.h @@ -48,7 +48,7 @@ public: NetworkSocketHandler() { this->has_quit = false; } /** Close the socket when destructing the socket handler */ - virtual ~NetworkSocketHandler() {} + virtual ~NetworkSocketHandler() = default; /** * Mark the connection as closed. diff --git a/src/network/core/host.cpp b/src/network/core/host.cpp index 65a54b358b..9288f92347 100644 --- a/src/network/core/host.cpp +++ b/src/network/core/host.cpp @@ -9,6 +9,7 @@ #include "../../stdafx.h" #include "../../debug.h" +#include "../../core/alloc_func.hpp" #include "address.h" #include "../../safeguards.h" diff --git a/src/network/core/http.h b/src/network/core/http.h index 78b5be87af..7bb03bc346 100644 --- a/src/network/core/http.h +++ b/src/network/core/http.h @@ -14,6 +14,8 @@ #include "tcp.h" +constexpr int HTTP_429_TOO_MANY_REQUESTS = 429; + /** Callback for when the HTTP handler has something to tell us. */ struct HTTPCallback { /** @@ -40,7 +42,7 @@ struct HTTPCallback { virtual bool IsCancelled() const = 0; /** Silentium */ - virtual ~HTTPCallback() {} + virtual ~HTTPCallback() = default; }; /** Base socket handler for HTTP traffic. */ diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp index 0a4aa14a0a..c3422d0789 100644 --- a/src/network/core/http_curl.cpp +++ b/src/network/core/http_curl.cpp @@ -117,6 +117,7 @@ void HttpThread() /* Reset to default settings. */ curl_easy_reset(curl); + curl_slist *headers = nullptr; if (_debug_net_level >= 5) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); @@ -147,8 +148,16 @@ void HttpThread() /* Prepare POST body and URI. */ if (!request->data.empty()) { + /* When the payload starts with a '{', it is a JSON payload. */ + if (StrStartsWith(request->data, "{")) { + headers = curl_slist_append(headers, "Content-Type: application/json"); + } else { + headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); + } + curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } curl_easy_setopt(curl, CURLOPT_URL, request->uri.c_str()); @@ -175,11 +184,17 @@ void HttpThread() /* Perform the request. */ CURLcode res = curl_easy_perform(curl); + curl_slist_free_all(headers); + if (res == CURLE_OK) { Debug(net, 1, "HTTP request succeeded"); request->callback->OnReceiveData(nullptr, 0); } else { - Debug(net, (request->callback->IsCancelled() || _http_thread_exit) ? 1 : 0, "HTTP request failed: {}", curl_easy_strerror(res)); + long status_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); + + /* No need to be verbose about rate limiting. */ + Debug(net, (request->callback->IsCancelled() || _http_thread_exit || status_code == HTTP_429_TOO_MANY_REQUESTS) ? 1 : 0, "HTTP request failed: status_code: {}, error: {}", status_code, curl_easy_strerror(res)); request->callback->OnFailure(); } } diff --git a/src/network/core/http_winhttp.cpp b/src/network/core/http_winhttp.cpp index e8eee7d8c1..547f275e0f 100644 --- a/src/network/core/http_winhttp.cpp +++ b/src/network/core/http_winhttp.cpp @@ -131,7 +131,8 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) /* If there is any error, we simply abort the request. */ if (status_code >= 400) { - Debug(net, 0, "HTTP request failed: status-code {}", status_code); + /* No need to be verbose about rate limiting. */ + Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0, "HTTP request failed: status-code {}", status_code); this->finished = true; this->callback->OnFailure(); return; @@ -242,7 +243,9 @@ void NetworkHTTPRequest::Connect() if (data.empty()) { WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, reinterpret_cast(this)); } else { - WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast(data.c_str()), static_cast(data.size()), static_cast(data.size()), reinterpret_cast(this)); + /* When the payload starts with a '{', it is a JSON payload. */ + LPCWSTR content_type = StrStartsWith(data, "{") ? L"Content-Type: application/json\r\n" : L"Content-Type: application/x-www-form-urlencoded\r\n"; + WinHttpSendRequest(this->request, content_type, -1, const_cast(data.c_str()), static_cast(data.size()), static_cast(data.size()), reinterpret_cast(this)); } } diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index ffdc9c2966..2d0d0ca03c 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -565,7 +565,7 @@ public: * @param status The reason the connection got closed. */ virtual NetworkRecvStatus CloseConnection(NetworkRecvStatus status) = 0; - virtual ~NetworkGameSocketHandler() {} + virtual ~NetworkGameSocketHandler() = default; /** * Sets the client info for this socket handler. diff --git a/src/network/core/tcp_listen.h b/src/network/core/tcp_listen.h index 47179462de..e71cf7fa6d 100644 --- a/src/network/core/tcp_listen.h +++ b/src/network/core/tcp_listen.h @@ -114,7 +114,7 @@ public: /* take care of listener port */ for (auto &s : sockets) { - FD_SET(s.second, &read_fd); + FD_SET(s.first, &read_fd); } tv.tv_sec = tv.tv_usec = 0; // don't block at all. @@ -122,7 +122,7 @@ public: /* accept clients.. */ for (auto &s : sockets) { - if (FD_ISSET(s.second, &read_fd)) AcceptClient(s.second); + if (FD_ISSET(s.first, &read_fd)) AcceptClient(s.first); } /* read stuff from clients */ @@ -164,7 +164,7 @@ public: static void CloseListeners() { for (auto &s : sockets) { - closesocket(s.second); + closesocket(s.first); } sockets.clear(); DEBUG(net, 5, "[%s] Closed listeners", Tsocket::GetName()); diff --git a/src/network/core/udp.cpp b/src/network/core/udp.cpp index e07b1d950a..4764445b45 100644 --- a/src/network/core/udp.cpp +++ b/src/network/core/udp.cpp @@ -61,7 +61,7 @@ bool NetworkUDPSocketHandler::Listen() void NetworkUDPSocketHandler::CloseSocket() { for (auto &s : this->sockets) { - closesocket(s.second); + closesocket(s.first); } this->sockets.clear(); } @@ -113,20 +113,20 @@ void NetworkUDPSocketHandler::SendPacket(Packet *p, NetworkAddress *recv, bool a NetworkAddress send(*recv); /* Not the same type */ - if (!send.IsFamily(s.first.GetAddress()->ss_family)) continue; + if (!send.IsFamily(s.second.GetAddress()->ss_family)) continue; p->PrepareToSend(); if (broadcast) { /* Enable broadcast */ unsigned long val = 1; - if (setsockopt(s.second, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) { + if (setsockopt(s.first, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) { DEBUG(net, 1, "Setting broadcast mode failed: %s", NetworkError::GetLast().AsString()); } } /* Send the buffer */ - ssize_t res = p->TransferOut(sendto, s.second, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength()); + ssize_t res = p->TransferOut(sendto, s.first, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength()); DEBUG(net, 7, "sendto(%s)", NetworkAddressDumper().GetAddressAsString(&send)); /* Check for any errors, but ignore it otherwise */ @@ -151,8 +151,8 @@ void NetworkUDPSocketHandler::ReceivePackets() socklen_t client_len = sizeof(client_addr); /* Try to receive anything */ - SetNonBlocking(s.second); // Some OSes seem to lose the non-blocking status of the socket - ssize_t nbytes = p.TransferIn(recvfrom, s.second, 0, (struct sockaddr *)&client_addr, &client_len); + SetNonBlocking(s.first); // Some OSes seem to lose the non-blocking status of the socket + ssize_t nbytes = p.TransferIn(recvfrom, s.first, 0, (struct sockaddr *)&client_addr, &client_len); /* Did we get the bytes for the base header of the packet? */ if (nbytes <= 0) break; // No data, i.e. no packet diff --git a/src/network/network.cpp b/src/network/network.cpp index 04f5a32491..4fffac79bc 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -102,6 +102,8 @@ static_assert((int)NETWORK_COMPANY_NAME_LENGTH == MAX_LENGTH_COMPANY_NAME_CHARS /** The amount of clients connected */ byte _network_clients_connected = 0; +extern std::string GenerateUid(std::string_view subject); + /** * Return whether there is any client connected or trying to connect at all. * @return whether we have any client activity @@ -1298,24 +1300,7 @@ void NetworkGameLoop() static void NetworkGenerateServerId() { - Md5 checksum; - uint8 digest[16]; - char hex_output[16 * 2 + 1]; - char coding_string[NETWORK_NAME_LENGTH]; - int di; - - seprintf(coding_string, lastof(coding_string), "%d%s", (uint)Random(), "OpenTTD Server ID"); - - /* Generate the MD5 hash */ - checksum.Append((const uint8*)coding_string, strlen(coding_string)); - checksum.Finish(digest); - - for (di = 0; di < 16; ++di) { - seprintf(hex_output + di * 2, lastof(hex_output), "%02x", digest[di]); - } - - /* _settings_client.network.network_id is our id */ - _settings_client.network.network_id = hex_output; + _settings_client.network.network_id = GenerateUid("OpenTTD Server ID"); } std::string BytesToHexString(const byte *data, uint length) diff --git a/src/network/network_chat_gui.cpp b/src/network/network_chat_gui.cpp index d8dfe0f111..6ac51b2adf 100644 --- a/src/network/network_chat_gui.cpp +++ b/src/network/network_chat_gui.cpp @@ -21,6 +21,7 @@ #include "network.h" #include "network_client.h" #include "network_base.h" +#include "../3rdparty/fmt/format.h" #include "../widgets/network_chat_widget.h" @@ -28,6 +29,7 @@ #include /* va_list */ #include +#include #include "../safeguards.h" @@ -326,18 +328,16 @@ struct NetworkChatWindow : public Window { * Find the next item of the list of things that can be auto-completed. * @param item The current indexed item to return. This function can, and most * likely will, alter item, to skip empty items in the arrays. - * @return Returns the char that matched to the index. + * @return Returns the view that matched to the index. */ - const char *ChatTabCompletionNextItem(uint *item) + std::optional ChatTabCompletionNextItem(uint *item) { - static char chat_tab_temp_buffer[64]; - /* First, try clients */ if (*item < MAX_CLIENT_SLOTS) { /* Skip inactive clients */ for (NetworkClientInfo *ci : NetworkClientInfo::Iterate(*item)) { *item = ci->index; - return ci->client_name.c_str(); + return ci->client_name; } *item = MAX_CLIENT_SLOTS; } @@ -349,12 +349,11 @@ struct NetworkChatWindow : public Window { for (const Town *t : Town::Iterate(*item - MAX_CLIENT_SLOTS)) { /* Get the town-name via the string-system */ SetDParam(0, t->index); - GetString(chat_tab_temp_buffer, STR_TOWN_NAME, lastof(chat_tab_temp_buffer)); - return &chat_tab_temp_buffer[0]; + return GetString(STR_TOWN_NAME); } } - return nullptr; + return std::nullopt; } /** @@ -362,13 +361,14 @@ struct NetworkChatWindow : public Window { * the word right from that as to complete. It also writes a \0 at the * position of the space (if any). If nothing found, buf is returned. */ - static char *ChatTabCompletionFindText(char *buf) + static std::string_view ChatTabCompletionFindText(std::string_view &buf) { - char *p = strrchr(buf, ' '); - if (p == nullptr) return buf; + auto it = buf.find_last_of(' '); + if (it == std::string_view::npos) return buf; - *p = '\0'; - return p + 1; + std::string_view res = buf.substr(it + 1); + buf.remove_suffix(res.size() + 1); + return res; } /** @@ -376,46 +376,44 @@ struct NetworkChatWindow : public Window { */ void ChatTabCompletion() { - static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH]; - assert(this->message_editbox.text.max_bytes == lengthof(_chat_tab_completion_buf)); + static std::string _chat_tab_completion_buf; Textbuf *tb = &this->message_editbox.text; - size_t len, tb_len; - uint item; - char *tb_buf, *pre_buf; - const char *cur_name; + uint item = 0; bool second_scan = false; - item = 0; - - /* Copy the buffer so we can modify it without damaging the real data */ - pre_buf = (_chat_tab_completion_active) ? stredup(_chat_tab_completion_buf) : stredup(tb->buf); + /* Create views, so we do not need to copy the data for now. */ + std::string_view pre_buf = _chat_tab_completion_active ? std::string_view(_chat_tab_completion_buf) : std::string_view(tb->buf); + std::string_view tb_buf = ChatTabCompletionFindText(pre_buf); - tb_buf = ChatTabCompletionFindText(pre_buf); - tb_len = strlen(tb_buf); + /* + * Comparing pointers of the data, as both "Hi:" and "Hi: Hi:" will result in + * tb_buf and pre_buf being "Hi:", which would be equal in content but not in context. + */ + bool begin_of_line = tb_buf.data() == pre_buf.data(); - while ((cur_name = ChatTabCompletionNextItem(&item)) != nullptr) { + std::optional cur_item; + while ((cur_item = ChatTabCompletionNextItem(&item)).has_value()) { + std::string_view cur_name = cur_item.value(); item++; if (_chat_tab_completion_active) { /* We are pressing TAB again on the same name, is there another name * that starts with this? */ if (!second_scan) { - size_t offset; - size_t length; + std::string_view view; /* If we are completing at the begin of the line, skip the ': ' we added */ - if (tb_buf == pre_buf) { - offset = 0; - length = (tb->bytes - 1) - 2; + if (begin_of_line) { + view = std::string_view(tb->buf, (tb->bytes - 1) - 2); } else { /* Else, find the place we are completing at */ - offset = strlen(pre_buf) + 1; - length = (tb->bytes - 1) - offset; + size_t offset = pre_buf.size() + 1; + view = std::string_view(tb->buf + offset, (tb->bytes - 1) - offset); } /* Compare if we have a match */ - if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true; + if (cur_name == view) second_scan = true; continue; } @@ -423,21 +421,19 @@ struct NetworkChatWindow : public Window { /* Now any match we make on _chat_tab_completion_buf after this, is perfect */ } - len = strlen(cur_name); - if (tb_len < len && StrStartsWith(cur_name, tb_buf)) { + if (tb_buf.size() < cur_name.size() && StrStartsWith(cur_name, tb_buf)) { /* Save the data it was before completion */ - if (!second_scan) seprintf(_chat_tab_completion_buf, lastof(_chat_tab_completion_buf), "%s", tb->buf); + if (!second_scan) _chat_tab_completion_buf = tb->buf; _chat_tab_completion_active = true; /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */ - if (pre_buf == tb_buf) { - this->message_editbox.text.Print("%s: ", cur_name); + if (begin_of_line) { + this->message_editbox.text.Assign(fmt::format("{}: ", cur_name)); } else { - this->message_editbox.text.Print("%s %s", pre_buf, cur_name); + this->message_editbox.text.Assign(fmt::format("{} {}", pre_buf, cur_name)); } this->SetDirty(); - free(pre_buf); return; } } @@ -449,7 +445,6 @@ struct NetworkChatWindow : public Window { this->SetDirty(); } - free(pre_buf); } Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number) override diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 0287e320a0..388ccc5b92 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -31,6 +31,7 @@ #include "../thread.h" #include "../crashlog.h" #include "../core/checksum_func.hpp" +#include "../core/alloc_func.hpp" #include "../fileio_func.h" #include "../debug_settings.h" #include "../3rdparty/monocypher/monocypher.h" diff --git a/src/network/network_content.h b/src/network/network_content.h index 02f1c996d8..a43caa499e 100644 --- a/src/network/network_content.h +++ b/src/network/network_content.h @@ -57,7 +57,7 @@ struct ContentCallback { virtual void OnDownloadComplete(ContentID cid) {} /** Silentium */ - virtual ~ContentCallback() {} + virtual ~ContentCallback() = default; }; /** diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp index 18daeac210..9c786b37dd 100644 --- a/src/network/network_content_gui.cpp +++ b/src/network/network_content_gui.cpp @@ -791,7 +791,7 @@ public: void OnClick(Point pt, int widget, int click_count) override { - if (widget >= WID_NCL_TEXTFILE && widget < WID_NCL_TEXTFILE + TFT_END) { + if (widget >= WID_NCL_TEXTFILE && widget < WID_NCL_TEXTFILE + TFT_CONTENT_END) { if (this->selected == nullptr || this->selected->state != ContentInfo::ALREADY_HERE) return; ShowContentTextfileWindow((TextfileType)(widget - WID_NCL_TEXTFILE), this->selected); @@ -800,11 +800,11 @@ public: switch (widget) { case WID_NCL_MATRIX: { - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NCL_MATRIX); - if (id_v >= this->content.size()) return; // click out of bounds + auto it = this->vscroll->GetScrolledItemFromWidget(this->content, pt.y, this, WID_NCL_MATRIX); + if (it == this->content.end()) return; // click out of bounds - this->selected = this->content[id_v]; - this->list_pos = id_v; + this->selected = *it; + this->list_pos = it - this->content.begin(); const NWidgetBase *checkbox = this->GetWidget(WID_NCL_CHECKBOX); if (click_count > 1 || IsInsideBS(pt.x, checkbox->pos_x, checkbox->current_x)) { @@ -998,7 +998,7 @@ public: this->SetWidgetDisabledState(WID_NCL_SELECT_ALL, !show_select_all); this->SetWidgetDisabledState(WID_NCL_SELECT_UPDATE, !show_select_upgrade); this->SetWidgetDisabledState(WID_NCL_OPEN_URL, this->selected == nullptr || this->selected->url.empty()); - for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) { + for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { this->SetWidgetDisabledState(WID_NCL_TEXTFILE + tft, this->selected == nullptr || this->selected->state != ContentInfo::ALREADY_HERE || this->selected->GetTextfile(tft) == nullptr); } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 63f18e972f..8e9990a4ae 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -19,6 +19,7 @@ #include "network_content.h" #include "network_server.h" #include "network_coordinator.h" +#include "network_survey.h" #include "../gui.h" #include "network_udp.h" #include "../window_func.h" @@ -38,6 +39,7 @@ #include "../zoom_func.h" #include "../sprite.h" #include "../settings_internal.h" +#include "../textfile_gui.h" #include "../widgets/network_widget.h" @@ -764,9 +766,9 @@ public: break; case WID_NG_MATRIX: { // Show available network games - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NG_MATRIX); - this->server = (id_v < this->servers.size()) ? this->servers[id_v] : nullptr; - this->list_pos = (server == nullptr) ? SLP_INVALID : id_v; + auto it = this->vscroll->GetScrolledItemFromWidget(this->servers, pt.y, this, WID_NG_MATRIX); + this->server = (it != this->servers.end()) ? *it : nullptr; + this->list_pos = (server == nullptr) ? SLP_INVALID : it - this->servers.begin(); this->SetDirty(); /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */ @@ -1462,7 +1464,7 @@ public: this->height = d.height + WidgetDimensions::scaled.framerect.Vertical(); this->width = d.width + WidgetDimensions::scaled.framerect.Horizontal(); } - virtual ~ButtonCommon() {} + virtual ~ButtonCommon() = default; /** * OnClick handler for when the button is pressed. @@ -2448,7 +2450,8 @@ struct NetworkAskRelayWindow : public Window { { if (widget == WID_NAR_TEXT) { *size = GetStringBoundingBox(STR_NETWORK_ASK_RELAY_TEXT); - size->height = GetStringHeight(STR_NETWORK_ASK_RELAY_TEXT, size->width - WidgetDimensions::scaled.frametext.Horizontal()) + WidgetDimensions::scaled.frametext.Vertical(); + size->width += WidgetDimensions::scaled.frametext.Horizontal(); + size->height += WidgetDimensions::scaled.frametext.Vertical(); } } @@ -2534,3 +2537,119 @@ void ShowNetworkAskRelay(const std::string &server_connection_string, const std: Window *parent = GetMainWindow(); new NetworkAskRelayWindow(&_network_ask_relay_desc, parent, server_connection_string, relay_connection_string, token); } + +/** + * Window used for asking if the user wants to participate in the automated survey. + */ +struct NetworkAskSurveyWindow : public Window { + NetworkAskSurveyWindow(WindowDesc *desc, Window *parent) : + Window(desc) + { + this->parent = parent; + this->InitNested(0); + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget == WID_NAS_TEXT) { + *size = GetStringBoundingBox(STR_NETWORK_ASK_SURVEY_TEXT); + size->width += WidgetDimensions::scaled.frametext.Horizontal(); + size->height += WidgetDimensions::scaled.frametext.Vertical(); + } + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget == WID_NAS_TEXT) { + DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.frametext), STR_NETWORK_ASK_SURVEY_TEXT, TC_BLACK, SA_CENTER); + } + } + + void FindWindowPlacementAndResize(int def_width, int def_height) override + { + /* Position query window over the calling window, ensuring it's within screen bounds. */ + this->left = Clamp(parent->left + (parent->width / 2) - (this->width / 2), 0, _screen.width - this->width); + this->top = Clamp(parent->top + (parent->height / 2) - (this->height / 2), 0, _screen.height - this->height); + this->SetDirty(); + } + + void OnClick(Point pt, int widget, int click_count) override + { + switch (widget) { + case WID_NAS_PREVIEW: + ShowSurveyResultTextfileWindow(); + break; + + case WID_NAS_LINK: + OpenBrowser(NETWORK_SURVEY_DETAILS_LINK.c_str()); + break; + + case WID_NAS_NO: + _settings_client.network.participate_survey = PS_NO; + this->Close(); + break; + + case WID_NAS_YES: + _settings_client.network.participate_survey = PS_YES; + this->Close(); + break; + } + } +}; + +static const NWidgetPart _nested_network_ask_survey_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_NAS_CAPTION), SetDataTip(STR_NETWORK_ASK_SURVEY_CAPTION, STR_NULL), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(0, 4, 8), + NWidget(WWT_TEXT, COLOUR_GREY, WID_NAS_TEXT), SetAlignment(SA_HOR_CENTER), SetFill(1, 1), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 15, 10), + NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NAS_PREVIEW), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_PREVIEW, STR_NULL), + NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NAS_LINK), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_LINK, STR_NULL), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 15, 10), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NAS_NO), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_NO, STR_NULL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NAS_YES), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_YES, STR_NULL), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _network_ask_survey_desc( + WDP_CENTER, nullptr, 0, 0, + WC_NETWORK_ASK_SURVEY, WC_NONE, + WDF_MODAL, + _nested_network_ask_survey_widgets, lengthof(_nested_network_ask_survey_widgets) +); + +/** + * Show a modal confirmation window with "no" / "preview" / "yes" buttons. + */ +void ShowNetworkAskSurvey() +{ + /* If we can't send a survey, don't ask the question. */ + if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) return; + + CloseWindowByClass(WC_NETWORK_ASK_SURVEY); + + Window *parent = GetMainWindow(); + new NetworkAskSurveyWindow(&_network_ask_survey_desc, parent); +} + +/** Window for displaying the textfile of a survey result. */ +struct SurveyResultTextfileWindow : public TextfileWindow { + const GRFConfig *grf_config; ///< View the textfile of this GRFConfig. + + SurveyResultTextfileWindow(TextfileType file_type) : TextfileWindow(file_type) + { + auto result = _survey.CreatePayload(NetworkSurveyHandler::Reason::PREVIEW, true); + this->LoadText(result); + this->InvalidateData(); + } +}; + +void ShowSurveyResultTextfileWindow() +{ + CloseWindowById(WC_TEXTFILE, TFT_SURVEY_RESULT); + new SurveyResultTextfileWindow(TFT_SURVEY_RESULT); +} diff --git a/src/network/network_gui.h b/src/network/network_gui.h index 1a4cc635b4..236719b2b3 100644 --- a/src/network/network_gui.h +++ b/src/network/network_gui.h @@ -25,7 +25,8 @@ void ShowNetworkGameWindow(); void ShowClientList(); void ShowNetworkCompanyPasswordWindow(Window *parent); void ShowNetworkAskRelay(const std::string &server_connection_string, const std::string &relay_connection_string, const std::string &token); - +void ShowNetworkAskSurvey(); +void ShowSurveyResultTextfileWindow(); /** Company information stored at the client side */ struct NetworkCompanyInfo : NetworkCompanyStats { diff --git a/src/network/network_survey.cpp b/src/network/network_survey.cpp new file mode 100644 index 0000000000..ae061427df --- /dev/null +++ b/src/network/network_survey.cpp @@ -0,0 +1,366 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_survey.cpp Opt-in survey part of the network protocol. */ + +#include "../stdafx.h" +#include "network_survey.h" +#include "network.h" +#include "network_internal.h" +#include "../company_base.h" +#include "../debug.h" +#include "../debug_fmt.h" +#include "../rev.h" +#include "../settings_type.h" +#include "../settings_internal.h" +#include "../timer/timer_game_tick.h" +#include "../sl/saveload.h" +#include "../date_func.h" + +#include "../currency.h" +#include "../fontcache.h" +#include "../language.h" + +#include "../ai/ai_info.hpp" +#include "../game/game.hpp" +#include "../game/game_info.hpp" + +#include "../music/music_driver.hpp" +#include "../sound/sound_driver.hpp" +#include "../video/video_driver.hpp" + +#include "../base_media_base.h" +#include "../blitter/factory.hpp" + +#ifdef WITH_NLOHMANN_JSON +#include +#endif /* WITH_NLOHMANN_JSON */ + +#include "../safeguards.h" + +extern std::string _savegame_id; + +NetworkSurveyHandler _survey = {}; + +#ifdef WITH_NLOHMANN_JSON + +NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurveyHandler::Reason, { + {NetworkSurveyHandler::Reason::PREVIEW, "preview"}, + {NetworkSurveyHandler::Reason::LEAVE, "leave"}, + {NetworkSurveyHandler::Reason::EXIT, "exit"}, + {NetworkSurveyHandler::Reason::CRASH, "crash"}, +}) + +NLOHMANN_JSON_SERIALIZE_ENUM(GRFStatus, { + {GRFStatus::GCS_UNKNOWN, "unknown"}, + {GRFStatus::GCS_DISABLED, "disabled"}, + {GRFStatus::GCS_NOT_FOUND, "not found"}, + {GRFStatus::GCS_INITIALISED, "initialised"}, + {GRFStatus::GCS_ACTIVATED, "activated"}, +}) + +static const std::string _vehicle_type_to_string[] = { + "train", + "roadveh", + "ship", + "aircraft", +}; + +/* Defined in one of the os/ survey files. */ +extern void SurveyOS(nlohmann::json &json); + +/** + * Convert a settings table to JSON. + * + * @param survey The JSON object. + * @param table The settings table to convert. + * @param object The object to get the settings from. + */ +static void SurveySettingsTable(nlohmann::json &survey, const SettingTable &table, void *object) +{ + char buf[512]; + for (auto &sd : table) { + /* Skip any old settings we no longer save/load. */ + if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue; + + auto name = sd->name; + sd->FormatValue(buf, lastof(buf), object); + + survey[name] = buf; + } +} + +/** + * Convert settings to JSON. + * + * @param survey The JSON object. + */ +static void SurveySettings(nlohmann::json &survey) +{ + IterateSettingsTables([&](const SettingTable &table, void *object) { + SurveySettingsTable(survey, table, object); + }); +} + +/** + * Convert generic OpenTTD information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyOpenTTD(nlohmann::json &survey) +{ + survey["version"] = std::string(_openttd_revision); + survey["newgrf_version"] = _openttd_newgrf_version; + survey["build_date"] = std::string(_openttd_build_date); + survey["bits"] = +#ifdef POINTER_IS_64BIT + 64 +#else + 32 +#endif + ; + survey["endian"] = +#if (TTD_ENDIAN == TTD_LITTLE_ENDIAN) + "little" +#else + "big" +#endif + ; + survey["dedicated_build"] = +#ifdef DEDICATED + "yes" +#else + "no" +#endif + ; +} + +/** + * Convert generic game information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyConfiguration(nlohmann::json &survey) +{ + survey["network"] = _networking ? (_network_server ? "server" : "client") : "no"; + if (_current_language != nullptr) { + std::string_view language_basename(_current_language->file); + auto e = language_basename.rfind(PATHSEPCHAR); + if (e != std::string::npos) { + language_basename = language_basename.substr(e + 1); + } + + survey["language"]["filename"] = language_basename; + survey["language"]["name"] = _current_language->name; + survey["language"]["isocode"] = _current_language->isocode; + } + if (BlitterFactory::GetCurrentBlitter() != nullptr) { + survey["blitter"] = BlitterFactory::GetCurrentBlitter()->GetName(); + } + if (MusicDriver::GetInstance() != nullptr) { + survey["music_driver"] = MusicDriver::GetInstance()->GetName(); + } + if (SoundDriver::GetInstance() != nullptr) { + survey["sound_driver"] = SoundDriver::GetInstance()->GetName(); + } + if (VideoDriver::GetInstance() != nullptr) { + survey["video_driver"] = VideoDriver::GetInstance()->GetName(); + survey["video_info"] = VideoDriver::GetInstance()->GetInfoString(); + } + if (BaseGraphics::GetUsedSet() != nullptr) { + survey["graphics_set"] = fmt::format("{}.{}", BaseGraphics::GetUsedSet()->name, BaseGraphics::GetUsedSet()->version); + } + if (BaseMusic::GetUsedSet() != nullptr) { + survey["music_set"] = fmt::format("{}.{}", BaseMusic::GetUsedSet()->name, BaseMusic::GetUsedSet()->version); + } + if (BaseSounds::GetUsedSet() != nullptr) { + survey["sound_set"] = fmt::format("{}.{}", BaseSounds::GetUsedSet()->name, BaseSounds::GetUsedSet()->version); + } +} + +/** + * Convert font information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyFont(nlohmann::json &survey) +{ + survey["small"] = FontCache::Get(FS_SMALL)->GetFontName(); + survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName(); + survey["large"] = FontCache::Get(FS_LARGE)->GetFontName(); + survey["mono"] = FontCache::Get(FS_MONO)->GetFontName(); +} + +/** + * Convert company information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyCompanies(nlohmann::json &survey) +{ + for (const Company *c : Company::Iterate()) { + auto &company = survey[std::to_string(c->index)]; + if (c->ai_info == nullptr) { + company["type"] = "human"; + } else { + company["type"] = "ai"; + company["script"] = fmt::format("{}.{}", c->ai_info->GetName(), c->ai_info->GetVersion()); + } + + for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) { + uint amount = c->group_all[type].num_vehicle; + company["vehicles"][_vehicle_type_to_string[type]] = amount; + } + + company["infrastructure"]["road"] = c->infrastructure.GetRoadTotal(); + company["infrastructure"]["tram"] = c->infrastructure.GetTramTotal(); + company["infrastructure"]["rail"] = c->infrastructure.GetRailTotal(); + company["infrastructure"]["signal"] = c->infrastructure.signal; + company["infrastructure"]["water"] = c->infrastructure.water; + company["infrastructure"]["station"] = c->infrastructure.station; + company["infrastructure"]["airport"] = c->infrastructure.airport; + } +} + +/** + * Convert GRF information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyGrfs(nlohmann::json &survey) +{ + for (GRFConfig *c = _grfconfig; c != nullptr; c = c->next) { + auto grfid = fmt::format("{:08x}", BSWAP32(c->ident.grfid)); + auto &grf = survey[grfid]; + + grf["md5sum"] = BytesToHexString(c->ident.md5sum, 16); + grf["status"] = c->status; + + if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_UNSET) grf["palette"] = "unset"; + if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_DOS) grf["palette"] = "dos"; + if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_WINDOWS) grf["palette"] = "windows"; + if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_ANY) grf["palette"] = "any"; + + if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_UNSET) grf["blitter"] = "unset"; + if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_32BPP) grf["blitter"] = "32bpp"; + + grf["is_static"] = HasBit(c->flags, GCF_STATIC); + + std::vector parameters; + for (int i = 0; i < c->num_params; i++) { + parameters.push_back(c->param[i]); + } + grf["parameters"] = parameters; + } +} + +/** + * Convert game-script information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyGameScript(nlohmann::json &survey) +{ + if (Game::GetInfo() == nullptr) return; + + survey = fmt::format("{}.{}", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion()); +} + +#endif /* WITH_NLOHMANN_JSON */ + +/** + * Create the payload for the survey. + * + * @param reason The reason for sending the survey. + * @param for_preview Whether the payload is meant for preview. This indents the result, and filters out the id/key. + * @return std::string The JSON payload as string for the survey. + */ +std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview) +{ +#ifndef WITH_NLOHMANN_JSON + return ""; +#else + nlohmann::json survey; + + survey["schema"] = NETWORK_SURVEY_VERSION; + survey["reason"] = reason; + survey["id"] = _savegame_id; + +#ifdef SURVEY_KEY + /* We censor the key to avoid people trying to be "clever" and use it to send their own surveys. */ + survey["key"] = for_preview ? "(redacted)" : SURVEY_KEY; +#else + survey["key"] = ""; +#endif + + { + auto &info = survey["info"]; + SurveyOS(info["os"]); + info["os"]["hardware_concurrency"] = std::thread::hardware_concurrency(); + + SurveyOpenTTD(info["openttd"]); + SurveyConfiguration(info["configuration"]); + SurveyFont(info["font"]); + } + + { + auto &game = survey["game"]; + game["ticks"] = _scaled_tick_counter; + game["time"] = std::chrono::duration_cast(std::chrono::steady_clock::now() - _switch_mode_time).count(); + SurveyCompanies(game["companies"]); + SurveySettings(game["settings"]); + SurveyGrfs(game["grfs"]); + SurveyGameScript(game["game_script"]); + } + + /* For preview, we indent with 4 whitespaces to make things more readable. */ + int indent = for_preview ? 4 : -1; + return survey.dump(indent); +#endif /* WITH_NLOHMANN_JSON */ +} + +/** + * Transmit the survey. + * + * @param reason The reason for sending the survey. + * @param blocking Whether to block until the survey is sent. + */ +void NetworkSurveyHandler::Transmit(Reason reason, bool blocking) +{ + if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) { + Debug(net, 4, "Survey: not possible to send survey; most likely due to missing JSON library at compile-time"); + return; + } + + if (_settings_client.network.participate_survey != PS_YES) { + Debug(net, 5, "Survey: user is not participating in survey; skipping survey"); + return; + } + + Debug(net, 1, "Survey: sending survey results"); + NetworkHTTPSocketHandler::Connect(NetworkSurveyUriString(), this, this->CreatePayload(reason)); + + if (blocking) { + std::unique_lock lock(this->mutex); + /* Block no longer than 2 seconds. If we failed to send the survey in that time, so be it. */ + this->loaded.wait_for(lock, std::chrono::seconds(2)); + } +} + +void NetworkSurveyHandler::OnFailure() +{ + Debug(net, 1, "Survey: failed to send survey results"); + this->loaded.notify_all(); +} + +void NetworkSurveyHandler::OnReceiveData(const char *data, size_t length) +{ + if (data == nullptr) { + Debug(net, 1, "Survey: survey results sent"); + this->loaded.notify_all(); + } +} diff --git a/src/network/network_survey.h b/src/network/network_survey.h new file mode 100644 index 0000000000..c957108ecf --- /dev/null +++ b/src/network/network_survey.h @@ -0,0 +1,54 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_survey.h Part of the network protocol handling opt-in survey. */ + +#ifndef NETWORK_SURVEY_H +#define NETWORK_SURVEY_H + +#include +#include +#include "core/http.h" + +/** + * Socket handler for the survey connection + */ +class NetworkSurveyHandler : public HTTPCallback { +protected: + void OnFailure() override; + void OnReceiveData(const char *data, size_t length) override; + bool IsCancelled() const override { return false; } + +public: + enum class Reason { + PREVIEW, ///< User is previewing the survey result. + LEAVE, ///< User is leaving the game (but not exiting the application). + EXIT, ///< User is exiting the application. + CRASH, ///< Game crashed. + }; + + void Transmit(Reason reason, bool blocking = false); + std::string CreatePayload(Reason reason, bool for_preview = false); + + constexpr static bool IsSurveyPossible() + { +#ifndef WITH_NLOHMANN_JSON + /* Without JSON library, we cannot send a payload; so we disable the survey. */ + return false; +#else + return true; +#endif /* WITH_NLOHMANN_JSON */ + } + +private: + std::mutex mutex; ///< Mutex for the condition variable. + std::condition_variable loaded; ///< Condition variable to wait for the survey to be sent. +}; + +extern NetworkSurveyHandler _survey; + +#endif /* NETWORK_SURVEY_H */ diff --git a/src/network/network_udp.cpp b/src/network/network_udp.cpp index fea9b31b1c..59d304dea6 100644 --- a/src/network/network_udp.cpp +++ b/src/network/network_udp.cpp @@ -82,7 +82,7 @@ public: * @param addresses The addresses to bind on. */ ServerNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {} - virtual ~ServerNetworkUDPSocketHandler() {} + virtual ~ServerNetworkUDPSocketHandler() = default; }; void ServerNetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) @@ -117,7 +117,7 @@ protected: void Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override; void Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override; public: - virtual ~ClientNetworkUDPSocketHandler() {} + virtual ~ClientNetworkUDPSocketHandler() = default; }; void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 69bfe0f562..5e916fe00a 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -9291,13 +9291,13 @@ static bool ChangeGRFParamValueNames(ByteReader *buf) byte langid = buf->ReadByte(); const char *name_string = buf->ReadString(); - std::pair *val_name = _cur_parameter->value_names.Find(id); - if (val_name != _cur_parameter->value_names.End()) { + auto val_name = _cur_parameter->value_names.find(id); + if (val_name != _cur_parameter->value_names.end()) { AddGRFTextToList(val_name->second, langid, _cur.grfconfig->ident.grfid, false, name_string); } else { GRFTextList list; AddGRFTextToList(list, langid, _cur.grfconfig->ident.grfid, false, name_string); - _cur_parameter->value_names.Insert(id, list); + _cur_parameter->value_names[id] = list; } type = buf->ReadByte(); diff --git a/src/newgrf_commons.h b/src/newgrf_commons.h index 76daa10f86..6f3b6c8def 100644 --- a/src/newgrf_commons.h +++ b/src/newgrf_commons.h @@ -204,7 +204,7 @@ public: std::vector mappings; ///< mapping of ids from grf files. Public out of convenience OverrideManagerBase(uint16 offset, uint16 maximum, uint16 invalid); - virtual ~OverrideManagerBase() {} + virtual ~OverrideManagerBase() = default; void ResetOverride(); void ResetMapping(); diff --git a/src/newgrf_config.cpp b/src/newgrf_config.cpp index c376eee054..268e027eca 100644 --- a/src/newgrf_config.cpp +++ b/src/newgrf_config.cpp @@ -274,7 +274,7 @@ void GRFParameterInfo::Finalize() { this->complete_labels = true; for (uint32 value = this->min_value; value <= this->max_value; value++) { - if (!this->value_names.Contains(value)) { + if (this->value_names.count(value) == 0) { this->complete_labels = false; break; } diff --git a/src/newgrf_config.h b/src/newgrf_config.h index 669b4b5094..2d78852d2b 100644 --- a/src/newgrf_config.h +++ b/src/newgrf_config.h @@ -12,11 +12,11 @@ #include "strings_type.h" #include "core/alloc_type.hpp" -#include "core/smallmap_type.hpp" #include "misc/countedptr.hpp" #include "fileio_type.h" #include "textfile_type.h" #include "newgrf_text.h" +#include static const uint MAX_NON_STATIC_GRF_COUNT = 256; @@ -145,7 +145,7 @@ struct GRFParameterInfo { byte param_nr; ///< GRF parameter to store content in byte first_bit; ///< First bit to use in the GRF parameter byte num_bit; ///< Number of bits to use for this parameter - SmallMap value_names; ///< Names for each value. + std::map value_names; ///< Names for each value. bool complete_labels; ///< True if all values have a label. uint32 GetValue(struct GRFConfig *config) const; @@ -222,7 +222,7 @@ extern bool _grf_bug_too_many_strings;///< NewGRF bug: Insufficient available st /** Callback for NewGRF scanning. */ struct NewGRFScanCallback { /** Make sure the right destructor gets called. */ - virtual ~NewGRFScanCallback() {} + virtual ~NewGRFScanCallback() = default; /** Called whenever the NewGRF scan completed. */ virtual void OnNewGRFsScanned() = 0; }; diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index 76524558f2..826ae4f636 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -132,7 +132,7 @@ struct NIExtraInfoOutput { class NIHelper { public: /** Silence a warning. */ - virtual ~NIHelper() {} + virtual ~NIHelper() = default; /** * Is the item with the given index inspectable? @@ -1266,7 +1266,7 @@ struct SpriteAlignerWindow : Window { SpriteID current_sprite; ///< The currently shown sprite. Scrollbar *vscroll; - SmallMap offs_start_map; ///< Mapping of starting offsets for the sprites which have been aligned in the sprite aligner window. + std::map offs_start_map; ///< Mapping of starting offsets for the sprites which have been aligned in the sprite aligner window. static bool centre; static bool crosshair; @@ -1302,7 +1302,7 @@ struct SpriteAlignerWindow : Window { /* Relative offset is new absolute offset - starting absolute offset. * Show 0, 0 as the relative offsets if entry is not in the map (meaning they have not been changed yet). */ - const auto key_offs_pair = this->offs_start_map.Find(this->current_sprite); + const auto key_offs_pair = this->offs_start_map.find(this->current_sprite); if (key_offs_pair != this->offs_start_map.end()) { SetDParam(0, spr->x_offs - key_offs_pair->second.first); SetDParam(1, spr->y_offs - key_offs_pair->second.second); @@ -1411,12 +1411,9 @@ struct SpriteAlignerWindow : Window { break; case WID_SA_LIST: { - const NWidgetBase *nwid = this->GetWidget(widget); - int step_size = nwid->resize_y; - - uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size; - if (i < _newgrf_debug_sprite_picker.sprites.size()) { - SpriteID spr = _newgrf_debug_sprite_picker.sprites[i]; + auto it = this->vscroll->GetScrolledItemFromWidget(_newgrf_debug_sprite_picker.sprites, pt.y, this, widget); + if (it != _newgrf_debug_sprite_picker.sprites.end()) { + SpriteID spr = *it; if (GetSpriteType(spr) == SpriteType::Normal) this->current_sprite = spr; } this->SetDirty(); @@ -1443,8 +1440,8 @@ struct SpriteAlignerWindow : Window { Sprite *spr = const_cast(GetSprite(this->current_sprite, SpriteType::Normal)); /* Remember the original offsets of the current sprite, if not already in mapping. */ - if (!(this->offs_start_map.Contains(this->current_sprite))) { - this->offs_start_map.Insert(this->current_sprite, XyOffs(spr->x_offs, spr->y_offs)); + if (this->offs_start_map.count(this->current_sprite) == 0) { + this->offs_start_map[this->current_sprite] = XyOffs(spr->x_offs, spr->y_offs); } switch (widget) { /* Move eight units at a time if ctrl is pressed. */ @@ -1461,7 +1458,7 @@ struct SpriteAlignerWindow : Window { case WID_SA_RESET_REL: /* Reset the starting offsets for the current sprite. */ - this->offs_start_map.Erase(this->current_sprite); + this->offs_start_map.erase(this->current_sprite); this->SetDirty(); break; diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp index 5665c46ace..0b048659db 100644 --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -289,8 +289,9 @@ struct NewGRFParametersWindow : public Window { } SetDParam(2, STR_JUST_INT); SetDParam(3, current_value); - if (par_info->value_names.Contains(current_value)) { - const char *label = GetGRFStringFromGRFText(par_info->value_names.Find(current_value)->second); + auto it = par_info->value_names.find(current_value); + if (it != par_info->value_names.end()) { + const char *label = GetGRFStringFromGRFText(it->second); if (label != nullptr) { SetDParam(2, STR_JUST_RAW_STRING); SetDParamStr(3, label); @@ -344,8 +345,10 @@ struct NewGRFParametersWindow : public Window { case WID_NP_BACKGROUND: { if (!this->editable) break; - uint num = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NP_BACKGROUND); - if (num >= this->vscroll->GetCount()) break; + auto it = this->vscroll->GetScrolledItemFromWidget(this->grf_config->param_info, pt.y, this, WID_NP_BACKGROUND); + if (it == this->grf_config->param_info.end()) break; + + uint num = it - this->grf_config->param_info.begin(); if (this->clicked_row != num) { DeleteChildWindows(WC_QUERY_STRING); HideDropDownMenu(this); @@ -357,7 +360,7 @@ struct NewGRFParametersWindow : public Window { int x = pt.x - r.left; if (_current_text_dir == TD_RTL) x = r.Width() - 1 - x; - GRFParameterInfo *par_info = (num < this->grf_config->param_info.size()) ? this->grf_config->param_info[num] : nullptr; + GRFParameterInfo *par_info = *it; if (par_info == nullptr) par_info = GetDummyParameterInfo(num); /* One of the arrows is clicked */ @@ -384,7 +387,7 @@ struct NewGRFParametersWindow : public Window { DropDownList list; for (uint32 i = par_info->min_value; i <= par_info->max_value; i++) { - list.emplace_back(new DropDownListCharStringItem(GetGRFStringFromGRFText(par_info->value_names.Find(i)->second), i, false)); + list.emplace_back(new DropDownListCharStringItem(GetGRFStringFromGRFText(par_info->value_names.find(i)->second), i, false)); } ShowDropDownListAt(this, std::move(list), old_val, -1, wi_rect, COLOUR_ORANGE); @@ -927,7 +930,7 @@ struct NewGRFWindow : public Window, NewGRFScanCallback { void OnClick(Point pt, int widget, int click_count) override { - if (widget >= WID_NS_NEWGRF_TEXTFILE && widget < WID_NS_NEWGRF_TEXTFILE + TFT_END) { + if (widget >= WID_NS_NEWGRF_TEXTFILE && widget < WID_NS_NEWGRF_TEXTFILE + TFT_CONTENT_END) { if (this->active_sel == nullptr && this->avail_sel == nullptr) return; ShowNewGRFTextfileWindow((TextfileType)(widget - WID_NS_NEWGRF_TEXTFILE), this->active_sel != nullptr ? this->active_sel : this->avail_sel); @@ -1081,13 +1084,13 @@ struct NewGRFWindow : public Window, NewGRFScanCallback { case WID_NS_AVAIL_LIST: { // Select a non-active GRF. ResetObjectToPlace(); - uint i = this->vscroll2->GetScrolledRowFromWidget(pt.y, this, WID_NS_AVAIL_LIST); + auto it = this->vscroll2->GetScrolledItemFromWidget(this->avails, pt.y, this, WID_NS_AVAIL_LIST); this->active_sel = nullptr; DeleteWindowByClass(WC_GRF_PARAMETERS); - if (i < this->avails.size()) { - if (this->avail_sel != this->avails[i]) DeleteWindowByClass(WC_TEXTFILE); - this->avail_sel = this->avails[i]; - this->avail_pos = i; + if (it != this->avails.end()) { + if (this->avail_sel != *it) DeleteWindowByClass(WC_TEXTFILE); + this->avail_sel = *it; + this->avail_pos = it - this->avails.begin(); } this->InvalidateData(); if (click_count == 1) { @@ -1295,7 +1298,7 @@ struct NewGRFWindow : public Window, NewGRFScanCallback { ); const GRFConfig *selected_config = (this->avail_sel == nullptr) ? this->active_sel : this->avail_sel; - for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) { + for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { this->SetWidgetDisabledState(WID_NS_NEWGRF_TEXTFILE + tft, selected_config == nullptr || selected_config->GetTextfile(tft) == nullptr); } this->SetWidgetDisabledState(WID_NS_OPEN_URL, selected_config == nullptr || StrEmpty(selected_config->GetURL())); @@ -2152,10 +2155,10 @@ struct SavePresetWindow : public Window { { switch (widget) { case WID_SVP_PRESET_LIST: { - uint row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SVP_PRESET_LIST); - if (row < this->presets.size()) { - this->selected = row; - this->presetname_editbox.text.Assign(this->presets[row].c_str()); + auto it = this->vscroll->GetScrolledItemFromWidget(this->presets, pt.y, this, WID_SVP_PRESET_LIST); + if (it != this->presets.end()) { + this->selected = it - this->presets.begin(); + this->presetname_editbox.text.Assign(it->c_str()); this->SetWidgetDirty(WID_SVP_PRESET_LIST); this->SetWidgetDirty(WID_SVP_EDITBOX); } diff --git a/src/newgrf_profiling.cpp b/src/newgrf_profiling.cpp index 5326740207..63667d22fc 100644 --- a/src/newgrf_profiling.cpp +++ b/src/newgrf_profiling.cpp @@ -14,12 +14,13 @@ #include "console_func.h" #include "spritecache.h" #include "walltime_func.h" +#include "timer/timer.h" +#include "timer/timer_game_tick.h" #include std::vector _newgrf_profilers; -Date _newgrf_profile_end_date; /** @@ -96,6 +97,8 @@ uint32 NewGRFProfiler::Finish() if (this->calls.empty()) { IConsolePrintF(CC_DEBUG, "Finished profile of NewGRF [%08X], no events collected, not writing a file", BSWAP32(this->grffile->grfid)); + + this->Abort(); return 0; } @@ -114,7 +117,6 @@ uint32 NewGRFProfiler::Finish() } this->Abort(); - return total_microseconds; } @@ -139,8 +141,10 @@ std::string NewGRFProfiler::GetOutputFilename() const return std::string(filepath); } -uint32 NewGRFProfiler::FinishAll() +/* static */ uint32 NewGRFProfiler::FinishAll() { + NewGRFProfiler::AbortTimer(); + uint64 max_ticks = 0; uint32 total_microseconds = 0; for (NewGRFProfiler &pr : _newgrf_profilers) { @@ -154,7 +158,29 @@ uint32 NewGRFProfiler::FinishAll() IConsolePrintF(CC_DEBUG, "Total NewGRF callback processing: %u microseconds over " OTTD_PRINTF64U " ticks", total_microseconds, max_ticks); } - _newgrf_profile_end_date = MAX_DAY; - return total_microseconds; } + +/** + * Check whether profiling is active and should be finished. + */ +static TimeoutTimer _profiling_finish_timeout(0, []() +{ + NewGRFProfiler::FinishAll(); +}); + +/** + * Start the timeout timer that will finish all profiling sessions. + */ +/* static */ void NewGRFProfiler::StartTimer(uint64 ticks) +{ + _profiling_finish_timeout.Reset(ticks); +} + +/** + * Abort the timeout timer, so the timer callback is never called. + */ +/* static */ void NewGRFProfiler::AbortTimer() +{ + _profiling_finish_timeout.Abort(); +} diff --git a/src/newgrf_profiling.h b/src/newgrf_profiling.h index 15adb87830..24cb059b4c 100644 --- a/src/newgrf_profiling.h +++ b/src/newgrf_profiling.h @@ -36,6 +36,8 @@ struct NewGRFProfiler { void Abort(); std::string GetOutputFilename() const; + static void StartTimer(uint64 ticks); + static void AbortTimer(); static uint32 FinishAll(); /** Measurement of a single sprite group resolution */ @@ -58,6 +60,5 @@ struct NewGRFProfiler { }; extern std::vector _newgrf_profilers; -extern Date _newgrf_profile_end_date; #endif /* NEWGRF_PROFILING_H */ diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index adc7a57bb5..e5b3b5204b 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -73,7 +73,7 @@ protected: virtual const SpriteGroup *Resolve(ResolverObject &object) const { return this; }; public: - virtual ~SpriteGroup() {} + virtual ~SpriteGroup() = default; uint32 nfo_line; SpriteGroupType type; @@ -640,7 +640,7 @@ struct ScopeResolver { ResolverObject &ro; ///< Surrounding resolver object. ScopeResolver(ResolverObject &ro) : ro(ro) {} - virtual ~ScopeResolver() {} + virtual ~ScopeResolver() = default; virtual uint32 GetRandomBits() const; virtual uint32 GetTriggers() const; @@ -669,7 +669,7 @@ struct ResolverObject { this->ResetState(); } - virtual ~ResolverObject() {} + virtual ~ResolverObject() = default; ScopeResolver default_scope; ///< Default implementation of the grf scope. diff --git a/src/newgrf_text.cpp b/src/newgrf_text.cpp index f0dc27d7d4..30f9bcf444 100644 --- a/src/newgrf_text.cpp +++ b/src/newgrf_text.cpp @@ -29,7 +29,6 @@ #include "date_type.h" #include "debug.h" #include "core/alloc_type.hpp" -#include "core/smallmap_type.hpp" #include "language.h" #include #include diff --git a/src/news_type.h b/src/news_type.h index 188305b52f..9399a5d9be 100644 --- a/src/news_type.h +++ b/src/news_type.h @@ -118,7 +118,7 @@ struct NewsTypeData { /** Container for any custom data that must be deleted after the news item has reached end-of-life. */ struct NewsAllocatedData { - virtual ~NewsAllocatedData() {} + virtual ~NewsAllocatedData() = default; }; diff --git a/src/object_gui.cpp b/src/object_gui.cpp index cf0a89ccc7..4328124a4d 100644 --- a/src/object_gui.cpp +++ b/src/object_gui.cpp @@ -180,19 +180,8 @@ public: } else { /* Check if the previously selected object class is not available anymore as a * result of starting a new game without the corresponding NewGRF. */ - bool available = false; - for (uint i = 0; ObjectClass::IsClassIDValid((ObjectClassID)i); ++i) { - if ((ObjectClassID)i == _selected_object_class) { - available = true; - break; - } - } - - if (available) { - this->SelectOtherClass(_selected_object_class); - } else { - this->SelectOtherClass(this->object_classes[0]); - } + bool available = _selected_object_class < ObjectClass::GetClassCount(); + this->SelectOtherClass(available ? _selected_object_class : this->object_classes[0]); } if (this->CanRestoreSelectedObject()) { @@ -520,10 +509,10 @@ public: { switch (GB(widget, 0, 16)) { case WID_BO_CLASS_LIST: { - int num_clicked = this->vscroll->GetPosition() + (pt.y - this->GetWidget(widget)->pos_y) / this->line_height; - if (num_clicked >= (int)this->object_classes.size()) break; + auto it = this->vscroll->GetScrolledItemFromWidget(this->object_classes, widget, this, pt.y); + if (it == this->object_classes.end()) break; - this->SelectOtherClass(this->object_classes[num_clicked]); + this->SelectOtherClass(*it); this->SelectFirstAvailableObject(false); break; } diff --git a/src/openttd.cpp b/src/openttd.cpp index 1b404ad72b..81919036aa 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -86,6 +86,7 @@ #include "tunnelbridge.h" #include "worker_thread.h" #include "scope_info.h" +#include "network/network_survey.h" #include "timer/timer.h" #include "timer/timer_game_tick.h" @@ -571,7 +572,7 @@ void MakeNewgameSettingsLive() if (_settings_newgame.ai_config[c] != nullptr) { _settings_game.ai_config[c] = new AIConfig(_settings_newgame.ai_config[c]); if (!AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->HasScript()) { - AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(nullptr); + AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(std::nullopt); } } } @@ -1044,6 +1045,7 @@ void HandleExitGameRequest() _exit_game = true; } else if (_settings_client.gui.autosave_on_exit) { DoExitSave(); + _survey.Transmit(NetworkSurveyHandler::Reason::EXIT, true); _exit_game = true; } else { AskExitGame(); @@ -1292,9 +1294,16 @@ void SwitchToMode(SwitchMode new_mode) /* Make sure all AI controllers are gone at quitting game */ if (new_mode != SM_SAVE_GAME) AI::KillAll(); + /* Transmit the survey if we were in normal-mode and not saving. It always means we leaving the current game. */ + if (_game_mode == GM_NORMAL && new_mode != SM_SAVE_GAME) _survey.Transmit(NetworkSurveyHandler::Reason::LEAVE); + + /* Keep track when we last switch mode. Used for survey, to know how long someone was in a game. */ + if (new_mode != SM_SAVE_GAME) _switch_mode_time = std::chrono::steady_clock::now(); + switch (new_mode) { case SM_EDITOR: // Switch to scenario editor MakeNewEditorWorld(); + GenerateSavegameId(); break; case SM_RELOADGAME: // Reload with what-ever started the game @@ -1311,11 +1320,13 @@ void SwitchToMode(SwitchMode new_mode) } MakeNewGame(false, new_mode == SM_NEWGAME); + GenerateSavegameId(); break; case SM_RESTARTGAME: // Restart --> 'Random game' with current settings case SM_NEWGAME: // New Game --> 'Random game' MakeNewGame(false, new_mode == SM_NEWGAME); + GenerateSavegameId(); break; case SM_LOAD_GAME: { // Load game, Play Scenario @@ -1339,6 +1350,7 @@ void SwitchToMode(SwitchMode new_mode) case SM_RESTART_HEIGHTMAP: // Load a heightmap and start a new game from it with current settings case SM_START_HEIGHTMAP: // Load a heightmap and start a new game from it MakeNewGame(true, new_mode == SM_START_HEIGHTMAP); + GenerateSavegameId(); break; case SM_LOAD_HEIGHTMAP: // Load heightmap from scenario editor @@ -1346,12 +1358,14 @@ void SwitchToMode(SwitchMode new_mode) FixConfigMapSize(); GenerateWorld(GWM_HEIGHTMAP, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y); + GenerateSavegameId(); MarkWholeScreenDirty(); break; case SM_LOAD_SCENARIO: { // Load scenario from scenario editor if (SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.detail_ftype, GM_EDITOR, NO_DIRECTORY)) { SetLocalCompany(OWNER_NONE); + GenerateSavegameId(); _settings_newgame.game_creation.starting_year = _cur_year; /* Cancel the saveload pausing */ DoCommandP(0, PM_PAUSED_SAVELOAD, 0, CMD_PAUSE); @@ -1373,6 +1387,14 @@ void SwitchToMode(SwitchMode new_mode) ShowErrorMessage(STR_WARNING_FALLBACK_SOUNDSET, INVALID_STRING_ID, WL_CRITICAL); BaseSounds::ini_set = BaseSounds::GetUsedSet()->name; } + if (_settings_client.network.participate_survey == PS_ASK) { + /* No matter how often you go back to the main menu, only ask the first time. */ + static bool asked_once = false; + if (!asked_once) { + asked_once = true; + ShowNetworkAskSurvey(); + } + } break; case SM_SAVE_GAME: { // Save game. diff --git a/src/openttd.h b/src/openttd.h index 3246a0b472..26743a0104 100644 --- a/src/openttd.h +++ b/src/openttd.h @@ -11,6 +11,7 @@ #define OPENTTD_H #include +#include #include "core/enum_type.hpp" /** Mode which defines the state of the game. */ @@ -60,6 +61,7 @@ enum ExtraDisplayOptions { extern GameMode _game_mode; extern SwitchMode _switch_mode; extern bool _check_special_modes; +extern std::chrono::steady_clock::time_point _switch_mode_time; extern std::atomic _exit_game; extern bool _save_config; @@ -93,6 +95,7 @@ void HandleExitGameRequest(); void SwitchToMode(SwitchMode new_mode); bool RequestNewGRFScan(struct NewGRFScanCallback *callback = nullptr); +void GenerateSavegameId(); void OpenBrowser(const char *url); diff --git a/src/order_gui.cpp b/src/order_gui.cpp index a7fdcc25a0..036c6e2a3f 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -1501,14 +1501,11 @@ private: */ VehicleOrderID GetOrderFromPt(int y) { - NWidgetBase *nwid = this->GetWidget(WID_O_ORDER_LIST); - int sel = (y - nwid->pos_y - WidgetDimensions::scaled.framerect.top) / nwid->resize_y; // Selected line in the WID_O_ORDER_LIST panel. - - if ((uint)sel >= this->vscroll->GetCapacity()) return INVALID_VEH_ORDER_ID; - - sel += this->vscroll->GetPosition(); - - return (sel <= vehicle->GetNumOrders() && sel >= 0) ? sel : INVALID_VEH_ORDER_ID; + int sel = this->vscroll->GetScrolledRowFromWidget(y, this, WID_O_ORDER_LIST, WidgetDimensions::scaled.framerect.top); + if (sel == INT_MAX) return INVALID_VEH_ORDER_ID; + /* One past the orders is the 'End of Orders' line. */ + assert(IsInsideBS(sel, 0, vehicle->GetNumOrders() + 1)); + return sel; } /** diff --git a/src/os/macosx/CMakeLists.txt b/src/os/macosx/CMakeLists.txt index 0b6264d294..eb5a727990 100644 --- a/src/os/macosx/CMakeLists.txt +++ b/src/os/macosx/CMakeLists.txt @@ -8,5 +8,6 @@ add_files( osx_stdafx.h string_osx.cpp string_osx.h + survey_osx.cpp CONDITION APPLE ) diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index 864325afb7..f017e8fb88 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -10,6 +10,7 @@ #include "../../stdafx.h" #include "../../debug.h" #include "font_osx.h" +#include "../../core/math_func.hpp" #include "../../blitter/factory.hpp" #include "../../fileio_func.h" #include "../../fontdetection.h" diff --git a/src/os/macosx/macos.h b/src/os/macosx/macos.h index 2a7a12a5fb..5dc6b3e095 100644 --- a/src/os/macosx/macos.h +++ b/src/os/macosx/macos.h @@ -38,6 +38,8 @@ bool IsMonospaceFont(CFStringRef name); void MacOSSetThreadName(const char *name); +uint64 MacOSGetPhysicalMemory(); + /** Deleter that calls CFRelease rather than deleting the pointer. */ template struct CFDeleter { diff --git a/src/os/macosx/macos.mm b/src/os/macosx/macos.mm index ac9fdb93a4..4eda4c6562 100644 --- a/src/os/macosx/macos.mm +++ b/src/os/macosx/macos.mm @@ -277,3 +277,8 @@ void MacOSSetThreadName(const char *name) [ cur performSelector:@selector(setName:) withObject:[ NSString stringWithUTF8String:name ] ]; } } + +uint64 MacOSGetPhysicalMemory() +{ + return [ [ NSProcessInfo processInfo ] physicalMemory ]; +} diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp index c0f291704d..68a683b876 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -104,8 +104,7 @@ public: /* Extract font information for this run. */ CFRange chars = CTRunGetStringRange(run); - auto map = fontMapping.begin(); - while (map < fontMapping.end() - 1 && map->first <= chars.location) map++; + auto map = fontMapping.upper_bound(chars.location); this->emplace_back(run, map->second, buff); } diff --git a/src/os/macosx/survey_osx.cpp b/src/os/macosx/survey_osx.cpp new file mode 100644 index 0000000000..88edebd9f6 --- /dev/null +++ b/src/os/macosx/survey_osx.cpp @@ -0,0 +1,38 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file survey_osx.cpp OSX implementation of OS-specific survey information. */ + +#ifdef WITH_NLOHMANN_JSON + +#include "../../stdafx.h" + +#include "../../3rdparty/fmt/format.h" +#include "macos.h" + +#include +#include + +#include "../../safeguards.h" + +void SurveyOS(nlohmann::json &json) +{ + int ver_maj, ver_min, ver_bug; + GetMacOSVersion(&ver_maj, &ver_min, &ver_bug); + + const NXArchInfo *arch = NXGetLocalArchInfo(); + + json["os"] = "MacOS"; + json["release"] = fmt::format("{}.{}.{}", ver_maj, ver_min, ver_bug); + json["machine"] = arch != nullptr ? arch->description : "unknown"; + json["min_ver"] = MAC_OS_X_VERSION_MIN_REQUIRED; + json["max_ver"] = MAC_OS_X_VERSION_MAX_ALLOWED; + + json["memory"] = MacOSGetPhysicalMemory(); +} + +#endif /* WITH_NLOHMANN_JSON */ diff --git a/src/os/unix/CMakeLists.txt b/src/os/unix/CMakeLists.txt index 8e74f96643..2db47d18f8 100644 --- a/src/os/unix/CMakeLists.txt +++ b/src/os/unix/CMakeLists.txt @@ -1,5 +1,6 @@ add_files( crashlog_unix.cpp + survey_unix.cpp CONDITION UNIX AND NOT APPLE AND NOT OPTION_OS2 ) diff --git a/src/os/unix/survey_unix.cpp b/src/os/unix/survey_unix.cpp new file mode 100644 index 0000000000..d86b58e98c --- /dev/null +++ b/src/os/unix/survey_unix.cpp @@ -0,0 +1,38 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file survey_unix.cpp Unix implementation of OS-specific survey information. */ + +#ifdef WITH_NLOHMANN_JSON + +#include "../../stdafx.h" + +#include +#include +#include + +#include "../../safeguards.h" + +void SurveyOS(nlohmann::json &json) +{ + struct utsname name; + if (uname(&name) < 0) { + json["os"] = "Unix"; + return; + } + + json["os"] = name.sysname; + json["release"] = name.release; + json["machine"] = name.machine; + json["version"] = name.version; + + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + json["memory"] = pages * page_size; +} + +#endif /* WITH_NLOHMANN_JSON */ diff --git a/src/os/windows/CMakeLists.txt b/src/os/windows/CMakeLists.txt index 8ac2de7acc..145d3b5242 100644 --- a/src/os/windows/CMakeLists.txt +++ b/src/os/windows/CMakeLists.txt @@ -4,6 +4,7 @@ add_files( font_win32.h string_uniscribe.cpp string_uniscribe.h + survey_win.cpp win32.cpp win32.h CONDITION WIN32 diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index 233dfb9651..861f784f9e 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -12,6 +12,7 @@ #include "../../blitter/factory.hpp" #include "../../core/alloc_func.hpp" #include "../../core/math_func.hpp" +#include "../../core/mem_func.hpp" #include "../../fileio_func.h" #include "../../fontdetection.h" #include "../../fontcache.h" diff --git a/src/os/windows/survey_win.cpp b/src/os/windows/survey_win.cpp new file mode 100644 index 0000000000..407ddb31d4 --- /dev/null +++ b/src/os/windows/survey_win.cpp @@ -0,0 +1,37 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file survey_win.cpp Windows implementation of OS-specific survey information. */ + +#ifdef WITH_NLOHMANN_JSON + +#include "../../stdafx.h" + +#include "../../3rdparty/fmt/format.h" + +#include +#include + +#include "../../safeguards.h" + +void SurveyOS(nlohmann::json &json) +{ + _OSVERSIONINFOA os; + os.dwOSVersionInfoSize = sizeof(os); + GetVersionExA(&os); + + json["os"] = "Windows"; + json["release"] = fmt::format("{}.{}.{} ({})", os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.szCSDVersion); + + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + + json["memory"] = status.ullTotalPhys; +} + +#endif /* WITH_NLOHMANN_JSON */ diff --git a/src/osk_gui.cpp b/src/osk_gui.cpp index f7a50dfceb..8632c5f1bf 100644 --- a/src/osk_gui.cpp +++ b/src/osk_gui.cpp @@ -50,8 +50,8 @@ struct OskWindow : public Window { NWidgetCore *par_wid = parent->GetWidget(button); assert(par_wid != nullptr); - assert(parent->querystrings.Contains(button)); - this->qs = parent->querystrings.Find(button)->second; + assert(parent->querystrings.count(button) != 0); + this->qs = parent->querystrings.find(button)->second; this->caption = (par_wid->widget_data != STR_NULL) ? par_wid->widget_data : this->qs->caption; this->text_btn = button; this->text = &this->qs->text; diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 82d187b711..53e3476298 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -1305,14 +1305,7 @@ public: } else { /* Check if the previously selected station class is not available anymore as a * result of starting a new game without the corresponding NewGRF. */ - bool available = false; - for (uint i = 0; StationClass::IsClassIDValid((StationClassID)i); ++i) { - if ((StationClassID)i == _railstation.station_class) { - available = true; - break; - } - } - + bool available = _railstation.station_class < StationClass::GetClassCount(); this->SelectOtherClass(available ? _railstation.station_class : StationClassID::STAT_CLASS_DFLT); } } @@ -1672,9 +1665,9 @@ public: break; case WID_BRAS_NEWST_LIST: { - int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BRAS_NEWST_LIST); - if (y >= (int)this->station_classes.size()) return; - StationClassID station_class_id = this->station_classes[y]; + auto it = this->vscroll->GetScrolledItemFromWidget(this->station_classes, pt.y, this, WID_BRAS_NEWST_LIST); + if (it == this->station_classes.end()) return; + StationClassID station_class_id = *it; this->SelectClass(station_class_id); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); this->SetDirty(); diff --git a/src/road.cpp b/src/road.cpp index c3f682c13e..78fd1ebb7b 100644 --- a/src/road.cpp +++ b/src/road.cpp @@ -146,7 +146,15 @@ bool HasRoadTypeAvail(const CompanyID company, RoadType roadtype) const RoadTypeInfo *rti = GetRoadTypeInfo(roadtype); if (rti->label == 0) return false; - bool available = (rti->flags & ROTFB_HIDDEN) == 0; + /* Not yet introduced at this date. */ + if (IsInsideMM(rti->introduction_date, 0, MAX_DAY) && rti->introduction_date > _date) return false; + + /* + * Do not allow building hidden road types, except when a town may build it. + * The GS under deity mode, as well as anybody in the editor builds roads that are + * owned by towns. So if a town may build it, it should be buildable by them too. + */ + bool available = (rti->flags & ROTFB_HIDDEN) == 0 || (rti->flags & ROTFB_TOWN_BUILD) != 0; if (!available && (company == OWNER_TOWN || _game_mode == GM_EDITOR || _generating_world)) { if (roadtype == GetTownRoadType()) return true; } diff --git a/src/road.h b/src/road.h index 92647a864d..642019cef3 100644 --- a/src/road.h +++ b/src/road.h @@ -41,14 +41,14 @@ enum RoadTypeFlags { ROTF_NO_LEVEL_CROSSING, ///< Bit number for disabling level crossing ROTF_NO_HOUSES, ///< Bit number for setting this roadtype as not house friendly ROTF_HIDDEN, ///< Bit number for hidden from construction. - ROTF_TOWN_BUILD, ///< Bit number for allowing towns to build this roadtype. Does not override ROTF_HIDDEN. + ROTF_TOWN_BUILD, ///< Bit number for allowing towns to build this roadtype. ROTFB_NONE = 0, ///< All flags cleared. ROTFB_CATENARY = 1 << ROTF_CATENARY, ///< Value for drawing a catenary. ROTFB_NO_LEVEL_CROSSING = 1 << ROTF_NO_LEVEL_CROSSING, ///< Value for disabling a level crossing. ROTFB_NO_HOUSES = 1 << ROTF_NO_HOUSES, ///< Value for for setting this roadtype as not house friendly. ROTFB_HIDDEN = 1 << ROTF_HIDDEN, ///< Value for hidden from construction. - ROTFB_TOWN_BUILD = 1 << ROTF_TOWN_BUILD, ///< Value for allowing towns to build this roadtype. Does not override ROTFB_HIDDEN. + ROTFB_TOWN_BUILD = 1 << ROTF_TOWN_BUILD, ///< Value for allowing towns to build this roadtype. }; DECLARE_ENUM_AS_BIT_SET(RoadTypeFlags) diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 7c2688e6cd..3f843b9165 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -1699,9 +1699,9 @@ public: break; case WID_BROS_NEWST_LIST: { - int y = this->vscrollList->GetScrolledRowFromWidget(pt.y, this, WID_BROS_NEWST_LIST); - if (y >= (int)this->roadstop_classes.size()) return; - RoadStopClassID class_id = this->roadstop_classes[y]; + auto it = this->vscrollList->GetScrolledItemFromWidget(this->roadstop_classes, pt.y, this, WID_BROS_NEWST_LIST); + if (it == this->roadstop_classes.end()) return; + RoadStopClassID class_id = *it; this->SelectClass(class_id); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); this->SetDirty(); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index f50f36a1d4..43891697b7 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -4213,6 +4213,10 @@ bool AfterLoadGame() _new_competitor_timeout.fired = _new_competitor_timeout.period == 0; } + if (SlXvIsFeatureMissing(XSLFI_SAVEGAME_ID) && IsSavegameVersionBefore(SLV_SAVEGAME_ID)) { + GenerateSavegameId(); + } + InitializeRoadGUI(); /* This needs to be done after conversion. */ diff --git a/src/saveload/ai_sl.cpp b/src/saveload/ai_sl.cpp index 97175e0057..c5a85fd87f 100644 --- a/src/saveload/ai_sl.cpp +++ b/src/saveload/ai_sl.cpp @@ -68,7 +68,7 @@ struct AIPLChunkHandler : ChunkHandler { /* Free all current data */ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { - AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(nullptr); + AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(std::nullopt); } CompanyID index; @@ -87,13 +87,13 @@ struct AIPLChunkHandler : ChunkHandler { AIConfig *config = AIConfig::GetConfig(index, AIConfig::SSS_FORCE_GAME); if (_ai_saveload_name.empty()) { /* A random AI. */ - config->Change(nullptr, -1, false, true); + config->Change(std::nullopt, -1, false, true); } else { - config->Change(_ai_saveload_name.c_str(), _ai_saveload_version, false, _ai_saveload_is_random); + config->Change(_ai_saveload_name, _ai_saveload_version, false, _ai_saveload_is_random); if (!config->HasScript()) { /* No version of the AI available that can load the data. Try to load the * latest version of the AI instead. */ - config->Change(_ai_saveload_name.c_str(), -1, false, _ai_saveload_is_random); + config->Change(_ai_saveload_name, -1, false, _ai_saveload_is_random); if (!config->HasScript()) { if (_ai_saveload_name.compare("%_dummy") != 0) { DEBUG(script, 0, "The savegame has an AI by the name '%s', version %u which is no longer available.", _ai_saveload_name.c_str(), _ai_saveload_version); diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 3506356832..fb4f8aa823 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -320,8 +320,8 @@ struct PLYRChunkHandler : ChunkHandler { int index; while ((index = SlIterateArray()) != -1) { - CompanyProperties *cprops = new CompanyProperties(); - SlObject(cprops, slt); + std::unique_ptr cprops = std::make_unique(); + SlObject(cprops.get(), slt); /* We do not load old custom names */ if (IsSavegameVersionBefore(SLV_84)) { @@ -341,7 +341,9 @@ struct PLYRChunkHandler : ChunkHandler { cprops->name_1 = STR_GAME_SAVELOAD_NOT_AVAILABLE; } - if (!_load_check_data.companies.Insert(index, cprops)) delete cprops; + if (_load_check_data.companies.count(index) == 0) { + _load_check_data.companies[index] = std::move(cprops); + } } } diff --git a/src/saveload/game_sl.cpp b/src/saveload/game_sl.cpp index 6be21bb2d1..111807d0bd 100644 --- a/src/saveload/game_sl.cpp +++ b/src/saveload/game_sl.cpp @@ -66,7 +66,7 @@ struct GSDTChunkHandler : ChunkHandler { const std::vector slt = SlCompatTableHeader(_game_script_desc, _game_script_sl_compat); /* Free all current data */ - GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME)->Change(nullptr); + GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME)->Change(std::nullopt); if (SlIterateArray() == -1) return; @@ -81,11 +81,11 @@ struct GSDTChunkHandler : ChunkHandler { GameConfig *config = GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME); if (!_game_saveload_name.empty()) { - config->Change(_game_saveload_name.c_str(), _game_saveload_version, false, _game_saveload_is_random); + config->Change(_game_saveload_name, _game_saveload_version, false, _game_saveload_is_random); if (!config->HasScript()) { /* No version of the GameScript available that can load the data. Try to load the * latest version of the GameScript instead. */ - config->Change(_game_saveload_name.c_str(), -1, false, _game_saveload_is_random); + config->Change(_game_saveload_name, -1, false, _game_saveload_is_random); if (!config->HasScript()) { if (_game_saveload_name.compare("%_dummy") != 0) { DEBUG(script, 0, "The savegame has an GameScript by the name '%s', version %u which is no longer available.", _game_saveload_name.c_str(), _game_saveload_version); diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index 6e563b6c2f..e597c4a4d4 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -29,6 +29,7 @@ extern TileIndex _cur_tileloop_tile; extern TileIndex _aux_tileloop_tile; extern uint16 _disaster_delay; extern byte _trees_tick_ctr; +extern std::string _savegame_id; /* Keep track of current game position */ extern int _saved_scrollpos_x; @@ -55,6 +56,7 @@ static const SaveLoad _date_desc[] = { SLEG_VAR("company_tick_counter", _cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32), SLEG_VAR("trees_tick_counter", _trees_tick_ctr, SLE_UINT8), SLEG_CONDVAR("pause_mode", _pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION), + SLEG_CONDSSTR("id", _savegame_id, SLE_STR, SLV_SAVEGAME_ID, SL_MAX_VERSION), /* For older savegames, we load the current value as the "period"; afterload will set the "fired" and "elapsed". */ SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109), SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_UINT32, SLV_109, SLV_AI_START_DATE), diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 902abc54de..8d53feec07 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -47,7 +47,7 @@ struct ChunkHandler { ChunkHandler(uint32 id, ChunkType type) : id(id), type(type) {} - virtual ~ChunkHandler() {} + virtual ~ChunkHandler() = default; /** * Save the chunk. @@ -91,7 +91,7 @@ class SaveLoadHandler { public: std::optional> load_description; - virtual ~SaveLoadHandler() {} + virtual ~SaveLoadHandler() = default; /** * Save the object to disk. diff --git a/src/screenshot.cpp b/src/screenshot.cpp index 0236e6fc68..786aaa9800 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -340,7 +340,7 @@ static bool MakePNGImage(const char *name, ScreenshotCallback *callb, void *user if (c->ai_info == nullptr) { p += seprintf(p, lastof(buf), "%2i: Human\n", (int)c->index); } else { - p += seprintf(p, lastof(buf), "%2i: %s (v%d)\n", (int)c->index, c->ai_info->GetName(), c->ai_info->GetVersion()); + p += seprintf(p, lastof(buf), "%2i: %s (v%d)\n", (int)c->index, c->ai_info->GetName().c_str(), c->ai_info->GetVersion()); } } text[1].key = const_cast("Description"); diff --git a/src/script/api/script_basestation.cpp b/src/script/api/script_basestation.cpp index f794c2f9a5..aef9e3b5f6 100644 --- a/src/script/api/script_basestation.cpp +++ b/src/script/api/script_basestation.cpp @@ -24,9 +24,9 @@ return st != nullptr && (st->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity() || st->owner == OWNER_NONE); } -/* static */ char *ScriptBaseStation::GetName(StationID station_id) +/* static */ std::optional ScriptBaseStation::GetName(StationID station_id) { - if (!IsValidBaseStation(station_id)) return nullptr; + if (!IsValidBaseStation(station_id)) return std::nullopt; ::SetDParam(0, station_id); return GetString(::Station::IsValidID(station_id) ? STR_STATION_NAME : STR_WAYPOINT_NAME); diff --git a/src/script/api/script_basestation.hpp b/src/script/api/script_basestation.hpp index 9f39368a30..cbd288261d 100644 --- a/src/script/api/script_basestation.hpp +++ b/src/script/api/script_basestation.hpp @@ -13,6 +13,8 @@ #include "script_text.hpp" #include "script_date.hpp" +#include + /** * Base class for stations and waypoints. * @api ai game @@ -43,7 +45,7 @@ public: * @pre IsValidBaseStation(station_id). * @return The name of the station. */ - static char *GetName(StationID station_id); + static std::optional GetName(StationID station_id); /** * Set the name this basestation. diff --git a/src/script/api/script_bridge.cpp b/src/script/api/script_bridge.cpp index 9b983b832f..cbe61a9408 100644 --- a/src/script/api/script_bridge.cpp +++ b/src/script/api/script_bridge.cpp @@ -141,10 +141,10 @@ static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance) return ScriptObject::DoCommand(tile, 0, 0, CMD_LANDSCAPE_CLEAR); } -/* static */ char *ScriptBridge::GetName(BridgeID bridge_id, ScriptVehicle::VehicleType vehicle_type) +/* static */ std::optional ScriptBridge::GetName(BridgeID bridge_id, ScriptVehicle::VehicleType vehicle_type) { - EnforcePrecondition(nullptr, vehicle_type == ScriptVehicle::VT_ROAD || vehicle_type == ScriptVehicle::VT_RAIL || vehicle_type == ScriptVehicle::VT_WATER); - if (!IsValidBridge(bridge_id)) return nullptr; + EnforcePrecondition(std::nullopt, vehicle_type == ScriptVehicle::VT_ROAD || vehicle_type == ScriptVehicle::VT_RAIL || vehicle_type == ScriptVehicle::VT_WATER); + if (!IsValidBridge(bridge_id)) return std::nullopt; return GetString(vehicle_type == ScriptVehicle::VT_WATER ? STR_LAI_BRIDGE_DESCRIPTION_AQUEDUCT : ::GetBridgeSpec(bridge_id)->transport_name[vehicle_type]); } diff --git a/src/script/api/script_bridge.hpp b/src/script/api/script_bridge.hpp index cce84f812d..81653bbdd4 100644 --- a/src/script/api/script_bridge.hpp +++ b/src/script/api/script_bridge.hpp @@ -11,6 +11,7 @@ #define SCRIPT_BRIDGE_HPP #include "script_vehicle.hpp" +#include /** * Class that handles all bridge related functions. @@ -69,7 +70,7 @@ public: * @pre vehicle_type == ScriptVehicle::VT_ROAD || vehicle_type == ScriptVehicle::VT_RAIL || vehicle_type == ScriptVehicle::VT_WATER * @return The name the bridge has. */ - static char *GetName(BridgeID bridge_id, ScriptVehicle::VehicleType vehicle_type); + static std::optional GetName(BridgeID bridge_id, ScriptVehicle::VehicleType vehicle_type); /** * Get the maximum speed of a bridge. diff --git a/src/script/api/script_cargo.cpp b/src/script/api/script_cargo.cpp index aad2425461..2bd80a06d6 100644 --- a/src/script/api/script_cargo.cpp +++ b/src/script/api/script_cargo.cpp @@ -27,26 +27,25 @@ return (towneffect_type >= (TownEffect)TE_BEGIN && towneffect_type < (TownEffect)TE_END); } -/* static */ char *ScriptCargo::GetName(CargoID cargo_type) +/* static */ std::optional ScriptCargo::GetName(CargoID cargo_type) { - if (!IsValidCargo(cargo_type)) return nullptr; + if (!IsValidCargo(cargo_type)) return std::nullopt; ::SetDParam(0, 1ULL << cargo_type); return GetString(STR_JUST_CARGO_LIST); } -/* static */ char *ScriptCargo::GetCargoLabel(CargoID cargo_type) +/* static */ std::optional ScriptCargo::GetCargoLabel(CargoID cargo_type) { - if (!IsValidCargo(cargo_type)) return nullptr; + if (!IsValidCargo(cargo_type)) return std::nullopt; const CargoSpec *cargo = ::CargoSpec::Get(cargo_type); /* cargo->label is a uint32 packing a 4 character non-terminated string, * like "PASS", "COAL", "OIL_". New ones can be defined by NewGRFs */ - char *cargo_label = MallocT(sizeof(cargo->label) + 1); + std::string cargo_label; for (uint i = 0; i < sizeof(cargo->label); i++) { - cargo_label[i] = GB(cargo->label, (uint8)(sizeof(cargo->label) - i - 1) * 8, 8); + cargo_label.push_back(GB(cargo->label, (uint8)(sizeof(cargo->label) - i - 1) * 8, 8)); } - cargo_label[sizeof(cargo->label)] = '\0'; return cargo_label; } diff --git a/src/script/api/script_cargo.hpp b/src/script/api/script_cargo.hpp index c5b5c1b5f0..817dfe60f7 100644 --- a/src/script/api/script_cargo.hpp +++ b/src/script/api/script_cargo.hpp @@ -13,6 +13,7 @@ #include "script_object.hpp" #include "../../cargotype.h" #include "../../linkgraph/linkgraph_type.h" +#include /** * Class that handles all cargo related functions. @@ -90,7 +91,7 @@ public: * @pre IsValidCargo(cargo_type). * @return The name of the cargo type. */ - static char *GetName(CargoID cargo_type); + static std::optional GetName(CargoID cargo_type); /** * Gets the string representation of the cargo label. @@ -107,7 +108,7 @@ public: * - In other words: Only use the cargo label, if you know more about the behaviour * of a specific cargo from a specific industry set, than the API methods can tell you. */ - static char *GetCargoLabel(CargoID cargo_type); + static std::optional GetCargoLabel(CargoID cargo_type); /** * Checks whether the give cargo is a freight or not. diff --git a/src/script/api/script_client.cpp b/src/script/api/script_client.cpp index ede1b925fe..87f90d65c8 100644 --- a/src/script/api/script_client.cpp +++ b/src/script/api/script_client.cpp @@ -32,11 +32,11 @@ static NetworkClientInfo *FindClientInfo(ScriptClient::ClientID client) return (FindClientInfo(client) == nullptr ? ScriptClient::CLIENT_INVALID : client); } -/* static */ char *ScriptClient::GetName(ScriptClient::ClientID client) +/* static */ std::optional ScriptClient::GetName(ScriptClient::ClientID client) { NetworkClientInfo *ci = FindClientInfo(client); - if (ci == nullptr) return nullptr; - return stredup(ci->client_name.c_str()); + if (ci == nullptr) return std::nullopt; + return ci->client_name; } /* static */ ScriptCompany::CompanyID ScriptClient::GetCompany(ScriptClient::ClientID client) diff --git a/src/script/api/script_client.hpp b/src/script/api/script_client.hpp index 7400e7247c..8ec2d2a2a1 100644 --- a/src/script/api/script_client.hpp +++ b/src/script/api/script_client.hpp @@ -14,6 +14,7 @@ #include "script_date.hpp" #include "script_company.hpp" #include "../../network/network_type.h" +#include /** * Class that handles all client related functions. @@ -45,7 +46,7 @@ public: * @pre ResolveClientID(client) != CLIENT_INVALID. * @return The name of the given client. */ - static char *GetName(ClientID client); + static std::optional GetName(ClientID client); /** * Get the company in which the given client is playing. diff --git a/src/script/api/script_company.cpp b/src/script/api/script_company.cpp index 4c081b68f1..bd43611da1 100644 --- a/src/script/api/script_company.cpp +++ b/src/script/api/script_company.cpp @@ -53,10 +53,10 @@ return ScriptObject::DoCommand(0, 0, 0, CMD_RENAME_COMPANY, text); } -/* static */ char *ScriptCompany::GetName(ScriptCompany::CompanyID company) +/* static */ std::optional ScriptCompany::GetName(ScriptCompany::CompanyID company) { company = ResolveCompanyID(company); - if (company == COMPANY_INVALID) return nullptr; + if (company == COMPANY_INVALID) return std::nullopt; ::SetDParam(0, company); return GetString(STR_COMPANY_NAME); @@ -75,20 +75,13 @@ return ScriptObject::DoCommand(0, 0, 0, CMD_RENAME_PRESIDENT, text); } -/* static */ char *ScriptCompany::GetPresidentName(ScriptCompany::CompanyID company) +/* static */ std::optional ScriptCompany::GetPresidentName(ScriptCompany::CompanyID company) { company = ResolveCompanyID(company); + if (company == COMPANY_INVALID) return std::nullopt; - static const int len = 64; - char *president_name = MallocT(len); - if (company != COMPANY_INVALID) { - ::SetDParam(0, company); - ::GetString(president_name, STR_PRESIDENT_NAME, &president_name[len - 1]); - } else { - *president_name = '\0'; - } - - return president_name; + ::SetDParam(0, company); + return GetString(STR_PRESIDENT_NAME); } /* static */ bool ScriptCompany::SetPresidentGender(Gender gender) diff --git a/src/script/api/script_company.hpp b/src/script/api/script_company.hpp index b57c4b8d8b..7aa590c975 100644 --- a/src/script/api/script_company.hpp +++ b/src/script/api/script_company.hpp @@ -14,6 +14,7 @@ #include "../../economy_type.h" #include "../../livery.h" #include "../../gfx_type.h" +#include /** * Class that handles all company related functions. @@ -151,7 +152,7 @@ public: * @pre ResolveCompanyID(company) != COMPANY_INVALID. * @return The name of the given company. */ - static char *GetName(CompanyID company); + static std::optional GetName(CompanyID company); /** * Set the name of your president. @@ -169,7 +170,7 @@ public: * @pre ResolveCompanyID(company) != COMPANY_INVALID. * @return The name of the president of the given company. */ - static char *GetPresidentName(CompanyID company); + static std::optional GetPresidentName(CompanyID company); /** * Set the gender of the president of your company. diff --git a/src/script/api/script_controller.cpp b/src/script/api/script_controller.cpp index f9062ddec0..da3a6422a3 100644 --- a/src/script/api/script_controller.cpp +++ b/src/script/api/script_controller.cpp @@ -45,15 +45,13 @@ throw Script_Suspend(ticks, nullptr); } -/* static */ void ScriptController::Break(const char* message) +/* static */ void ScriptController::Break(const std::string &message) { if (_network_dedicated || !_settings_client.gui.ai_developer_tools) return; ScriptObject::GetActiveInstance()->Pause(); - char log_message[1024]; - seprintf(log_message, lastof(log_message), "Break: %s", message); - ScriptLog::Log(ScriptLogTypes::LOG_SQ_ERROR, log_message); + ScriptLog::Log(ScriptLogTypes::LOG_SQ_ERROR, fmt::format("Break: {}", message)); /* Inform script developer that their script has been paused and * needs manual action to continue. */ @@ -64,7 +62,7 @@ } } -/* static */ void ScriptController::Print(bool error_msg, const char *message) +/* static */ void ScriptController::Print(bool error_msg, const std::string &message) { ScriptLog::Log(error_msg ? ScriptLogTypes::LOG_SQ_ERROR : ScriptLogTypes::LOG_SQ_INFO, message); } @@ -91,7 +89,7 @@ ScriptController::ScriptController(CompanyID company) : Squirrel::DecreaseOps(ScriptObject::GetActiveInstance()->engine->GetVM(), amount); } -/* static */ int ScriptController::GetSetting(const char *name) +/* static */ int ScriptController::GetSetting(const std::string &name) { return ScriptObject::GetActiveInstance()->GetSetting(name); } @@ -101,7 +99,7 @@ ScriptController::ScriptController(CompanyID company) : return _openttd_newgrf_version; } -/* static */ HSQOBJECT ScriptController::Import(const char *library, const char *class_name, int version) +/* static */ HSQOBJECT ScriptController::Import(const std::string &library, const std::string &class_name, int version) { ScriptController *controller = ScriptObject::GetActiveInstance()->GetController(); Squirrel *engine = ScriptObject::GetActiveInstance()->engine; @@ -109,9 +107,7 @@ ScriptController::ScriptController(CompanyID company) : ScriptInfo *lib = ScriptObject::GetActiveInstance()->FindLibrary(library, version); if (lib == nullptr) { - char error[1024]; - seprintf(error, lastof(error), "couldn't find library '%s' with version %d", library, version); - throw sq_throwerror(vm, error); + throw sq_throwerror(vm, fmt::format("couldn't find library '{}' with version {}", library, version)); } /* Internally we store libraries as 'library.version' */ @@ -138,9 +134,7 @@ ScriptController::ScriptController(CompanyID company) : sq_newclass(vm, SQFalse); /* Load the library */ if (!engine->LoadScript(vm, lib->GetMainScript(), false)) { - char error[1024]; - seprintf(error, lastof(error), "there was a compile error when importing '%s' version %d", library, version); - throw sq_throwerror(vm, error); + throw sq_throwerror(vm, fmt::format("there was a compile error when importing '{}' version {}", library, version)); } /* Create the fake class */ sq_newslot(vm, -3, SQFalse); @@ -157,15 +151,13 @@ ScriptController::ScriptController(CompanyID company) : } sq_pushstring(vm, lib->GetInstanceName(), -1); if (SQ_FAILED(sq_get(vm, -2))) { - char error[1024]; - seprintf(error, lastof(error), "unable to find class '%s' in the library '%s' version %d", lib->GetInstanceName(), library, version); - throw sq_throwerror(vm, error); + throw sq_throwerror(vm, fmt::format("unable to find class '{}' in the library '{}' version {}", lib->GetInstanceName(), library, version)); } HSQOBJECT obj; sq_getstackobj(vm, -1, &obj); sq_pop(vm, 3); - if (StrEmpty(class_name)) return obj; + if (class_name.empty()) return obj; /* Now link the name the user wanted to our 'fake' class */ sq_pushobject(vm, parent); diff --git a/src/script/api/script_controller.hpp b/src/script/api/script_controller.hpp index c2dab8d45c..120cef8e93 100644 --- a/src/script/api/script_controller.hpp +++ b/src/script/api/script_controller.hpp @@ -131,7 +131,7 @@ public: * @param name The name of the setting. * @return the value for the setting, or -1 if the setting is not known. */ - static int GetSetting(const char *name); + static int GetSetting(const std::string &name); /** * Get the OpenTTD version of this executable. The version is formatted @@ -187,7 +187,7 @@ public: * @note gui.ai_developer_tools setting must be enabled or the break is * ignored. */ - static void Break(const char* message); + static void Break(const std::string &message); /** * When Squirrel triggers a print, this function is called. @@ -196,7 +196,7 @@ public: * @param message The message Squirrel logged. * @note Use ScriptLog.Info/Warning/Error instead of 'print'. */ - static void Print(bool error_msg, const char *message); + static void Print(bool error_msg, const std::string &message); /** * Import a library. @@ -207,7 +207,7 @@ public: * @return The loaded library object. If class_name is set, it is also available (under the scope of the import) under that name. * @note This command can be called from the global space, and does not need an instance. */ - static HSQOBJECT Import(const char *library, const char *class_name, int version); + static HSQOBJECT Import(const std::string &library, const std::string &class_name, int version); private: typedef std::map LoadedLibraryList; ///< The type for loaded libraries. diff --git a/src/script/api/script_engine.cpp b/src/script/api/script_engine.cpp index 952dae5885..f5bdd6d815 100644 --- a/src/script/api/script_engine.cpp +++ b/src/script/api/script_engine.cpp @@ -40,9 +40,9 @@ return e != nullptr && ::IsEngineBuildable(engine_id, e->type, ScriptObject::GetCompany()); } -/* static */ char *ScriptEngine::GetName(EngineID engine_id) +/* static */ std::optional ScriptEngine::GetName(EngineID engine_id) { - if (!IsValidEngine(engine_id)) return nullptr; + if (!IsValidEngine(engine_id)) return std::nullopt; ::SetDParam(0, engine_id); return GetString(STR_ENGINE_NAME); diff --git a/src/script/api/script_engine.hpp b/src/script/api/script_engine.hpp index 73414c1b4c..ddab595549 100644 --- a/src/script/api/script_engine.hpp +++ b/src/script/api/script_engine.hpp @@ -14,6 +14,7 @@ #include "script_rail.hpp" #include "script_airport.hpp" #include "script_date.hpp" +#include /** * Class that handles all engine related functions. @@ -44,7 +45,7 @@ public: * @pre IsValidEngine(engine_id). * @return The name the engine has. */ - static char *GetName(EngineID engine_id); + static std::optional GetName(EngineID engine_id); /** * Get the cargo-type of an engine. In case it can transport multiple cargoes, it diff --git a/src/script/api/script_error.cpp b/src/script/api/script_error.cpp index 318a6ff494..3d365c23bb 100644 --- a/src/script/api/script_error.cpp +++ b/src/script/api/script_error.cpp @@ -23,9 +23,9 @@ ScriptError::ScriptErrorMapString ScriptError::error_map_string = ScriptError::S return ScriptObject::GetLastError(); } -/* static */ char *ScriptError::GetLastErrorString() +/* static */ std::optional ScriptError::GetLastErrorString() { - return stredup((*error_map_string.find(ScriptError::GetLastError())).second); + return (*error_map_string.find(ScriptError::GetLastError())).second; } /* static */ ScriptErrorType ScriptError::StringToError(StringID internal_string_id) diff --git a/src/script/api/script_error.hpp b/src/script/api/script_error.hpp index e461a7d46d..ba1e398298 100644 --- a/src/script/api/script_error.hpp +++ b/src/script/api/script_error.hpp @@ -13,6 +13,7 @@ #include "script_object.hpp" #include "script_companymode.hpp" #include +#include /** * Helper to write precondition enforcers for the script API in an abbreviated manner. @@ -193,7 +194,7 @@ public: * Get the last error in string format (for human readability). * @return An ErrorMessage enum item, as string. */ - static char *GetLastErrorString(); + static std::optional GetLastErrorString(); /** * Get the error based on the OpenTTD StringID. diff --git a/src/script/api/script_event_types.cpp b/src/script/api/script_event_types.cpp index 56bbd2ba4e..2d388e8012 100644 --- a/src/script/api/script_event_types.cpp +++ b/src/script/api/script_event_types.cpp @@ -26,9 +26,9 @@ bool ScriptEventEnginePreview::IsEngineValid() const return e != nullptr && e->IsEnabled(); } -char *ScriptEventEnginePreview::GetName() +std::optional ScriptEventEnginePreview::GetName() { - if (!this->IsEngineValid()) return nullptr; + if (!this->IsEngineValid()) return std::nullopt; ::SetDParam(0, this->engine); return GetString(STR_ENGINE_NAME); diff --git a/src/script/api/script_event_types.hpp b/src/script/api/script_event_types.hpp index 24e50ae4b0..c43fff0781 100644 --- a/src/script/api/script_event_types.hpp +++ b/src/script/api/script_event_types.hpp @@ -13,6 +13,7 @@ #include "script_event.hpp" #include "script_goal.hpp" #include "script_window.hpp" +#include /** * Event Vehicle Crash, indicating a vehicle of yours is crashed. @@ -239,7 +240,7 @@ public: * Get the name of the offered engine. * @return The name the engine has. */ - char *GetName(); + std::optional GetName(); /** * Get the cargo-type of the offered engine. In case it can transport multiple cargoes, it diff --git a/src/script/api/script_gamesettings.cpp b/src/script/api/script_gamesettings.cpp index 485f7612a9..c6da9d1822 100644 --- a/src/script/api/script_gamesettings.cpp +++ b/src/script/api/script_gamesettings.cpp @@ -15,13 +15,13 @@ #include "../../safeguards.h" -/* static */ bool ScriptGameSettings::IsValid(const char *setting) +/* static */ bool ScriptGameSettings::IsValid(const std::string &setting) { const SettingDesc *sd = GetSettingFromName(setting); return sd != nullptr && sd->IsIntSetting(); } -/* static */ SQInteger ScriptGameSettings::GetValue(const char *setting) +/* static */ SQInteger ScriptGameSettings::GetValue(const std::string &setting) { if (!IsValid(setting)) return -1; @@ -30,7 +30,7 @@ return sd->AsIntSetting()->Read(&_settings_game); } -/* static */ bool ScriptGameSettings::SetValue(const char *setting, SQInteger value) +/* static */ bool ScriptGameSettings::SetValue(const std::string &setting, SQInteger value) { EnforceDeityOrCompanyModeValid(false); if (!IsValid(setting)) return false; diff --git a/src/script/api/script_gamesettings.hpp b/src/script/api/script_gamesettings.hpp index 83d023e70f..d4bbf609de 100644 --- a/src/script/api/script_gamesettings.hpp +++ b/src/script/api/script_gamesettings.hpp @@ -44,7 +44,7 @@ public: * @note Results achieved in the past offer no guarantee for the future. * @return True if and only if the setting is valid. */ - static bool IsValid(const char *setting); + static bool IsValid(const std::string &setting); /** * Gets the value of the game setting. @@ -57,7 +57,7 @@ public: * @note Results achieved in the past offer no guarantee for the future. * @return The value for the setting. */ - static SQInteger GetValue(const char *setting); + static SQInteger GetValue(const std::string &setting); /** * Sets the value of the game setting. @@ -69,7 +69,7 @@ public: * @note Results achieved in the past offer no guarantee for the future. * @api -ai */ - static bool SetValue(const char *setting, SQInteger value); + static bool SetValue(const std::string &setting, SQInteger value); /** * Checks whether the given vehicle-type is disabled for companies. diff --git a/src/script/api/script_group.cpp b/src/script/api/script_group.cpp index 5d2fdbeccf..9b0a95659b 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -65,9 +65,9 @@ return ScriptObject::DoCommand(0, group_id, 0, CMD_ALTER_GROUP, text); } -/* static */ char *ScriptGroup::GetName(GroupID group_id) +/* static */ std::optional ScriptGroup::GetName(GroupID group_id) { - if (!IsValidGroup(group_id)) return nullptr; + if (!IsValidGroup(group_id)) return std::nullopt; ::SetDParam(0, group_id); return GetString(STR_GROUP_NAME); diff --git a/src/script/api/script_group.hpp b/src/script/api/script_group.hpp index ba61be3fd4..15f8b7cbef 100644 --- a/src/script/api/script_group.hpp +++ b/src/script/api/script_group.hpp @@ -12,6 +12,7 @@ #include "script_vehicle.hpp" #include "../../group_type.h" +#include /** * Class that handles all group related functions. @@ -84,7 +85,7 @@ public: * @pre IsValidGroup(group_id). * @return The name the group has. */ - static char *GetName(GroupID group_id); + static std::optional GetName(GroupID group_id); /** * Set parent group of a group. diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index ef5ece5f88..971c714937 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -40,9 +40,9 @@ return ::GetIndustryIndex(tile); } -/* static */ char *ScriptIndustry::GetName(IndustryID industry_id) +/* static */ std::optional ScriptIndustry::GetName(IndustryID industry_id) { - if (!IsValidIndustry(industry_id)) return nullptr; + if (!IsValidIndustry(industry_id)) return std::nullopt; ::SetDParam(0, industry_id); return GetString(STR_INDUSTRY_NAME); diff --git a/src/script/api/script_industry.hpp b/src/script/api/script_industry.hpp index d7fca868d0..fefb7cbfd2 100644 --- a/src/script/api/script_industry.hpp +++ b/src/script/api/script_industry.hpp @@ -14,6 +14,7 @@ #include "script_date.hpp" #include "script_object.hpp" #include "../../industry.h" +#include /** * Class that handles all industry related functions. @@ -79,7 +80,7 @@ public: * @pre IsValidIndustry(industry_id). * @return The name of the industry. */ - static char *GetName(IndustryID industry_id); + static std::optional GetName(IndustryID industry_id); /** * Set the custom text of an industry, shown in the GUI. diff --git a/src/script/api/script_industrytype.cpp b/src/script/api/script_industrytype.cpp index 3155a8078f..beaf512999 100644 --- a/src/script/api/script_industrytype.cpp +++ b/src/script/api/script_industrytype.cpp @@ -56,9 +56,9 @@ return ::GetIndustrySpec(industry_type)->GetConstructionCost(); } -/* static */ char *ScriptIndustryType::GetName(IndustryType industry_type) +/* static */ std::optional ScriptIndustryType::GetName(IndustryType industry_type) { - if (!IsValidIndustryType(industry_type)) return nullptr; + if (!IsValidIndustryType(industry_type)) return std::nullopt; return GetString(::GetIndustrySpec(industry_type)->name); } diff --git a/src/script/api/script_industrytype.hpp b/src/script/api/script_industrytype.hpp index a581e58444..0642f095a1 100644 --- a/src/script/api/script_industrytype.hpp +++ b/src/script/api/script_industrytype.hpp @@ -11,6 +11,7 @@ #define SCRIPT_INDUSTRYTYPE_HPP #include "script_list.hpp" +#include /** * Class that handles all industry-type related functions. @@ -39,7 +40,7 @@ public: * @pre IsValidIndustryType(industry_type). * @return The name of an industry. */ - static char *GetName(IndustryType industry_type); + static std::optional GetName(IndustryType industry_type); /** * Get a list of CargoID possible produced by this industry-type. diff --git a/src/script/api/script_list.cpp b/src/script/api/script_list.cpp index 0821b8368a..6c2c1d5d3a 100644 --- a/src/script/api/script_list.cpp +++ b/src/script/api/script_list.cpp @@ -28,7 +28,7 @@ public: /** * Virtual dtor, needed to mute warnings. */ - virtual ~ScriptListSorter() { } + virtual ~ScriptListSorter() = default; /** * Get the first item of the sorter. diff --git a/src/script/api/script_log.cpp b/src/script/api/script_log.cpp index 2b601a014f..aeb8b03fbe 100644 --- a/src/script/api/script_log.cpp +++ b/src/script/api/script_log.cpp @@ -17,22 +17,22 @@ #include "../../safeguards.h" -/* static */ void ScriptLog::Info(const char *message) +/* static */ void ScriptLog::Info(const std::string &message) { ScriptLog::Log(ScriptLogTypes::LOG_INFO, message); } -/* static */ void ScriptLog::Warning(const char *message) +/* static */ void ScriptLog::Warning(const std::string &message) { ScriptLog::Log(ScriptLogTypes::LOG_WARNING, message); } -/* static */ void ScriptLog::Error(const char *message) +/* static */ void ScriptLog::Error(const std::string &message) { ScriptLog::Log(ScriptLogTypes::LOG_ERROR, message); } -/* static */ void ScriptLog::Log(ScriptLogTypes::ScriptLogType level, const char *message) +/* static */ void ScriptLog::Log(ScriptLogTypes::ScriptLogType level, const std::string &message) { ScriptLogTypes::LogData &logdata = ScriptObject::GetLogData(); @@ -43,8 +43,7 @@ line.type = level; /* Cut string after first \n */ - const char *newline = strchr(message, '\n'); - line.text = std::string(message, 0, newline == nullptr ? strlen(message) : newline - message); + line.text = message.substr(0, message.find_first_of('\n')); char logc; diff --git a/src/script/api/script_log.hpp b/src/script/api/script_log.hpp index c17ea6bf55..afc12151cf 100644 --- a/src/script/api/script_log.hpp +++ b/src/script/api/script_log.hpp @@ -27,21 +27,21 @@ public: * @param message The message to log. * @note Special characters such as U+0000-U+0019 and U+E000-U+E1FF are not supported and removed or replaced by a question mark. This includes newlines and tabs. */ - static void Info(const char *message); + static void Info(const std::string &message); /** * Print a Warning message to the logs. * @param message The message to log. * @note Special characters such as U+0000-U+0019 and U+E000-U+E1FF are not supported and removed or replaced by a question mark. This includes newlines and tabs. */ - static void Warning(const char *message); + static void Warning(const std::string &message); /** * Print an Error message to the logs. * @param message The message to log. * @note Special characters such as U+0000-U+0019 and U+E000-U+E1FF are not supported and removed or replaced by a question mark. This includes newlines and tabs. */ - static void Error(const char *message); + static void Error(const std::string &message); /** * Log this message once. @@ -53,7 +53,7 @@ private: /** * Internal command to log the message in a common way. */ - static void Log(ScriptLogTypes::ScriptLogType level, const char *message); + static void Log(ScriptLogTypes::ScriptLogType level, const std::string &message); }; #endif /* SCRIPT_LOG_HPP */ diff --git a/src/script/api/script_newgrf.cpp b/src/script/api/script_newgrf.cpp index 10a0a966c3..884918c549 100644 --- a/src/script/api/script_newgrf.cpp +++ b/src/script/api/script_newgrf.cpp @@ -50,15 +50,15 @@ ScriptNewGRFList::ScriptNewGRFList() return 0; } -/* static */ char *ScriptNewGRF::GetName(SQInteger grfid) +/* static */ std::optional ScriptNewGRF::GetName(SQInteger grfid) { grfid = BSWAP32(GB(grfid, 0, 32)); // Match people's expectations. for (auto c = _grfconfig; c != nullptr; c = c->next) { if (!HasBit(c->flags, GCF_STATIC) && c->ident.grfid == grfid) { - return ::stredup(c->GetName()); + return c->GetName(); } } - return nullptr; + return std::nullopt; } diff --git a/src/script/api/script_newgrf.hpp b/src/script/api/script_newgrf.hpp index c11202a112..b57e5ed62b 100644 --- a/src/script/api/script_newgrf.hpp +++ b/src/script/api/script_newgrf.hpp @@ -11,6 +11,7 @@ #define SCRIPT_NEWGRF_HPP #include "script_list.hpp" +#include /** * Create a list of loaded NewGRFs. @@ -50,7 +51,7 @@ public: * @pre ScriptNewGRF::IsLoaded(grfid). * @return The name of the NewGRF or null if no name is defined. */ - static char *GetName(SQInteger grfid); + static std::optional GetName(SQInteger grfid); }; #endif /* SCRIPT_NEWGRF_HPP */ diff --git a/src/script/api/script_object.cpp b/src/script/api/script_object.cpp index 86404d202d..b6a0385f77 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -311,12 +311,9 @@ ScriptObject::ActiveInstance::~ActiveInstance() return GetStorage()->log_data; } -/* static */ char *ScriptObject::GetString(StringID string) +/* static */ std::string ScriptObject::GetString(StringID string) { - char buffer[64]; - ::GetString(buffer, string, lastof(buffer)); - ::StrMakeValidInPlace(buffer, lastof(buffer), SVS_NONE); - return ::stredup(buffer); + return ::StrMakeValid(::GetString(string)); } /* static */ void ScriptObject::SetCallbackVariable(int index, int value) diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index 333c9a2cd1..b865bac777 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -323,7 +323,7 @@ protected: /** * Get an allocated string with all control codes stripped off. */ - static char *GetString(StringID string); + static std::string GetString(StringID string); static bool IsNewUniqueLogMessage(const std::string &msg); diff --git a/src/script/api/script_objecttype.cpp b/src/script/api/script_objecttype.cpp index cbd6934e5f..b8b593977b 100644 --- a/src/script/api/script_objecttype.cpp +++ b/src/script/api/script_objecttype.cpp @@ -22,9 +22,9 @@ return ObjectSpec::Get(object_type)->IsEverAvailable(); } -/* static */ char *ScriptObjectType::GetName(ObjectType object_type) +/* static */ std::optional ScriptObjectType::GetName(ObjectType object_type) { - EnforcePrecondition(nullptr, IsValidObjectType(object_type)); + EnforcePrecondition(std::nullopt, IsValidObjectType(object_type)); return GetString(ObjectSpec::Get(object_type)->name); } diff --git a/src/script/api/script_objecttype.hpp b/src/script/api/script_objecttype.hpp index 6b387ad5bd..109b578508 100644 --- a/src/script/api/script_objecttype.hpp +++ b/src/script/api/script_objecttype.hpp @@ -13,6 +13,7 @@ #include "script_list.hpp" #include "../../newgrf_object.h" +#include /** * Class that handles all object-type related functions. @@ -33,7 +34,7 @@ public: * @pre IsValidObjectType(object_type). * @return The name of an object. */ - static char *GetName(ObjectType object_type); + static std::optional GetName(ObjectType object_type); /** * Get the number of views for an object-type. diff --git a/src/script/api/script_rail.cpp b/src/script/api/script_rail.cpp index 500453801e..bc4295a701 100644 --- a/src/script/api/script_rail.cpp +++ b/src/script/api/script_rail.cpp @@ -21,9 +21,9 @@ #include "../../safeguards.h" -/* static */ char *ScriptRail::GetName(RailType rail_type) +/* static */ std::optional ScriptRail::GetName(RailType rail_type) { - if (!IsRailTypeAvailable(rail_type)) return nullptr; + if (!IsRailTypeAvailable(rail_type)) return std::nullopt; return GetString(GetRailTypeInfo((::RailType)rail_type)->strings.menu_text); } diff --git a/src/script/api/script_rail.hpp b/src/script/api/script_rail.hpp index 83a141e554..a64ed9566d 100644 --- a/src/script/api/script_rail.hpp +++ b/src/script/api/script_rail.hpp @@ -13,6 +13,7 @@ #include "script_tile.hpp" #include "../../signal_type.h" #include "../../track_type.h" +#include /** * Class that handles all rail related functions. @@ -101,7 +102,7 @@ public: * means that the name could be something like "Maglev construction" instead * of just "Maglev". */ - static char *GetName(RailType rail_type); + static std::optional GetName(RailType rail_type); /** * Checks whether the given tile is actually a tile with rail that can be diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index 8f69671529..4077a4ca5e 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -21,9 +21,9 @@ return ScriptCargo::HasCargoClass(cargo_type, ScriptCargo::CC_PASSENGERS) ? ROADVEHTYPE_BUS : ROADVEHTYPE_TRUCK; } -/* static */ char *ScriptRoad::GetName(RoadType road_type) +/* static */ std::optional ScriptRoad::GetName(RoadType road_type) { - if (!IsRoadTypeAvailable(road_type)) return nullptr; + if (!IsRoadTypeAvailable(road_type)) return std::nullopt; return GetString(GetRoadTypeInfo((::RoadType)road_type)->strings.name); } @@ -392,7 +392,7 @@ static bool NormaliseTileOffset(int32 *tile) return false; } -/* static */ SQInteger ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::Slope slope_, Array<> existing, TileIndex start_, TileIndex end_) +/* static */ SQInteger ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::Slope slope_, Array<> &&existing, TileIndex start_, TileIndex end_) { ::Slope slope = (::Slope)slope_; int32 start = start_; @@ -433,7 +433,7 @@ static bool NormaliseTileOffset(int32 *tile) if (HasBit(rb, i)) existing.emplace_back(neighbours[i]); } - return ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::GetSlope(tile), existing, start - tile, end - tile); + return ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::GetSlope(tile), std::move(existing), start - tile, end - tile); } /** diff --git a/src/script/api/script_road.hpp b/src/script/api/script_road.hpp index 7f99f84c85..fb215d333b 100644 --- a/src/script/api/script_road.hpp +++ b/src/script/api/script_road.hpp @@ -13,6 +13,7 @@ #include "script_tile.hpp" #include "../squirrel_helper_type.hpp" #include "../../../road.h" +#include /** * Class that handles all road related functions. @@ -90,7 +91,7 @@ public: * @pre IsRoadTypeAvailable(road_type). * @return The name the road type has. */ - static char *GetName(RoadType road_type); + static std::optional GetName(RoadType road_type); /** * Determines whether a busstop or a truckstop is needed to transport a certain cargo. @@ -265,7 +266,7 @@ public: * they are build or 2 when building the first part automatically * builds the second part. -1 means the preconditions are not met. */ - static SQInteger CanBuildConnectedRoadParts(ScriptTile::Slope slope, Array<> existing, TileIndex start, TileIndex end); + static SQInteger CanBuildConnectedRoadParts(ScriptTile::Slope slope, Array<> &&existing, TileIndex start, TileIndex end); /** * Lookup function for building road parts independent of whether the diff --git a/src/script/api/script_roadtypelist.cpp b/src/script/api/script_roadtypelist.cpp index c462054c12..0bb8df8efc 100644 --- a/src/script/api/script_roadtypelist.cpp +++ b/src/script/api/script_roadtypelist.cpp @@ -18,7 +18,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)) && + if (::HasRoadTypeAvail(ScriptObject::GetCompany(), rt) && !HasBit(GetRoadTypeInfo(rt)->extra_flags, RXTF_NOT_AVAILABLE_AI_GS)) { this->AddItem(rt); } diff --git a/src/script/api/script_sign.cpp b/src/script/api/script_sign.cpp index 2f84298593..c8259f2236 100644 --- a/src/script/api/script_sign.cpp +++ b/src/script/api/script_sign.cpp @@ -46,9 +46,9 @@ return ScriptObject::DoCommand(0, sign_id, 0, CMD_RENAME_SIGN, text); } -/* static */ char *ScriptSign::GetName(SignID sign_id) +/* static */ std::optional ScriptSign::GetName(SignID sign_id) { - if (!IsValidSign(sign_id)) return nullptr; + if (!IsValidSign(sign_id)) return std::nullopt; ::SetDParam(0, sign_id); return GetString(STR_SIGN_NAME); diff --git a/src/script/api/script_sign.hpp b/src/script/api/script_sign.hpp index 06a17da92b..11a5c69d3f 100644 --- a/src/script/api/script_sign.hpp +++ b/src/script/api/script_sign.hpp @@ -12,6 +12,7 @@ #include "script_company.hpp" #include "script_error.hpp" +#include /** * Class that handles all sign related functions. @@ -55,7 +56,7 @@ public: * @pre IsValidSign(sign_id). * @return The name of the sign. */ - static char *GetName(SignID sign_id); + static std::optional GetName(SignID sign_id); /** * Get the owner of a sign. diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp index 1c9cb1e619..21e0663f2a 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -23,7 +23,7 @@ #include "../../safeguards.h" -RawText::RawText(const char *text) : text(text) +RawText::RawText(const std::string &text) : text(text) { } diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp index 13d61f0710..5f8acd9f62 100644 --- a/src/script/api/script_text.hpp +++ b/src/script/api/script_text.hpp @@ -42,7 +42,7 @@ public: */ class RawText : public Text { public: - RawText(const char *text); + RawText(const std::string &text); const std::string GetEncodedText() override { return this->text; } private: diff --git a/src/script/api/script_town.cpp b/src/script/api/script_town.cpp index 05e04fd759..244c121a85 100644 --- a/src/script/api/script_town.cpp +++ b/src/script/api/script_town.cpp @@ -32,9 +32,9 @@ return ::Town::IsValidID(town_id); } -/* static */ char *ScriptTown::GetName(TownID town_id) +/* static */ std::optional ScriptTown::GetName(TownID town_id) { - if (!IsValidTown(town_id)) return nullptr; + if (!IsValidTown(town_id)) return std::nullopt; ::SetDParam(0, town_id); return GetString(STR_TOWN_NAME); diff --git a/src/script/api/script_town.hpp b/src/script/api/script_town.hpp index bd2167f0f7..7f91f317ea 100644 --- a/src/script/api/script_town.hpp +++ b/src/script/api/script_town.hpp @@ -13,6 +13,7 @@ #include "script_cargo.hpp" #include "script_company.hpp" #include "../../town_type.h" +#include /** * Class that handles all town related functions. @@ -142,7 +143,7 @@ public: * @pre IsValidTown(town_id). * @return The name of the town. */ - static char *GetName(TownID town_id); + static std::optional GetName(TownID town_id); /** * Rename a town. diff --git a/src/script/api/script_vehicle.cpp b/src/script/api/script_vehicle.cpp index f8352725eb..1d6f432589 100644 --- a/src/script/api/script_vehicle.cpp +++ b/src/script/api/script_vehicle.cpp @@ -298,9 +298,9 @@ return ::Vehicle::Get(vehicle_id)->unitnumber; } -/* static */ char *ScriptVehicle::GetName(VehicleID vehicle_id) +/* static */ std::optional ScriptVehicle::GetName(VehicleID vehicle_id) { - if (!IsPrimaryVehicle(vehicle_id)) return nullptr; + if (!IsPrimaryVehicle(vehicle_id)) return std::nullopt; ::SetDParam(0, vehicle_id); return GetString(STR_VEHICLE_NAME); diff --git a/src/script/api/script_vehicle.hpp b/src/script/api/script_vehicle.hpp index e76ffccec5..0f9a2e6fce 100644 --- a/src/script/api/script_vehicle.hpp +++ b/src/script/api/script_vehicle.hpp @@ -11,6 +11,7 @@ #define SCRIPT_VEHICLE_HPP #include "script_road.hpp" +#include /** * Class that handles all vehicle related functions. @@ -137,7 +138,7 @@ public: * @pre IsPrimaryVehicle(vehicle_id). * @return The name the vehicle has. */ - static char *GetName(VehicleID vehicle_id); + static std::optional GetName(VehicleID vehicle_id); /** * Get the owner of a vehicle. diff --git a/src/script/script_config.cpp b/src/script/script_config.cpp index e3ed8f2174..2f9d1027b7 100644 --- a/src/script/script_config.cpp +++ b/src/script/script_config.cpp @@ -15,14 +15,18 @@ #include "../textfile_gui.h" #include "../string_func.h" #include "../3rdparty/fmt/format.h" +#include #include "../safeguards.h" -void ScriptConfig::Change(const char *name, int version, bool force_exact_match, bool is_random) +void ScriptConfig::Change(std::optional name, int version, bool force_exact_match, bool is_random) { - free(this->name); - this->name = (name == nullptr) ? nullptr : stredup(name); - this->info = (name == nullptr) ? nullptr : this->FindInfo(this->name, version, force_exact_match); + if (name.has_value()) { + this->name = std::move(name.value()); + this->info = this->FindInfo(this->name, version, force_exact_match); + } else { + this->info = nullptr; + } this->version = (info == nullptr) ? -1 : info->GetVersion(); this->is_random = is_random; this->config_list.reset(); @@ -45,7 +49,7 @@ void ScriptConfig::Change(const char *name, int version, bool force_exact_match, ScriptConfig::ScriptConfig(const ScriptConfig *config) { - this->name = (config->name == nullptr) ? nullptr : stredup(config->name); + this->name = config->name; this->info = config->info; this->version = config->version; this->is_random = config->is_random; @@ -61,7 +65,6 @@ ScriptConfig::ScriptConfig(const ScriptConfig *config) ScriptConfig::~ScriptConfig() { - free(this->name); this->ResetSettings(); this->to_load_data.reset(); } @@ -101,7 +104,7 @@ int ScriptConfig::GetSetting(const std::string &name) const return (*it).second; } -void ScriptConfig::SetSetting(const std::string &name, int value) +void ScriptConfig::SetSetting(const std::string_view name, int value) { /* You can only set Script specific settings if an Script is selected. */ if (this->info == nullptr) return; @@ -111,7 +114,7 @@ void ScriptConfig::SetSetting(const std::string &name, int value) value = Clamp(value, config_item->min_value, config_item->max_value); - this->settings[name] = value; + this->settings[std::string{name}] = value; } void ScriptConfig::ResetSettings() @@ -157,7 +160,7 @@ bool ScriptConfig::IsRandom() const return this->is_random; } -const char *ScriptConfig::GetName() const +const std::string &ScriptConfig::GetName() const { return this->name; } @@ -169,28 +172,24 @@ int ScriptConfig::GetVersion() const void ScriptConfig::StringToSettings(const std::string &value) { - char *value_copy = stredup(value.c_str()); - char *s = value_copy; - - while (s != nullptr) { + std::string_view to_process = value; + for (;;) { /* Analyze the string ('name=value,name=value\0') */ - char *item_name = s; - s = strchr(s, '='); - if (s == nullptr) break; - if (*s == '\0') break; - *s = '\0'; - s++; - - char *item_value = s; - s = strchr(s, ','); - if (s != nullptr) { - *s = '\0'; - s++; - } + size_t pos = to_process.find_first_of('='); + if (pos == std::string_view::npos) return; + + std::string_view item_name = to_process.substr(0, pos); + + to_process.remove_prefix(pos + 1); + pos = to_process.find_first_of(','); + int item_value = 0; + std::from_chars(to_process.data(), to_process.data() + std::min(pos, to_process.size()), item_value); + + this->SetSetting(item_name, item_value); - this->SetSetting(item_name, atoi(item_value)); + if (pos == std::string_view::npos) return; + to_process.remove_prefix(pos + 1); } - free(value_copy); } std::string ScriptConfig::SettingsToString() const diff --git a/src/script/script_config.hpp b/src/script/script_config.hpp index 777766c790..f15cae2faf 100644 --- a/src/script/script_config.hpp +++ b/src/script/script_config.hpp @@ -12,10 +12,10 @@ #include #include -#include "../core/smallmap_type.hpp" #include "../company_type.h" #include "../textfile_gui.h" #include "script_instance.hpp" +#include /** Maximum of 10 digits for MIN / MAX_INT32, 1 for the sign and 1 for '\0'. */ static const int INT32_DIGITS_WITH_SIGN_AND_TERMINATION = 10 + 1 + 1; @@ -60,7 +60,6 @@ protected: public: ScriptConfig() : - name(nullptr), version(-1), info(nullptr), is_random(false), @@ -84,7 +83,7 @@ public: * as specified. If false any compatible version is ok. * @param is_random Is the Script chosen randomly? */ - void Change(const char *name, int version = -1, bool force_exact_match = false, bool is_random = false); + void Change(std::optional name, int version = -1, bool force_exact_match = false, bool is_random = false); /** * Get the ScriptInfo linked to this ScriptConfig. @@ -128,7 +127,7 @@ public: /** * Set the value of a setting for this config. */ - void SetSetting(const std::string &name, int value); + void SetSetting(const std::string_view name, int value); /** * Reset all settings to their default value. @@ -159,7 +158,7 @@ public: /** * Get the name of the Script. */ - const char *GetName() const; + const std::string &GetName() const; /** * Get the version of the Script. @@ -190,7 +189,7 @@ public: ScriptInstance::ScriptData *GetToLoadData(); protected: - const char *name; ///< Name of the Script + std::string name; ///< Name of the Script int version; ///< Version of the Script class ScriptInfo *info; ///< ScriptInfo object for related to this Script version SettingValueList settings; ///< List with all setting=>value pairs that are configure for this Script @@ -207,7 +206,7 @@ protected: * This function should call back to the Scanner in charge of this Config, * to find the ScriptInfo belonging to a name+version. */ - virtual ScriptInfo *FindInfo(const char *name, int version, bool force_exact_match) = 0; + virtual ScriptInfo *FindInfo(const std::string &name, int version, bool force_exact_match) = 0; }; #endif /* SCRIPT_CONFIG_HPP */ diff --git a/src/script/script_gui.cpp b/src/script/script_gui.cpp index 8aae6518a9..670e6844b6 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -154,7 +154,7 @@ struct ScriptListWindow : public Window { SetDParam(0, selected_info->GetVersion()); DrawString(tr, STR_AI_LIST_VERSION); tr.top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal; - if (selected_info->GetURL() != nullptr) { + if (!selected_info->GetURL().empty()) { SetDParamStr(0, selected_info->GetURL()); DrawString(tr, STR_AI_LIST_URL); tr.top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal; @@ -174,7 +174,7 @@ struct ScriptListWindow : public Window { { if (_game_mode == GM_NORMAL && slot == OWNER_DEITY) Game::Uninitialize(false); if (this->selected == -1) { - GetConfig(slot)->Change(nullptr); + GetConfig(slot)->Change(std::nullopt); } else { ScriptInfoList::const_iterator it = this->info_list->cbegin(); std::advance(it, this->selected); @@ -439,13 +439,13 @@ struct ScriptSettingsWindow : public Window { { switch (widget) { case WID_SCRS_BACKGROUND: { - Rect r = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero); - int num = (pt.y - r.top) / this->line_height + this->vscroll->GetPosition(); - if (num >= (int)this->visible_settings.size()) break; + auto it = this->vscroll->GetScrolledItemFromWidget(this->visible_settings, pt.y, this, widget); + if (it == this->visible_settings.end()) break; - const ScriptConfigItem &config_item = *this->visible_settings[num]; + const ScriptConfigItem &config_item = **it; if (!this->IsEditableItem(config_item)) return; + int num = it - this->visible_settings.begin(); if (this->clicked_row != num) { this->DeleteChildWindows(WC_QUERY_STRING); HideDropDownMenu(this); @@ -455,6 +455,7 @@ struct ScriptSettingsWindow : public Window { bool bool_item = (config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0; + Rect r = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero); int x = pt.x - r.left; if (_current_text_dir == TD_RTL) x = r.Width() - 1 - x; @@ -711,7 +712,7 @@ struct ScriptDebugWindow : public Window { bool autoscroll; ///< Whether automatically scrolling should be enabled or not. bool show_break_box; ///< Whether the break/debug box is visible. static bool break_check_enabled; ///< Stop an AI when it prints a matching string - static char break_string[MAX_BREAK_STR_STRING_LENGTH]; ///< The string to match to the AI output + static std::string break_string; ///< The string to match to the AI output QueryString break_editbox; ///< Break editbox static StringFilter break_string_filter; ///< Log filter for break. static bool case_sensitive_break_check; ///< Is the matching done case-sensitive @@ -1044,7 +1045,7 @@ struct ScriptDebugWindow : public Window { if (wid != WID_SCRD_BREAK_STR_EDIT_BOX) return; /* Save the current string to static member so it can be restored next time the window is opened. */ - strecpy(this->break_string, this->break_editbox.text.buf, lastof(this->break_string)); + this->break_string = this->break_editbox.text.buf; break_string_filter.SetFilterTerm(this->break_string); } @@ -1121,7 +1122,7 @@ struct ScriptDebugWindow : public Window { }; CompanyID ScriptDebugWindow::script_debug_company = INVALID_COMPANY; -char ScriptDebugWindow::break_string[MAX_BREAK_STR_STRING_LENGTH] = ""; +std::string ScriptDebugWindow::break_string; bool ScriptDebugWindow::break_check_enabled = true; bool ScriptDebugWindow::case_sensitive_break_check = false; StringFilter ScriptDebugWindow::break_string_filter(&ScriptDebugWindow::case_sensitive_break_check); diff --git a/src/script/script_info.cpp b/src/script/script_info.cpp index 9f815f3e21..ec3c668fa6 100644 --- a/src/script/script_info.cpp +++ b/src/script/script_info.cpp @@ -14,27 +14,14 @@ #include "script_info.hpp" #include "script_scanner.hpp" +#include "../3rdparty/fmt/format.h" #include "../safeguards.h" -ScriptInfo::~ScriptInfo() -{ - free(this->author); - free(this->name); - free(this->short_name); - free(this->description); - free(this->date); - free(this->instance_name); - free(this->url); - free(this->SQ_instance); -} - bool ScriptInfo::CheckMethod(const char *name) const { - if (!this->engine->MethodExists(*this->SQ_instance, name)) { - char error[1024]; - seprintf(error, lastof(error), "your info.nut/library.nut doesn't have the method '%s'", name); - this->engine->ThrowError(error); + if (!this->engine->MethodExists(this->SQ_instance, name)) { + this->engine->ThrowError(fmt::format("your info.nut/library.nut doesn't have the method '{}'", name)); return false; } return true; @@ -43,10 +30,9 @@ bool ScriptInfo::CheckMethod(const char *name) const /* static */ SQInteger ScriptInfo::Constructor(HSQUIRRELVM vm, ScriptInfo *info) { /* Set some basic info from the parent */ - info->SQ_instance = MallocT(1); - Squirrel::GetInstance(vm, info->SQ_instance, 2); + Squirrel::GetInstance(vm, &info->SQ_instance, 2); /* Make sure the instance stays alive over time */ - sq_addref(vm, info->SQ_instance); + sq_addref(vm, &info->SQ_instance); info->scanner = (ScriptScanner *)Squirrel::GetGlobalPointer(vm); info->engine = info->scanner->GetEngine(); @@ -70,21 +56,21 @@ bool ScriptInfo::CheckMethod(const char *name) const info->tar_file = info->scanner->GetTarFile(); /* Cache the data the info file gives us. */ - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author, MAX_GET_OPS)) return SQ_ERROR; - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name, MAX_GET_OPS)) return SQ_ERROR; - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name, MAX_GET_OPS)) return SQ_ERROR; - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description, MAX_GET_OPS)) return SQ_ERROR; - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date, MAX_GET_OPS)) return SQ_ERROR; - if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version, MAX_GET_OPS)) return SQ_ERROR; - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "GetAuthor", &info->author, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "GetName", &info->name, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "GetShortName", &info->short_name, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "GetDescription", &info->description, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "GetDate", &info->date, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallIntegerMethod(info->SQ_instance, "GetVersion", &info->version, MAX_GET_OPS)) return SQ_ERROR; + if (!info->engine->CallStringMethod(info->SQ_instance, "CreateInstance", &info->instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR; /* The GetURL function is optional. */ - if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) { - if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR; + if (info->engine->MethodExists(info->SQ_instance, "GetURL")) { + if (!info->engine->CallStringMethod(info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR; } /* Check if we have settings */ - if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) { + if (info->engine->MethodExists(info->SQ_instance, "GetSettings")) { if (!info->GetSettings()) return SQ_ERROR; } @@ -93,7 +79,7 @@ bool ScriptInfo::CheckMethod(const char *name) const bool ScriptInfo::GetSettings() { - return this->engine->CallMethod(*this->SQ_instance, "GetSettings", nullptr, MAX_GET_SETTING_OPS); + return this->engine->CallMethod(this->SQ_instance, "GetSettings", nullptr, MAX_GET_SETTING_OPS); } SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm) @@ -168,9 +154,7 @@ SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm) config.flags = (ScriptConfigFlags)res; items |= 0x100; } else { - char error[1024]; - seprintf(error, lastof(error), "unknown setting property '%s'", key); - this->engine->ThrowError(error); + this->engine->ThrowError(fmt::format("unknown setting property '{}'", key)); return SQ_ERROR; } @@ -181,9 +165,7 @@ SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm) /* Don't allow both random_deviation and SCRIPTCONFIG_RANDOM to * be set for the same config item. */ if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_RANDOM) != 0) { - char error[1024]; - seprintf(error, lastof(error), "Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed"); - this->engine->ThrowError(error); + this->engine->ThrowError("Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed"); return SQ_ERROR; } /* Reset the bit for random_deviation as it's optional. */ @@ -192,9 +174,7 @@ SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm) /* Make sure all properties are defined */ uint mask = (config.flags & SCRIPTCONFIG_BOOLEAN) ? 0x1F3 : 0x1FF; if (items != mask) { - char error[1024]; - seprintf(error, lastof(error), "please define all properties of a setting (min/max not allowed for booleans)"); - this->engine->ThrowError(error); + this->engine->ThrowError("please define all properties of a setting (min/max not allowed for booleans)"); return SQ_ERROR; } @@ -214,9 +194,7 @@ SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm) } if (config == nullptr) { - char error[1024]; - seprintf(error, lastof(error), "Trying to add labels for non-defined setting '%s'", setting_name); - this->engine->ThrowError(error); + this->engine->ThrowError(fmt::format("Trying to add labels for non-defined setting '{}'", setting_name)); return SQ_ERROR; } if (!config->labels.empty()) return SQ_ERROR; @@ -263,7 +241,7 @@ const ScriptConfigItemList *ScriptInfo::GetConfigList() const return &this->config_list; } -const ScriptConfigItem *ScriptInfo::GetConfigItem(const std::string &name) const +const ScriptConfigItem *ScriptInfo::GetConfigItem(const std::string_view name) const { for (const auto &item : this->config_list) { if (item.name == name) return &item; diff --git a/src/script/script_info.hpp b/src/script/script_info.hpp index 0120e8a1bc..b1a7af1d9c 100644 --- a/src/script/script_info.hpp +++ b/src/script/script_info.hpp @@ -31,38 +31,29 @@ class ScriptInfo : public SimpleCountedObject { public: ScriptInfo() : engine(nullptr), - SQ_instance(nullptr), - author(nullptr), - name(nullptr), - short_name(nullptr), - description(nullptr), - date(nullptr), - instance_name(nullptr), version(0), - url(nullptr), scanner(nullptr) {} - ~ScriptInfo(); /** * Get the Author of the script. */ - const char *GetAuthor() const { return this->author; } + const std::string &GetAuthor() const { return this->author; } /** * Get the Name of the script. */ - const char *GetName() const { return this->name; } + const std::string &GetName() const { return this->name; } /** * Get the 4 character long short name of the script. */ - const char *GetShortName() const { return this->short_name; } + const std::string &GetShortName() const { return this->short_name; } /** * Get the description of the script. */ - const char *GetDescription() const { return this->description; } + const std::string &GetDescription() const { return this->description; } /** * Get the version of the script. @@ -72,27 +63,27 @@ public: /** * Get the last-modified date of the script. */ - const char *GetDate() const { return this->date; } + const std::string &GetDate() const { return this->date; } /** * Get the name of the instance of the script to create. */ - const char *GetInstanceName() const { return this->instance_name; } + const std::string &GetInstanceName() const { return this->instance_name; } /** * Get the website for this script. */ - const char *GetURL() const { return this->url; } + const std::string &GetURL() const { return this->url; } /** * Get the filename of the main.nut script. */ - const char *GetMainScript() const { return this->main_script.c_str(); } + const std::string &GetMainScript() const { return this->main_script; } /** * Get the filename of the tar the script is in. */ - std::string GetTarFile() const { return this->tar_file; } + const std::string &GetTarFile() const { return this->tar_file; } /** * Check if a given method exists. @@ -122,7 +113,7 @@ public: /** * Get the description of a certain Script config option. */ - const ScriptConfigItem *GetConfigItem(const std::string &name) const; + const ScriptConfigItem *GetConfigItem(const std::string_view name) const; /** * Set a setting. @@ -146,20 +137,20 @@ public: protected: class Squirrel *engine; ///< Engine used to register for Squirrel. - HSQOBJECT *SQ_instance; ///< The Squirrel instance created for this info. + HSQOBJECT SQ_instance; ///< The Squirrel instance created for this info. ScriptConfigItemList config_list; ///< List of settings from this Script. private: std::string main_script; ///< The full path of the script. std::string tar_file; ///< If, which tar file the script was in. - const char *author; ///< Author of the script. - const char *name; ///< Full name of the script. - const char *short_name; ///< Short name (4 chars) which uniquely identifies the script. - const char *description; ///< Small description of the script. - const char *date; ///< The date the script was written at. - const char *instance_name; ///< Name of the main class in the script. + std::string author; ///< Author of the script. + std::string name; ///< Full name of the script. + std::string short_name; ///< Short name (4 chars) which uniquely identifies the script. + std::string description; ///< Small description of the script. + std::string date; ///< The date the script was written at. + std::string instance_name; ///< Name of the main class in the script. int version; ///< Version of the script. - const char *url; ///< URL of the script. + std::string url; ///< URL of the script. class ScriptScanner *scanner; ///< ScriptScanner object that was used to scan this script info. }; diff --git a/src/script/script_info_dummy.cpp b/src/script/script_info_dummy.cpp index 9438b77f09..70447b4b97 100644 --- a/src/script/script_info_dummy.cpp +++ b/src/script/script_info_dummy.cpp @@ -12,6 +12,7 @@ #include "../string_func.h" #include "../strings_func.h" +#include "../3rdparty/fmt/format.h" #include "../safeguards.h" @@ -27,24 +28,21 @@ /** Run the dummy info.nut. */ void Script_CreateDummyInfo(HSQUIRRELVM vm, const char *type, const char *dir) { - char dummy_script[4096]; - char *dp = dummy_script; - dp += seprintf(dp, lastof(dummy_script), "class Dummy%s extends %sInfo {\n", type, type); - dp += seprintf(dp, lastof(dummy_script), "function GetAuthor() { return \"OpenTTD Developers Team\"; }\n"); - dp += seprintf(dp, lastof(dummy_script), "function GetName() { return \"Dummy%s\"; }\n", type); - dp += seprintf(dp, lastof(dummy_script), "function GetShortName() { return \"DUMM\"; }\n"); - dp += seprintf(dp, lastof(dummy_script), "function GetDescription() { return \"A Dummy %s that is loaded when your %s/ dir is empty\"; }\n", type, dir); - dp += seprintf(dp, lastof(dummy_script), "function GetVersion() { return 1; }\n"); - dp += seprintf(dp, lastof(dummy_script), "function GetDate() { return \"2008-07-26\"; }\n"); - dp += seprintf(dp, lastof(dummy_script), "function CreateInstance() { return \"Dummy%s\"; }\n", type); - dp += seprintf(dp, lastof(dummy_script), "} RegisterDummy%s(Dummy%s());\n", type, type); - - const SQChar *sq_dummy_script = dummy_script; + std::string dummy_script = fmt::format( + "class Dummy{0} extends {0}Info {{\n" + "function GetAuthor() {{ return \"OpenTTD Developers Team\"; }}\n" + "function GetName() {{ return \"Dummy{0}\"; }}\n" + "function GetShortName() {{ return \"DUMM\"; }}\n" + "function GetDescription() {{ return \"A Dummy {0} that is loaded when your {1}/ dir is empty\"; }}\n" + "function GetVersion() {{ return 1; }}\n" + "function GetDate() {{ return \"2008-07-26\"; }}\n" + "function CreateInstance() {{ return \"Dummy{0}\"; }}\n" + "}} RegisterDummy{0}(Dummy{0}());\n", type, dir); sq_pushroottable(vm); /* Load and run the script */ - if (SQ_SUCCEEDED(sq_compilebuffer(vm, sq_dummy_script, strlen(sq_dummy_script), "dummy", SQTrue))) { + if (SQ_SUCCEEDED(sq_compilebuffer(vm, dummy_script.c_str(), dummy_script.size(), "dummy", SQTrue))) { sq_push(vm, -2); if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue))) { sq_pop(vm, 1); @@ -54,52 +52,56 @@ void Script_CreateDummyInfo(HSQUIRRELVM vm, const char *type, const char *dir) NOT_REACHED(); } +/** + * Split the given message on newlines ('\n') and escape quotes and (back)slashes, + * so they can be properly interpreted as string constants by the Squirrel compiler. + * @param message The message that we want to sanitize for use in Squirrel code. + * @return Vector with sanitized strings to use as string constant in Squirrel code. + */ +static std::vector EscapeQuotesAndSlashesAndSplitOnNewLines(const std::string &message) +{ + std::vector messages; + + std::string safe_message; + for (auto c : message) { + if (c == '\n') { + messages.emplace_back(std::move(safe_message)); + continue; + } + + if (c == '"' || c == '\\') safe_message.push_back('\\'); + safe_message.push_back(c); + } + messages.emplace_back(std::move(safe_message)); + return messages; +} + /** Run the dummy AI and let it generate an error message. */ void Script_CreateDummy(HSQUIRRELVM vm, StringID string, const char *type) { /* We want to translate the error message. * We do this in three steps: - * 1) We get the error message + * 1) We get the error message, escape quotes and slashes, and split on + * newlines because Log.Error terminates passed strings at newlines. */ - char error_message[1024]; - GetString(error_message, string, lastof(error_message)); - - /* Make escapes for all quotes and slashes. */ - char safe_error_message[1024]; - char *q = safe_error_message; - for (const char *p = error_message; *p != '\0' && q < lastof(safe_error_message) - 2; p++, q++) { - if (*p == '"' || *p == '\\') *q++ = '\\'; - *q = *p; - } - *q = '\0'; + std::string error_message = GetString(string); + std::vector messages = EscapeQuotesAndSlashesAndSplitOnNewLines(error_message); /* 2) We construct the AI's code. This is done by merging a header, body and footer */ - char dummy_script[4096]; - char *dp = dummy_script; - dp += seprintf(dp, lastof(dummy_script), "class Dummy%s extends %sController {\n function Start()\n {\n", type, type); - - /* As special trick we need to split the error message on newlines and - * emit each newline as a separate error printing string. */ - char *newline; - char *p = safe_error_message; - do { - newline = strchr(p, '\n'); - if (newline != nullptr) *newline = '\0'; - - dp += seprintf(dp, lastof(dummy_script), " %sLog.Error(\"%s\");\n", type, p); - p = newline + 1; - } while (newline != nullptr); - - strecpy(dp, " }\n}\n", lastof(dummy_script)); - - /* 3) We translate the error message in the character format that Squirrel wants. - * We can use the fact that the wchar string printing also uses %s to print - * old style char strings, which is what was generated during the script generation. */ - const SQChar *sq_dummy_script = dummy_script; + std::string dummy_script; + auto back_inserter = std::back_inserter(dummy_script); + /* Just a rough ballpark estimate. */ + dummy_script.reserve(error_message.size() + 128 + 64 * messages.size()); + + fmt::format_to(back_inserter, "class Dummy{0} extends {0}Controller {{\n function Start()\n {{\n", type); + for (std::string &message : messages) { + fmt::format_to(back_inserter, " {}Log.Error(\"{}\");\n", type, message); + } + dummy_script += " }\n}\n"; - /* And finally we load and run the script */ + /* 3) Finally we load and run the script */ sq_pushroottable(vm); - if (SQ_SUCCEEDED(sq_compilebuffer(vm, sq_dummy_script, strlen(sq_dummy_script), "dummy", SQTrue))) { + if (SQ_SUCCEEDED(sq_compilebuffer(vm, dummy_script.c_str(), dummy_script.size(), "dummy", SQTrue))) { sq_push(vm, -2); if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue))) { sq_pop(vm, 1); diff --git a/src/script/script_instance.cpp b/src/script/script_instance.cpp index 15da2093e9..a0f6652966 100644 --- a/src/script/script_instance.cpp +++ b/src/script/script_instance.cpp @@ -52,7 +52,6 @@ static void PrintFunc(bool error_msg, const SQChar *message) ScriptInstance::ScriptInstance(const char *APIName, ScriptType script_type) : engine(nullptr), - versionAPI(nullptr), controller(nullptr), storage(nullptr), instance(nullptr), @@ -72,7 +71,7 @@ ScriptInstance::ScriptInstance(const char *APIName, ScriptType script_type) : this->engine->SetPrintFunction(&PrintFunc); } -void ScriptInstance::Initialize(const char *main_script, const char *instance_name, CompanyID company) +void ScriptInstance::Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company) { ScriptObject::ActiveInstance active(this); @@ -85,7 +84,7 @@ void ScriptInstance::Initialize(const char *main_script, const char *instance_na try { ScriptObject::SetAllowDoCommand(false); /* Load and execute the script for this script */ - if (strcmp(main_script, "%_dummy") == 0) { + if (main_script == "%_dummy") { this->LoadDummyScript(); } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) { if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started."); @@ -94,7 +93,7 @@ void ScriptInstance::Initialize(const char *main_script, const char *instance_na } if (this->script_type == ScriptType::GS) { - if (strcmp(instance_name, "BeeRewardClass") == 0) { + if (instance_name == "BeeRewardClass") { this->LoadCompatibilityScripts("brgs", GAME_DIR); } } @@ -112,7 +111,7 @@ void ScriptInstance::Initialize(const char *main_script, const char *instance_na ScriptObject::SetAllowDoCommand(true); } catch (Script_FatalError &e) { this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage().c_str()); + this->engine->ThrowError(e.GetErrorMessage()); this->engine->ResumeError(); this->Died(); } @@ -123,12 +122,12 @@ void ScriptInstance::RegisterAPI() squirrel_register_std(this->engine); } -bool ScriptInstance::LoadCompatibilityScripts(const char *api_version, Subdirectory dir) +bool ScriptInstance::LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir) { const char *api_vers[] = { "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12", "13", "14" }; uint api_idx = 0; for (; api_idx < lengthof(api_vers) ; api_idx++) { - if (strcmp(api_version, api_vers[api_idx]) == 0) break; + if (api_version == api_vers[api_idx]) break; } if (api_idx < 12) { /* 13 and below */ @@ -136,13 +135,13 @@ bool ScriptInstance::LoadCompatibilityScripts(const char *api_version, Subdirect } char script_name[32]; - seprintf(script_name, lastof(script_name), "compat_%s.nut", api_version); + seprintf(script_name, lastof(script_name), "compat_%s.nut", api_version.c_str()); for (Searchpath sp : _valid_searchpaths) { std::string buf = FioGetDirectory(sp, dir); buf += script_name; if (!FileExists(buf)) continue; - if (this->engine->LoadScript(buf.c_str())) return true; + if (this->engine->LoadScript(buf)) return true; ScriptLog::Error("Failed to load API compatibility script"); DEBUG(script, 0, "Error compiling / running API compatibility script: %s", buf.c_str()); @@ -265,7 +264,7 @@ void ScriptInstance::GameLoop() this->callback = e.GetSuspendCallback(); } catch (Script_FatalError &e) { this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage().c_str()); + this->engine->ThrowError(e.GetErrorMessage()); this->engine->ResumeError(); this->Died(); } @@ -286,7 +285,7 @@ void ScriptInstance::GameLoop() this->callback = e.GetSuspendCallback(); } catch (Script_FatalError &e) { this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage().c_str()); + this->engine->ThrowError(e.GetErrorMessage()); this->engine->ResumeError(); this->Died(); } @@ -544,7 +543,7 @@ void ScriptInstance::Save() /* If we don't mark the script as dead here cleaning up the squirrel * stack could throw Script_FatalError again. */ this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage().c_str()); + this->engine->ThrowError(e.GetErrorMessage()); this->engine->ResumeError(); SaveEmpty(); /* We can't kill the script here, so mark it as crashed (not dead) and diff --git a/src/script/script_instance.hpp b/src/script/script_instance.hpp index f3fc4aedf2..e587b6d25f 100644 --- a/src/script/script_instance.hpp +++ b/src/script/script_instance.hpp @@ -56,14 +56,14 @@ public: * @param instance_name The name of the instance out of the script to load. * @param company Which company this script is serving. */ - void Initialize(const char *main_script, const char *instance_name, CompanyID company); + void Initialize(const std::string &main_script, const std::string &instance_name, CompanyID company); /** * Get the value of a setting of the current instance. * @param name The name of the setting. * @return the value for the setting, or -1 if the setting is not known. */ - virtual int GetSetting(const char *name) = 0; + virtual int GetSetting(const std::string &name) = 0; /** * Find a library. @@ -71,7 +71,7 @@ public: * @param version The version the library should have. * @return The library if found, nullptr otherwise. */ - virtual class ScriptInfo *FindLibrary(const char *library, int version) = 0; + virtual class ScriptInfo *FindLibrary(const std::string &library, int version) = 0; /** * A script in multiplayer waits for the server to handle its DoCommand. @@ -256,7 +256,7 @@ public: protected: class Squirrel *engine; ///< A wrapper around the squirrel vm. - const char *versionAPI; ///< Current API used by this script. + std::string versionAPI; ///< Current API used by this script. /** * Register all API functions to the VM. @@ -269,7 +269,7 @@ protected: * @param dir Subdirectory to find the scripts in * @return true iff script loading should proceed */ - bool LoadCompatibilityScripts(const char *api_version, Subdirectory dir); + bool LoadCompatibilityScripts(const std::string &api_version, Subdirectory dir); /** * Tell the script it died. diff --git a/src/script/script_scanner.cpp b/src/script/script_scanner.cpp index 4e539ec95b..cd8a09faf7 100644 --- a/src/script/script_scanner.cpp +++ b/src/script/script_scanner.cpp @@ -29,7 +29,7 @@ bool ScriptScanner::AddFile(const std::string &filename, size_t basepath_length, this->main_script = filename; this->tar_file = tar_filename; - auto p = this->main_script.rfind(PATHSEPCHAR); + auto p = this->main_script.find_last_of(PATHSEPCHAR); this->main_script.erase(p != std::string::npos ? p + 1 : 0); this->main_script += "main.nut"; @@ -37,7 +37,7 @@ bool ScriptScanner::AddFile(const std::string &filename, size_t basepath_length, this->ResetEngine(); try { - this->engine->LoadScript(filename.c_str()); + this->engine->LoadScript(filename); } catch (Script_FatalError &e) { DEBUG(script, 0, "Fatal error '%s' when trying to load the script '%s'.", e.GetErrorMessage().c_str(), filename.c_str()); return false; @@ -98,8 +98,8 @@ void ScriptScanner::RegisterScript(ScriptInfo *info) std::string script_name = fmt::format("{}.{}", script_original_name, info->GetVersion()); /* Check if GetShortName follows the rules */ - if (strlen(info->GetShortName()) != 4) { - DEBUG(script, 0, "The script '%s' returned a string from GetShortName() which is not four characaters. Unable to load the script.", info->GetName()); + if (info->GetShortName().size() != 4) { + DEBUG(script, 0, "The script '%s' returned a string from GetShortName() which is not four characaters. Unable to load the script.", info->GetName().c_str()); delete info; return; } @@ -108,17 +108,17 @@ void ScriptScanner::RegisterScript(ScriptInfo *info) /* This script was already registered */ #ifdef _WIN32 /* Windows doesn't care about the case */ - if (StrEqualsIgnoreCase(this->info_list[script_name]->GetMainScript(), info->GetMainScript()) == 0) { + if (StrEqualsIgnoreCase(this->info_list[script_name]->GetMainScript(), info->GetMainScript())) { #else - if (strcmp(this->info_list[script_name]->GetMainScript(), info->GetMainScript()) == 0) { + if (this->info_list[script_name]->GetMainScript() == info->GetMainScript()) { #endif delete info; return; } DEBUG(script, 1, "Registering two scripts with the same name and version"); - DEBUG(script, 1, " 1: %s", this->info_list[script_name]->GetMainScript()); - DEBUG(script, 1, " 2: %s", info->GetMainScript()); + DEBUG(script, 1, " 1: %s", this->info_list[script_name]->GetMainScript().c_str()); + DEBUG(script, 1, " 2: %s", info->GetMainScript().c_str()); DEBUG(script, 1, "The first is taking precedence."); delete info; @@ -146,7 +146,7 @@ std::string ScriptScanner::GetConsoleList(bool newest_only) const const ScriptInfoList &list = newest_only ? this->info_single_list : this->info_list; for (const auto &item : list) { ScriptInfo *i = item.second; - p += stdstr_fmt("%10s (v%d): %s\n", i->GetName(), i->GetVersion(), i->GetDescription()); + p += stdstr_fmt("%10s (v%d): %s\n", i->GetName().c_str(), i->GetVersion(), i->GetDescription().c_str()); } p += "\n"; @@ -207,7 +207,7 @@ struct ScriptFileChecksumCreator : FileScanner { static bool IsSameScript(const ContentInfo *ci, bool md5sum, ScriptInfo *info, Subdirectory dir) { uint32 id = 0; - const char *str = info->GetShortName(); + const char *str = info->GetShortName().c_str(); for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j); if (id != ci->unique_id) return false; @@ -230,12 +230,11 @@ static bool IsSameScript(const ContentInfo *ci, bool md5sum, ScriptInfo *info, S checksum.AddFile(tar.first, 0, tar_filename); } } else { - char path[MAX_PATH]; - strecpy(path, info->GetMainScript(), lastof(path)); /* There'll always be at least 1 path separator character in a script * main script name as the search algorithm requires the main script to * be in a subdirectory of the script directory; so //main.nut. */ - *strrchr(path, PATHSEPCHAR) = '\0'; + const std::string &main_script = info->GetMainScript(); + std::string path = main_script.substr(0, main_script.find_last_of(PATHSEPCHAR)); checksum.Scan(".nut", path); } @@ -253,7 +252,7 @@ bool ScriptScanner::HasScript(const ContentInfo *ci, bool md5sum) const char *ScriptScanner::FindMainScript(const ContentInfo *ci, bool md5sum) { for (const auto &item : this->info_list) { - if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return item.second->GetMainScript(); + if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return item.second->GetMainScript().c_str(); } return nullptr; } diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp index d696b43d91..e3008c4414 100644 --- a/src/script/squirrel.cpp +++ b/src/script/squirrel.cpp @@ -459,13 +459,12 @@ bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT return true; } -bool Squirrel::CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend) +bool Squirrel::CallStringMethod(HSQOBJECT instance, const char *method_name, std::string *res, int suspend) { HSQOBJECT ret; if (!this->CallMethod(instance, method_name, &ret, suspend)) return false; if (ret._type != OT_STRING) return false; - *res = stredup(ObjectToString(&ret)); - StrMakeValidInPlace(const_cast(*res)); + *res = StrMakeValid(ObjectToString(&ret)); return true; } @@ -487,7 +486,7 @@ bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool return true; } -/* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name) +/* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name) { Squirrel *engine = (Squirrel *)sq_getforeignptr(vm); @@ -497,24 +496,22 @@ bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool sq_pushroottable(vm); if (prepend_API_name) { - size_t len = strlen(class_name) + strlen(engine->GetAPIName()) + 1; - char *class_name2 = (char *)alloca(len); - seprintf(class_name2, class_name2 + len - 1, "%s%s", engine->GetAPIName(), class_name); - - sq_pushstring(vm, class_name2, -1); + std::string prepended_class_name = engine->GetAPIName(); + prepended_class_name += class_name; + sq_pushstring(vm, prepended_class_name, -1); } else { sq_pushstring(vm, class_name, -1); } if (SQ_FAILED(sq_get(vm, -2))) { - DEBUG(misc, 0, "[squirrel] Failed to find class by the name '%s%s'", prepend_API_name ? engine->GetAPIName() : "", class_name); + DEBUG(misc, 0, "[squirrel] Failed to find class by the name '%s%s'", prepend_API_name ? engine->GetAPIName() : "", class_name.c_str()); sq_settop(vm, oldtop); return false; } /* Create the instance */ if (SQ_FAILED(sq_createinstance(vm, -1))) { - DEBUG(misc, 0, "[squirrel] Failed to create instance for class '%s%s'", prepend_API_name ? engine->GetAPIName() : "", class_name); + DEBUG(misc, 0, "[squirrel] Failed to create instance for class '%s%s'", prepend_API_name ? engine->GetAPIName() : "", class_name.c_str()); sq_settop(vm, oldtop); return false; } @@ -537,7 +534,7 @@ bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool return true; } -bool Squirrel::CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance) +bool Squirrel::CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance) { ScriptAllocatorScope alloc_scope(this); return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, nullptr); @@ -651,7 +648,7 @@ static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger return ret; } -SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printerror) +SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror) { ScriptAllocatorScope alloc_scope(this); @@ -729,7 +726,7 @@ SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printer } SQFile f(file, size); - if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename, printerror))) { + if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) { FioFCloseFile(file); return SQ_OK; } @@ -737,7 +734,7 @@ SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printer return SQ_ERROR; } -bool Squirrel::LoadScript(HSQUIRRELVM vm, const char *script, bool in_root) +bool Squirrel::LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root) { ScriptAllocatorScope alloc_scope(this); @@ -757,11 +754,11 @@ bool Squirrel::LoadScript(HSQUIRRELVM vm, const char *script, bool in_root) } vm->_ops_till_suspend = ops_left; - DEBUG(misc, 0, "[squirrel] Failed to compile '%s'", script); + DEBUG(misc, 0, "[squirrel] Failed to compile '%s'", script.c_str()); return false; } -bool Squirrel::LoadScript(const char *script) +bool Squirrel::LoadScript(const std::string &script) { return LoadScript(this->vm, script); } diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index 1d7f535b10..0ff98d19b5 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -84,13 +84,13 @@ public: * @param script The full script-name to load. * @return False if loading failed. */ - bool LoadScript(const char *script); - bool LoadScript(HSQUIRRELVM vm, const char *script, bool in_root = true); + bool LoadScript(const std::string &script); + bool LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root = true); /** * Load a file to a given VM. */ - SQRESULT LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printerror); + SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror); /** * Adds a function to the stack. Depending on the current state this means @@ -159,7 +159,7 @@ public: */ bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend); bool CallMethod(HSQOBJECT instance, const char *method_name, int suspend) { return this->CallMethod(instance, method_name, nullptr, suspend); } - bool CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend); + bool CallStringMethod(HSQOBJECT instance, const char *method_name, std::string *res, int suspend); bool CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend); bool CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend); @@ -178,12 +178,12 @@ public: * @param prepend_API_name Optional parameter; if true, the class_name is prefixed with the current API name. * @return False if creating failed. */ - static bool CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name = false); + static bool CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name = false); /** * Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel. */ - bool CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance); + bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance); /** * Get the real-instance pointer. @@ -233,7 +233,7 @@ public: /** * Throw a Squirrel error that will be nicely displayed to the user. */ - void ThrowError(const char *error) { sq_throwerror(this->vm, error); } + void ThrowError(const std::string_view error) { sq_throwerror(this->vm, error); } /** * Release a SQ object. diff --git a/src/script/squirrel_helper.hpp b/src/script/squirrel_helper.hpp index bfa55bfa1a..7447a1dedf 100644 --- a/src/script/squirrel_helper.hpp +++ b/src/script/squirrel_helper.hpp @@ -16,6 +16,7 @@ #include "../string_func.h" #include "../tile_type.h" #include "squirrel_helper_type.hpp" +#include template const char *GetClassName(); @@ -23,19 +24,6 @@ template const char *GetClassName(); * The Squirrel convert routines */ namespace SQConvert { - /** - * Pointers assigned to this class will be free'd when this instance - * comes out of scope. Useful to make sure you can use stredup(), - * without leaking memory. - */ - struct SQAutoFreePointers : std::vector { - ~SQAutoFreePointers() - { - for (void * p : *this) free(p); - } - }; - - /** * To return a value to squirrel, we use this helper class. It converts to the right format. * We use a class instead of a plain function to allow us to use partial template specializations. @@ -52,48 +40,58 @@ namespace SQConvert { template <> struct Return { static inline int Set(HSQUIRRELVM vm, Money res) { sq_pushinteger(vm, res); return 1; } }; //template <> struct Return { static inline int Set(HSQUIRRELVM vm, TileIndex res) { sq_pushinteger(vm, (int32)res.value); return 1; } }; template <> struct Return { static inline int Set(HSQUIRRELVM vm, bool res) { sq_pushbool (vm, res); return 1; } }; - template <> struct Return { static inline int Set(HSQUIRRELVM vm, char *res) { if (res == nullptr) sq_pushnull(vm); else { sq_pushstring(vm, res, -1); free(res); } return 1; } }; - template <> struct Return { static inline int Set(HSQUIRRELVM vm, const char *res) { if (res == nullptr) sq_pushnull(vm); else { sq_pushstring(vm, res, -1); } return 1; } }; + template <> struct Return { /* Do not use char *, use std::optional instead. */ }; + template <> struct Return { /* Do not use const char *, use std::optional instead. */ }; template <> struct Return { static inline int Set(HSQUIRRELVM vm, void *res) { sq_pushuserpointer(vm, res); return 1; } }; template <> struct Return { static inline int Set(HSQUIRRELVM vm, HSQOBJECT res) { sq_pushobject(vm, res); return 1; } }; + template <> struct Return> { + static inline int Set(HSQUIRRELVM vm, std::optional res) { + if (res.has_value()) { + sq_pushstring(vm, res.value(), -1); + } else { + sq_pushnull(vm); + } + return 1; + } + }; + /** * To get a param from squirrel, we use this helper class. It converts to the right format. * We use a class instead of a plain function to allow us to use partial template specializations. */ template struct Param; - template <> struct Param { static inline uint8 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline uint16 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline uint32 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline int8 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline int16 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline int32 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline int64 Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - //template <> struct Param { static inline TileIndex Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return TileIndex((uint32)(int32)tmp); } }; - template <> struct Param { static inline Money Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; - template <> struct Param { static inline bool Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQBool tmp; sq_getbool (vm, index, &tmp); return tmp != 0; } }; - template <> struct Param { static inline void *Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer tmp; sq_getuserpointer(vm, index, &tmp); return tmp; } }; - - template <> struct Param { - static inline const char *Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) + template <> struct Param { static inline uint8 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline uint16 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline uint32 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline int8 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline int16 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline int32 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline int64 Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + //template <> struct Param { static inline TileIndex Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return TileIndex((uint32)(int32)tmp); } }; + template <> struct Param { static inline Money Get(HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger (vm, index, &tmp); return tmp; } }; + template <> struct Param { static inline bool Get(HSQUIRRELVM vm, int index) { SQBool tmp; sq_getbool (vm, index, &tmp); return tmp != 0; } }; + template <> struct Param { /* Do not use const char *, use std::string& instead. */ }; + template <> struct Param { static inline void *Get(HSQUIRRELVM vm, int index) { SQUserPointer tmp; sq_getuserpointer(vm, index, &tmp); return tmp; } }; + + template <> struct Param { + static inline const std::string Get(HSQUIRRELVM vm, int index) { /* Convert what-ever there is as parameter to a string */ sq_tostring(vm, index); const SQChar *tmp; sq_getstring(vm, -1, &tmp); - char *tmp_str = stredup(tmp); + std::string result = StrMakeValid(tmp); sq_poptop(vm); - ptr->push_back((void *)tmp_str); - StrMakeValidInPlace(tmp_str); - return tmp_str; + return result; } }; template - struct Param> { - static inline Array Get(HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) + struct Param &&> { + static inline Array Get(HSQUIRRELVM vm, int index) { /* Sanity check of the size. */ if (sq_getsize(vm, index) > UINT16_MAX) throw sq_throwerror(vm, "an array used as parameter to a function is too large"); @@ -106,7 +104,7 @@ namespace SQConvert { Array data; while (SQ_SUCCEEDED(sq_next(vm, -2))) { - data.emplace_back(Param::Get(vm, -1, ptr)); + data.emplace_back(Param::Get(vm, -1)); sq_pop(vm, 2); } sq_pop(vm, 2); @@ -136,15 +134,14 @@ namespace SQConvert { template static int SQCall(void *instance, Tretval(*func)(Targs...), [[maybe_unused]] HSQUIRRELVM vm, std::index_sequence) { - [[maybe_unused]] SQAutoFreePointers ptr; if constexpr (std::is_void_v) { (*func)( - Param::Get(vm, 2 + i, &ptr)... + Param::Get(vm, 2 + i)... ); return 0; } else { Tretval ret = (*func)( - Param::Get(vm, 2 + i, &ptr)... + Param::Get(vm, 2 + i)... ); return Return::Set(vm, ret); } @@ -170,15 +167,14 @@ namespace SQConvert { template static int SQCall(Tcls *instance, Tretval(Tcls:: *func)(Targs...), [[maybe_unused]] HSQUIRRELVM vm, std::index_sequence) { - [[maybe_unused]] SQAutoFreePointers ptr; if constexpr (std::is_void_v) { (instance->*func)( - Param::Get(vm, 2 + i, &ptr)... + Param::Get(vm, 2 + i)... ); return 0; } else { Tretval ret = (instance->*func)( - Param::Get(vm, 2 + i, &ptr)... + Param::Get(vm, 2 + i)... ); return Return::Set(vm, ret); } @@ -187,9 +183,8 @@ namespace SQConvert { template static Tcls *SQConstruct(Tcls *, Tretval(Tcls:: *func)(Targs...), [[maybe_unused]] HSQUIRRELVM vm, std::index_sequence) { - [[maybe_unused]] SQAutoFreePointers ptr; Tcls *inst = new Tcls( - Param::Get(vm, 2 + i, &ptr)... + Param::Get(vm, 2 + i)... ); return inst; diff --git a/src/script/squirrel_std.cpp b/src/script/squirrel_std.cpp index 502721f36f..69831649fd 100644 --- a/src/script/squirrel_std.cpp +++ b/src/script/squirrel_std.cpp @@ -54,18 +54,14 @@ SQInteger SquirrelStd::require(HSQUIRRELVM vm) return SQ_ERROR; } - char path[MAX_PATH]; - strecpy(path, si.source, lastof(path)); /* Keep the dir, remove the rest */ - SQChar *s = strrchr(path, PATHSEPCHAR); - if (s != nullptr) { - /* Keep the PATHSEPCHAR there, remove the rest */ - s++; - *s = '\0'; - } - strecat(path, filename, lastof(path)); + std::string path = si.source; + auto p = path.find_last_of(PATHSEPCHAR); + /* Keep the PATHSEPCHAR there, remove the rest */ + if (p != std::string::npos) path.erase(p + 1); + path += filename; #if (PATHSEPCHAR != '/') - for (char *n = path; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR; + std::transform(path.begin(), path.end(), path.begin(), [](char &c) { return c == '/' ? PATHSEPCHAR : c; }); #endif Squirrel *engine = (Squirrel *)sq_getforeignptr(vm); diff --git a/src/settings.cpp b/src/settings.cpp index 4d0cd845e0..f7e60e6d03 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -126,6 +126,19 @@ static const SettingTable _generic_setting_tables[] = { _network_settings, }; +void IterateSettingsTables(std::function handler) +{ + handler(_misc_settings, nullptr); +#if defined(_WIN32) && !defined(DEDICATED) + handler(_win32_settings, nullptr); +#endif + for (auto &table : _generic_setting_tables) { + handler(table, &_settings_game); + } + handler(_currency_settings, &_custom_currency); + handler(_company_settings, &_settings_client.company); +} + /** * List of all the private setting tables. */ @@ -1974,7 +1987,7 @@ static void AILoadConfig(IniFile &ini, const char *grpname) /* Clean any configured AI */ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { - AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME)->Change(nullptr); + AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME)->Change(std::nullopt); } /* If no group exists, return */ @@ -1984,7 +1997,7 @@ static void AILoadConfig(IniFile &ini, const char *grpname) for (item = group->item; c < MAX_COMPANIES && item != nullptr; c++, item = item->next) { AIConfig *config = AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME); - config->Change(item->name.c_str()); + config->Change(item->name); if (!config->HasScript()) { if (item->name != "none") { DEBUG(script, 0, "The AI by the name '%s' was no longer found, and removed from the list.", item->name.c_str()); @@ -2001,7 +2014,7 @@ static void GameLoadConfig(IniFile &ini, const char *grpname) IniItem *item; /* Clean any configured GameScript */ - GameConfig::GetConfig(GameConfig::SSS_FORCE_NEWGAME)->Change(nullptr); + GameConfig::GetConfig(GameConfig::SSS_FORCE_NEWGAME)->Change(std::nullopt); /* If no group exists, return */ if (group == nullptr) return; @@ -2011,7 +2024,7 @@ static void GameLoadConfig(IniFile &ini, const char *grpname) GameConfig *config = GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME); - config->Change(item->name.c_str()); + config->Change(item->name); if (!config->HasScript()) { if (item->name != "none") { DEBUG(script, 0, "The GameScript by the name '%s' was no longer found, and removed from the list.", item->name.c_str()); @@ -2185,7 +2198,7 @@ static void AISaveConfig(IniFile &ini, const char *grpname) for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { AIConfig *config = AIConfig::GetConfig(c, AIConfig::SSS_FORCE_NEWGAME); - const char *name; + std::string name; std::string value = config->SettingsToString(); if (config->HasScript()) { @@ -2207,7 +2220,7 @@ static void GameSaveConfig(IniFile &ini, const char *grpname) group->Clear(); GameConfig *config = GameConfig::GetConfig(AIConfig::SSS_FORCE_NEWGAME); - const char *name; + std::string name; std::string value = config->SettingsToString(); if (config->HasScript()) { diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 7dd1f17c17..591fb8c226 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -42,6 +42,9 @@ #include "gui.h" #include "mixer.h" #include "scope.h" +#include "network/core/config.h" +#include "network/network_gui.h" +#include "network/network_survey.h" #include #include @@ -209,6 +212,8 @@ struct GameOptionsWindow : Window { this->OnInvalidateData(0); this->SetTab(WID_GO_TAB_GENERAL); + + if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) this->GetWidget(WID_GO_SURVEY_SEL)->SetDisplayedPlane(SZSP_NONE); } ~GameOptionsWindow() @@ -493,19 +498,19 @@ struct GameOptionsWindow : Window { void OnClick(Point pt, int widget, int click_count) override { - if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) { + if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END) { if (BaseGraphics::GetUsedSet() == nullptr) return; ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS); return; } - if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) { + if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_CONTENT_END) { if (BaseSounds::GetUsedSet() == nullptr) return; ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS); return; } - if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) { + if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_CONTENT_END) { if (BaseMusic::GetUsedSet() == nullptr) return; ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC); @@ -518,6 +523,30 @@ struct GameOptionsWindow : Window { this->SetTab(widget); break; + case WID_GO_SURVEY_PARTICIPATE_BUTTON: + switch (_settings_client.network.participate_survey) { + case PS_ASK: + case PS_NO: + _settings_client.network.participate_survey = PS_YES; + break; + + case PS_YES: + _settings_client.network.participate_survey = PS_NO; + break; + } + + this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON, _settings_client.network.participate_survey == PS_YES); + this->SetWidgetDirty(WID_GO_SURVEY_PARTICIPATE_BUTTON); + break; + + case WID_GO_SURVEY_LINK_BUTTON: + OpenBrowser(NETWORK_SURVEY_DETAILS_LINK.c_str()); + break; + + case WID_GO_SURVEY_PREVIEW_BUTTON: + ShowSurveyResultTextfileWindow(); + break; + case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off /* try to toggle full-screen on/off */ if (!ToggleFullScreen(!_fullscreen)) { @@ -765,6 +794,7 @@ struct GameOptionsWindow : Window { void OnInvalidateData(int data = 0, bool gui_scope = true) override { if (!gui_scope) return; + this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON, _settings_client.network.participate_survey == PS_YES); this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen); this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel); this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync); @@ -781,7 +811,7 @@ struct GameOptionsWindow : Window { bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0; this->GetWidget(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL); - for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) { + for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->GetTextfile(tft) == nullptr); this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->GetTextfile(tft) == nullptr); this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->GetTextfile(tft) == nullptr); @@ -819,6 +849,20 @@ static const NWidgetPart _nested_game_options_widgets[] = { NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_CURRENCY_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING, STR_GAME_OPTIONS_CURRENCY_UNITS_DROPDOWN_TOOLTIP), SetFill(1, 0), EndContainer(), + + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_GO_SURVEY_SEL), + NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_FRAME, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_PARTICIPATE_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_PREVIEW_BUTTON), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_LINK_BUTTON), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK_TOOLTIP), + EndContainer(), + EndContainer(), + EndContainer(), EndContainer(), /* Graphics tab */ @@ -998,7 +1042,7 @@ struct BaseSettingEntry { byte level; ///< Nesting level of this setting entry BaseSettingEntry() : flags(0), level(0) {} - virtual ~BaseSettingEntry() {} + virtual ~BaseSettingEntry() = default; virtual void Init(byte level = 0); virtual void FoldAll() {} diff --git a/src/settings_internal.h b/src/settings_internal.h index dedc88c9a5..90eb39ce89 100644 --- a/src/settings_internal.h +++ b/src/settings_internal.h @@ -12,6 +12,8 @@ #include "sl/saveload_types.h" +#include + enum SettingFlag : uint32 { SF_NONE = 0, SF_GUI_0_IS_SPECIAL = 1 << 0, ///< A value of zero is possible and has a custom string (the one after "strval"). @@ -114,7 +116,7 @@ struct SettingDesc { name(name), flags(flags), guiproc(guiproc), startup(startup), save(save), patx_name(patx_name) {} SettingDesc(XrefContructorTag tag, SaveLoad save, SettingsXref xref) : name(nullptr), flags(SF_NONE), guiproc(nullptr), startup(false), save(save), patx_name(nullptr), xref(xref) {} - virtual ~SettingDesc() {} + virtual ~SettingDesc() = default; const char *name; ///< Name of the setting. Used in configuration file and for console SettingFlag flags; ///< Handles how a setting would show up in the GUI (text/currency, etc.) @@ -193,7 +195,6 @@ struct IntSettingDesc : SettingDesc { SettingDesc(save, name, flags, guiproc, startup, patx_name), def(def), min(min), max(max), interval(interval), str(str), str_help(str_help), str_val(str_val), cat(cat), pre_check(pre_check), post_callback(post_callback), enumlist(enumlist) {} - virtual ~IntSettingDesc() {} int32 def; ///< default value given when none is present int32 min; ///< minimum values @@ -236,7 +237,6 @@ struct BoolSettingDesc : IntSettingDesc { StringID str, StringID str_help, StringID str_val, SettingCategory cat, PreChangeCheck pre_check, PostChangeCallback post_callback) : IntSettingDesc(save, name, flags, guiproc, startup, patx_name, def, 0, 1, 0, str, str_help, str_val, cat, pre_check, post_callback, nullptr) {} - virtual ~BoolSettingDesc() {} bool IsBoolSetting() const override { return true; } size_t ParseValue(const char *str) const override; @@ -256,8 +256,6 @@ struct OneOfManySettingDesc : IntSettingDesc { for (auto one : many) this->many.push_back(one); } - virtual ~OneOfManySettingDesc() {} - std::vector many; ///< possible values for this type OnConvert *many_cnvt; ///< callback procedure when loading value mechanism fails @@ -276,7 +274,6 @@ struct ManyOfManySettingDesc : OneOfManySettingDesc { std::initializer_list many, OnConvert *many_cnvt) : OneOfManySettingDesc(save, name, flags, guiproc, startup, patx_name, def, (1 << many.size()) - 1, str, str_help, str_val, cat, pre_check, post_callback, many, many_cnvt) {} - virtual ~ManyOfManySettingDesc() {} size_t ParseValue(const char *str) const override; void FormatIntValue(char *buf, const char *last, uint32 value) const override; @@ -303,7 +300,6 @@ struct StringSettingDesc : SettingDesc { uint32 max_length, PreChangeCheck pre_check, PostChangeCallback post_callback) : SettingDesc(save, name, flags, guiproc, startup, patx_name), def(def == nullptr ? "" : def), max_length(max_length), pre_check(pre_check), post_callback(post_callback) {} - virtual ~StringSettingDesc() {} std::string def; ///< Default value given when none is present uint32 max_length; ///< Maximum length of the string, 0 means no maximum length @@ -327,7 +323,6 @@ private: struct ListSettingDesc : SettingDesc { ListSettingDesc(const SaveLoad &save, const char *name, SettingFlag flags, OnGuiCtrl *guiproc, bool startup, const char *patx_name, const char *def) : SettingDesc(save, name, flags, guiproc, startup, patx_name), def(def) {} - virtual ~ListSettingDesc() {} const char *def; ///< default value given when none is present @@ -342,7 +337,6 @@ struct NullSettingDesc : SettingDesc { SettingDesc(save, "", SF_NOT_IN_CONFIG, nullptr, false, nullptr) {} NullSettingDesc(const SaveLoad &save, const char *name, const char *patx_name) : SettingDesc(save, name, SF_NOT_IN_CONFIG, nullptr, false, patx_name) {} - virtual ~NullSettingDesc() {} void FormatValue(char *buf, const char *last, const void *object) const override { NOT_REACHED(); } void ParseValue(const IniItem *item, void *object) const override { NOT_REACHED(); } @@ -353,7 +347,6 @@ struct NullSettingDesc : SettingDesc { struct XrefSettingDesc : SettingDesc { XrefSettingDesc(const SaveLoad &save, SettingsXref xref) : SettingDesc(SettingDesc::XrefContructorTag(), save, xref) {} - virtual ~XrefSettingDesc() {} void FormatValue(char *buf, const char *last, const void *object) const override { NOT_REACHED(); } void ParseValue(const IniItem *item, void *object) const override { NOT_REACHED(); } @@ -363,7 +356,14 @@ struct XrefSettingDesc : SettingDesc { typedef std::initializer_list> SettingTable; const SettingDesc *GetSettingFromName(const char *name); +inline const SettingDesc *GetSettingFromName(const std::string &name) +{ + return GetSettingFromName(name.c_str()); +} + bool SetSettingValue(const IntSettingDesc *sd, int32 value, bool force_newgame = false); bool SetSettingValue(const StringSettingDesc *sd, const std::string value, bool force_newgame = false); +void IterateSettingsTables(std::function handler); + #endif /* SETTINGS_INTERNAL_H */ diff --git a/src/settings_type.h b/src/settings_type.h index 3cf3bd98ce..b26081db84 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -64,13 +64,20 @@ enum IndustryDensity { ID_END, ///< Number of industry density settings. }; -/** Possible values for "userelayservice" setting. */ +/** Possible values for "use_relay_service" setting. */ enum UseRelayService { URS_NEVER = 0, URS_ASK, URS_ALLOW, }; +/** Possible values for "participate_survey" setting. */ +enum ParticipateSurvey { + PS_ASK = 0, + PS_NO, + PS_YES, +}; + /** Settings related to the difficulty of the game */ struct DifficultySettings { byte competitor_start_time; ///< Unused value, used to load old savegames. @@ -395,8 +402,9 @@ struct NetworkSettings { uint8 min_active_clients; ///< minimum amount of active clients to unpause the game bool reload_cfg; ///< reload the config file before restarting std::string last_joined; ///< Last joined server - bool no_http_content_downloads; ///< do not do content downloads over HTTP + bool no_http_content_downloads; ///< do not do content downloads over HTTP UseRelayService use_relay_service; ///< Use relay service? + ParticipateSurvey participate_survey; ///< Participate in the automated survey }; /** Settings related to the creation of games. */ diff --git a/src/signs_gui.cpp b/src/signs_gui.cpp index 4407c751ae..5a17bd7a84 100644 --- a/src/signs_gui.cpp +++ b/src/signs_gui.cpp @@ -232,10 +232,10 @@ struct SignListWindow : Window, SignList { { switch (widget) { case WID_SIL_LIST: { - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SIL_LIST, WidgetDimensions::scaled.framerect.top); - if (id_v == INT_MAX) return; + auto it = this->vscroll->GetScrolledItemFromWidget(this->signs, pt.y, this, WID_SIL_LIST, WidgetDimensions::scaled.framerect.top); + if (it == this->signs.end()) return; - const Sign *si = this->signs[id_v]; + const Sign *si = *it; ScrollMainWindowToTile(TileVirtXY(si->x, si->y)); break; } diff --git a/src/sl/ai_sl.cpp b/src/sl/ai_sl.cpp index 0b5b9f6e54..87a8a649ed 100644 --- a/src/sl/ai_sl.cpp +++ b/src/sl/ai_sl.cpp @@ -58,7 +58,7 @@ static void Load_AIPL() { /* Free all current data */ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { - AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(nullptr); + AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->Change(std::nullopt); } CompanyID index; @@ -77,13 +77,13 @@ static void Load_AIPL() AIConfig *config = AIConfig::GetConfig(index, AIConfig::SSS_FORCE_GAME); if (_ai_saveload_name.empty()) { /* A random AI. */ - config->Change(nullptr, -1, false, true); + config->Change(std::nullopt, -1, false, true); } else { - config->Change(_ai_saveload_name.c_str(), _ai_saveload_version, false, _ai_saveload_is_random); + config->Change(_ai_saveload_name, _ai_saveload_version, false, _ai_saveload_is_random); if (!config->HasScript()) { /* No version of the AI available that can load the data. Try to load the * latest version of the AI instead. */ - config->Change(_ai_saveload_name.c_str(), -1, false, _ai_saveload_is_random); + config->Change(_ai_saveload_name, -1, false, _ai_saveload_is_random); if (!config->HasScript()) { if (_ai_saveload_name.compare("%_dummy") != 0) { DEBUG(script, 0, "The savegame has an AI by the name '%s', version %d which is no longer available.", _ai_saveload_name.c_str(), _ai_saveload_version); diff --git a/src/sl/company_sl.cpp b/src/sl/company_sl.cpp index f299896fca..4bf791441c 100644 --- a/src/sl/company_sl.cpp +++ b/src/sl/company_sl.cpp @@ -516,8 +516,8 @@ static void Check_PLYR() { int index; while ((index = SlIterateArray()) != -1) { - CompanyProperties *cprops = new CompanyProperties(); - SaveLoad_PLYR_common(nullptr, cprops); + std::unique_ptr cprops = std::make_unique(); + SaveLoad_PLYR_common(nullptr, cprops.get()); /* We do not load old custom names */ if (IsSavegameVersionBefore(SLV_84)) { @@ -537,7 +537,9 @@ static void Check_PLYR() cprops->name_1 = STR_GAME_SAVELOAD_NOT_AVAILABLE; } - if (!_load_check_data.companies.Insert(index, cprops)) delete cprops; + if (_load_check_data.companies.count(index) == 0) { + _load_check_data.companies[index] = std::move(cprops); + } } } diff --git a/src/sl/extended_ver_sl.cpp b/src/sl/extended_ver_sl.cpp index 2a2bb89566..d131eecd14 100644 --- a/src/sl/extended_ver_sl.cpp +++ b/src/sl/extended_ver_sl.cpp @@ -200,6 +200,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_AI_START_DATE, XSCF_NULL, 1, 1, "slv_ai_start_date", nullptr, nullptr, nullptr }, { XSLFI_EXTEND_VEHICLE_RANDOM, XSCF_NULL, 1, 1, "slv_extend_vehicle_random", nullptr, nullptr, nullptr }, { XSLFI_DISASTER_VEH_STATE, XSCF_NULL, 1, 1, "slv_disaster_veh_state", nullptr, nullptr, nullptr }, + { XSLFI_SAVEGAME_ID, XSCF_NULL, 1, 1, "slv_savegame_id", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/sl/extended_ver_sl.h b/src/sl/extended_ver_sl.h index 90840d5782..4749e2c932 100644 --- a/src/sl/extended_ver_sl.h +++ b/src/sl/extended_ver_sl.h @@ -152,6 +152,7 @@ enum SlXvFeatureIndex { XSLFI_AI_START_DATE, ///< See: SLV_AI_START_DATE (PR #10653) XSLFI_EXTEND_VEHICLE_RANDOM, ///< See: SLV_EXTEND_VEHICLE_RANDOM (PR #10701) XSLFI_DISASTER_VEH_STATE, ///< See: SLV_DISASTER_VEH_STATE (PR #10798) + XSLFI_SAVEGAME_ID, ///< See: SLV_SAVEGAME_ID (PR #10719) XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk diff --git a/src/sl/game_sl.cpp b/src/sl/game_sl.cpp index 7da7da71ed..1bfd9148d4 100644 --- a/src/sl/game_sl.cpp +++ b/src/sl/game_sl.cpp @@ -55,7 +55,7 @@ static void SaveReal_GSDT(int *index_ptr) static void Load_GSDT() { /* Free all current data */ - GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME)->Change(nullptr); + GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME)->Change(std::nullopt); if ((CompanyID)SlIterateArray() == (CompanyID)-1) return; @@ -70,11 +70,11 @@ static void Load_GSDT() GameConfig *config = GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME); if (!_game_saveload_name.empty()) { - config->Change(_game_saveload_name.c_str(), _game_saveload_version, false, _game_saveload_is_random); + config->Change(_game_saveload_name, _game_saveload_version, false, _game_saveload_is_random); if (!config->HasScript()) { /* No version of the GameScript available that can load the data. Try to load the * latest version of the GameScript instead. */ - config->Change(_game_saveload_name.c_str(), -1, false, _game_saveload_is_random); + config->Change(_game_saveload_name, -1, false, _game_saveload_is_random); if (!config->HasScript()) { if (_game_saveload_name.compare("%_dummy") != 0) { DEBUG(script, 0, "The savegame has an GameScript by the name '%s', version %d which is no longer available.", _game_saveload_name.c_str(), _game_saveload_version); diff --git a/src/sl/misc_sl.cpp b/src/sl/misc_sl.cpp index 26071eef7a..ddc64b7ca4 100644 --- a/src/sl/misc_sl.cpp +++ b/src/sl/misc_sl.cpp @@ -31,6 +31,7 @@ extern TileIndex _aux_tileloop_tile; extern uint16 _disaster_delay; extern byte _trees_tick_ctr; extern uint64 _aspect_cfg_hash; +extern std::string _savegame_id; /* Keep track of current game position */ int _saved_scrollpos_x; @@ -178,9 +179,19 @@ static void SaveLoad_VIEW() SlGlobList(_view_desc); } +static const SaveLoad _misc_desc[] = { + SLEG_CONDSSTR_X(_savegame_id, 0, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_SAVEGAME_ID)), +}; + +static void SaveLoad_MISC() +{ + SlGlobList(_misc_desc); +} + static const ChunkHandler misc_chunk_handlers[] = { { 'DATE', SaveLoad_DATE, SaveLoad_DATE, nullptr, Check_DATE, CH_RIFF }, { 'VIEW', SaveLoad_VIEW, SaveLoad_VIEW, nullptr, nullptr, CH_RIFF }, + { 'MISC', SaveLoad_MISC, SaveLoad_MISC, nullptr, nullptr, CH_RIFF }, }; extern const ChunkHandlerTable _misc_chunk_handlers(misc_chunk_handlers); diff --git a/src/spriteloader/spriteloader.hpp b/src/spriteloader/spriteloader.hpp index 8646283048..aae2649181 100644 --- a/src/spriteloader/spriteloader.hpp +++ b/src/spriteloader/spriteloader.hpp @@ -77,14 +77,14 @@ public: */ virtual uint8 LoadSprite(SpriteLoader::Sprite *sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint count, byte control_flags) = 0; - virtual ~SpriteLoader() { } + virtual ~SpriteLoader() = default; }; /** Interface for something that can encode a sprite. */ class SpriteEncoder { public: - virtual ~SpriteEncoder() { } + virtual ~SpriteEncoder() = default; /** * Can the sprite encoder make use of RGBA sprites? diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 16058c2338..168b73dffd 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -625,10 +625,10 @@ public: { switch (widget) { case WID_STL_LIST: { - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_STL_LIST); - if (id_v >= this->stations.size()) return; // click out of list bound + auto it = this->vscroll->GetScrolledItemFromWidget(this->stations, pt.y, this, WID_STL_LIST); + if (it == this->stations.end()) return; // click out of list bound - const Station *st = this->stations[id_v]; + const Station *st = *it; /* do not check HasStationInUse - it is slow and may be invalid */ assert(st->owner == (Owner)this->window_number || st->owner == OWNER_NONE); @@ -2408,12 +2408,12 @@ static bool AddNearbyStation(TileIndex tile, void *user_data) TileArea *ctx = &(data->ctx); /* First check if there were deleted stations here */ - for (uint i = 0; i < _deleted_stations_nearby.size(); i++) { - auto ts = _deleted_stations_nearby.begin() + i; - if (ts->tile == tile) { - _stations_nearby_list.push_back(_deleted_stations_nearby[i].station); - _deleted_stations_nearby.erase(ts); - i--; + for (auto it = _deleted_stations_nearby.begin(); it != _deleted_stations_nearby.end(); /* nothing */) { + if (it->tile == tile) { + _stations_nearby_list.push_back(it->station); + it = _deleted_stations_nearby.erase(it); + } else { + ++it; } } diff --git a/src/strgen/strgen.h b/src/strgen/strgen.h index bba76eb188..9c3fc187f6 100644 --- a/src/strgen/strgen.h +++ b/src/strgen/strgen.h @@ -119,7 +119,7 @@ struct HeaderWriter { virtual void Finalise(const StringData &data) = 0; /** Especially destroy the subclasses. */ - virtual ~HeaderWriter() {}; + virtual ~HeaderWriter() = default; void WriteHeader(const StringData &data); }; @@ -146,7 +146,7 @@ struct LanguageWriter { virtual void Finalise() = 0; /** Especially destroy the subclasses. */ - virtual ~LanguageWriter() {} + virtual ~LanguageWriter() = default; virtual void WriteLength(uint length); virtual void WriteLang(const StringData &data); diff --git a/src/string_base.h b/src/string_base.h index d7414c9156..6546d3cdae 100644 --- a/src/string_base.h +++ b/src/string_base.h @@ -28,7 +28,7 @@ public: */ static std::unique_ptr Create(); - virtual ~StringIterator() {} + virtual ~StringIterator() = default; /** * Set a new iteration string. Must also be called if the string contents diff --git a/src/stringfilter.cpp b/src/stringfilter.cpp index b9f4cb8462..65714b6a81 100644 --- a/src/stringfilter.cpp +++ b/src/stringfilter.cpp @@ -82,6 +82,15 @@ void StringFilter::SetFilterTerm(const char *str) } } +/** + * Set the term to filter on. + * @param str Filter term + */ +void StringFilter::SetFilterTerm(const std::string &str) +{ + this->SetFilterTerm(str.c_str()); +} + /** * Reset the matching state to process a new item. */ diff --git a/src/stringfilter_type.h b/src/stringfilter_type.h index 68b359c9d5..d71a1115d7 100644 --- a/src/stringfilter_type.h +++ b/src/stringfilter_type.h @@ -51,6 +51,7 @@ public: ~StringFilter() { free(this->filter_buffer); } void SetFilterTerm(const char *str); + void SetFilterTerm(const std::string &str); /** * Check whether any filter words were entered. diff --git a/src/strings.cpp b/src/strings.cpp index 32950e7802..b16d7d9691 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -41,6 +41,7 @@ #include "core/y_combinator.hpp" #include #include +#include #include "table/strings.h" #include "table/control_codes.h" diff --git a/src/strings_func.h b/src/strings_func.h index fb35b2e7c4..b1cba014a6 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -261,7 +261,7 @@ bool StringIDSorter(const StringID &a, const StringID &b); class MissingGlyphSearcher { public: /** Make sure everything gets destructed right. */ - virtual ~MissingGlyphSearcher() {} + virtual ~MissingGlyphSearcher() = default; /** * Get the next string to search through. diff --git a/src/table/settings/network_private_settings.ini b/src/table/settings/network_private_settings.ini index dd7d39b85b..85fda03eaa 100644 --- a/src/table/settings/network_private_settings.ini +++ b/src/table/settings/network_private_settings.ini @@ -7,7 +7,8 @@ ; Network settings as stored in the private configuration file ("private.cfg"). [pre-amble] -static std::initializer_list _use_relay_service{"never", "ask", "allow"}; +static constexpr std::initializer_list _use_relay_service{"never", "ask", "allow"}; +static constexpr std::initializer_list _participate_survey{"ask", "no", "yes"}; static const SettingTable _network_private_settings = { [post-amble] @@ -91,3 +92,12 @@ str = STR_CONFIG_SETTING_USE_RELAY_SERVICE strhelp = STR_CONFIG_SETTING_USE_RELAY_SERVICE_HELPTEXT strval = STR_CONFIG_SETTING_USE_RELAY_SERVICE_NEVER cat = SC_BASIC + +[SDTC_OMANY] +var = network.participate_survey +type = SLE_UINT8 +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC +def = PS_ASK +min = PS_ASK +max = PS_YES +full = _participate_survey diff --git a/src/textbuf.cpp b/src/textbuf.cpp index 14d24c5d86..2edff11f94 100644 --- a/src/textbuf.cpp +++ b/src/textbuf.cpp @@ -408,6 +408,15 @@ void Textbuf::Assign(const char *text) this->UpdateSize(); } +/** + * Copy a string into the textbuffer. + * @param text Source. + */ +void Textbuf::Assign(const std::string &text) +{ + this->Assign(text.c_str()); +} + /** * Print a formatted string into the textbuffer. */ diff --git a/src/textbuf_type.h b/src/textbuf_type.h index 14e92cbb3d..61bd903022 100644 --- a/src/textbuf_type.h +++ b/src/textbuf_type.h @@ -48,6 +48,7 @@ struct Textbuf { void Assign(StringID string); void Assign(const char *text); + void Assign(const std::string &text); void CDECL Print(const char *format, ...) WARN_FORMAT(2, 3); void DeleteAll(); diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index 15adddae67..fe68f22c80 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -371,8 +371,21 @@ static void Xunzip(byte **bufp, size_t *sizep) if (StrStartsWith(sv_buf, u8"\ufeff")) sv_buf.remove_prefix(3); /* Replace any invalid characters with a question-mark. This copies the buf in the process. */ - this->text = StrMakeValid(sv_buf, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE | SVS_REPLACE_TAB_CR_NL_WITH_SPACE); + this->LoadText(sv_buf); free(buf); +} + +/** + * Load a text into the textfile viewer. + * + * This will split the text into newlines and stores it for fast drawing. + * + * @param buf The text to load. + */ +void TextfileWindow::LoadText(std::string_view buf) +{ + this->text = StrMakeValid(buf, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE | SVS_REPLACE_TAB_CR_NL_WITH_SPACE); + this->lines.clear(); /* Split the string on newlines. */ std::string_view p(this->text); @@ -411,7 +424,7 @@ const char *GetTextfile(TextfileType type, Subdirectory dir, const char *filenam "changelog", "license", }; - static_assert(lengthof(prefixes) == TFT_END); + static_assert(lengthof(prefixes) == TFT_CONTENT_END); const char *prefix = prefixes[type]; diff --git a/src/textfile_gui.h b/src/textfile_gui.h index 5fc3a0e98e..f71f8e5686 100644 --- a/src/textfile_gui.h +++ b/src/textfile_gui.h @@ -18,6 +18,11 @@ const char *GetTextfile(TextfileType type, Subdirectory dir, const char *filename); +inline const char *GetTextfile(TextfileType type, Subdirectory dir, const std::string &filename) +{ + return GetTextfile(type, dir, filename.c_str()); +} + /** Window for displaying a textfile */ struct TextfileWindow : public Window, MissingGlyphSearcher { TextfileType file_type; ///< Type of textfile to view. @@ -43,6 +48,9 @@ struct TextfileWindow : public Window, MissingGlyphSearcher { virtual void LoadTextfile(const char *textfile, Subdirectory dir); +protected: + void LoadText(std::string_view buf); + private: struct Line { int top; ///< Top scroll position. diff --git a/src/textfile_type.h b/src/textfile_type.h index ddb26f6a65..f45c016440 100644 --- a/src/textfile_type.h +++ b/src/textfile_type.h @@ -12,13 +12,15 @@ /** Additional text files accompanying Tar archives */ enum TextfileType { - TFT_BEGIN, + TFT_CONTENT_BEGIN, - TFT_README = TFT_BEGIN, ///< NewGRF readme - TFT_CHANGELOG, ///< NewGRF changelog - TFT_LICENSE, ///< NewGRF license + TFT_README = TFT_CONTENT_BEGIN, ///< Content readme + TFT_CHANGELOG, ///< Content changelog + TFT_LICENSE, ///< Content license - TFT_END, + TFT_CONTENT_END, // This marker is used to generate the above three buttons in sequence by various of places in the code. + + TFT_SURVEY_RESULT = TFT_CONTENT_END, ///< Survey result (preview) }; DECLARE_POSTFIX_INCREMENT(TextfileType) diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp index 55032ea670..7f6a63ece4 100644 --- a/src/timetable_gui.cpp +++ b/src/timetable_gui.cpp @@ -428,13 +428,10 @@ struct TimetableWindow : GeneralVehicleWindow { int GetOrderFromTimetableWndPt(int y, const Vehicle *v) { - int sel = (y - this->GetWidget(WID_VT_TIMETABLE_PANEL)->pos_y - WidgetDimensions::scaled.framerect.top) / std::max(FONT_HEIGHT_NORMAL, GetSpriteSize(SPR_LOCK).height); - - if ((uint)sel >= this->vscroll->GetCapacity()) return INVALID_ORDER; - - sel += this->vscroll->GetPosition(); - - return (sel < v->GetNumOrders() * 2 && sel >= 0) ? sel : INVALID_ORDER; + int sel = this->vscroll->GetScrolledRowFromWidget(y, this, WID_VT_TIMETABLE_PANEL, WidgetDimensions::scaled.framerect.top); + if (sel == INT_MAX) return INVALID_ORDER; + assert(IsInsideBS(sel, 0, v->GetNumOrders() * 2)); + return sel; } /** diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index fb67e42d41..07c8b86fa1 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -4208,7 +4208,7 @@ Town *ClosestTownFromTile(TileIndex tile, uint threshold) } static bool _town_rating_test = false; ///< If \c true, town rating is in test-mode. -static SmallMap _town_test_ratings; ///< Map of towns to modified ratings, while in town rating test-mode. +static std::map _town_test_ratings; ///< Map of towns to modified ratings, while in town rating test-mode. /** * Switch the town rating to test-mode, to allow commands to be tested without affecting current ratings. @@ -4238,8 +4238,8 @@ void SetTownRatingTestMode(bool mode) static int GetRating(const Town *t) { if (_town_rating_test) { - SmallMap::iterator it = _town_test_ratings.Find(t); - if (it != _town_test_ratings.End()) { + auto it = _town_test_ratings.find(t); + if (it != _town_test_ratings.end()) { return it->second; } } diff --git a/src/town_gui.cpp b/src/town_gui.cpp index e4372501f4..984f0f2ab2 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -1126,10 +1126,10 @@ public: break; case WID_TD_LIST: { // Click on Town Matrix - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_TD_LIST, WidgetDimensions::scaled.framerect.top); - if (id_v >= this->towns.size()) return; // click out of town bounds + auto it = this->vscroll->GetScrolledItemFromWidget(this->towns, pt.y, this, WID_TD_LIST, WidgetDimensions::scaled.framerect.top); + if (it == this->towns.end()) return; // click out of town bounds - const Town *t = this->towns[id_v]; + const Town *t = *it; assert(t != nullptr); if (_ctrl_pressed) { ShowExtraViewportWindow(t->xy); diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 4e14b3f295..687188264a 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -10,7 +10,6 @@ #ifndef VEHICLE_BASE_H #define VEHICLE_BASE_H -#include "core/smallmap_type.hpp" #include "track_type.h" #include "command_type.h" #include "order_base.h" diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 1016229a00..60d8fba767 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -2461,10 +2461,10 @@ public: break; case WID_VL_LIST: { // Matrix to show vehicles - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VL_LIST); - if (id_v >= this->vehgroups.size()) return; // click out of list bound + auto it = this->vscroll->GetScrolledItemFromWidget(this->vehgroups, pt.y, this, WID_VL_LIST); + if (it == this->vehgroups.end()) return; // click out of list bound - const GUIVehicleGroup &vehgroup = this->vehgroups[id_v]; + const GUIVehicleGroup &vehgroup = *it; switch (this->grouping) { case GB_NONE: { const Vehicle *v = vehgroup.GetSingleVehicle(); diff --git a/src/widget.cpp b/src/widget.cpp index 25deb6dac5..96f6a5e776 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -1171,7 +1171,7 @@ NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, uint fill_x, uint fill_y this->widget_data = widget_data; this->tool_tip = tool_tip; this->scrollbar_index = -1; - this->text_colour = TC_BLACK; + this->text_colour = tp == WWT_CAPTION ? TC_WHITE : TC_BLACK; this->text_size = FS_NORMAL; this->align = SA_CENTER; } @@ -2423,11 +2423,12 @@ void NWidgetViewport::UpdateViewportCoordinates(Window *w) * @param w The window the click was in. * @param widget Widget number of the widget clicked in. * @param padding Amount of empty space between the widget edge and the top of the first row. Default value is \c 0. + * @param line_height Height of a single row. A negative value means using the vertical resize step of the widget. * @return Row number clicked at. If clicked at a wrong position, #INT_MAX is returned. */ -int Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, int widget, int padding) const +int Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, int widget, int padding, int line_height) const { - uint pos = w->GetRowFromWidget(clickpos, widget, padding, -1); + uint pos = w->GetRowFromWidget(clickpos, widget, padding, line_height); if (pos != INT_MAX) pos += this->GetPosition(); return (pos >= this->GetCount()) ? INT_MAX : pos; } diff --git a/src/widget_type.h b/src/widget_type.h index f31820c370..9381a9cdb3 100644 --- a/src/widget_type.h +++ b/src/widget_type.h @@ -814,7 +814,30 @@ public: } } - int GetScrolledRowFromWidget(int clickpos, const Window * const w, int widget, int padding = 0) const; + int GetScrolledRowFromWidget(int clickpos, const Window * const w, int widget, int padding = 0, int line_height = -1) const; + + /** + * Return an iterator pointing to the element of a scrolled widget that a user clicked in. + * @param container Container of elements represented by the scrollbar. + * @param clickpos Vertical position of the mouse click (without taking scrolling into account). + * @param w The window the click was in. + * @param widget Widget number of the widget clicked in. + * @param padding Amount of empty space between the widget edge and the top of the first row. Default value is \c 0. + * @param line_height Height of a single row. A negative value means using the vertical resize step of the widget. + * @return Iterator to the element clicked at. If clicked at a wrong position, returns as interator to the end of the container. + */ + template + typename Tcontainer::iterator GetScrolledItemFromWidget(Tcontainer &container, int clickpos, const Window * const w, int widget, int padding = 0, int line_height = -1) const + { + assert(this->GetCount() == container.size()); // Scrollbar and container size must match. + int row = this->GetScrolledRowFromWidget(clickpos, w, widget, padding, line_height); + if (row == INT_MAX) return std::end(container); + + typename Tcontainer::iterator it = std::begin(container); + std::advance(it, row); + return it; + } + EventState UpdateListPositionOnKeyPress(int &list_position, uint16 keycode) const; }; diff --git a/src/widgets/ai_widget.h b/src/widgets/ai_widget.h index 4d6418bcfe..3753941609 100644 --- a/src/widgets/ai_widget.h +++ b/src/widgets/ai_widget.h @@ -29,7 +29,7 @@ enum AIConfigWidgets { WID_AIC_CONFIGURE, ///< Change AI settings button. WID_AIC_CLOSE, ///< Close window button. WID_AIC_TEXTFILE, ///< Open AI readme, changelog (+1) or license (+2). - WID_AIC_CONTENT_DOWNLOAD = WID_AIC_TEXTFILE + TFT_END, ///< Download content button. + WID_AIC_CONTENT_DOWNLOAD = WID_AIC_TEXTFILE + TFT_CONTENT_END, ///< Download content button. }; #endif /* WIDGETS_AI_WIDGET_H */ diff --git a/src/widgets/dropdown_type.h b/src/widgets/dropdown_type.h index 60bbe2a04e..01e3075ba0 100644 --- a/src/widgets/dropdown_type.h +++ b/src/widgets/dropdown_type.h @@ -32,7 +32,7 @@ public: bool masked; ///< Masked and unselectable item DropDownListItem(int result, bool masked) : result(result), masked(masked) {} - virtual ~DropDownListItem() {} + virtual ~DropDownListItem() = default; virtual bool Selectable() const { return false; } virtual uint Height(uint width) const { return FONT_HEIGHT_NORMAL; } diff --git a/src/widgets/game_widget.h b/src/widgets/game_widget.h index f791a43749..5694bc4607 100644 --- a/src/widgets/game_widget.h +++ b/src/widgets/game_widget.h @@ -20,7 +20,7 @@ enum GSConfigWidgets { WID_GSC_SCROLLBAR, ///< Scrollbar to scroll through the selected AIs. WID_GSC_CHANGE, ///< Select another Game Script button. WID_GSC_TEXTFILE, ///< Open GS readme, changelog (+1) or license (+2). - WID_GSC_CONTENT_DOWNLOAD = WID_GSC_TEXTFILE + TFT_END, ///< Download content button. + WID_GSC_CONTENT_DOWNLOAD = WID_GSC_TEXTFILE + TFT_CONTENT_END, ///< Download content button. WID_GSC_ACCEPT, ///< Accept ("Close") button WID_GSC_RESET, ///< Reset button. }; diff --git a/src/widgets/network_content_widget.h b/src/widgets/network_content_widget.h index 49c8153d4a..c8092aefa8 100644 --- a/src/widgets/network_content_widget.h +++ b/src/widgets/network_content_widget.h @@ -36,7 +36,7 @@ enum NetworkContentListWidgets { WID_NCL_DETAILS, ///< Panel with content details. WID_NCL_TEXTFILE, ///< Open readme, changelog (+1) or license (+2) of a file in the content window. - WID_NCL_SELECT_ALL = WID_NCL_TEXTFILE + TFT_END, ///< 'Select all' button. + WID_NCL_SELECT_ALL = WID_NCL_TEXTFILE + TFT_CONTENT_END, ///< 'Select all' button. WID_NCL_SELECT_UPDATE, ///< 'Select updates' button. WID_NCL_UNSELECT, ///< 'Unselect all' button. WID_NCL_OPEN_URL, ///< 'Open url' button. diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h index 058cb090b0..8bfc3818f5 100644 --- a/src/widgets/network_widget.h +++ b/src/widgets/network_widget.h @@ -119,4 +119,14 @@ enum NetworkAskRelayWidgets { WID_NAR_YES_ALWAYS, ///< "Yes, always" button. }; +/** Widgets of the #NetworkAskSurveyWindow class. */ +enum NetworkAskSurveyWidgets { + WID_NAS_CAPTION, ///< Caption of the window. + WID_NAS_TEXT, ///< Text in the window. + WID_NAS_PREVIEW, ///< "Preview" button. + WID_NAS_LINK, ///< "Details & Privacy" button. + WID_NAS_NO, ///< "No" button. + WID_NAS_YES, ///< "Yes" button. +}; + #endif /* WIDGETS_NETWORK_WIDGET_H */ diff --git a/src/widgets/newgrf_widget.h b/src/widgets/newgrf_widget.h index 7f5fefde06..03b81bb584 100644 --- a/src/widgets/newgrf_widget.h +++ b/src/widgets/newgrf_widget.h @@ -47,7 +47,7 @@ enum NewGRFStateWidgets { WID_NS_NEWGRF_INFO, ///< Panel for Info on selected NewGRF. WID_NS_OPEN_URL, ///< Open URL of NewGRF. WID_NS_NEWGRF_TEXTFILE, ///< Open NewGRF readme, changelog (+1) or license (+2). - WID_NS_SET_PARAMETERS = WID_NS_NEWGRF_TEXTFILE + TFT_END, ///< Open Parameters Window for selected NewGRF for editing parameters. + WID_NS_SET_PARAMETERS = WID_NS_NEWGRF_TEXTFILE + TFT_CONTENT_END, ///< Open Parameters Window for selected NewGRF for editing parameters. WID_NS_VIEW_PARAMETERS, ///< Open Parameters Window for selected NewGRF for viewing parameters. WID_NS_TOGGLE_PALETTE, ///< Toggle Palette of selected, active NewGRF. WID_NS_APPLY_CHANGES, ///< Apply changes to NewGRF config. diff --git a/src/widgets/settings_widget.h b/src/widgets/settings_widget.h index 86cdc2af66..02aa294641 100644 --- a/src/widgets/settings_widget.h +++ b/src/widgets/settings_widget.h @@ -29,23 +29,27 @@ enum GameOptionsWidgets { WID_GO_BASE_GRF_DROPDOWN, ///< Use to select a base GRF. WID_GO_BASE_GRF_STATUS, ///< Info about missing files etc. WID_GO_BASE_GRF_TEXTFILE, ///< Open base GRF readme, changelog (+1) or license (+2). - WID_GO_BASE_GRF_DESCRIPTION = WID_GO_BASE_GRF_TEXTFILE + TFT_END, ///< Description of selected base GRF. + WID_GO_BASE_GRF_DESCRIPTION = WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END, ///< Description of selected base GRF. WID_GO_BASE_SFX_DROPDOWN, ///< Use to select a base SFX. WID_GO_TEXT_SFX_VOLUME, ///< Sound effects volume label. WID_GO_BASE_SFX_VOLUME, ///< Change sound effects volume. WID_GO_BASE_SFX_TEXTFILE, ///< Open base SFX readme, changelog (+1) or license (+2). - WID_GO_BASE_SFX_DESCRIPTION = WID_GO_BASE_SFX_TEXTFILE + TFT_END, ///< Description of selected base SFX. + WID_GO_BASE_SFX_DESCRIPTION = WID_GO_BASE_SFX_TEXTFILE + TFT_CONTENT_END, ///< Description of selected base SFX. WID_GO_BASE_MUSIC_DROPDOWN, ///< Use to select a base music set. WID_GO_TEXT_MUSIC_VOLUME, ///< Music volume label. WID_GO_BASE_MUSIC_VOLUME, ///< Change music volume. WID_GO_BASE_MUSIC_JUKEBOX, ///< Open the jukebox. WID_GO_BASE_MUSIC_STATUS, ///< Info about corrupted files etc. WID_GO_BASE_MUSIC_TEXTFILE, ///< Open base music readme, changelog (+1) or license (+2). - WID_GO_BASE_MUSIC_DESCRIPTION = WID_GO_BASE_MUSIC_TEXTFILE + TFT_END, ///< Description of selected base music set. + WID_GO_BASE_MUSIC_DESCRIPTION = WID_GO_BASE_MUSIC_TEXTFILE + TFT_CONTENT_END, ///< Description of selected base music set. WID_GO_VIDEO_ACCEL_BUTTON, ///< Toggle for video acceleration. WID_GO_VIDEO_VSYNC_BUTTON, ///< Toggle for video vsync. WID_GO_REFRESH_RATE_DROPDOWN, ///< Dropdown for all available refresh rates. WID_GO_VIDEO_DRIVER_INFO, ///< Label showing details about the current video driver. + WID_GO_SURVEY_SEL, ///< Selection to hide survey if no JSON library is compiled in. + WID_GO_SURVEY_PARTICIPATE_BUTTON, ///< Toggle for participating in the automated survey. + WID_GO_SURVEY_LINK_BUTTON, ///< Button to open browser to go to the survey website. + WID_GO_SURVEY_PREVIEW_BUTTON, ///< Button to open a preview window with the survey results }; /** Widgets of the #GameSettingsWindow class. */ diff --git a/src/window.cpp b/src/window.cpp index 0ae991a608..fd09fdd1e4 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -343,7 +343,7 @@ Scrollbar *Window::GetScrollbar(uint widnum) */ const QueryString *Window::GetQueryString(uint widnum) const { - auto query = this->querystrings.Find(widnum); + auto query = this->querystrings.find(widnum); return query != this->querystrings.end() ? query->second : nullptr; } @@ -354,8 +354,8 @@ const QueryString *Window::GetQueryString(uint widnum) const */ QueryString *Window::GetQueryString(uint widnum) { - SmallMap::Pair *query = this->querystrings.Find(widnum); - return query != this->querystrings.End() ? query->second : nullptr; + auto query = this->querystrings.find(widnum); + return query != this->querystrings.end() ? query->second : nullptr; } /** @@ -363,8 +363,7 @@ QueryString *Window::GetQueryString(uint widnum) */ void Window::UpdateQueryStringSize() { - for (auto &qs : this->querystrings) - { + for (auto &qs : this->querystrings) { qs.second->text.UpdateSize(); } } @@ -2051,7 +2050,7 @@ static void DecreaseWindowCounters() } /* Handle editboxes */ - for (SmallMap::Pair &pair : w->querystrings) { + for (auto &pair : w->querystrings) { pair.second->HandleEditBox(w, pair.first); } diff --git a/src/window_gui.h b/src/window_gui.h index e1bdc41854..f17deda109 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -16,8 +16,8 @@ #include "tile_type.h" #include "widget_type.h" #include "core/smallvec_type.hpp" -#include "core/smallmap_type.hpp" #include "string_type.h" +#include "3rdparty/cpp-btree/btree_map.h" #include #include @@ -330,7 +330,7 @@ public: ViewportData *viewport; ///< Pointer to viewport data, if present. NWidgetViewport *viewport_widget; ///< Pointer to viewport widget, if present. NWidgetCore *nested_focus; ///< Currently focused nested widget, or \c nullptr if no nested widget has focus. - SmallMap querystrings; ///< QueryString associated to WWT_EDITBOX widgets. + btree::btree_map querystrings; ///< QueryString associated to WWT_EDITBOX widgets. NWidgetBase *nested_root; ///< Root of the nested tree. NWidgetBase **nested_array; ///< Array of pointers into the tree. Do not access directly, use #Window::GetWidget() instead. uint nested_array_size; ///< Size of the nested array. diff --git a/src/window_type.h b/src/window_type.h index 5847fa1719..6968c0ae1d 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -513,6 +513,12 @@ enum WindowClass { */ WC_NETWORK_ASK_RELAY, + /** + * Network ask survey window; %Window numbers: + * - 0 - #NetworkAskSurveyWidgets + */ + WC_NETWORK_ASK_SURVEY, + /** * Chatbox; %Window numbers: * - #DestType = #NetWorkChatWidgets