diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c4bf40139b..8af0245668 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -50,7 +50,6 @@ Describe here Some things are not automated, and forgotten often. This list is a reminder for the reviewers. * The bug fix is important enough to be backported? (label: 'backport requested') * This PR touches english.txt or translations? Check the [guidelines](https://github.com/OpenTTD/OpenTTD/blob/master/docs/eints.md) -* This PR affects the save game format? (label 'savegame upgrade') * This PR affects the GS/AI API? (label 'needs review: Script API') * ai_changelog.hpp, game_changelog.hpp need updating. * The compatibility wrappers (compat_*.nut) need updating. diff --git a/.github/actions/setup-vcpkg/action.yaml b/.github/actions/setup-vcpkg/action.yaml new file mode 100644 index 0000000000..ebd37190a8 --- /dev/null +++ b/.github/actions/setup-vcpkg/action.yaml @@ -0,0 +1,50 @@ +name: 'Setup vcpkg' +description: 'Installs vcpkg and initialises binary caching via NuGet' +inputs: + vcpkg-location: + description: 'Where to install vcpkg' + required: true + mono-install-command: + description: 'Command to run to install mono' + required: false + +runs: + using: "composite" + steps: + - name: Install vcpkg + shell: bash + run: | + git clone https://github.com/microsoft/vcpkg "${{ inputs.vcpkg-location }}" + cd "${{ inputs.vcpkg-location }}" + ./bootstrap-vcpkg.$(if [ "${{ runner.os }}" = "Windows" ]; then echo "bat"; else echo "sh"; fi) -disableMetrics + + - name: Install mono + if: inputs.mono-install-command + shell: bash + run: | + ${{ inputs.mono-install-command }} + echo "MONO=mono" >> "$GITHUB_ENV" + + - name: Setup NuGet Credentials + shell: bash + env: + FEED_URL: 'https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json' + run: | + cd "${{ inputs.vcpkg-location }}" + ${{ env.MONO }} $(./vcpkg fetch nuget | tail -n 1) \ + sources add \ + -source "${{ env.FEED_URL }}" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "${{ github.repository_owner }}" \ + -password "${{ github.token }}" + ${{ env.MONO }} $(./vcpkg fetch nuget | tail -n 1) \ + setapikey "${{ github.token }}" \ + -source "${{ env.FEED_URL }}" + + - name: Setup vcpkg caching + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;nuget,GitHub,readwrite') + diff --git a/.github/unused-strings.py b/.github/unused-strings.py index e85355344e..2232fdf59f 100644 --- a/.github/unused-strings.py +++ b/.github/unused-strings.py @@ -161,7 +161,7 @@ def scan_source_files(path, strings_found): # Most files we can just open, but some use magic, that requires the # G++ preprocessor before we can make sense out of it. if new_path == "src/table/cargo_const.h": - p = subprocess.run(["g++", "-E", new_path], stdout=subprocess.PIPE) + p = subprocess.run(["g++", "-E", "-DCHECK_UNUSED_STRINGS", new_path], stdout=subprocess.PIPE) output = p.stdout.decode() else: with open(new_path) as fp: diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 1abd005d06..2883cb55d7 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -32,18 +32,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup vcpkg caching - uses: actions/github-script@v7 + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - - name: Install vcpkg - run: | - git clone https://github.com/microsoft/vcpkg ${{ runner.temp }}/vcpkg - ${{ runner.temp }}/vcpkg/bootstrap-vcpkg.sh -disableMetrics + vcpkg-location: ${{ runner.temp }}/vcpkg + mono-install-command: 'sudo apt-get install -y --no-install-recommends mono-complete' - name: Install dependencies run: | diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 2a0cd00428..972615f865 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -34,18 +34,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup vcpkg caching - uses: actions/github-script@v7 + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - - name: Install vcpkg - run: | - git clone https://github.com/microsoft/vcpkg ${{ runner.temp }}/vcpkg - ${{ runner.temp }}/vcpkg/bootstrap-vcpkg.sh -disableMetrics + vcpkg-location: ${{ runner.temp }}/vcpkg + mono-install-command: 'brew install mono' - name: Install OpenGFX run: | diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 789017bdb9..7830c64d64 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -20,18 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup vcpkg caching - uses: actions/github-script@v7 + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - - name: Install vcpkg - run: | - git clone https://github.com/microsoft/vcpkg ${{ runner.temp }}\vcpkg - ${{ runner.temp }}\vcpkg\bootstrap-vcpkg.bat -disableMetrics + vcpkg-location: ${{ runner.temp }}/vcpkg - name: Install OpenGFX shell: bash diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf08f47ed0..94971ce175 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -26,18 +26,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup vcpkg caching - uses: actions/github-script@v7 + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - - name: Install vcpkg - run: | - git clone https://github.com/microsoft/vcpkg ${{ runner.temp }}/vcpkg - ${{ runner.temp }}/vcpkg/bootstrap-vcpkg.sh -disableMetrics + vcpkg-location: ${{ runner.temp }}/vcpkg + mono-install-command: 'sudo apt-get install -y --no-install-recommends mono-complete' - name: Install dependencies run: | diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 93006d56a2..bb36325570 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -35,14 +35,6 @@ jobs: - name: Enable Rust cache uses: Swatinem/rust-cache@v2 - - name: Setup vcpkg caching - uses: actions/github-script@v7 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - name: Install dependencies run: | echo "::group::Install system dependencies" @@ -114,20 +106,16 @@ jobs: # EOF echo "::endgroup::" - # We use vcpkg for our dependencies, to get more up-to-date version. - echo "::group::Install vcpkg and dependencies" - - git clone https://github.com/microsoft/vcpkg /vcpkg - - ( - cd /vcpkg - ./bootstrap-vcpkg.sh -disableMetrics - ) - echo "::group::Install breakpad dependencies" cargo install --locked dump_syms echo "::endgroup::" + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg + with: + vcpkg-location: /vcpkg + mono-install-command: 'yum install -y mono-complete' + - name: Install GCC problem matcher uses: ammaraskar/gcc-problem-matcher@master diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 5ca5a7d384..ee5549512a 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -37,18 +37,11 @@ jobs: - name: Enable Rust cache uses: Swatinem/rust-cache@v2 - - name: Setup vcpkg caching - uses: actions/github-script@v7 + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - - name: Install vcpkg - run: | - git clone https://github.com/microsoft/vcpkg ${{ runner.temp }}/vcpkg - ${{ runner.temp }}/vcpkg/bootstrap-vcpkg.sh -disableMetrics + vcpkg-location: ${{ runner.temp }}/vcpkg + mono-install-command: 'brew install mono' - name: Install dependencies env: @@ -86,7 +79,7 @@ jobs: echo "::endgroup::" - name: Import code signing certificates - uses: Apple-Actions/import-codesign-certs@v3 + uses: Apple-Actions/import-codesign-certs@v5 with: # The certificates in a PKCS12 file encoded as a base64 string p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index aa0bbc604b..0b240b11ba 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -45,18 +45,10 @@ jobs: - name: Enable Rust cache uses: Swatinem/rust-cache@v2 - - name: Setup vcpkg caching - uses: actions/github-script@v7 + - name: Setup vcpkg + uses: ./.github/actions/setup-vcpkg with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite') - - - name: Install vcpkg - run: | - git clone https://github.com/microsoft/vcpkg ${{ runner.temp }}\vcpkg - ${{ runner.temp }}\vcpkg\bootstrap-vcpkg.bat -disableMetrics + vcpkg-location: ${{ runner.temp }}/vcpkg - name: Install dependencies shell: bash diff --git a/.github/workflows/upload-gog.yml b/.github/workflows/upload-gog.yml index 66d3520404..a1a6526e7e 100644 --- a/.github/workflows/upload-gog.yml +++ b/.github/workflows/upload-gog.yml @@ -77,7 +77,7 @@ jobs: curl -L https://cdn.openttd.org/openmsx-releases/0.4.2/openmsx-0.4.2-all.zip -o openmsx-all.zip echo "::endgroup::" - echo "::group::Unpack OpenGFX" + echo "::group::Unpack OpenMSX" unzip openmsx-all.zip tar xf openmsx-*.tar echo "::endgroup::" diff --git a/.version b/.version index 0886b5458e..9dbfb37f39 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -15.0-beta1 +15.0-beta3 diff --git a/README.md b/README.md index 96a3a6eb93..a0e50bdef8 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,10 @@ Both 'stable' and 'nightly' versions are available for download: OpenTTD is also available for free on [Steam](https://store.steampowered.com/app/1536610/OpenTTD/), [GOG.com](https://www.gog.com/game/openttd), and the [Microsoft Store](https://www.microsoft.com/p/openttd-official/9ncjg5rvrr1c). On some platforms OpenTTD will be available via your OS package manager or a similar service. - ## 1.2) OpenTTD gameplay manual OpenTTD has a [community-maintained wiki](https://wiki.openttd.org/), including a gameplay manual and tips. - ## 1.3) Supported platforms OpenTTD has been ported to several platforms and operating systems. @@ -56,6 +54,7 @@ The currently supported platforms are: Other platforms may also work (in particular various BSD systems), but we don't actively test or maintain these. ### 1.3.1) Legacy support + Platforms, languages and compilers change. We'll keep support going on old platforms as long as someone is interested in supporting them, except where it means the project can't move forward to keep up with language and compiler features. @@ -72,7 +71,6 @@ For some platforms these will be downloaded during the installation process if r For some platforms, you will need to refer to [the installation guide](https://wiki.openttd.org/en/Manual/Installation). - ### 1.4.1) Free graphics and sound files The free data files, split into OpenGFX for graphics, OpenSFX for sounds and @@ -85,7 +83,6 @@ OpenMSX for music can be found at: Please follow the readme of these packages about the installation procedure. The Windows installer can optionally download and install these packages. - ### 1.4.2) Original Transport Tycoon Deluxe graphics and sound files If you want to play with the original Transport Tycoon Deluxe data files you have to copy the data files from the CD-ROM into the baseset/ directory. @@ -100,7 +97,6 @@ You need to copy the following files: - trgir.grf or TRGI.GRF - trgtr.grf or TRGT.GRF - ### 1.4.3) Original Transport Tycoon Deluxe music If you want the Transport Tycoon Deluxe music, copy the appropriate files from the original game into the baseset folder. @@ -108,7 +104,6 @@ If you want the Transport Tycoon Deluxe music, copy the appropriate files from t - TTD for DOS: The GM.CAT file - Transport Tycoon Original: The GM.CAT file, but rename it to GM-TTO.CAT - ## 1.5) Add-on content / mods OpenTTD features multiple types of add-on content, which modify gameplay in different ways. @@ -117,7 +112,6 @@ Most types of add-on content can be downloaded within OpenTTD via the 'Check Onl Add-on content can also be installed manually, but that's more complicated; the [OpenTTD wiki](https://wiki.openttd.org/) may offer help with that, or the [OpenTTD directory structure guide](./docs/directory_structure.md). - ### 1.5.1) Social Integration OpenTTD has the ability to load plugins to integrate with Social Platforms like Steam, Discord, etc. @@ -126,7 +120,6 @@ To enable such integration, the plugin for the specific platform has to be downl See [OpenTTD's website](https://www.openttd.org), under Downloads, for what plugins are available. - ### 1.6) OpenTTD directories OpenTTD uses its own directory structure to store game data, add-on content etc. @@ -137,7 +130,6 @@ For more information, see the [directory structure guide](./docs/directory_struc If you want to compile OpenTTD from source, instructions can be found in [COMPILING.md](./COMPILING.md). - ## 2.0) Contact and Community 'Official' channels @@ -160,12 +152,10 @@ You can play OpenTTD with others, either cooperatively or competitively. See the [multiplayer documentation](./docs/multiplayer.md) for more details. - ### 2.2) Contributing to OpenTTD We welcome contributors to OpenTTD. More information for contributors can be found in [CONTRIBUTING.md](./CONTRIBUTING.md) - ### 2.3) Reporting bugs Good bug reports are very helpful. We have a [guide to reporting bugs](./CONTRIBUTING.md#bug-reports) to help with this. @@ -173,12 +163,10 @@ Good bug reports are very helpful. We have a [guide to reporting bugs](./CONTRI Desyncs in multiplayer are complex to debug and report (some software development skils are required). Instructions can be found in [debugging and reporting desyncs](./docs/debugging_desyncs.md). - ### 2.4) Translating OpenTTD is translated into many languages. Translations are added and updated via the [online translation tool](https://translator.openttd.org). - ## 3.0) Licensing OpenTTD is licensed under the GNU General Public License version 2.0. @@ -215,6 +203,6 @@ See `src/3rdparty/openttd_social_integration_api/LICENSE` for the complete licen The atomic datatype support detection in `cmake/3rdparty/llvm/CheckAtomic.cmake` is licensed under the Apache 2.0 license. See `cmake/3rdparty/llvm/LICENSE.txt` for the complete license text. -## 4.0 Credits +## 4.0) Credits See [CREDITS.md](./CREDITS.md) diff --git a/bin/ai/compat_14.nut b/bin/ai/compat_14.nut index 012a8d070c..721278c24e 100644 --- a/bin/ai/compat_14.nut +++ b/bin/ai/compat_14.nut @@ -36,3 +36,11 @@ AITown.FoundTown <- function(tile, size, city, layout, name) { return AITown.Fou AIVehicle.SetNameCompat14 <- AIVehicle.SetName; AIVehicle.SetName <- function(id, name) { return AIVehicle.SetNameCompat14(id, AICompat14.Text(name)); } + +AIObject.constructorCompat14 <- AIObject.constructor; +foreach(name, object in CompatScriptRootTable) { + if (type(object) != "class") continue; + if (!object.rawin("constructor")) continue; + if (object.constructor != AIObject.constructorCompat14) continue; + object.constructor <- function() : (name) { AILog.Error("'" + name + "' is not instantiable"); } +} diff --git a/bin/game/compat_14.nut b/bin/game/compat_14.nut index 00efae0b65..652f6724ca 100644 --- a/bin/game/compat_14.nut +++ b/bin/game/compat_14.nut @@ -9,6 +9,9 @@ GSBridge.GetBridgeID <- GSBridge.GetBridgeType; +/* Emulate old GSText parameter padding behaviour */ +GSText.SCRIPT_TEXT_MAX_PARAMETERS <- 20; + class GSCompat14 { function Text(text) { @@ -78,3 +81,11 @@ GSTown.FoundTown <- function(tile, size, city, layout, name) { return GSTown.Fou GSVehicle.SetNameCompat14 <- GSVehicle.SetName; GSVehicle.SetName <- function(id, name) { return GSVehicle.SetNameCompat14(id, GSCompat14.Text(name)); } + +GSObject.constructorCompat14 <- GSObject.constructor; +foreach(name, object in CompatScriptRootTable) { + if (type(object) != "class") continue; + if (!object.rawin("constructor")) continue; + if (object.constructor != GSObject.constructorCompat14) continue; + object.constructor <- function() : (name) { GSLog.Error("'" + name + "' is not instantiable"); } +} diff --git a/changelog.md b/changelog.md index e70a8b5069..f43b705793 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,153 @@ ## 15.x +### 15.0-beta3 (2025-08-31) + +- Feature: Identify cities in the main viewport by appending an icon to their names (#14504) +- Feature: Allow stations and roadstops under bridges (#14477) +- Feature: Separate ships travelling in opposite direction (#14493) +- Feature: Town and Industry cargo history graphs (#14321, #14461) +- Feature: New company face definition system and UI (#14319) +- Feature: Rework intro screen menu (#14220, #14233) +- Feature: Scrollbar for infrastructure window (#14056) +- Feature: Double-Ctrl+Click on default size box to clear saved size (#14055) +- Feature: Configure badges in build vehicle and picker windows (#13993, #14021) +- Feature: Player configuration of badge display (#13993, #14021) +- Feature: Merge Game Options and Game Settings together (#13242, #14088) +- Feature: Option to disable activate-on-release behaviour of toolbar dropdown buttons (#10938) +- Add: Maori (New Zealand) language (#14540) +- Add: Vietnamese Dong currency (#14474) +- Add: Buttons to change picker preview image height (#14373) +- Add: Show and sort online content version in list (#14355) +- Add: Allow separate expansion of town buildings and roads in scenario editor (#14341) +- Add: Hover on graph legend to highlight line (#14314) +- Add: [Script] Ability to clone ScriptList objects (#14305) +- Add: [NewGRF] All callbacks returning D0xx strings, have now the option to return any string id via register 0x100 (#14224) +- Add: [BaseSet] Allow basesets to set minor and patch versions in obg/obs/obm files (#14169) +- Add: "Many random towns" button in scenario editor now prompts for the number of towns, with defaults based on new game settings (#14158) +- Add: [NewGRF] Special value 0x7FFE for VarAction2 results specifying 'return calculated result' (#14149) +- Add: [NewGRF] Special value 0x7FFF for Action2 references specifying explicit 'callback/sprite-resolving failed' (#14149) +- Add: [NewGRF] Add purchase list Action3 CID for houses, industries, industry tiles, airports and airport tiles (#14121) +- Add: [NewGRF] Provide random bits in var10 of house callback 1C 'construction stage changed' (#14095) +- Add: [NewGRF] Station/roadstop animation-triggers 'tile loop' (bit 7) and 'path reservation' (bit 8) (#14080) +- Add: [Script] ScriptVehicleList_Waypoint (#13456) +- Change: Update OpenTTD TTF fonts to v0.8 (#14546) +- Change: Hide bridge pillars if obstructed by tile below (#14517) +- Change: Reduce visual height of default rail waypoints (#14503) +- Change: Improve sprite ellipsis appearance (#14404) +- Change: [Script] Don't allow scripts to instantiate ScriptEvent (#14276) +- Change: [Script] ScriptVehicleList_Station accepts an optional VehicleType parameter (#14260) +- Change: Remove Apply button from NewGRF config window when unneeded (#14254) +- Change: [NewGRF] Increase the textstack for all callbacks to 16 registers (#14224) +- Change: Remove button to toggle showing advanced signal types (#14219) +- Change: [Script] Remove the limit of 20 parameters to a Text string (#14193) +- Change: [NewGRF] If Action123 does not resolve in a valid SpriteSet, prefer drawing the default sprite instead of an invalid sprite (#14143) +- Change: Remove extra close buttons from some windows (#14124) +- Change: Limit height of settings description, and add scrollbar (#14102) +- Change: [NewGRF] Provide shared random bits in multi-tile animation-triggers of airport tiles and objects, just like for other features (#14090) +- Change: [NewGRF] Animation-trigger 'construction stage changed' of houses and industries now also triggers at construction start (#14089) +- Change: "setting" console command now shows default value (#14061) +- Change: Include dragged train in depot tile length display (#14060) +- Change: Don't select content when toggling its download status (#14059) +- Change: Draw boolean settings toggles as a slider widget (#14051, #14071) +- Change: Don't replace stripped control codes with '?' for scripts (#14028) +- Change: [Script] Move GSStation::GetOwner to GSBaseStation::GetOwner (#13406) +- Fix #14561: Vehicle effects were missing for vehicle on bridge (#14563) +- Fix #14553: Parameter configuration of pre-action 14 NewGRFs did not work (#14554) +- Fix: File/directory titles not updated if language is changed (#14542) +- Fix: Missing space after old file type identifier (#14541) +- Fix: Account for both text and icon size in station waiting cargo display (#14535) +- Fix: Crash when buying out company with groups (#14534) +- Fix #10222: Off by one drawing lines of certain widths (#14520, #14522, #14523) +- Fix: [Script] Invalid title on GSGoal.Question windows (#14519) +- Fix: 'Map edges' GUI buttons shouldn't initialize with water on northeast edge (#14514) +- Fix #14415: Update survey option text when changing setting (#14487) +- Fix #14480: Music player playlist buttons are clickable but non-operational in intro menu (#14482) +- Fix: Missing button beeps (#14470) +- Fix #14464: Invalid string parameter in scenario editor when unable to build industry (#14465) +- Fix: [Linkgraph] Use correct station ID when erasing flows (#14459) +- Fix: Vehicle liveries did not update when switching company (#14456) +- Fix: Engine preview window could have the wrong size (#14455) +- Fix #8167: No error sub-message when trying to clear protected buildings (#14444) +- Fix #14433: [NewGRF] Road tile for drive-through stops was not drawn (#14434) +- Fix: [Win32] Build failure with newer Windows SDK version due to WinRT changes (#14432) +- Fix #14396: Industry production graph showed zero when data was unavailable (#14416) +- Fix #14385: Crash in industry view due to incorrect string parameter calculations (#14413) +- Fix #14360: Stop reusing strings for Low/Medium/High setting values to improve translations (#14409) +- Fix #14377: Make house picker window remember house protection state when closed (#14406) +- Fix #14375: When loading config, validate timekeeping mode and minutes per year (#14405) +- Fix: Include sort button width in content list header minimal size (#14402) +- Fix: Don't draw truncation ellipsis if it's too wide (#14401) +- Fix: Don't include ellipsis width in RTL truncation offset (#14400) +- Fix: Buildings with non-zero subtile offsets broken in house picker (#14390) +- Fix #12900: Could not use join station with new non-square stations (#14378) +- Fix: Allow object picker to resize if there are no classes (#14372) +- Fix #14081: Only allow "rm" & "del" console commands to remove savegames (#14371) +- Fix: Train path reservations on different railtypes could join leading to train crashes (#14366) +- Fix #14356: Incorrect sprite group chosen for stations and road stops (#14359) +- Fix: Wrong error message about script Save() returned value (#14334) +- Fix: Hidden-by-facility station signs were not ignored when handling clicks (#14326) +- Fix: [Script] SQOpsLimiter had no effect for native valuator/filter (#14322) +- Fix: Crash if loading compatibility scripts fails (#14318) +- Fix #14277: Aircraft could land when a zeppelin was on the runway (#14317) +- Fix: Drag drop line position when dragging NewGRF from file to active panel (#14316) +- Fix: Upgrade button in NewGRF window no longer worked (#14315) +- Fix: [NewGRF] Clear rail vehicle flipped flag if reverse probability callback returns false (#14281) +- Fix: [Script] Conversion from ScriptEvent to ScriptEventCompanyTown subclasses (#14274) +- Fix: Empty first parameter of EncodedString was skipped (#14273) +- Fix #14271: Crash due to incorrect parameter index in string (#14272) +- Fix: Restore the behaviour when entering numbers in query windows: clamp integers out of range to the maximum valid value (#14268) +- Fix: Replacing a dropdown list could reposition it partially off-screen (#14261) +- Fix: Click+Dragging of selected order was not possible (#14259) +- Fix #14256: Company finance windows not updated when paused (#14258) +- Fix: Incorrect tile colour in smallmap for snowy clear tiles in vegetation mode (#14257) +- Fix #14241: Invalid string parameters in subsidies list window (#14243) +- Fix #13854: 40bpp-anim blitter recolouring failed for 32bpp pixels without mask channel (#14242) +- Fix #14234: Crash due to broken invalid string parameter handling (#14235, #14236) +- Fix: Closing the Game Options window closes all textfile windows (#14210) +- Fix: Zoom-scroll extra viewports only if the mouse cursor is over the viewport (#14209) +- Fix: Changing monospace font didn't refresh the monospace width cache, causing wrapping issues (#14185) +- Fix: Crash/Undefined behaviour in station view window (#14183) +- Fix: Handle tab characters when loading GS strings from savegame (#14180) +- Fix: [Script] Access to enum/consts defined outside of main.nut (#14176) +- Fix: Small order list arrow was wrong direction for RTL text (#14174) +- Fix #14170: Missing spaces for timetabled order strings (#14172) +- Fix #14166: Loan was no longer invalidated when refreshing finance window (#14168) +- Fix #14098: Ability to play MIDI tracks over 7 minutes long (#14144) +- Fix: [NewGRF] AI station construction callback did not work for stations with ID >= 0x100 (#14142) +- Fix: Out-of-bounds read, if NewGRF stations provided no spritesets (#14140) +- Fix: [NewGRF] The result of Action123 evaluation affected rerandomisation in a weird corner case (#14139) +- Fix: [NewGRF] Computed VarAction2 callback results were not always properly masked to 15 bit results (#14138) +- Fix #14125: NewGRF sounds were not loaded (#14130) +- Fix: List Ctrl+Click behaviour for vehicle details tooltip (#14127) +- Fix: Road vehicle blocking and train collisions used slightly inconsistent distance thresholds (#14114) +- Fix #14107: Mark the company value in the company view window dirty when it changes (#14112) +- Fix #14107: Invalidate all shared vehicle windows when changing group (#14110) +- Fix #14104: Don't base timetable timing decisions on client settings (#14105) +- Fix: Focus settings filter box (only) when the tab becomes active (#14100) +- Fix: [Script] Reject scripts using negative version (#14096) +- Fix: Map size column in server list could be too wide (#14094) +- Fix: [NewGRF] Cargo-types for airport-tile animation-triggers were not properly translated (#14092) +- Fix: [NewGRF] For animation-triggers which do not supply a cargo-type in var18, the var18 bits should remain empty (#14091) +- Fix: Changing language or interface scale could hang (#14087) +- Fix: [NewGRF] For roadstop multi-tile-animation-triggers, the upper 16 bits were not the same for all tiles (#14084) +- Fix: Town view caption lost "(City)" flag (#14082) +- Fix: [NewGRF] Randomisation-trigger 'path reservation' did not work for waypoints (#14076) +- Fix: Incorrect test of object flags allowed HQ to be removed (#14062) +- Fix: Odd drawing and crash if scrollbar is not tall enough (#14052) +- Fix: Incorrect string display for autoreplace wagon removal status (#14038) +- Fix: House picker crashes if selection is not available (#14030) +- Fix: [NewGRF] PrepareLayout worked on a copy of the data resulting in bad sprite layouts (#14013) +- Fix: [NewGRF] Strange things happened, when using the synchronised tile loop animation trigger for houses (#14011) +- Fix: [NewGRF] Animation speed properties of houses and stations had wrong default (#14005) +- Fix: [NewGRF] Roadstop animation/randomisation was not triggered on vehicle arrival (#14003) +- Fix: Don't display badges if the class has no name (#13994) +- Fix #13954: Plotting graphs with limited data to the right (#13956) +- Fix #13307: Return non-zero value when there is a mistake in command-line arguments (#13547) +- Fix: [Script] Make ScriptOrder functions aware of road waypoints (#13419) +- Fix: Scale graph gridlines and axes with GUI scale (#12131) +- Fix: Original graphics' tycoon-of-the-century sprite assumes a black background (#11679, #14545) + + ### 15.0-beta2 (2025-04-13) - Feature: Snow-covered rocks are now visible (#13627) @@ -20,6 +168,7 @@ - Add: Ukrainian Hryvnia currency (#12877) - Add: Convert 32bpp-only sprites to 8bpp when needed (#11602) - Change: [Script] Start GS (but don't run it) when generating world in scenario editor (#13961) +- Change: [Script] Add vehicle owner to crash event (#13878) - Change: Make tree placement at world generation look more organic (#13515) - Change: [MacOS] Put the icon in a rounded rectangle (#13446) - Change: [Script] GetWaypointID to return the StationID of any waypoint (#13407) diff --git a/cmake/scripts/SquirrelExport.cmake b/cmake/scripts/SquirrelExport.cmake index 6549078844..8b9ca55621 100644 --- a/cmake/scripts/SquirrelExport.cmake +++ b/cmake/scripts/SquirrelExport.cmake @@ -17,41 +17,48 @@ if(NOT APILC) endif() macro(dump_fileheader) - get_filename_component(SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE}" NAME) - string(APPEND SQUIRREL_EXPORT "\n#include \"../${SCRIPT_API_FILE_NAME}\"") + get_filename_component(SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE}" NAME_WE) + string(APPEND SQUIRREL_EXPORT "\n#include \"../${SCRIPT_API_FILE_NAME}.hpp\"") if(NOT "${APIUC}" STREQUAL "Template") string(REPLACE "script_" "template_" SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE_NAME}") - string(APPEND SQUIRREL_EXPORT "\n#include \"../template/${SCRIPT_API_FILE_NAME}.sq\"") + string(APPEND SQUIRREL_EXPORT "\n#include \"../template/${SCRIPT_API_FILE_NAME}.sq.hpp\"") + endif() +endmacro() + +macro(open_namespace) + if(NOT NAMESPACE_OPENED) + string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") + set(NAMESPACE_OPENED TRUE) endif() 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) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param<${NAME} &> { static inline ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") - string(APPEND SQUIRREL_EXPORT "\n template <> struct Param { static inline const ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param<${NAME} *> { static inline ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param<${NAME} &> { static inline ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param { static inline const ${NAME} *Get(HSQUIRRELVM vm, int index) { return static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param { static inline const ${NAME} &Get(HSQUIRRELVM vm, int index) { return *static_cast<${NAME} *>(Squirrel::GetRealInstance(vm, index, \"${REALNAME}\")); } };") 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; } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> 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) {") - string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_INSTANCE) {") - 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));") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n if (sq_gettype(vm, index) == OT_NULL) {") - string(APPEND SQUIRREL_EXPORT "\n return nullptr;") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n throw sq_throwerror(vm, fmt::format(\"parameter {} has an invalid type ; expected: 'Text'\", index - 1));") - string(APPEND SQUIRREL_EXPORT "\n }") - string(APPEND SQUIRREL_EXPORT "\n };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Param {") + string(APPEND SQUIRREL_EXPORT "\n\t\tstatic inline Text *Get(HSQUIRRELVM vm, int index) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tif (sq_gettype(vm, index) == OT_INSTANCE) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t\treturn Param::Get(vm, index);") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tif (sq_gettype(vm, index) == OT_STRING) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t\treturn new RawText(Param::Get(vm, index));") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tif (sq_gettype(vm, index) == OT_NULL) {") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t\treturn nullptr;") + string(APPEND SQUIRREL_EXPORT "\n\t\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t\t\tthrow sq_throwerror(vm, fmt::format(\"parameter {} has an invalid type ; expected: 'Text'\", index - 1));") + string(APPEND SQUIRREL_EXPORT "\n\t\t}") + string(APPEND SQUIRREL_EXPORT "\n\t};") else() - 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; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") + string(APPEND SQUIRREL_EXPORT "\n\ttemplate <> struct Return<${NAME} *> { static inline int Set(HSQUIRRELVM vm, ${NAME} *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, \"${REALNAME}\", res, nullptr, DefSQDestructorCallback<${NAME}>, true); return 1; } };") endif() endmacro() @@ -66,7 +73,6 @@ macro(reset_reader) unset(STATIC_METHODS) unset(CLS) unset(START_SQUIRREL_DEFINE_ON_NEXT_LINE) - set(CLS_LEVEL 0) unset(CLS_IN_API) endmacro() @@ -75,6 +81,9 @@ reset_reader() file(STRINGS "${SCRIPT_API_FILE}" SOURCE_LINES) set(NUM_LINE 0) +set(CLS_LEVEL 0) +set(BRACE_LEVEL 0) + macro(doxygen_check) if(NOT "${DOXYGEN_SKIP}" STREQUAL "") message(FATAL_ERROR "${SCRIPT_API_FILE}:${NUM_LINE}: a DOXYGEN_API block was not properly closed") @@ -110,7 +119,7 @@ foreach(LINE IN LISTS SOURCE_LINES) continue() endif() - if("${LINE}" MATCHES "^([ ]*)\\* @api (.*)$") + if("${LINE}" MATCHES "^([\t ]*)\\* @api (.*)$") set(LINE ${CMAKE_MATCH_2}) # By default, classes are not selected if(NOT CLS_LEVEL) @@ -148,22 +157,28 @@ foreach(LINE IN LISTS SOURCE_LINES) continue() endif() + # Count braces to skip function bodies + string(REGEX REPLACE "[^{]" "" OPENING_BRACES "${LINE}") + string(LENGTH "${OPENING_BRACES}" OPENING_BRACES) + string(REGEX REPLACE "[^}]" "" CLOSING_BRACES "${LINE}") + string(LENGTH "${CLOSING_BRACES}" CLOSING_BRACES) + math(EXPR BRACE_LEVEL "${BRACE_LEVEL} + ${OPENING_BRACES} - ${CLOSING_BRACES}") + # Ignore forward declarations of classes - if("${LINE}" MATCHES "^( *)class(.*);") + if("${LINE}" MATCHES "^(\t*)class(.*);") continue() endif() # We only want to have public functions exported for now - if("${LINE}" MATCHES "^( *)class (.*) (: public|: protected|: private|:) ([^ ]*)") + if("${LINE}" MATCHES "^(\t*)class (.*) (: public|: protected|: private|:) ([^ ]*)") if(NOT CLS_LEVEL) if(NOT DEFINED API_SELECTED) - message(WARNING "Class '${CMAKE_MATCH_2}' has no @api. It won't be published to any API.") + message(WARNING "${SCRIPT_API_FILE}:${NUM_LINE}: Class '${CMAKE_MATCH_2}' has no @api. It won't be published to any API.") set(API_SELECTED FALSE) endif() unset(IS_PUBLIC) - unset(CLS_PARAM_0) - set(CLS_PARAM_1 1) - set(CLS_PARAM_2 "x") + unset(CLS_PARAMS) + set(CLS_TYPES "x") set(CLS_IN_API ${API_SELECTED}) unset(API_SELECTED) set(CLS "${CMAKE_MATCH_2}") @@ -181,19 +196,19 @@ foreach(LINE IN LISTS SOURCE_LINES) math(EXPR CLS_LEVEL "${CLS_LEVEL} + 1") continue() endif() - if("${LINE}" MATCHES "^( *)public") + if("${LINE}" MATCHES "^(\t*)public") if(CLS_LEVEL EQUAL 1) set(IS_PUBLIC TRUE) endif() continue() endif() - if("${LINE}" MATCHES "^( *)protected") + if("${LINE}" MATCHES "^(\t*)protected") if(CLS_LEVEL EQUAL 1) unset(IS_PUBLIC) endif() continue() endif() - if("${LINE}" MATCHES "^( *)private") + if("${LINE}" MATCHES "^(\t*)private") if(CLS_LEVEL EQUAL 1) unset(IS_PUBLIC) endif() @@ -221,7 +236,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # We need to make specialized conversions for structs - if("${LINE}" MATCHES "^( *)struct ([^ ]*)") + if("${LINE}" MATCHES "^(\t*)struct ([^ ]*)") math(EXPR CLS_LEVEL "${CLS_LEVEL} + 1") # Check if we want to publish this struct @@ -243,7 +258,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # We need to make specialized conversions for enums - if("${LINE}" MATCHES "^( *)enum ([^ ]*)") + if("${LINE}" MATCHES "^(\t*)enum ([^ ]*)") math(EXPR CLS_LEVEL "${CLS_LEVEL} + 1") # Check if we want to publish this enum @@ -266,7 +281,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # Maybe the end of the class, if so we can start with the Squirrel export pretty soon - if("${LINE}" MATCHES "};") + if(BRACE_LEVEL LESS CLS_LEVEL) math(EXPR CLS_LEVEL "${CLS_LEVEL} - 1") if(CLS_LEVEL) unset(IN_ENUM) @@ -280,7 +295,7 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # Empty/white lines. When we may do the Squirrel export, do that export. - if("${LINE}" MATCHES "^([ ]*)$") + if("${LINE}" MATCHES "^([ \t]*)$") if(NOT START_SQUIRREL_DEFINE_ON_NEXT_LINE) continue() endif() @@ -304,33 +319,17 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SQUIRREL_EXPORT "\n") if("${APIUC}" STREQUAL "Template") - # First check whether we have enums to print - if(DEFINED ENUMS) - if(NOT NAMESPACE_OPENED) - string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") - set(NAMESPACE_OPENED TRUE) - endif() - endif() - # Then check whether we have structs/classes to print if(DEFINED STRUCTS) - if(NOT NAMESPACE_OPENED) - string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") - set(NAMESPACE_OPENED TRUE) - endif() - string(APPEND SQUIRREL_EXPORT "\n /* Allow inner classes/structs to be used as Squirrel parameters */") + open_namespace() + string(APPEND SQUIRREL_EXPORT "\n\t/* Allow inner classes/structs to be used as Squirrel parameters */") foreach(STRUCT IN LISTS STRUCTS) dump_class_templates(${STRUCT}) endforeach() endif() - if(NOT NAMESPACE_OPENED) - string(APPEND SQUIRREL_EXPORT "\nnamespace SQConvert {") - set(NAMESPACE_OPENED TRUE) - else() - string(APPEND SQUIRREL_EXPORT "\n") - endif() - string(APPEND SQUIRREL_EXPORT "\n /* Allow ${CLS} to be used as Squirrel parameter */") + open_namespace() + string(APPEND SQUIRREL_EXPORT "\n\t/* Allow ${CLS} to be used as Squirrel parameter */") dump_class_templates(${CLS}) string(APPEND SQUIRREL_EXPORT "\n} // namespace SQConvert") @@ -340,23 +339,23 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() string(APPEND SQUIRREL_EXPORT "\n") - string(APPEND SQUIRREL_EXPORT "\ntemplate <> SQInteger PushClassName<${CLS}, ScriptType::${APIUC}>(HSQUIRRELVM vm) { sq_pushstring(vm, \"${API_CLS}\", -1); return 1; }") + string(APPEND SQUIRREL_EXPORT "\ntemplate <> SQInteger PushClassName<${CLS}, ScriptType::${APIUC}>(HSQUIRRELVM vm) { sq_pushstring(vm, \"${API_CLS}\"); return 1; }") string(APPEND SQUIRREL_EXPORT "\n") # Then do the registration functions of the class. - string(APPEND SQUIRREL_EXPORT "\nvoid SQ${API_CLS}_Register(Squirrel *engine)") + string(APPEND SQUIRREL_EXPORT "\nvoid SQ${API_CLS}_Register(Squirrel &engine)") string(APPEND SQUIRREL_EXPORT "\n{") - string(APPEND SQUIRREL_EXPORT "\n DefSQClass<${CLS}, ScriptType::${APIUC}> SQ${API_CLS}(\"${API_CLS}\");") + string(APPEND SQUIRREL_EXPORT "\n\tDefSQClass<${CLS}, ScriptType::${APIUC}> SQ${API_CLS}(\"${API_CLS}\");") if("${SUPER_CLS}" STREQUAL "Text") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PreRegister(engine);") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.PreRegister(engine);") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PreRegister(engine, \"${API_SUPER_CLS}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.PreRegister(engine, \"${API_SUPER_CLS}\");") endif() - if(NOT "${SUPER_CLS}" MATCHES "^ScriptEvent") - if("${CLS_PARAM_2}" STREQUAL "v") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.AddSQAdvancedConstructor(engine);") + if((DEFINED CLS_PARAMS OR DEFINED METHODS) AND NOT "${SUPER_CLS}" MATCHES "^ScriptEvent" AND NOT "${CLS}" STREQUAL "ScriptEvent") + if("${CLS_TYPES}" STREQUAL "v") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.AddSQAdvancedConstructor(engine);") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.AddConstructor(engine, \"${CLS_PARAM_2}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.AddConstructor(engine, \"${CLS_TYPES}\");") endif() endif() string(APPEND SQUIRREL_EXPORT "\n") @@ -376,7 +375,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQConst(engine, ${CLS}::${ENUM_VALUE},${SPACES}\"${ENUM_VALUE}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQConst(engine, ${CLS}::${ENUM_VALUE},${SPACES}\"${ENUM_VALUE}\");") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -397,7 +396,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQConst(engine, ${CLS}::${CONST_VALUE},${SPACES}\"${CONST_VALUE}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQConst(engine, ${CLS}::${CONST_VALUE},${SPACES}\"${CONST_VALUE}\");") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -423,7 +422,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n ScriptError::RegisterErrorMap(${ENUM_STRING},${SPACES}${CLS}::${ENUM_ERROR});") + string(APPEND SQUIRREL_EXPORT "\n\tScriptError::RegisterErrorMap(${ENUM_STRING},${SPACES}${CLS}::${ENUM_ERROR});") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -444,7 +443,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(i RANGE ${LEN}) string(APPEND SPACES " ") endforeach() - string(APPEND SQUIRREL_EXPORT "\n ScriptError::RegisterErrorMapString(${CLS}::${ENUM_ERROR_TO_STRING},${SPACES}\"${ENUM_ERROR_TO_STRING}\");") + string(APPEND SQUIRREL_EXPORT "\n\tScriptError::RegisterErrorMapString(${CLS}::${ENUM_ERROR_TO_STRING},${SPACES}\"${ENUM_ERROR_TO_STRING}\");") endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") @@ -463,8 +462,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(STATIC_METHOD IN LISTS STATIC_METHODS) string(REPLACE ":" ";" STATIC_METHOD "${STATIC_METHOD}") list(GET STATIC_METHOD 0 FUNCNAME) - list(GET STATIC_METHOD 1 ARGC) - list(GET STATIC_METHOD 2 TYPES) + list(GET STATIC_METHOD 1 TYPES) string(LENGTH "${FUNCNAME}" LEN) math(EXPR LEN "${MLEN} - ${LEN}") if("${TYPES}" STREQUAL "v") @@ -479,9 +477,9 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SPACES " ") endforeach() if("${TYPES}" STREQUAL "v") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQAdvancedStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQAdvancedStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}${ARGC}, \"${TYPES}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQStaticMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}\"${TYPES}\");") endif() endforeach() if(MLEN) @@ -501,8 +499,7 @@ foreach(LINE IN LISTS SOURCE_LINES) foreach(METHOD IN LISTS METHODS) string(REPLACE ":" ";" METHOD "${METHOD}") list(GET METHOD 0 FUNCNAME) - list(GET METHOD 1 ARGC) - list(GET METHOD 2 TYPES) + list(GET METHOD 1 TYPES) string(LENGTH "${FUNCNAME}" LEN) math(EXPR LEN "${MLEN} - ${LEN}") if("${TYPES}" STREQUAL "v") @@ -517,16 +514,16 @@ foreach(LINE IN LISTS SOURCE_LINES) string(APPEND SPACES " ") endforeach() if("${TYPES}" STREQUAL "v") - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQAdvancedMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQAdvancedMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\");") else() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.DefSQMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}${ARGC}, \"${TYPES}\");") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.DefSQMethod(engine, &${CLS}::${FUNCNAME},${SPACES}\"${FUNCNAME}\",${SPACES}\"${TYPES}\");") endif() endforeach() if(MLEN) string(APPEND SQUIRREL_EXPORT "\n") endif() - string(APPEND SQUIRREL_EXPORT "\n SQ${API_CLS}.PostRegister(engine);") + string(APPEND SQUIRREL_EXPORT "\n\tSQ${API_CLS}.PostRegister(engine);") string(APPEND SQUIRREL_EXPORT "\n}") reset_reader() @@ -539,9 +536,13 @@ foreach(LINE IN LISTS SOURCE_LINES) continue() endif() + if(NOT BRACE_LEVEL EQUAL CLS_LEVEL) + continue() + endif() + # Add enums if(IN_ENUM) - string(REGEX MATCH "([^, ]+)" ENUM_VALUE "${LINE}") + string(REGEX MATCH "([^,\t ]+)" ENUM_VALUE "${LINE}") list(APPEND ENUM_VALUES "${ENUM_VALUE}") # Check if this a special error enum @@ -549,12 +550,12 @@ foreach(LINE IN LISTS SOURCE_LINES) if("${ENUM}" MATCHES ".*::ErrorMessages") # syntax: # enum ErrorMessages { - # ERR_SOME_ERROR, // [STR_ITEM1, STR_ITEM2, ...] + #\tERR_SOME_ERROR,\t// [STR_ITEM1, STR_ITEM2, ...] # } # Set the mappings if("${LINE}" MATCHES "\\[(.*)\\]") - string(REGEX REPLACE "[ ]" "" MAPPINGS "${CMAKE_MATCH_1}") + string(REGEX REPLACE "[ \t]" "" MAPPINGS "${CMAKE_MATCH_1}") string(REPLACE "," ";" MAPPINGS "${MAPPINGS}") foreach(MAPPING IN LISTS MAPPINGS) @@ -568,11 +569,11 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() # Add a const (non-enum) value - if("${LINE}" MATCHES "^[ ]*static const [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") + if("${LINE}" MATCHES "^[ \t]*static const [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") list(APPEND CONST_VALUES "${CMAKE_MATCH_1}") continue() endif() - if("${LINE}" MATCHES "^[ ]*static constexpr [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") + if("${LINE}" MATCHES "^[ \t]*static constexpr [^ ]+ ([^ ]+) = -?\\(?[^ ]*\\)?[^ ]+;") list(APPEND CONST_VALUES "${CMAKE_MATCH_1}") continue() endif() @@ -584,41 +585,43 @@ foreach(LINE IN LISTS SOURCE_LINES) endif() if("${LINE}" MATCHES "~") if(DEFINED API_SELECTED) - message(WARNING "Destructor for '${CLS}' has @api. Tag ignored.") + message(WARNING "${SCRIPT_API_FILE}:${NUM_LINE}: Destructor for '${CLS}' has @api. Tag ignored.") unset(API_SELECTED) endif() continue() endif() unset(IS_STATIC) - if("${LINE}" MATCHES "static") + if("${LINE}" MATCHES "static ") set(IS_STATIC TRUE) endif() - string(REGEX REPLACE "(virtual|static|const)[ ]+" "" LINE "${LINE}") + string(REGEX REPLACE "(virtual|static|const)[ \t]+" "" LINE "${LINE}") string(REGEX REPLACE "{.*" "" LINE "${LINE}") set(PARAM_S "${LINE}") string(REGEX REPLACE "\\*" "" LINE "${LINE}") string(REGEX REPLACE "\\(.*" "" LINE "${LINE}") - string(REGEX REPLACE ".*\\(" "" PARAM_S "${PARAM_S}") + # Parameters start at first "(". Further "(" will appear in ctor lists. + string(REGEX MATCH "\\(.*" PARAM_S "${PARAM_S}") string(REGEX REPLACE "\\).*" "" PARAM_S "${PARAM_S}") + string(REGEX REPLACE "^\\(" "" PARAM_S "${PARAM_S}") - string(REGEX MATCH "([^ ]+)( ([^ ]+))?" RESULT "${LINE}") + string(REGEX MATCH "([^ \t]+)( ([^ ]+))?" RESULT "${LINE}") set(FUNCTYPE "${CMAKE_MATCH_1}") set(FUNCNAME "${CMAKE_MATCH_3}") if("${FUNCTYPE}" STREQUAL "${CLS}" AND NOT FUNCNAME) if(DEFINED API_SELECTED) - message(WARNING "Constructor for '${CLS}' has @api. Tag ignored.") + message(WARNING "${SCRIPT_API_FILE}:${NUM_LINE}: Constructor for '${CLS}' has @api. Tag ignored.") unset(API_SELECTED) endif() - set(CLS_PARAM_0 "${PARAM_S}") + set(CLS_PARAMS "${PARAM_S}") if(NOT PARAM_S) continue() endif() elseif(NOT FUNCNAME) continue() - endif() + endif() string(REPLACE "," ";" PARAMS "${PARAM_S}") if(IS_STATIC) @@ -627,9 +630,7 @@ foreach(LINE IN LISTS SOURCE_LINES) set(TYPES "x") endif() - set(LEN 1) foreach(PARAM IN LISTS PARAMS) - math(EXPR LEN "${LEN} + 1") string(STRIP "${PARAM}" PARAM) if("${PARAM}" MATCHES "\\*|&") if("${PARAM}" MATCHES "^char") @@ -668,13 +669,12 @@ foreach(LINE IN LISTS SOURCE_LINES) unset(API_SELECTED) if("${FUNCTYPE}" STREQUAL "${CLS}" AND NOT FUNCNAME) - set(CLS_PARAM_1 ${LEN}) - set(CLS_PARAM_2 "${TYPES}") + set(CLS_TYPES "${TYPES}") elseif("${FUNCNAME}" MATCHES "^_" AND NOT "${TYPES}" STREQUAL "v") elseif(IS_STATIC) - list(APPEND STATIC_METHODS "${FUNCNAME}:${LEN}:${TYPES}") + list(APPEND STATIC_METHODS "${FUNCNAME}:${TYPES}") else() - list(APPEND METHODS "${FUNCNAME}:${LEN}:${TYPES}") + list(APPEND METHODS "${FUNCNAME}:${TYPES}") endif() continue() endif() diff --git a/cmake/scripts/SquirrelIncludes.cmake b/cmake/scripts/SquirrelIncludes.cmake index d6022f73dd..5e1cab355b 100644 --- a/cmake/scripts/SquirrelIncludes.cmake +++ b/cmake/scripts/SquirrelIncludes.cmake @@ -19,7 +19,7 @@ endif() file(READ "${API_FILES}" SCRIPT_API_BINARY_FILES) foreach(FILE IN LISTS SCRIPT_API_BINARY_FILES) - file(STRINGS "${FILE}" LINES REGEX "^void SQ${APIUC}.*_Register\\(Squirrel \\*engine\\)$") + file(STRINGS "${FILE}" LINES REGEX "^void SQ${APIUC}.*_Register\\(Squirrel &engine\\)$") if(LINES) string(REGEX REPLACE ".*api/${APILC}/(.*)" "#include \"\\1\"" FILE "${FILE}") list(APPEND SQUIRREL_INCLUDES "${FILE}") @@ -28,7 +28,7 @@ foreach(FILE IN LISTS SCRIPT_API_BINARY_FILES) continue() endif() string(REGEX REPLACE "^.*void " " " LINE "${LINE}") - string(REGEX REPLACE "Squirrel \\*" "" LINE "${LINE}") + string(REGEX REPLACE "Squirrel &" "" LINE "${LINE}") list(APPEND SQUIRREL_REGISTER "${LINE}") endforeach() endif() diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index 495d6de112..e80a0369ca 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -246,7 +246,7 @@ the array so you can quickly see what is used and what is not. OOOO OOOO OOOO OOOO OOOO OOOX OOOO OOOO - 0000 OOO0 + 0000 OOOO OOOO OOOO OOOO OOOO OOOO OOOO OOOO OOOO diff --git a/docs/obg_format.txt b/docs/obg_format.txt index f43ea7edd7..2aa9bec0d2 100644 --- a/docs/obg_format.txt +++ b/docs/obg_format.txt @@ -16,8 +16,11 @@ ; - `openttd -I ` starts OpenTTD with the given set (case sensitive) ; - adding `graphicsset = ` to the misc section of openttd.cfg makes ; OpenTTD start with that graphics set by default -; - there is a command line tool for all platforms called md5sum that can -; create the MD5 checksum you need. +; - `grfid -m` can give the GRF file MD5 checksums that you need +; - The `--md5` output option for `nmlc` can also give the MD5 if you are +; encoding from an nml source +; - Simple file MD5 checksums, eg. using `md5sum` are not correct for grf +; container versions other than 1 ; - all files specified in this file are search relatively to the path where ; this file is found, i.e. if the graphics files are in a subdir you have ; to add that subdir to the names in this file to! It will NOT search for @@ -44,6 +47,8 @@ description.en_US = howdie palette = DOS ; preferred blitter, optional; either 8bpp (default) or 32bpp. blitter = 8bpp +; url, optional +url = https://github.com/my/baseset ; The files section lists the files that replace sprites. ; The file names are case sensitive. diff --git a/known-bugs.md b/known-bugs.md index b50fff766d..64c9628025 100644 --- a/known-bugs.md +++ b/known-bugs.md @@ -2,8 +2,8 @@ ## Table of contents -- 1.0) About -- 2.0) Known bugs +- 1.0) [About](#10-about) +- 2.0) [Known bugs](#20-known-bugs) ## 1.0) About @@ -12,7 +12,7 @@ that are the same as these. If you do, do not act surprised, because we WILL flame you! The current list of known bugs that we intend to fix can be found in our -bug tracking system at https://github.com/OpenTTD/OpenTTD/issues +bug tracking system at [https://github.com/OpenTTD/OpenTTD/issues](https://github.com/OpenTTD/OpenTTD/issues) Also check the closed bugs when searching for your bug in this system as we might have fixed the bug in the mean time. diff --git a/media/baseset/OpenTTD-Mono.ttf b/media/baseset/OpenTTD-Mono.ttf index 853e8527c0..3195040f1a 100644 Binary files a/media/baseset/OpenTTD-Mono.ttf and b/media/baseset/OpenTTD-Mono.ttf differ diff --git a/media/baseset/OpenTTD-Sans.ttf b/media/baseset/OpenTTD-Sans.ttf index 202cef8dd2..cc46c3aeb1 100644 Binary files a/media/baseset/OpenTTD-Sans.ttf and b/media/baseset/OpenTTD-Sans.ttf differ diff --git a/media/baseset/OpenTTD-Serif.ttf b/media/baseset/OpenTTD-Serif.ttf index 318aa340f5..d708b38f7e 100644 Binary files a/media/baseset/OpenTTD-Serif.ttf and b/media/baseset/OpenTTD-Serif.ttf differ diff --git a/media/baseset/OpenTTD-Small.ttf b/media/baseset/OpenTTD-Small.ttf index 701de01d3c..53ad7ebfe2 100644 Binary files a/media/baseset/OpenTTD-Small.ttf and b/media/baseset/OpenTTD-Small.ttf differ diff --git a/media/baseset/openttd.grf b/media/baseset/openttd.grf index 540ddf3fe9..620cc8ab98 100644 Binary files a/media/baseset/openttd.grf and b/media/baseset/openttd.grf differ diff --git a/media/baseset/openttd.grf.hash b/media/baseset/openttd.grf.hash index b0a772e8d0..5b07da7ff6 100644 --- a/media/baseset/openttd.grf.hash +++ b/media/baseset/openttd.grf.hash @@ -1 +1 @@ -019dba4830a64ee4345d3d647633e1da +eb8390a0569e66ec417c64ad254f9d05 diff --git a/media/baseset/openttd/chars.nfo b/media/baseset/openttd/chars.nfo index 8d1acb814f..43544a841b 100644 --- a/media/baseset/openttd/chars.nfo +++ b/media/baseset/openttd/chars.nfo @@ -671,9 +671,9 @@ // U+2026: Horizontal Ellipsis -1 * 18 12 04 00 01 26 20 01 01 26 20 02 01 26 20 03 01 26 20 - -1 sprites/chars.png 8bpp 560 370 11 12 0 -1 normal + -1 sprites/chars.png 8bpp 560 370 9 12 0 -1 normal -1 sprites/chars.png 8bpp 560 390 5 7 0 0 normal - -1 sprites/chars.png 8bpp 560 400 15 21 0 -2 normal + -1 sprites/chars.png 8bpp 560 400 14 21 0 -2 normal -1 sprites/mono.png 8bpp 265 270 7 13 0 0 normal // U+2039: Single Left-Pointing Angle Quotation Mark @@ -840,3 +840,13 @@ -1 sprites/chars.png 8bpp 630 400 6 21 0 -2 normal -1 sprites/mono.png 8bpp 325 270 7 13 0 0 normal -1 sprites/mono.png 8bpp 340 270 7 13 0 0 normal + +// U+E29D: Small left arrow + -1 * 6 12 01 01 01 9D E2 + -1 sprites/chars.png 8bpp 10 430 5 5 0 1 normal + +// U+E29B: Town +// U+E29C: City + -1 * 6 12 01 00 02 9B E2 + -1 sprites/chars.png 8bpp 20 430 10 10 0 0 normal + -1 sprites/chars.png 8bpp 40 430 10 10 0 0 normal diff --git a/media/baseset/openttd/chars.png b/media/baseset/openttd/chars.png index 34a4a67c5d..426e7a8348 100644 Binary files a/media/baseset/openttd/chars.png and b/media/baseset/openttd/chars.png differ diff --git a/media/baseset/openttd/openttdgui.nfo b/media/baseset/openttd/openttdgui.nfo index 2fd5a5bb4c..b6466f98b6 100644 --- a/media/baseset/openttd/openttdgui.nfo +++ b/media/baseset/openttd/openttdgui.nfo @@ -4,7 +4,7 @@ // 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 . // -1 * 0 0C "OpenTTD GUI graphics" - -1 * 3 05 15 \b 191 // OPENTTD_SPRITE_COUNT + -1 * 3 05 15 \b 192 // OPENTTD_SPRITE_COUNT -1 sprites/openttdgui.png 8bpp 66 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 146 8 64 31 -31 7 normal -1 sprites/openttdgui.png 8bpp 226 8 64 31 -31 7 normal @@ -196,3 +196,4 @@ -1 sprites/openttdgui.png 8bpp 567 440 12 10 0 0 normal -1 sprites/openttdgui.png 8bpp 581 440 10 10 0 0 normal -1 sprites/openttdgui.png 8bpp 593 440 10 10 0 0 normal + -1 sprites/openttdgui.png 8bpp 605 440 8 10 0 0 normal diff --git a/media/baseset/openttd/openttdgui.png b/media/baseset/openttd/openttdgui.png index 5b80c33260..fdb68002f7 100644 Binary files a/media/baseset/openttd/openttdgui.png and b/media/baseset/openttd/openttdgui.png differ diff --git a/media/baseset/orig_extra.grf b/media/baseset/orig_extra.grf index 25193b8020..dab472210e 100644 Binary files a/media/baseset/orig_extra.grf and b/media/baseset/orig_extra.grf differ diff --git a/media/baseset/orig_extra/fix_graphics.png b/media/baseset/orig_extra/fix_graphics.png index 1dc7bad825..6b7246ef24 100644 Binary files a/media/baseset/orig_extra/fix_graphics.png and b/media/baseset/orig_extra/fix_graphics.png differ diff --git a/os/windows/sign_azure.ps1 b/os/windows/sign_azure.ps1 index e19476fa6d..a9fc93f427 100644 --- a/os/windows/sign_azure.ps1 +++ b/os/windows/sign_azure.ps1 @@ -24,7 +24,7 @@ if (!$Env:AZURE_CODESIGN_ENDPOINT -or !$Env:AZURE_CODESIGN_ACCOUNT_NAME -or !$En exit } -Install-Module -Name AzureCodeSigning -Scope CurrentUser -RequiredVersion 0.3.0 -Force -Repository PSGallery +Install-Module -Name TrustedSigning -Scope CurrentUser -RequiredVersion 0.5.3 -Force -Repository PSGallery $params = @{} @@ -37,4 +37,4 @@ $params["FileDigest"] = "SHA256" $params["TimestampRfc3161"] = "http://timestamp.acs.microsoft.com" $params["TimestampDigest"] = "SHA256" -Invoke-AzureCodeSigning @params +Invoke-TrustedSigning @params diff --git a/regression/regression/main.nut b/regression/regression/main.nut index c0fe80ea4b..951c9d4598 100644 --- a/regression/regression/main.nut +++ b/regression/regression/main.nut @@ -17,6 +17,10 @@ function Regression::TestInit() print(" IsValid(vehicle.plane_speed): " + AIGameSettings.IsValid("vehicle.plane_speed")); print(" vehicle.plane_speed: " + AIGameSettings.GetValue("vehicle.plane_speed")); require("require.nut"); + print(" TestEnum.value1: " + ::TestEnum.value1); + print(" test_constant: " + ::test_constant); + print(" TestEnum.value2: " + TestEnum.value2); + print(" test_constant: " + test_constant); print(" min(6, 3): " + min(6, 3)); print(" min(3, 6): " + min(3, 6)); print(" max(6, 3): " + max(6, 3)); @@ -824,6 +828,13 @@ function Regression::List() print(" []:"); print(" 4000 => " + list[4000]); + print(" clone:"); + local list3 = clone list; + print(" Clone ListDump:"); + foreach (idx, val in list3) { + print(" " + idx + " => " + val); + } + list.Clear(); print(" IsEmpty(): " + list.IsEmpty()); @@ -856,6 +867,12 @@ function Regression::List() it = list.Next(); print(" " + it + " => " + list.GetValue(it)); } + + print(" Clone ListDump:"); + foreach (idx, val in list3) { + print(" " + idx + " => " + val); + } + } function Regression::Map() @@ -1017,6 +1034,28 @@ function Regression::Order() foreach (idx, val in list) { print(" " + idx + " => " + val); } + list = AIVehicleList_Station(3, AIVehicle.VT_ROAD); + print(" Count(): " + list.Count()); + list.Valuate(AIVehicle.GetLocation); + print(" Location ListDump:"); + for (local i = list.Begin(); !list.IsEnd(); i = list.Next()) { + print(" " + i + " => " + list.GetValue(i)); + } + print(" foreach():"); + foreach (idx, val in list) { + print(" " + idx + " => " + val); + } + list = AIVehicleList_Station(3, AIVehicle.VT_RAIL); + print(" Count(): " + list.Count()); + list.Valuate(AIVehicle.GetLocation); + print(" Location ListDump:"); + for (local i = list.Begin(); !list.IsEnd(); i = list.Next()) { + print(" " + i + " => " + list.GetValue(i)); + } + print(" foreach():"); + foreach (idx, val in list) { + print(" " + idx + " => " + val); + } } function Regression::RailTypeList() @@ -1675,13 +1714,22 @@ function Regression::TownList() } print(" HasStatue(): " + AITown.HasStatue(list.Begin())); - print(" GetRoadReworkDuration(): " + AITown.GetRoadReworkDuration(list.Begin())); - print(" GetExclusiveRightsCompany(): " + AITown.GetExclusiveRightsCompany(list.Begin())); - print(" GetExclusiveRightsDuration(): " + AITown.GetExclusiveRightsDuration(list.Begin())); print(" IsActionAvailable(BUILD_STATUE): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUILD_STATUE)); print(" PerformTownAction(BUILD_STATUE): " + AITown.PerformTownAction(list.Begin(), AITown.TOWN_ACTION_BUILD_STATUE)); print(" IsActionAvailable(BUILD_STATUE): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUILD_STATUE)); print(" HasStatue(): " + AITown.HasStatue(list.Begin())); + print(" GetRoadReworkDuration(): " + AITown.GetRoadReworkDuration(list.Begin())); + print(" IsActionAvailable(ROAD_REBUILD): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_ROAD_REBUILD)); + print(" PerformTownAction(ROAD_REBUILD): " + AITown.PerformTownAction(list.Begin(), AITown.TOWN_ACTION_ROAD_REBUILD)); + print(" IsActionAvailable(ROAD_REBUILD): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_ROAD_REBUILD)); + print(" GetRoadReworkDuration(): " + AITown.GetRoadReworkDuration(list.Begin())); + print(" GetExclusiveRightsCompany(): " + AITown.GetExclusiveRightsCompany(list.Begin())); + print(" GetExclusiveRightsDuration(): " + AITown.GetExclusiveRightsDuration(list.Begin())); + print(" IsActionAvailable(BUY_RIGHTS): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUY_RIGHTS)); + print(" PerformTownAction(BUY_RIGHTS): " + AITown.PerformTownAction(list.Begin(), AITown.TOWN_ACTION_BUY_RIGHTS)); + print(" IsActionAvailable(BUY_RIGHTS): " + AITown.IsActionAvailable(list.Begin(), AITown.TOWN_ACTION_BUY_RIGHTS)); + print(" GetExclusiveRightsCompany(): " + AITown.GetExclusiveRightsCompany(list.Begin())); + print(" GetExclusiveRightsDuration(): " + AITown.GetExclusiveRightsDuration(list.Begin())); } function Regression::Tunnel() @@ -1975,6 +2023,33 @@ function Regression::Math() print(" 13725 > -2147483648: " + ( 13725 > -2147483648)); } +function Regression::PriorityQueue() +{ + print(""); + print("--PriorityQueue--"); + local queue = AIPriorityQueue(); + print(" IsEmpty(): " + queue.IsEmpty()); + print(" Count(): " + queue.Count()); + print(" --Insert--") + for (local i = 0; i < 10; i++) { + print(" Insert(" + i + ", " + i + "): " + queue.Insert(i, i)); + } + print(" Exists(5): " + queue.Exists(5)); + print(" Insert(5, 5): "+ queue.Insert(5, 5)); + print(" IsEmpty(): " + queue.IsEmpty()); + print(" Count(): " + queue.Count()); + local item = queue.Peek(); + print(" Peek(): " + item); + print(" Count(): " + queue.Count()); + local item2 = queue.Pop(); + print(" Pop(): " + item2); + print(" Count(): " + queue.Count()); + print(" " + item + " == " + item2 + " : " + (item == item2)); + print(" Clear(): " + queue.Clear()); + print(" IsEmpty(): " + queue.IsEmpty()); + print(" Count(): " + queue.Count()); +} + function Regression::Start() { this.TestInit(); @@ -2049,6 +2124,20 @@ function Regression::Start() print(" PresidentName: " + c.GetNewName()); } break; + case AIEvent.ET_EXCLUSIVE_TRANSPORT_RIGHTS: { + local c = AIEventExclusiveTransportRights.Convert(e); + print(" EventName: ExclusiveTransportRights"); + print(" CompanyID: " + c.GetCompanyID()); + print(" TownID: " + c.GetTownID()); + } break; + + case AIEvent.ET_ROAD_RECONSTRUCTION: { + local c = AIEventRoadReconstruction.Convert(e); + print(" EventName: RoadReconstruction"); + print(" CompanyID: " + c.GetCompanyID()); + print(" TownID: " + c.GetTownID()); + } break; + default: print(" Unknown Event"); break; @@ -2057,12 +2146,18 @@ function Regression::Start() print(" IsEventWaiting: false"); this.Math(); + this.PriorityQueue(); /* Check Valuate() is actually limited, MUST BE THE LAST TEST. */ print("--Valuate() with excessive CPU usage--") local list = AIList(); list.AddItem(0, 0); local Infinite = function(id) { while(true); } + try { + list = AIIndustryList(Infinite); + } catch (e) { + print("constructor failed with: " + e); + } list.Valuate(Infinite); } diff --git a/regression/regression/require.nut b/regression/regression/require.nut index d8dc4baa7d..872a929a5f 100644 --- a/regression/regression/require.nut +++ b/regression/regression/require.nut @@ -1,2 +1,9 @@ print(" Required this file"); +const test_constant = 1; + +enum TestEnum { + value0, + value1, + value2 +}; diff --git a/regression/regression/result.txt b/regression/regression/result.txt index f00c9e3813..18afcb5f2f 100644 --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -8,6 +8,10 @@ IsValid(vehicle.plane_speed): true vehicle.plane_speed: 2 Required this file + TestEnum.value1: 1 + test_constant: 1 + TestEnum.value2: 2 + test_constant: 1 min(6, 3): 3 min(3, 6): 3 max(6, 3): 6 @@ -81,7 +85,7 @@ 20 30 40 - Ops: 8673 + Ops: 8649 --Std-- abs(-21): 21 @@ -571,6 +575,13 @@ 4006 => 12 []: 4000 => 50 + clone: + Clone ListDump: + 1005 => 1005 + 4000 => 50 + 4001 => 8002 + 4002 => 8004 + 4006 => 12 IsEmpty(): true 0 => 5 (true) ERROR: Next() is invalid as Begin() is never called @@ -580,6 +591,12 @@ ERROR: IsEnd() is invalid as Begin() is never called 2 => 6 (true) 3 => 6 (true) 9 => 0 (false) + Clone ListDump: + 1005 => 1005 + 4000 => 50 + 4001 => 8002 + 4002 => 8004 + 4006 => 12 --Company-- SetName(): true @@ -9423,13 +9440,22 @@ ERROR: IsEnd() is invalid as Begin() is never called 23 => 652 25 => 563 HasStatue(): false - GetRoadReworkDuration(): 0 - GetExclusiveRightsCompany(): -1 - GetExclusiveRightsDuration(): 0 IsActionAvailable(BUILD_STATUE): true PerformTownAction(BUILD_STATUE): true IsActionAvailable(BUILD_STATUE): false HasStatue(): true + GetRoadReworkDuration(): 0 + IsActionAvailable(ROAD_REBUILD): true + PerformTownAction(ROAD_REBUILD): true + IsActionAvailable(ROAD_REBUILD): true + GetRoadReworkDuration(): 6 + GetExclusiveRightsCompany(): -1 + GetExclusiveRightsDuration(): 0 + IsActionAvailable(BUY_RIGHTS): true + PerformTownAction(BUY_RIGHTS): true + IsActionAvailable(BUY_RIGHTS): false + GetExclusiveRightsCompany(): 1 + GetExclusiveRightsDuration(): 12 --Tunnel-- IsTunnelTile(): false @@ -9694,6 +9720,14 @@ ERROR: IsEnd() is invalid as Begin() is never called GetStopLocation(): 1 --VehicleList_Station-- + Count(): 1 + Location ListDump: + 20 => 23596 + foreach(): + 20 => 23596 + Count(): 0 + Location ListDump: + foreach(): Count(): 1 Location ListDump: 20 => 23596 @@ -9726,6 +9760,16 @@ ERROR: IsEnd() is invalid as Begin() is never called EventName: CompanyRenamed CompanyID: 1 CompanyName: Little Frutford Transport + GetNextEvent: instance + GetEventType: 28 + EventName: RoadReconstruction + CompanyID: 1 + TownID: 12 + GetNextEvent: instance + GetEventType: 27 + EventName: ExclusiveTransportRights + CompanyID: 1 + TownID: 12 IsEventWaiting: false --Math-- @@ -9760,21 +9804,38 @@ ERROR: IsEnd() is invalid as Begin() is never called -1 > 2147483647: false -2147483648 > 2147483647: false 13725 > -2147483648: true + +--PriorityQueue-- + IsEmpty(): true + Count(): 0 + --Insert-- + Insert(0, 0): true + Insert(1, 1): true + Insert(2, 2): true + Insert(3, 3): true + Insert(4, 4): true + Insert(5, 5): true + Insert(6, 6): true + Insert(7, 7): true + Insert(8, 8): true + Insert(9, 9): true + Exists(5): true + Insert(5, 5): true + IsEmpty(): false + Count(): 11 + Peek(): 0 + Count(): 11 + Pop(): 0 + Count(): 10 + 0 == 0 : true + Clear(): (null : 0x00000000) + IsEmpty(): true + Count(): 0 --Valuate() with excessive CPU usage-- +constructor failed with: excessive CPU usage in list filter function Your script made an error: excessive CPU usage in valuator function -*FUNCTION [unknown()] regression/main.nut line [2065] -*FUNCTION [Valuate()] NATIVE line [-1] -*FUNCTION [Start()] regression/main.nut line [2066] - -[id] 0 -[this] TABLE -[Infinite] CLOSURE -[list] INSTANCE -[this] INSTANCE -Your script made an error: excessive CPU usage in valuator function - -*FUNCTION [Start()] regression/main.nut line [2066] +*FUNCTION [Start()] regression/main.nut line [2161] [Infinite] CLOSURE [list] INSTANCE diff --git a/src/3rdparty/icu/scriptrun.h b/src/3rdparty/icu/scriptrun.h index 792d5c55c9..21d97645b0 100644 --- a/src/3rdparty/icu/scriptrun.h +++ b/src/3rdparty/icu/scriptrun.h @@ -19,6 +19,7 @@ #include #include #include +#include U_NAMESPACE_BEGIN diff --git a/src/3rdparty/md5/md5.cpp b/src/3rdparty/md5/md5.cpp index 0e58d989b6..d758f0e974 100644 --- a/src/3rdparty/md5/md5.cpp +++ b/src/3rdparty/md5/md5.cpp @@ -281,7 +281,7 @@ void Md5::Append(const void *data, const size_t nbytes) if (offset) { size_t copy = (offset + nbytes > 64 ? 64 - offset : nbytes); - memcpy(this->buf + offset, p, copy); + std::copy_n(p, copy, this->buf + offset); if (offset + copy < 64) return; @@ -294,7 +294,7 @@ void Md5::Append(const void *data, const size_t nbytes) for (; left >= 64; p += 64, left -= 64) this->Process(p); /* Process a final partial block. */ - if (left) memcpy(this->buf, p, left); + if (left) std::copy_n(p, left, this->buf); } void Md5::Finish(MD5Hash &digest) diff --git a/src/3rdparty/squirrel/include/CMakeLists.txt b/src/3rdparty/squirrel/include/CMakeLists.txt index 5237360d31..249e97f326 100644 --- a/src/3rdparty/squirrel/include/CMakeLists.txt +++ b/src/3rdparty/squirrel/include/CMakeLists.txt @@ -1,6 +1,5 @@ add_files( sqstdaux.h sqstdmath.h - sqstdstring.h squirrel.h ) diff --git a/src/3rdparty/squirrel/include/sqstdstring.h b/src/3rdparty/squirrel/include/sqstdstring.h deleted file mode 100644 index 65dae348b3..0000000000 --- a/src/3rdparty/squirrel/include/sqstdstring.h +++ /dev/null @@ -1,23 +0,0 @@ -/* see copyright notice in squirrel.h */ -#ifndef _SQSTD_STRING_H_ -#define _SQSTD_STRING_H_ - -typedef unsigned int SQRexBool; -typedef struct SQRex SQRex; - -typedef struct { - const SQChar *begin; - SQInteger len; -} SQRexMatch; - -SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error); -void sqstd_rex_free(SQRex *exp); -SQBool sqstd_rex_match(SQRex* exp,const SQChar* text); -SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end); -SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end); -SQInteger sqstd_rex_getsubexpcount(SQRex* exp); -SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp); - -SQRESULT sqstd_register_stringlib(HSQUIRRELVM v); - -#endif /*_SQSTD_STRING_H_*/ diff --git a/src/3rdparty/squirrel/include/squirrel.h b/src/3rdparty/squirrel/include/squirrel.h index bf0b6fec49..eb65c69c50 100644 --- a/src/3rdparty/squirrel/include/squirrel.h +++ b/src/3rdparty/squirrel/include/squirrel.h @@ -68,7 +68,6 @@ struct SQClass; struct SQInstance; struct SQDelegable; -typedef char SQChar; #define MAX_CHAR 0xFFFF #define SQUIRREL_VERSION "Squirrel 2.2.5 stable - With custom OpenTTD modifications" @@ -164,17 +163,17 @@ typedef struct tagSQObject }SQObject; typedef struct tagSQStackInfos{ - const SQChar* funcname; - const SQChar* source; - SQInteger line; + std::string_view funcname; + std::string_view source; + SQInteger line = -1; }SQStackInfos; typedef struct SQVM* HSQUIRRELVM; typedef SQObject HSQOBJECT; typedef SQInteger (*SQFUNCTION)(HSQUIRRELVM); typedef SQInteger (*SQRELEASEHOOK)(SQUserPointer,SQInteger size); -typedef void (*SQCOMPILERERROR)(HSQUIRRELVM,const SQChar * /*desc*/,const SQChar * /*source*/,SQInteger /*line*/,SQInteger /*column*/); -typedef void (*SQPRINTFUNCTION)(HSQUIRRELVM,const std::string &); +typedef void (*SQCOMPILERERROR)(HSQUIRRELVM,std::string_view /*desc*/,std::string_view /*source*/,SQInteger /*line*/,SQInteger /*column*/); +typedef void (*SQPRINTFUNCTION)(HSQUIRRELVM,std::string_view); typedef SQInteger (*SQWRITEFUNC)(SQUserPointer,SQUserPointer,SQInteger); typedef SQInteger (*SQREADFUNC)(SQUserPointer,SQUserPointer,SQInteger); @@ -182,16 +181,16 @@ typedef SQInteger (*SQREADFUNC)(SQUserPointer,SQUserPointer,SQInteger); typedef char32_t (*SQLEXREADFUNC)(SQUserPointer); typedef struct tagSQRegFunction{ - const SQChar *name; + std::string_view name; SQFUNCTION f; SQInteger nparamscheck; - const SQChar *typemask; + std::optional typemask; }SQRegFunction; typedef struct tagSQFunctionInfo { SQUserPointer funcid; - const SQChar *name; - const SQChar *source; + std::string_view name; + std::string_view source; }SQFunctionInfo; @@ -213,8 +212,8 @@ SQInteger sq_getvmstate(HSQUIRRELVM v); void sq_decreaseops(HSQUIRRELVM v, int amount); /*compiler*/ -SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror); -SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror); +SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,std::string_view sourcename,SQBool raiseerror); +SQRESULT sq_compilebuffer(HSQUIRRELVM v,std::string_view buffer,std::string_view sourcename,SQBool raiseerror); void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable); void sq_notifyallexceptions(HSQUIRRELVM v, SQBool enable); void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f); @@ -235,10 +234,9 @@ SQUserPointer sq_newuserdata(HSQUIRRELVM v,SQUnsignedInteger size); void sq_newtable(HSQUIRRELVM v); void sq_newarray(HSQUIRRELVM v,SQInteger size); void sq_newclosure(HSQUIRRELVM v,SQFUNCTION func,SQUnsignedInteger nfreevars); -SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask); +SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,std::optional typemask); SQRESULT sq_bindenv(HSQUIRRELVM v,SQInteger idx); -void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len); -inline void sq_pushstring(HSQUIRRELVM v, const std::string &str, SQInteger len = -1) { sq_pushstring(v, str.data(), len == -1 ? str.size() : len); } +void sq_pushstring(HSQUIRRELVM v, std::string_view str); void sq_pushfloat(HSQUIRRELVM v,SQFloat f); void sq_pushinteger(HSQUIRRELVM v,SQInteger n); void sq_pushbool(HSQUIRRELVM v,SQBool b); @@ -250,7 +248,7 @@ SQRESULT sq_getbase(HSQUIRRELVM v,SQInteger idx); SQBool sq_instanceof(HSQUIRRELVM v); void sq_tostring(HSQUIRRELVM v,SQInteger idx); void sq_tobool(HSQUIRRELVM v, SQInteger idx, SQBool *b); -SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c); +SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,std::string_view &str); SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i); SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f); SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b); @@ -260,10 +258,10 @@ SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPoint SQRESULT sq_settypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer typetag); SQRESULT sq_gettypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer *typetag); void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook); -SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize); +std::span sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize); SQRESULT sq_getfunctioninfo(HSQUIRRELVM v,SQInteger idx,SQFunctionInfo *fi); SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger *nparams,SQUnsignedInteger *nfreevars); -SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name); +SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,std::string_view name); SQRESULT sq_setinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer p); SQRESULT sq_getinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p,SQUserPointer typetag); SQRESULT sq_setclassudsize(HSQUIRRELVM v, SQInteger idx, SQInteger udsize); @@ -305,10 +303,9 @@ SQRESULT sq_clear(HSQUIRRELVM v,SQInteger idx); /*calls*/ SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror, int suspend = -1); 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, SQInteger len = -1); -inline SQRESULT sq_throwerror(HSQUIRRELVM v, const std::string_view err) { return sq_throwerror(v, err.data(), err.size()); } +std::optional sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx); +std::optional sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval); +SQRESULT sq_throwerror(HSQUIRRELVM v,std::string_view err); void sq_reseterror(HSQUIRRELVM v); void sq_getlasterror(HSQUIRRELVM v); @@ -318,7 +315,7 @@ void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj); void sq_addref(HSQUIRRELVM v,HSQOBJECT *po); SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po); void sq_resetobject(HSQOBJECT *po); -const SQChar *sq_objtostring(HSQOBJECT *o); +std::optional sq_objtostring(HSQOBJECT *o); SQBool sq_objtobool(HSQOBJECT *o); SQInteger sq_objtointeger(HSQOBJECT *o); SQFloat sq_objtofloat(HSQOBJECT *o); @@ -363,7 +360,7 @@ void sq_setdebughook(HSQUIRRELVM v); /* Limit the total number of ops that can be consumed by an operation */ struct SQOpsLimiter { - SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label); + SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, std::string_view label); ~SQOpsLimiter(); private: HSQUIRRELVM _v; diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp index 1bdee97e69..f5763589c2 100644 --- a/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp +++ b/src/3rdparty/squirrel/sqstdlib/sqstdaux.cpp @@ -16,28 +16,22 @@ void sqstd_printcallstack(HSQUIRRELVM v) SQInteger i; SQBool b; SQFloat f; - const SQChar *s; SQInteger level=1; //1 is to skip this function that is level 0 - const SQChar *name=nullptr; SQInteger seq=0; pf(v,"\nCALLSTACK\n"); while(SQ_SUCCEEDED(sq_stackinfos(v,level,&si))) { - const SQChar *fn="unknown"; - const SQChar *src="unknown"; - if(si.funcname)fn=si.funcname; - if(si.source) { + std::string_view fn="unknown"; + std::string_view src="unknown"; + if(!si.funcname.empty())fn=si.funcname; + if(!si.source.empty()) { /* We don't want to bother users with absolute paths to all AI files. * Since the path only reaches NoAI code in a formatted string we have * to strip it here. Let's hope nobody installs openttd in a subdirectory * of a directory named /ai/. */ - src = strstr(si.source, "\\ai\\"); - if (!src) src = strstr(si.source, "/ai/"); - if (src) { - src += 4; - } else { - src = si.source; - } + auto p = si.source.find("\\ai\\"); + if (p == std::string_view::npos) p = si.source.find("/ai/"); + src = (p == std::string_view::npos) ? si.source : si.source.substr(p + 4); } pf(v,fmt::format("*FUNCTION [{}()] {} line [{}]\n",fn,src,si.line)); level++; @@ -47,8 +41,9 @@ void sqstd_printcallstack(HSQUIRRELVM v) for(level=0;level<10;level++){ seq=0; - while((name = sq_getlocal(v,level,seq))) - { + std::optional opt; + while ((opt = sq_getlocal(v,level,seq)).has_value()) { + std::string_view name = *opt; seq++; switch(sq_gettype(v,-1)) { @@ -66,10 +61,12 @@ void sqstd_printcallstack(HSQUIRRELVM v) case OT_USERPOINTER: pf(v,fmt::format("[{}] USERPOINTER\n",name)); break; - case OT_STRING: - sq_getstring(v,-1,&s); - pf(v,fmt::format("[{}] \"{}\"\n",name,s)); + case OT_STRING: { + std::string_view view; + sq_getstring(v,-1,view); + pf(v,fmt::format("[{}] \"{}\"\n",name,view)); break; + } case OT_TABLE: pf(v,fmt::format("[{}] TABLE\n",name)); break; @@ -117,10 +114,10 @@ static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v) { SQPRINTFUNCTION pf = sq_getprintfunc(v); if(pf) { - const SQChar *sErr = nullptr; + std::string_view error; if(sq_gettop(v)>=1) { - if(SQ_SUCCEEDED(sq_getstring(v,2,&sErr))) { - pf(v,fmt::format("\nAN ERROR HAS OCCURRED [{}]\n",sErr)); + if(SQ_SUCCEEDED(sq_getstring(v,2,error))) { + pf(v,fmt::format("\nAN ERROR HAS OCCURRED [{}]\n",error)); } else{ pf(v,"\nAN ERROR HAS OCCURRED [unknown]\n"); @@ -131,7 +128,7 @@ static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v) return 0; } -void _sqstd_compiler_error(HSQUIRRELVM v,const SQChar *sErr,const SQChar *sSource,SQInteger line,SQInteger column) +void _sqstd_compiler_error(HSQUIRRELVM v,std::string_view sErr,std::string_view sSource,SQInteger line,SQInteger column) { SQPRINTFUNCTION pf = sq_getprintfunc(v); if(pf) { diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp index 7f07271f20..5345f9b21b 100644 --- a/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp +++ b/src/3rdparty/squirrel/sqstdlib/sqstdmath.cpp @@ -67,7 +67,7 @@ SINGLE_ARG_FUNC(ceil, 1) SINGLE_ARG_FUNC(exp, 100) #define _DECL_FUNC(name,nparams,tycheck) {#name,math_##name,nparams,tycheck} -static SQRegFunction mathlib_funcs[] = { +static const std::initializer_list mathlib_funcs = { _DECL_FUNC(sqrt,2,".n"), _DECL_FUNC(sin,2,".n"), _DECL_FUNC(cos,2,".n"), @@ -84,11 +84,10 @@ static SQRegFunction mathlib_funcs[] = { _DECL_FUNC(exp,2,".n"), #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS _DECL_FUNC(srand,2,".n"), - _DECL_FUNC(rand,1,nullptr), + _DECL_FUNC(rand,1,std::nullopt), #endif /* EXPORT_DEFAULT_SQUIRREL_FUNCTIONS */ _DECL_FUNC(fabs,2,".n"), _DECL_FUNC(abs,2,".n"), - {nullptr,nullptr,0,nullptr}, }; #ifndef M_PI @@ -97,21 +96,19 @@ static SQRegFunction mathlib_funcs[] = { SQRESULT sqstd_register_mathlib(HSQUIRRELVM v) { - SQInteger i=0; - while(mathlib_funcs[i].name!=nullptr) { - sq_pushstring(v,mathlib_funcs[i].name,-1); - sq_newclosure(v,mathlib_funcs[i].f,0); - sq_setparamscheck(v,mathlib_funcs[i].nparamscheck,mathlib_funcs[i].typemask); - sq_setnativeclosurename(v,-1,mathlib_funcs[i].name); + for(auto &func : mathlib_funcs) { + sq_pushstring(v,func.name); + sq_newclosure(v,func.f,0); + sq_setparamscheck(v,func.nparamscheck,func.typemask); + sq_setnativeclosurename(v,-1,func.name); sq_createslot(v,-3); - i++; } #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS - sq_pushstring(v,"RAND_MAX",-1); + sq_pushstring(v,"RAND_MAX"); sq_pushinteger(v,RAND_MAX); sq_createslot(v,-3); #endif /* EXPORT_DEFAULT_SQUIRREL_FUNCTIONS */ - sq_pushstring(v,"PI",-1); + sq_pushstring(v,"PI"); sq_pushfloat(v,(SQFloat)M_PI); sq_createslot(v,-3); return SQ_OK; diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdrex.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdrex.cpp deleted file mode 100644 index 16e87b3d24..0000000000 --- a/src/3rdparty/squirrel/sqstdlib/sqstdrex.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/* see copyright notice in squirrel.h */ -#include -#include -#include "sqstdstring.h" - -#ifdef _UNICODE -#define scisprint iswprint -#else -#define scisprint isprint -#endif - -#ifdef _DEBUG - -static const SQChar *g_nnames[] = -{ - "NONE","OP_GREEDY", "OP_OR", - "OP_EXPR","OP_NOCAPEXPR","OP_DOT", "OP_CLASS", - "OP_CCLASS","OP_NCLASS","OP_RANGE","OP_CHAR", - "OP_EOL","OP_BOL","OP_WB" -}; - -#endif - -#define OP_GREEDY (MAX_CHAR+1) // * + ? {n} -#define OP_OR (MAX_CHAR+2) -#define OP_EXPR (MAX_CHAR+3) //parentesis () -#define OP_NOCAPEXPR (MAX_CHAR+4) //parentesis (?:) -#define OP_DOT (MAX_CHAR+5) -#define OP_CLASS (MAX_CHAR+6) -#define OP_CCLASS (MAX_CHAR+7) -#define OP_NCLASS (MAX_CHAR+8) //negates class the [^ -#define OP_RANGE (MAX_CHAR+9) -#define OP_CHAR (MAX_CHAR+10) -#define OP_EOL (MAX_CHAR+11) -#define OP_BOL (MAX_CHAR+12) -#define OP_WB (MAX_CHAR+13) - -#define SQREX_SYMBOL_ANY_CHAR ('.') -#define SQREX_SYMBOL_GREEDY_ONE_OR_MORE ('+') -#define SQREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*') -#define SQREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?') -#define SQREX_SYMBOL_BRANCH ('|') -#define SQREX_SYMBOL_END_OF_STRING ('$') -#define SQREX_SYMBOL_BEGINNING_OF_STRING ('^') -#define SQREX_SYMBOL_ESCAPE_CHAR ('\\') - - -typedef int SQRexNodeType; - -typedef struct tagSQRexNode{ - SQRexNodeType type; - SQInteger left; - SQInteger right; - SQInteger next; -}SQRexNode; - -struct SQRex{ - const SQChar *_eol; - const SQChar *_bol; - const SQChar *_p; - SQInteger _first; - SQInteger _op; - SQRexNode *_nodes; - SQInteger _nallocated; - SQInteger _nsize; - SQInteger _nsubexpr; - SQRexMatch *_matches; - SQInteger _currsubexp; - const SQChar **_error; -}; - -static SQInteger sqstd_rex_list(SQRex *exp); - -static SQInteger sqstd_rex_newnode(SQRex *exp, SQRexNodeType type) -{ - SQRexNode n; - n.type = type; - n.next = n.right = n.left = -1; - if(type == OP_EXPR) - n.right = exp->_nsubexpr++; - if(exp->_nallocated < (exp->_nsize + 1)) { - SQInteger oldsize = exp->_nallocated; - exp->_nallocated *= 2; - exp->_nodes = (SQRexNode *)sq_realloc(exp->_nodes, oldsize * sizeof(SQRexNode) ,exp->_nallocated * sizeof(SQRexNode)); - } - exp->_nodes[exp->_nsize++] = n; - SQInteger newid = exp->_nsize - 1; - return (SQInteger)newid; -} - -static void sqstd_rex_error(SQRex *exp,const SQChar *error) -{ - if(exp->_error) *exp->_error = error; - throw std::exception(); -} - -static void sqstd_rex_expect(SQRex *exp, SQChar n){ - if((*exp->_p) != n) - sqstd_rex_error(exp, "expected paren"); - exp->_p++; -} - -static SQChar sqstd_rex_escapechar(SQRex *exp) -{ - if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR){ - exp->_p++; - switch(*exp->_p) { - case 'v': exp->_p++; return '\v'; - case 'n': exp->_p++; return '\n'; - case 't': exp->_p++; return '\t'; - case 'r': exp->_p++; return '\r'; - case 'f': exp->_p++; return '\f'; - default: return (*exp->_p++); - } - } else if(!scisprint(*exp->_p)) sqstd_rex_error(exp,"letter expected"); - return (*exp->_p++); -} - -static SQInteger sqstd_rex_charclass(SQRex *exp,SQInteger classid) -{ - SQInteger n = sqstd_rex_newnode(exp,OP_CCLASS); - exp->_nodes[n].left = classid; - return n; -} - -static SQInteger sqstd_rex_charnode(SQRex *exp,SQBool isclass) -{ - SQChar t; - if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR) { - exp->_p++; - switch(*exp->_p) { - case 'n': exp->_p++; return sqstd_rex_newnode(exp,'\n'); - case 't': exp->_p++; return sqstd_rex_newnode(exp,'\t'); - case 'r': exp->_p++; return sqstd_rex_newnode(exp,'\r'); - case 'f': exp->_p++; return sqstd_rex_newnode(exp,'\f'); - case 'v': exp->_p++; return sqstd_rex_newnode(exp,'\v'); - case 'a': case 'A': case 'w': case 'W': case 's': case 'S': - case 'd': case 'D': case 'x': case 'X': case 'c': case 'C': - case 'p': case 'P': case 'l': case 'u': - { - t = *exp->_p; exp->_p++; - return sqstd_rex_charclass(exp,t); - } - case 'b': - case 'B': - if(!isclass) { - SQInteger node = sqstd_rex_newnode(exp,OP_WB); - exp->_nodes[node].left = *exp->_p; - exp->_p++; - return node; - } //else default - default: - t = *exp->_p; exp->_p++; - return sqstd_rex_newnode(exp,t); - } - } - else if(!scisprint(*exp->_p)) { - - sqstd_rex_error(exp,"letter expected"); - } - t = *exp->_p; exp->_p++; - return sqstd_rex_newnode(exp,t); -} -static SQInteger sqstd_rex_class(SQRex *exp) -{ - SQInteger ret = -1; - SQInteger first = -1,chain; - if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING){ - ret = sqstd_rex_newnode(exp,OP_NCLASS); - exp->_p++; - }else ret = sqstd_rex_newnode(exp,OP_CLASS); - - if(*exp->_p == ']') sqstd_rex_error(exp,"empty class"); - chain = ret; - while(*exp->_p != ']' && exp->_p != exp->_eol) { - if(*exp->_p == '-' && first != -1){ - SQInteger r; - if(*exp->_p++ == ']') sqstd_rex_error(exp,"unfinished range"); - r = sqstd_rex_newnode(exp,OP_RANGE); - if(exp->_nodes[first].type>*exp->_p) sqstd_rex_error(exp,"invalid range"); - if(exp->_nodes[first].type == OP_CCLASS) sqstd_rex_error(exp,"cannot use character classes in ranges"); - exp->_nodes[r].left = exp->_nodes[first].type; - SQInteger t = sqstd_rex_escapechar(exp); - exp->_nodes[r].right = t; - exp->_nodes[chain].next = r; - chain = r; - first = -1; - } - else{ - if(first!=-1){ - SQInteger c = first; - exp->_nodes[chain].next = c; - chain = c; - first = sqstd_rex_charnode(exp,SQTrue); - } - else{ - first = sqstd_rex_charnode(exp,SQTrue); - } - } - } - if(first!=-1){ - SQInteger c = first; - exp->_nodes[chain].next = c; - chain = c; - first = -1; - } - /* hack? */ - exp->_nodes[ret].left = exp->_nodes[ret].next; - exp->_nodes[ret].next = -1; - return ret; -} - -static SQInteger sqstd_rex_parsenumber(SQRex *exp) -{ - SQInteger ret = *exp->_p-'0'; - SQInteger positions = 10; - exp->_p++; - while(isdigit(*exp->_p)) { - ret = ret*10+(*exp->_p++-'0'); - if(positions==1000000000) sqstd_rex_error(exp,"overflow in numeric constant"); - positions *= 10; - }; - return ret; -} - -static SQInteger sqstd_rex_element(SQRex *exp) -{ - SQInteger ret = -1; - switch(*exp->_p) - { - case '(': { - SQInteger expr; - exp->_p++; - - - if(*exp->_p =='?') { - exp->_p++; - sqstd_rex_expect(exp,':'); - expr = sqstd_rex_newnode(exp,OP_NOCAPEXPR); - } - else - expr = sqstd_rex_newnode(exp,OP_EXPR); - SQInteger newn = sqstd_rex_list(exp); - exp->_nodes[expr].left = newn; - ret = expr; - sqstd_rex_expect(exp,')'); - } - break; - case '[': - exp->_p++; - ret = sqstd_rex_class(exp); - sqstd_rex_expect(exp,']'); - break; - case SQREX_SYMBOL_END_OF_STRING: exp->_p++; ret = sqstd_rex_newnode(exp,OP_EOL);break; - case SQREX_SYMBOL_ANY_CHAR: exp->_p++; ret = sqstd_rex_newnode(exp,OP_DOT);break; - default: - ret = sqstd_rex_charnode(exp,SQFalse); - break; - } - - - SQInteger op; - SQBool isgreedy = SQFalse; - unsigned short p0 = 0, p1 = 0; - switch(*exp->_p){ - case SQREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break; - case SQREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break; - case SQREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = SQTrue; break; - case '{': - exp->_p++; - if(!isdigit(*exp->_p)) sqstd_rex_error(exp,"number expected"); - p0 = (unsigned short)sqstd_rex_parsenumber(exp); - /*******************************/ - switch(*exp->_p) { - case '}': - p1 = p0; exp->_p++; - break; - case ',': - exp->_p++; - p1 = 0xFFFF; - if(isdigit(*exp->_p)){ - p1 = (unsigned short)sqstd_rex_parsenumber(exp); - } - sqstd_rex_expect(exp,'}'); - break; - default: - sqstd_rex_error(exp,", or } expected"); - } - /*******************************/ - isgreedy = SQTrue; - break; - - } - if(isgreedy) { - SQInteger nnode = sqstd_rex_newnode(exp,OP_GREEDY); - op = OP_GREEDY; - exp->_nodes[nnode].left = ret; - exp->_nodes[nnode].right = ((p0)<<16)|p1; - ret = nnode; - } - - if((*exp->_p != SQREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != SQREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != SQREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) { - SQInteger nnode = sqstd_rex_element(exp); - exp->_nodes[ret].next = nnode; - } - - return ret; -} - -static SQInteger sqstd_rex_list(SQRex *exp) -{ - SQInteger ret=-1,e; - if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING) { - exp->_p++; - ret = sqstd_rex_newnode(exp,OP_BOL); - } - e = sqstd_rex_element(exp); - if(ret != -1) { - exp->_nodes[ret].next = e; - } - else ret = e; - - if(*exp->_p == SQREX_SYMBOL_BRANCH) { - SQInteger temp,tright; - exp->_p++; - temp = sqstd_rex_newnode(exp,OP_OR); - exp->_nodes[temp].left = ret; - tright = sqstd_rex_list(exp); - exp->_nodes[temp].right = tright; - ret = temp; - } - return ret; -} - -static SQBool sqstd_rex_matchcclass(SQInteger cclass,SQChar c) -{ - switch(cclass) { - case 'a': return isalpha(c)?SQTrue:SQFalse; - case 'A': return !isalpha(c)?SQTrue:SQFalse; - case 'w': return (isalnum(c) || c == '_')?SQTrue:SQFalse; - case 'W': return (!isalnum(c) && c != '_')?SQTrue:SQFalse; - case 's': return isspace(c)?SQTrue:SQFalse; - case 'S': return !isspace(c)?SQTrue:SQFalse; - case 'd': return isdigit(c)?SQTrue:SQFalse; - case 'D': return !isdigit(c)?SQTrue:SQFalse; - case 'x': return isxdigit(c)?SQTrue:SQFalse; - case 'X': return !isxdigit(c)?SQTrue:SQFalse; - case 'c': return iscntrl(c)?SQTrue:SQFalse; - case 'C': return !iscntrl(c)?SQTrue:SQFalse; - case 'p': return ispunct(c)?SQTrue:SQFalse; - case 'P': return !ispunct(c)?SQTrue:SQFalse; - case 'l': return islower(c)?SQTrue:SQFalse; - case 'u': return isupper(c)?SQTrue:SQFalse; - } - return SQFalse; /*cannot happen*/ -} - -static SQBool sqstd_rex_matchclass(SQRex* exp,SQRexNode *node,SQInteger c) -{ - do { - switch(node->type) { - case OP_RANGE: - if(c >= node->left && c <= node->right) return SQTrue; - break; - case OP_CCLASS: - if(sqstd_rex_matchcclass(node->left,c)) return SQTrue; - break; - default: - if(c == node->type)return SQTrue; - } - } while((node->next != -1) && (node = &exp->_nodes[node->next])); - return SQFalse; -} - -static const SQChar *sqstd_rex_matchnode(SQRex* exp,SQRexNode *node,const SQChar *str,SQRexNode *next) -{ - - SQRexNodeType type = node->type; - switch(type) { - case OP_GREEDY: { - //SQRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : nullptr; - SQRexNode *greedystop = nullptr; - SQInteger p0 = (node->right >> 16)&0x0000FFFF, p1 = node->right&0x0000FFFF, nmaches = 0; - const SQChar *s=str, *good = str; - - if(node->next != -1) { - greedystop = &exp->_nodes[node->next]; - } - else { - greedystop = next; - } - - while((nmaches == 0xFFFF || nmaches < p1)) { - - const SQChar *stop; - if(!(s = sqstd_rex_matchnode(exp,&exp->_nodes[node->left],s,greedystop))) - break; - nmaches++; - good=s; - if(greedystop) { - //checks that 0 matches satisfy the expression(if so skips) - //if not would always stop(for instance if is a '?') - if(greedystop->type != OP_GREEDY || - (greedystop->type == OP_GREEDY && ((greedystop->right >> 16)&0x0000FFFF) != 0)) - { - SQRexNode *gnext = nullptr; - if(greedystop->next != -1) { - gnext = &exp->_nodes[greedystop->next]; - }else if(next && next->next != -1){ - gnext = &exp->_nodes[next->next]; - } - stop = sqstd_rex_matchnode(exp,greedystop,s,gnext); - if(stop) { - //if satisfied stop it - if(p0 == p1 && p0 == nmaches) break; - else if(nmaches >= p0 && p1 == 0xFFFF) break; - else if(nmaches >= p0 && nmaches <= p1) break; - } - } - } - - if(s >= exp->_eol) - break; - } - if(p0 == p1 && p0 == nmaches) return good; - else if(nmaches >= p0 && p1 == 0xFFFF) return good; - else if(nmaches >= p0 && nmaches <= p1) return good; - return nullptr; - } - case OP_OR: { - const SQChar *asd = str; - SQRexNode *temp=&exp->_nodes[node->left]; - while( (asd = sqstd_rex_matchnode(exp,temp,asd,nullptr)) ) { - if(temp->next != -1) - temp = &exp->_nodes[temp->next]; - else - return asd; - } - asd = str; - temp = &exp->_nodes[node->right]; - while( (asd = sqstd_rex_matchnode(exp,temp,asd,nullptr)) ) { - if(temp->next != -1) - temp = &exp->_nodes[temp->next]; - else - return asd; - } - return nullptr; - break; - } - case OP_EXPR: - case OP_NOCAPEXPR:{ - SQRexNode *n = &exp->_nodes[node->left]; - const SQChar *cur = str; - SQInteger capture = -1; - if(node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) { - capture = exp->_currsubexp; - exp->_matches[capture].begin = cur; - exp->_currsubexp++; - } - - do { - SQRexNode *subnext = nullptr; - if(n->next != -1) { - subnext = &exp->_nodes[n->next]; - }else { - subnext = next; - } - if(!(cur = sqstd_rex_matchnode(exp,n,cur,subnext))) { - if(capture != -1){ - exp->_matches[capture].begin = 0; - exp->_matches[capture].len = 0; - } - return nullptr; - } - } while((n->next != -1) && (n = &exp->_nodes[n->next])); - - if(capture != -1) - exp->_matches[capture].len = cur - exp->_matches[capture].begin; - return cur; - } - case OP_WB: - if((str == exp->_bol && !isspace(*str)) - || (str == exp->_eol && !isspace(*(str-1))) - || (!isspace(*str) && isspace(*(str+1))) - || (isspace(*str) && !isspace(*(str+1))) ) { - return (node->left == 'b')?str:nullptr; - } - return (node->left == 'b')?nullptr:str; - case OP_BOL: - if(str == exp->_bol) return str; - return nullptr; - case OP_EOL: - if(str == exp->_eol) return str; - return nullptr; - case OP_DOT:{ - *str++; - } - return str; - case OP_NCLASS: - case OP_CLASS: - if(sqstd_rex_matchclass(exp,&exp->_nodes[node->left],*str)?(type == OP_CLASS?SQTrue:SQFalse):(type == OP_NCLASS?SQTrue:SQFalse)) { - *str++; - return str; - } - return nullptr; - case OP_CCLASS: - if(sqstd_rex_matchcclass(node->left,*str)) { - *str++; - return str; - } - return nullptr; - default: /* char */ - if(*str != (SQChar)node->type) return nullptr; - *str++; - return str; - } - return nullptr; -} - -/* public api */ -SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error) -{ - SQRex *exp = (SQRex *)sq_malloc(sizeof(SQRex)); - exp->_eol = exp->_bol = nullptr; - exp->_p = pattern; - exp->_nallocated = (SQInteger)strlen(pattern) * sizeof(SQChar); - exp->_nodes = (SQRexNode *)sq_malloc(exp->_nallocated * sizeof(SQRexNode)); - exp->_nsize = 0; - exp->_matches = 0; - exp->_nsubexpr = 0; - exp->_first = sqstd_rex_newnode(exp,OP_EXPR); - exp->_error = error; - try { - SQInteger res = sqstd_rex_list(exp); - exp->_nodes[exp->_first].left = res; - if(*exp->_p!='\0') - sqstd_rex_error(exp,"unexpected character"); -#ifdef _DEBUG - { - SQInteger nsize,i; - SQRexNode *t; - nsize = exp->_nsize; - t = &exp->_nodes[0]; - printf("\n"); - /* XXX -- The (int) casts are needed to silent warnings on 64bit systems (SQInteger is 64bit, %d assumes 32bit, (int) is 32bit) */ - for(i = 0;i < nsize; i++) { - if(exp->_nodes[i].type>MAX_CHAR) - printf("[%02d] %10s ",(int)i,g_nnames[exp->_nodes[i].type-MAX_CHAR]); - else - printf("[%02d] %10c ",(int)i,exp->_nodes[i].type); - printf("left %02d right %02d next %02d\n",(int)exp->_nodes[i].left,(int)exp->_nodes[i].right,(int)exp->_nodes[i].next); - } - printf("\n"); - } -#endif - exp->_matches = (SQRexMatch *) sq_malloc(exp->_nsubexpr * sizeof(SQRexMatch)); - memset(exp->_matches,0,exp->_nsubexpr * sizeof(SQRexMatch)); - } - catch (...) { - sqstd_rex_free(exp); - return nullptr; - } - return exp; -} - -void sqstd_rex_free(SQRex *exp) -{ - if(exp) { - if(exp->_nodes) sq_free(exp->_nodes,exp->_nallocated * sizeof(SQRexNode)); - if(exp->_matches) sq_free(exp->_matches,exp->_nsubexpr * sizeof(SQRexMatch)); - sq_free(exp,sizeof(SQRex)); - } -} - -SQBool sqstd_rex_match(SQRex* exp,const SQChar* text) -{ - const SQChar* res = nullptr; - exp->_bol = text; - exp->_eol = text + strlen(text); - exp->_currsubexp = 0; - res = sqstd_rex_matchnode(exp,exp->_nodes,text,nullptr); - if(res == nullptr || res != exp->_eol) - return SQFalse; - return SQTrue; -} - -SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end) -{ - const SQChar *cur = nullptr; - SQInteger node = exp->_first; - if(text_begin >= text_end) return SQFalse; - exp->_bol = text_begin; - exp->_eol = text_end; - do { - cur = text_begin; - while(node != -1) { - exp->_currsubexp = 0; - cur = sqstd_rex_matchnode(exp,&exp->_nodes[node],cur,nullptr); - if(!cur) - break; - node = exp->_nodes[node].next; - } - *text_begin++; - } while(cur == nullptr && text_begin != text_end); - - if(cur == nullptr) - return SQFalse; - - --text_begin; - - if(out_begin) *out_begin = text_begin; - if(out_end) *out_end = cur; - return SQTrue; -} - -SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end) -{ - return sqstd_rex_searchrange(exp,text,text + strlen(text),out_begin,out_end); -} - -SQInteger sqstd_rex_getsubexpcount(SQRex* exp) -{ - return exp->_nsubexpr; -} - -SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp) -{ - if( n<0 || n >= exp->_nsubexpr) return SQFalse; - *subexp = exp->_matches[n]; - return SQTrue; -} - diff --git a/src/3rdparty/squirrel/sqstdlib/sqstdstring.cpp b/src/3rdparty/squirrel/sqstdlib/sqstdstring.cpp deleted file mode 100644 index f042ac7df8..0000000000 --- a/src/3rdparty/squirrel/sqstdlib/sqstdstring.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/* see copyright notice in squirrel.h */ -#include -#include - -#define scstrchr strchr -#define scatoi atoi -#define scstrtok strtok -#define MAX_FORMAT_LEN 20 -#define MAX_WFORMAT_LEN 3 -#define ADDITIONAL_FORMAT_SPACE (100*sizeof(SQChar)) - -static SQInteger validate_format(HSQUIRRELVM v, SQChar *fmt, const SQChar *src, SQInteger n,SQInteger &width) -{ - SQChar swidth[MAX_WFORMAT_LEN]; - SQInteger wc = 0; - SQInteger start = n; - fmt[0] = '%'; - while (scstrchr("-+ #0", src[n])) n++; - while (isdigit(src[n])) { - swidth[wc] = src[n]; - n++; - wc++; - if(wc>=MAX_WFORMAT_LEN) - return sq_throwerror(v,"width format too long"); - } - swidth[wc] = '\0'; - if(wc > 0) { - width = atoi(swidth); - } - else - width = 0; - if (src[n] == '.') { - n++; - - wc = 0; - while (isdigit(src[n])) { - swidth[wc] = src[n]; - n++; - wc++; - if(wc>=MAX_WFORMAT_LEN) - return sq_throwerror(v,"precision format too long"); - } - swidth[wc] = '\0'; - if(wc > 0) { - width += atoi(swidth); - } - } - if (n-start > MAX_FORMAT_LEN ) - return sq_throwerror(v,"format too long"); - memcpy(&fmt[1],&src[start],((n-start)+1)*sizeof(SQChar)); - fmt[(n-start)+2] = '\0'; - return n; -} - -static void __strip_l(const SQChar *str,const SQChar **start) -{ - const SQChar *t = str; - while(((*t) != '\0') && isspace(*t)){ t++; } - *start = t; -} - -static void __strip_r(const SQChar *str,SQInteger len,const SQChar **end) -{ - if(len == 0) { - *end = str; - return; - } - const SQChar *t = &str[len-1]; - while(t != str && isspace(*t)) { t--; } - *end = t+1; -} - -static SQInteger _string_strip(HSQUIRRELVM v) -{ - const SQChar *str,*start,*end; - sq_getstring(v,2,&str); - SQInteger len = sq_getsize(v,2); - __strip_l(str,&start); - __strip_r(str,len,&end); - sq_pushstring(v,start,end - start); - return 1; -} - -static SQInteger _string_lstrip(HSQUIRRELVM v) -{ - const SQChar *str,*start; - sq_getstring(v,2,&str); - __strip_l(str,&start); - sq_pushstring(v,start,-1); - return 1; -} - -static SQInteger _string_rstrip(HSQUIRRELVM v) -{ - const SQChar *str,*end; - sq_getstring(v,2,&str); - SQInteger len = sq_getsize(v,2); - __strip_r(str,len,&end); - sq_pushstring(v,str,end - str); - return 1; -} - -static SQInteger _string_split(HSQUIRRELVM v) -{ - const SQChar *str,*seps; - SQChar *stemp,*tok; - sq_getstring(v,2,&str); - sq_getstring(v,3,&seps); - if(sq_getsize(v,3) == 0) return sq_throwerror(v,"empty separators string"); - SQInteger memsize = (sq_getsize(v,2)+1)*sizeof(SQChar); - stemp = sq_getscratchpad(v,memsize); - memcpy(stemp,str,memsize); - tok = scstrtok(stemp,seps); - sq_newarray(v,0); - while( tok != nullptr ) { - sq_pushstring(v,tok,-1); - sq_arrayappend(v,-2); - tok = scstrtok( nullptr, seps ); - } - return 1; -} - -#define SETUP_REX(v) \ - SQRex *self = nullptr; \ - sq_getinstanceup(v,1,(SQUserPointer *)&self,0); - -static SQInteger _rexobj_releasehook(SQUserPointer p, SQInteger size) -{ - SQRex *self = ((SQRex *)p); - sqstd_rex_free(self); - return 1; -} - -static SQInteger _regexp_match(HSQUIRRELVM v) -{ - SETUP_REX(v); - const SQChar *str; - sq_getstring(v,2,&str); - if(sqstd_rex_match(self,str) == SQTrue) - { - sq_pushbool(v,SQTrue); - return 1; - } - sq_pushbool(v,SQFalse); - return 1; -} - -static void _addrexmatch(HSQUIRRELVM v,const SQChar *str,const SQChar *begin,const SQChar *end) -{ - sq_newtable(v); - sq_pushstring(v,"begin",-1); - sq_pushinteger(v,begin - str); - sq_rawset(v,-3); - sq_pushstring(v,"end",-1); - sq_pushinteger(v,end - str); - sq_rawset(v,-3); -} - -static SQInteger _regexp_search(HSQUIRRELVM v) -{ - SETUP_REX(v); - const SQChar *str,*begin,*end; - SQInteger start = 0; - sq_getstring(v,2,&str); - if(sq_gettop(v) > 2) sq_getinteger(v,3,&start); - if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) { - _addrexmatch(v,str,begin,end); - return 1; - } - return 0; -} - -static SQInteger _regexp_capture(HSQUIRRELVM v) -{ - SETUP_REX(v); - const SQChar *str,*begin,*end; - SQInteger start = 0; - sq_getstring(v,2,&str); - if(sq_gettop(v) > 2) sq_getinteger(v,3,&start); - if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) { - SQInteger n = sqstd_rex_getsubexpcount(self); - SQRexMatch match; - sq_newarray(v,0); - for(SQInteger i = 0;i < n; i++) { - sqstd_rex_getsubexp(self,i,&match); - if(match.len > 0) - _addrexmatch(v,str,match.begin,match.begin+match.len); - else - _addrexmatch(v,str,str,str); //empty match - sq_arrayappend(v,-2); - } - return 1; - } - return 0; -} - -static SQInteger _regexp_subexpcount(HSQUIRRELVM v) -{ - SETUP_REX(v); - sq_pushinteger(v,sqstd_rex_getsubexpcount(self)); - return 1; -} - -static SQInteger _regexp_constructor(HSQUIRRELVM v) -{ - const SQChar *error,*pattern; - sq_getstring(v,2,&pattern); - SQRex *rex = sqstd_rex_compile(pattern,&error); - if(!rex) return sq_throwerror(v,error); - sq_setinstanceup(v,1,rex); - sq_setreleasehook(v,1,_rexobj_releasehook); - return 0; -} - -static SQInteger _regexp__typeof(HSQUIRRELVM v) -{ - sq_pushstring(v,"regexp",-1); - return 1; -} - -#define _DECL_REX_FUNC(name,nparams,pmask) {#name,_regexp_##name,nparams,pmask} -static SQRegFunction rexobj_funcs[]={ - _DECL_REX_FUNC(constructor,2,".s"), - _DECL_REX_FUNC(search,-2,"xsn"), - _DECL_REX_FUNC(match,2,"xs"), - _DECL_REX_FUNC(capture,-2,"xsn"), - _DECL_REX_FUNC(subexpcount,1,"x"), - _DECL_REX_FUNC(_typeof,1,"x"), - {0,0,0,0} -}; - -#define _DECL_FUNC(name,nparams,pmask) {#name,_string_##name,nparams,pmask} -static SQRegFunction stringlib_funcs[]={ - _DECL_FUNC(format,-2,".s"), - _DECL_FUNC(strip,2,".s"), - _DECL_FUNC(lstrip,2,".s"), - _DECL_FUNC(rstrip,2,".s"), - _DECL_FUNC(split,3,".ss"), - {0,0,0,0} -}; - - -SQInteger sqstd_register_stringlib(HSQUIRRELVM v) -{ - sq_pushstring(v,"regexp",-1); - sq_newclass(v,SQFalse); - SQInteger i = 0; - while(rexobj_funcs[i].name != 0) { - SQRegFunction &f = rexobj_funcs[i]; - sq_pushstring(v,f.name,-1); - sq_newclosure(v,f.f,0); - sq_setparamscheck(v,f.nparamscheck,f.typemask); - sq_setnativeclosurename(v,-1,f.name); - sq_createslot(v,-3); - i++; - } - sq_createslot(v,-3); - - i = 0; - while(stringlib_funcs[i].name!=0) - { - sq_pushstring(v,stringlib_funcs[i].name,-1); - sq_newclosure(v,stringlib_funcs[i].f,0); - sq_setparamscheck(v,stringlib_funcs[i].nparamscheck,stringlib_funcs[i].typemask); - sq_setnativeclosurename(v,-1,stringlib_funcs[i].name); - sq_createslot(v,-3); - i++; - } - return 1; -} diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp index 189fe67329..18b31ad990 100644 --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -18,6 +18,7 @@ #include "sqfuncstate.h" #include "sqclass.h" +#include "../../../core/string_consumer.hpp" #include "../../../string_func.h" #include "../../../safeguards.h" @@ -131,7 +132,7 @@ void sq_close(HSQUIRRELVM v) sq_delete(ss, SQSharedState); } -SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror) +SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,std::string_view sourcename,SQBool raiseerror) { SQObjectPtr o; if(Compile(v, read, p, sourcename, o, raiseerror != 0, _ss(v)->_debuginfo)) { @@ -172,12 +173,12 @@ SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po) #endif } -const SQChar *sq_objtostring(HSQOBJECT *o) +std::optional sq_objtostring(HSQOBJECT *o) { if(sq_type(*o) == OT_STRING) { return _stringval(*o); } - return nullptr; + return std::nullopt; } SQInteger sq_objtointeger(HSQOBJECT *o) @@ -209,11 +210,9 @@ void sq_pushnull(HSQUIRRELVM v) v->Push(_null_); } -void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len) +void sq_pushstring(HSQUIRRELVM v,std::string_view s) { - if(s) - v->Push(SQObjectPtr(SQString::Create(_ss(v), s, len))); - else v->Push(_null_); + v->Push(SQObjectPtr(SQString::Create(_ss(v), s))); } void sq_pushinteger(HSQUIRRELVM v,SQInteger n) @@ -375,7 +374,7 @@ SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger *nparam return sq_throwerror(v,"the object is not a closure"); } -SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name) +SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,std::string_view name) { SQObject o = stack_get(v, idx); if(sq_isnativeclosure(o)) { @@ -386,16 +385,16 @@ SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name) return sq_throwerror(v,"the object is not a nativeclosure"); } -SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask) +SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,std::optional typemask) { SQObject o = stack_get(v, -1); if(!sq_isnativeclosure(o)) return sq_throwerror(v, "native closure expected"); SQNativeClosure *nc = _nativeclosure(o); nc->_nparamscheck = nparamscheck; - if(typemask) { + if(typemask.has_value()) { SQIntVec res; - if(!CompileTypemask(res, typemask)) + if(!CompileTypemask(res, *typemask)) return sq_throwerror(v, "invalid typemask"); nc->_typecheck.copy(res); } @@ -552,11 +551,11 @@ SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b) return SQ_ERROR; } -SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c) +SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,std::string_view &str) { SQObjectPtr *o = nullptr; _GETSAFE_OBJ(v, idx, OT_STRING,o); - *c = _stringval(*o); + str = _stringval(*o); return SQ_OK; } @@ -584,7 +583,7 @@ SQInteger sq_getsize(HSQUIRRELVM v, SQInteger idx) SQObjectPtr &o = stack_get(v, idx); SQObjectType type = type(o); switch(type) { - case OT_STRING: return _string(o)->_len; + case OT_STRING: return _string(o)->View().size(); case OT_TABLE: return _table(o)->CountUsed(); case OT_ARRAY: return _array(o)->Size(); case OT_USERDATA: return _userdata(o)->_size; @@ -896,7 +895,7 @@ SQRESULT sq_getstackobj(HSQUIRRELVM v,SQInteger idx,HSQOBJECT *po) return SQ_OK; } -const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx) +std::optional sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx) { SQUnsignedInteger cstksize=v->_callsstacksize; SQUnsignedInteger lvl=(cstksize-level)-1; @@ -908,7 +907,7 @@ const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedIntege } SQVM::CallInfo &ci=v->_callsstack[lvl]; if(type(ci._closure)!=OT_CLOSURE) - return nullptr; + return std::nullopt; SQClosure *c=_closure(ci._closure); SQFunctionProto *func=_funcproto(c->_function); if(func->_noutervalues > (SQInteger)idx) { @@ -918,7 +917,7 @@ const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedIntege idx -= func->_noutervalues; return func->GetLocal(v,stackbase,idx,(SQInteger)(ci._ip-func->_instructions)-1); } - return nullptr; + return std::nullopt; } void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj) @@ -931,9 +930,9 @@ void sq_resetobject(HSQOBJECT *po) po->_unVal.pUserPointer=nullptr;po->_type=OT_NULL; } -SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err, SQInteger len) +SQRESULT sq_throwerror(HSQUIRRELVM v,std::string_view error) { - v->_lasterror=SQString::Create(_ss(v),err, len); + v->_lasterror=SQString::Create(_ss(v),error); return -1; } @@ -978,6 +977,10 @@ SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror, if(!v->_suspended) { v->Pop(params);//pop closure and args } + if (!v->_can_suspend && v->IsOpsTillSuspendError()) { + v->Raise_Error(fmt::format("excessive CPU usage in {}", v->_ops_till_suspend_error_label)); + return SQ_ERROR; + } if(retval){ v->Push(res); return SQ_OK; } @@ -1075,7 +1078,7 @@ SQRESULT sq_readclosure(HSQUIRRELVM v,SQREADFUNC r,SQUserPointer up) return SQ_OK; } -SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize) +std::span sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize) { return _ss(v)->GetScratchPad(minsize); } @@ -1089,19 +1092,18 @@ SQInteger sq_collectgarbage(HSQUIRRELVM v) #endif } -const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) +std::optional sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) { SQObjectPtr &self = stack_get(v,idx); - const SQChar *name = nullptr; if(type(self) == OT_CLOSURE) { if(_closure(self)->_outervalues.size()>nval) { v->Push(_closure(self)->_outervalues[nval]); SQFunctionProto *fp = _funcproto(_closure(self)->_function); SQOuterVar &ov = fp->_outervalues[nval]; - name = _stringval(ov._name); + return _stringval(ov._name); } } - return name; + return std::nullopt; } SQRESULT sq_setfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) @@ -1253,44 +1255,16 @@ SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx) return SQ_ERROR; } -struct BufState{ - const SQChar *buf; - SQInteger ptr; - SQInteger size; -}; - char32_t buf_lexfeed(SQUserPointer file) { /* Convert an UTF-8 character into a char32_t */ - BufState *buf = (BufState *)file; - const char *p = &buf->buf[buf->ptr]; - - if (buf->size < buf->ptr + 1) return 0; - - /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */ - uint len = Utf8EncodedCharLen(*p); - if (len == 0) { - buf->ptr++; - return -1; - } - - /* Read the remaining bits. */ - if (buf->size < buf->ptr + len) return 0; - buf->ptr += len; - - /* Convert the character, and when definitely invalid, bail out as well. */ - char32_t c; - if (Utf8Decode(&c, p) != len) return -1; - - return c; + StringConsumer &consumer = *reinterpret_cast(file); + return consumer.AnyBytesLeft() ? consumer.ReadUtf8(-1) : 0; } -SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror) { - BufState buf; - buf.buf = s; - buf.size = size; - buf.ptr = 0; - return sq_compile(v, buf_lexfeed, &buf, sourcename, raiseerror); +SQRESULT sq_compilebuffer(HSQUIRRELVM v,std::string_view buffer,std::string_view sourcename,SQBool raiseerror) { + StringConsumer consumer{buffer}; + return sq_compile(v, buf_lexfeed, &consumer, sourcename, raiseerror); } void sq_move(HSQUIRRELVM dest,HSQUIRRELVM src,SQInteger idx) @@ -1323,7 +1297,7 @@ void sq_free(void *p,SQUnsignedInteger size) SQ_FREE(p,size); } -SQOpsLimiter::SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label) : _v(v) +SQOpsLimiter::SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, std::string_view label) : _v(v) { this->_ops = v->_ops_till_suspend_error_threshold; if (this->_ops == INT64_MIN) { diff --git a/src/3rdparty/squirrel/squirrel/sqbaselib.cpp b/src/3rdparty/squirrel/squirrel/sqbaselib.cpp index 26c02510dd..ba00fde96b 100644 --- a/src/3rdparty/squirrel/squirrel/sqbaselib.cpp +++ b/src/3rdparty/squirrel/squirrel/sqbaselib.cpp @@ -5,6 +5,7 @@ #include "../../../stdafx.h" #include "../../fmt/format.h" +#include "../../../core/string_consumer.hpp" #include "sqpcheader.h" #include "sqvm.h" #include "sqstring.h" @@ -17,19 +18,20 @@ #include "../../../safeguards.h" -bool str2num(const SQChar *s,SQObjectPtr &res) +bool str2num(std::string_view s,SQObjectPtr &res) { - SQChar *end; - if(strstr(s,".")){ - SQFloat r = SQFloat(strtod(s,&end)); - if(s == end) return false; + if(s.find('.') != std::string_view::npos){ + char *end; + std::string str{s}; + SQFloat r = SQFloat(strtod(str.c_str(),&end)); + if(str.c_str() == end) return false; res = r; return true; } else{ - SQInteger r = SQInteger(strtol(s,&end,10)); - if(s == end) return false; - res = r; + auto val = ParseInteger(s); + if (!val.has_value()) return false; + res = *val; return true; } } @@ -101,29 +103,29 @@ static SQInteger base_getstackinfos(HSQUIRRELVM v) SQInteger level; SQStackInfos si; SQInteger seq = 0; - const SQChar *name = nullptr; sq_getinteger(v, -1, &level); if (SQ_SUCCEEDED(sq_stackinfos(v, level, &si))) { - const SQChar *fn = "unknown"; - const SQChar *src = "unknown"; + std::string_view fn = "unknown"; + std::string_view src = "unknown"; if(si.funcname)fn = si.funcname; if(si.source)src = si.source; sq_newtable(v); - sq_pushstring(v, "func", -1); - sq_pushstring(v, fn, -1); + sq_pushstring(v, "func"); + sq_pushstring(v, fn); sq_createslot(v, -3); - sq_pushstring(v, "src", -1); - sq_pushstring(v, src, -1); + sq_pushstring(v, "src"); + sq_pushstring(v, src); sq_createslot(v, -3); - sq_pushstring(v, "line", -1); + sq_pushstring(v, "line"); sq_pushinteger(v, si.line); sq_createslot(v, -3); - sq_pushstring(v, "locals", -1); + sq_pushstring(v, "locals"); sq_newtable(v); seq=0; - while ((name = sq_getlocal(v, level, seq))) { - sq_pushstring(v, name, -1); + std::optional name; + while ((name = sq_getlocal(v, level, seq)).has_value()) { + sq_pushstring(v, *name); sq_push(v, -2); sq_createslot(v, -4); sq_pop(v, 1); @@ -169,9 +171,9 @@ static SQInteger get_slice_params(HSQUIRRELVM v,SQInteger &sidx,SQInteger &eidx, static SQInteger base_print(HSQUIRRELVM v) { - const SQChar *str; + std::string_view str; sq_tostring(v,2); - sq_getstring(v,-1,&str); + sq_getstring(v,-1,str); if(_ss(v)->_printfunc) _ss(v)->_printfunc(v,str); return 0; } @@ -180,14 +182,14 @@ static SQInteger base_print(HSQUIRRELVM v) static SQInteger base_compilestring(HSQUIRRELVM v) { SQInteger nargs=sq_gettop(v); - const SQChar *src=nullptr,*name="unnamedbuffer"; + std::string_view src, name="unnamedbuffer"; SQInteger size; - sq_getstring(v,2,&src); + sq_getstring(v,2,src); size=sq_getsize(v,2); if(nargs>2){ - sq_getstring(v,3,&name); + sq_getstring(v,3,name); } - if(SQ_SUCCEEDED(sq_compilebuffer(v,src,size,name,SQFalse))) + if(SQ_SUCCEEDED(sq_compilebuffer(v,src.substr(0, size),name,SQFalse))) return 1; else return SQ_ERROR; @@ -232,62 +234,59 @@ static SQInteger base_array(HSQUIRRELVM v) static SQInteger base_type(HSQUIRRELVM v) { SQObjectPtr &o = stack_get(v,2); - v->Push(SQString::Create(_ss(v),GetTypeName(o),-1)); + v->Push(SQString::Create(_ss(v),GetTypeName(o))); return 1; } -static SQRegFunction base_funcs[]={ +static const std::initializer_list base_funcs={ //generic #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS - {"seterrorhandler",base_seterrorhandler,2, nullptr}, - {"setdebughook",base_setdebughook,2, nullptr}, - {"enabledebuginfo",base_enabledebuginfo,2, nullptr}, + {"seterrorhandler",base_seterrorhandler,2, std::nullopt}, + {"setdebughook",base_setdebughook,2, std::nullopt}, + {"enabledebuginfo",base_enabledebuginfo,2, std::nullopt}, {"getstackinfos",base_getstackinfos,2, ".n"}, - {"getroottable",base_getroottable,1, nullptr}, - {"setroottable",base_setroottable,2, nullptr}, - {"getconsttable",base_getconsttable,1, nullptr}, - {"setconsttable",base_setconsttable,2, nullptr}, + {"getroottable",base_getroottable,1, std::nullopt}, + {"setroottable",base_setroottable,2, std::nullopt}, + {"getconsttable",base_getconsttable,1, std::nullopt}, + {"setconsttable",base_setconsttable,2, std::nullopt}, #endif - {"assert",base_assert,2, nullptr}, - {"print",base_print,2, nullptr}, + {"assert",base_assert,2, std::nullopt}, + {"print",base_print,2, std::nullopt}, #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS {"compilestring",base_compilestring,-2, ".ss"}, {"newthread",base_newthread,2, ".c"}, - {"suspend",base_suspend,-1, nullptr}, + {"suspend",base_suspend,-1, std::nullopt}, #endif {"array",base_array,-2, ".n"}, - {"type",base_type,2, nullptr}, + {"type",base_type,2, std::nullopt}, #ifdef EXPORT_DEFAULT_SQUIRREL_FUNCTIONS - {"dummy",base_dummy,0,nullptr}, + {"dummy",base_dummy,0,std::nullopt}, #ifndef NO_GARBAGE_COLLECTOR {"collectgarbage",base_collectgarbage,1, "t"}, #endif #endif - {nullptr,nullptr,0,nullptr} }; void sq_base_register(HSQUIRRELVM v) { - SQInteger i=0; sq_pushroottable(v); - while(base_funcs[i].name!=nullptr) { - sq_pushstring(v,base_funcs[i].name,-1); - sq_newclosure(v,base_funcs[i].f,0); - sq_setnativeclosurename(v,-1,base_funcs[i].name); - sq_setparamscheck(v,base_funcs[i].nparamscheck,base_funcs[i].typemask); + for(auto &func : base_funcs) { + sq_pushstring(v,func.name); + sq_newclosure(v,func.f,0); + sq_setnativeclosurename(v,-1,func.name); + sq_setparamscheck(v,func.nparamscheck,func.typemask); sq_createslot(v,-3); - i++; } - sq_pushstring(v,"_version_",-1); - sq_pushstring(v,SQUIRREL_VERSION,-1); + sq_pushstring(v,"_version_"); + sq_pushstring(v,SQUIRREL_VERSION); sq_createslot(v,-3); - sq_pushstring(v,"_charsize_",-1); - sq_pushinteger(v,sizeof(SQChar)); + sq_pushstring(v,"_charsize_"); + sq_pushinteger(v,sizeof(char)); sq_createslot(v,-3); - sq_pushstring(v,"_intsize_",-1); + sq_pushstring(v,"_intsize_"); sq_pushinteger(v,sizeof(SQInteger)); sq_createslot(v,-3); - sq_pushstring(v,"_floatsize_",-1); + sq_pushstring(v,"_floatsize_"); sq_pushinteger(v,sizeof(SQFloat)); sq_createslot(v,-3); sq_pop(v,1); @@ -370,8 +369,8 @@ static SQInteger obj_clear(HSQUIRRELVM v) static SQInteger number_delegate_tochar(HSQUIRRELVM v) { SQObject &o=stack_get(v,1); - SQChar c = (SQChar)tointeger(o); - v->Push(SQString::Create(_ss(v),(const SQChar *)&c,1)); + char c = static_cast(tointeger(o)); + v->Push(SQString::Create(_ss(v),std::string_view(&c, 1))); return 1; } @@ -409,16 +408,15 @@ static SQInteger table_rawget(HSQUIRRELVM v) } -SQRegFunction SQSharedState::_table_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_table_default_delegate_funcz={ {"len",default_delegate_len,1, "t"}, {"rawget",table_rawget,2, "t"}, {"rawset",table_rawset,3, "t"}, {"rawdelete",table_rawdelete,2, "t"}, {"rawin",container_rawexists,2, "t"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"clear",obj_clear,1, "."}, - {nullptr,nullptr,0,nullptr} }; //ARRAY DEFAULT DELEGATE/////////////////////////////////////// @@ -611,7 +609,7 @@ static SQInteger array_slice(HSQUIRRELVM v) } -SQRegFunction SQSharedState::_array_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_array_default_delegate_funcz={ {"len",default_delegate_len,1, "a"}, {"append",array_append,2, "a"}, {"extend",array_extend,2, "aa"}, @@ -624,10 +622,9 @@ SQRegFunction SQSharedState::_array_default_delegate_funcz[]={ {"reverse",array_reverse,1, "a"}, {"sort",array_sort,-1, "ac"}, {"slice",array_slice,-1, "ann"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"clear",obj_clear,1, "."}, - {nullptr,nullptr,0,nullptr} }; //STRING DEFAULT DELEGATE////////////////////////// @@ -636,25 +633,25 @@ static SQInteger string_slice(HSQUIRRELVM v) SQInteger sidx,eidx; SQObjectPtr o; if(SQ_FAILED(get_slice_params(v,sidx,eidx,o)))return -1; - SQInteger slen = _string(o)->_len; + SQInteger slen = _string(o)->View().size(); if(sidx < 0)sidx = slen + sidx; if(eidx < 0)eidx = slen + eidx; if(eidx < sidx) return sq_throwerror(v,"wrong indexes"); if(eidx > slen) return sq_throwerror(v,"slice out of range"); - v->Push(SQString::Create(_ss(v),&_stringval(o)[sidx],eidx-sidx)); + v->Push(SQString::Create(_ss(v),_stringval(o).substr(sidx,eidx-sidx))); return 1; } static SQInteger string_find(HSQUIRRELVM v) { SQInteger top,start_idx=0; - const SQChar *str,*substr,*ret; - if(((top=sq_gettop(v))>1) && SQ_SUCCEEDED(sq_getstring(v,1,&str)) && SQ_SUCCEEDED(sq_getstring(v,2,&substr))){ + std::string_view str,substr; + if(((top=sq_gettop(v))>1) && SQ_SUCCEEDED(sq_getstring(v,1,str)) && SQ_SUCCEEDED(sq_getstring(v,2,substr))){ if(top>2)sq_getinteger(v,3,&start_idx); if((sq_getsize(v,1)>start_idx) && (start_idx>=0)){ - ret=strstr(&str[start_idx],substr); - if(ret){ - sq_pushinteger(v,(SQInteger)(ret-str)); + auto ret = str.find(substr, start_idx); + if(ret != std::string_view::npos){ + sq_pushinteger(v,static_cast(ret)); return 1; } } @@ -666,11 +663,11 @@ static SQInteger string_find(HSQUIRRELVM v) #define STRING_TOFUNCZ(func) static SQInteger string_##func(HSQUIRRELVM v) \ { \ SQObject str=stack_get(v,1); \ - SQInteger len=_string(str)->_len; \ - const SQChar *sThis=_stringval(str); \ - SQChar *sNew=(_ss(v)->GetScratchPad(len)); \ - for(SQInteger i=0;iPush(SQString::Create(_ss(v),sNew,len)); \ + std::string_view sThis=_stringval(str); \ + size_t len=sThis.size(); \ + std::span sNew=(_ss(v)->GetScratchPad(len)); \ + for(size_t i=0;iPush(SQString::Create(_ss(v),std::string_view(sNew.data(), len))); \ return 1; \ } @@ -678,7 +675,7 @@ static SQInteger string_find(HSQUIRRELVM v) STRING_TOFUNCZ(tolower) STRING_TOFUNCZ(toupper) -SQRegFunction SQSharedState::_string_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_string_default_delegate_funcz={ {"len",default_delegate_len,1, "s"}, {"tointeger",default_delegate_tointeger,1, "s"}, {"tofloat",default_delegate_tofloat,1, "s"}, @@ -687,18 +684,16 @@ SQRegFunction SQSharedState::_string_default_delegate_funcz[]={ {"find",string_find,-2, "s s n "}, {"tolower",string_tolower,1, "s"}, {"toupper",string_toupper,1, "s"}, - {"weakref",obj_delegate_weakref,1, nullptr }, - {nullptr,nullptr,0,nullptr} + {"weakref",obj_delegate_weakref,1, std::nullopt }, }; //INTEGER DEFAULT DELEGATE////////////////////////// -SQRegFunction SQSharedState::_number_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_number_default_delegate_funcz={ {"tointeger",default_delegate_tointeger,1, "n|b"}, {"tofloat",default_delegate_tofloat,1, "n|b"}, {"tostring",default_delegate_tostring,1, "."}, {"tochar",number_delegate_tochar,1, "n|b"}, - {"weakref",obj_delegate_weakref,1, nullptr }, - {nullptr,nullptr,0,nullptr} + {"weakref",obj_delegate_weakref,1, std::nullopt }, }; //CLOSURE DEFAULT DELEGATE////////////////////////// @@ -749,19 +744,19 @@ static SQInteger closure_getinfos(HSQUIRRELVM v) { _array(params)->Set((SQInteger)n,f->_parameters[n]); } if(f->_varparams) { - _array(params)->Set(nparams-1,SQString::Create(_ss(v),"...",-1)); + _array(params)->Set(nparams-1,SQString::Create(_ss(v),"...")); } - res->NewSlot(SQString::Create(_ss(v),"native",-1),false); - res->NewSlot(SQString::Create(_ss(v),"name",-1),f->_name); - res->NewSlot(SQString::Create(_ss(v),"src",-1),f->_sourcename); - res->NewSlot(SQString::Create(_ss(v),"parameters",-1),params); - res->NewSlot(SQString::Create(_ss(v),"varargs",-1),f->_varparams); + res->NewSlot(SQString::Create(_ss(v),"native"),false); + res->NewSlot(SQString::Create(_ss(v),"name"),f->_name); + res->NewSlot(SQString::Create(_ss(v),"src"),f->_sourcename); + res->NewSlot(SQString::Create(_ss(v),"parameters"),params); + res->NewSlot(SQString::Create(_ss(v),"varargs"),f->_varparams); } else { //OT_NATIVECLOSURE SQNativeClosure *nc = _nativeclosure(o); - res->NewSlot(SQString::Create(_ss(v),"native",-1),true); - res->NewSlot(SQString::Create(_ss(v),"name",-1),nc->_name); - res->NewSlot(SQString::Create(_ss(v),"paramscheck",-1),nc->_nparamscheck); + res->NewSlot(SQString::Create(_ss(v),"native"),true); + res->NewSlot(SQString::Create(_ss(v),"name"),nc->_name); + res->NewSlot(SQString::Create(_ss(v),"paramscheck"),nc->_nparamscheck); SQObjectPtr typecheck; if(!nc->_typecheck.empty()) { typecheck = @@ -770,23 +765,22 @@ static SQInteger closure_getinfos(HSQUIRRELVM v) { _array(typecheck)->Set((SQInteger)n,nc->_typecheck[n]); } } - res->NewSlot(SQString::Create(_ss(v),"typecheck",-1),typecheck); + res->NewSlot(SQString::Create(_ss(v),"typecheck"),typecheck); } v->Push(res); return 1; } -SQRegFunction SQSharedState::_closure_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_closure_default_delegate_funcz={ {"call",closure_call,-1, "c"}, {"pcall",closure_pcall,-1, "c"}, {"acall",closure_acall,2, "ca"}, {"pacall",closure_pacall,2, "ca"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"bindenv",closure_bindenv,2, "c x|y|t"}, {"getinfos",closure_getinfos,1, "c"}, - {nullptr,nullptr,0,nullptr} }; //GENERATOR DEFAULT DELEGATE @@ -801,11 +795,10 @@ static SQInteger generator_getstatus(HSQUIRRELVM v) return 1; } -SQRegFunction SQSharedState::_generator_default_delegate_funcz[]={ +/* static */ const std::initializer_list SQSharedState::_generator_default_delegate_funcz={ {"getstatus",generator_getstatus,1, "g"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr} }; //THREAD DEFAULT DELEGATE @@ -871,13 +864,13 @@ static SQInteger thread_getstatus(HSQUIRRELVM v) SQObjectPtr &o = stack_get(v,1); switch(sq_getvmstate(_thread(o))) { case SQ_VMSTATE_IDLE: - sq_pushstring(v,"idle",-1); + sq_pushstring(v,"idle"); break; case SQ_VMSTATE_RUNNING: - sq_pushstring(v,"running",-1); + sq_pushstring(v,"running"); break; case SQ_VMSTATE_SUSPENDED: - sq_pushstring(v,"suspended",-1); + sq_pushstring(v,"suspended"); break; default: return sq_throwerror(v,"internal VM error"); @@ -885,13 +878,12 @@ static SQInteger thread_getstatus(HSQUIRRELVM v) return 1; } -SQRegFunction SQSharedState::_thread_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_thread_default_delegate_funcz = { {"call", thread_call, -1, "v"}, {"wakeup", thread_wakeup, -1, "v"}, {"getstatus", thread_getstatus, 1, "v"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr}, }; static SQInteger class_getattributes(HSQUIRRELVM v) @@ -915,14 +907,13 @@ static SQInteger class_instance(HSQUIRRELVM v) return SQ_ERROR; } -SQRegFunction SQSharedState::_class_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_class_default_delegate_funcz = { {"getattributes", class_getattributes, 2, "y."}, {"setattributes", class_setattributes, 3, "y.."}, {"rawin",container_rawexists,2, "y"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, {"instance",class_instance,1, "y"}, - {nullptr,nullptr,0,nullptr} }; static SQInteger instance_getclass(HSQUIRRELVM v) @@ -932,12 +923,11 @@ static SQInteger instance_getclass(HSQUIRRELVM v) return SQ_ERROR; } -SQRegFunction SQSharedState::_instance_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_instance_default_delegate_funcz = { {"getclass", instance_getclass, 1, "x"}, {"rawin",container_rawexists,2, "x"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr} }; static SQInteger weakref_ref(HSQUIRRELVM v) @@ -947,11 +937,10 @@ static SQInteger weakref_ref(HSQUIRRELVM v) return 1; } -SQRegFunction SQSharedState::_weakref_default_delegate_funcz[] = { +/* static */ const std::initializer_list SQSharedState::_weakref_default_delegate_funcz = { {"ref",weakref_ref,1, "r"}, - {"weakref",obj_delegate_weakref,1, nullptr }, + {"weakref",obj_delegate_weakref,1, std::nullopt }, {"tostring",default_delegate_tostring,1, "."}, - {nullptr,nullptr,0,nullptr} }; diff --git a/src/3rdparty/squirrel/squirrel/sqcompiler.cpp b/src/3rdparty/squirrel/squirrel/sqcompiler.cpp index 560edfc66b..2d7c7d5703 100644 --- a/src/3rdparty/squirrel/squirrel/sqcompiler.cpp +++ b/src/3rdparty/squirrel/squirrel/sqcompiler.cpp @@ -57,16 +57,12 @@ typedef sqvector ExpStateVec; class SQCompiler { public: - SQCompiler(SQVM *v, SQLEXREADFUNC rg, SQUserPointer up, const SQChar* sourcename, bool raiseerror, bool lineinfo) : _token(0), _fs(nullptr), _lex(_ss(v), rg, up), _debugline(0), _debugop(0) + SQCompiler(SQVM *v, SQLEXREADFUNC rg, SQUserPointer up, std::string_view sourcename, bool raiseerror, bool lineinfo) : _token(0), _fs(nullptr), _lex(_ss(v), rg, up), _debugline(0), _debugop(0) { _vm=v; _sourcename = SQString::Create(_ss(v), sourcename); _lineinfo = lineinfo;_raiseerror = raiseerror; } - [[noreturn]] void Error(const std::string &msg) - { - throw CompileException(msg); - } void Lex(){ _token = _lex.Lex();} void PushExpState(){ _expstates.push_back(ExpState()); } bool IsDerefToken(SQInteger tok) @@ -92,7 +88,7 @@ public: //do nothing } else { - const SQChar *etypename; + std::string_view etypename; if(tok > 255) { switch(tok) { @@ -109,21 +105,21 @@ public: etypename = "FLOAT"; break; default: - etypename = _lex.Tok2Str(tok); + etypename = _lex.Tok2Str(tok).value_or(""); } - Error(fmt::format("expected '{}'", etypename)); + throw CompileException(fmt::format("expected '{}'", etypename)); } - Error(fmt::format("expected '{:c}'", tok)); + throw CompileException(fmt::format("expected '{:c}'", tok)); } } SQObjectPtr ret; switch(tok) { case TK_IDENTIFIER: - ret = _fs->CreateString(_lex._svalue); + ret = _fs->CreateString(_lex.View()); break; case TK_STRING_LITERAL: - ret = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1); + ret = _fs->CreateString(_lex.View()); break; case TK_INTEGER: ret = SQObjectPtr(_lex._nvalue); @@ -140,7 +136,7 @@ public: { if(_token == ';') { Lex(); return; } if(!IsEndOfStatement()) { - Error("end of statement expected (; or lf)"); + throw CompileException("end of statement expected (; or lf)"); } } void MoveIfCurrentTargetIsLocal() { @@ -233,7 +229,7 @@ public: } break;} case TK_BREAK: - if(_fs->_breaktargets.size() <= 0)Error("'break' has to be in a loop block"); + if(_fs->_breaktargets.size() <= 0)throw CompileException("'break' has to be in a loop block"); if(_fs->_breaktargets.top() > 0){ _fs->AddInstruction(_OP_POPTRAP, _fs->_breaktargets.top(), 0); } @@ -243,7 +239,7 @@ public: Lex(); break; case TK_CONTINUE: - if(_fs->_continuetargets.size() <= 0)Error("'continue' has to be in a loop block"); + if(_fs->_continuetargets.size() <= 0)throw CompileException("'continue' has to be in a loop block"); if(_fs->_continuetargets.top() > 0) { _fs->AddInstruction(_OP_POPTRAP, _fs->_continuetargets.top(), 0); } @@ -356,19 +352,19 @@ public: SQInteger op = _token; SQInteger ds = _exst._deref; bool freevar = _exst._freevar; - if(ds == DEREF_NO_DEREF) Error("can't assign expression"); + if(ds == DEREF_NO_DEREF) throw CompileException("can't assign expression"); Lex(); Expression(); switch(op){ case TK_NEWSLOT: - if(freevar) Error("free variables cannot be modified"); + if(freevar) throw CompileException("free variables cannot be modified"); if(ds == DEREF_FIELD) EmitDerefOp(_OP_NEWSLOT); else //if _derefstate != DEREF_NO_DEREF && DEREF_FIELD so is the index of a local - Error("can't 'create' a local slot"); + throw CompileException("can't 'create' a local slot"); break; case '=': //ASSIGN - if(freevar) Error("free variables cannot be modified"); + if(freevar) throw CompileException("free variables cannot be modified"); if(ds == DEREF_FIELD) EmitDerefOp(_OP_SET); else {//if _derefstate != DEREF_NO_DEREF && DEREF_FIELD so is the index of a local @@ -533,7 +529,7 @@ public: if(_token == TK_PARENT) { Lex(); if(!NeedGet()) - Error("parent cannot be set"); + throw CompileException("parent cannot be set"); SQInteger src = _fs->PopTarget(); _fs->AddInstruction(_OP_GETPARENT, _fs->PushTarget(), src); } @@ -546,7 +542,7 @@ public: } break; case '[': - if(_lex._prevtoken == '\n') Error("cannot brake deref/or comma needed after [exp]=exp slot declaration"); + if(_lex._prevtoken == '\n') throw CompileException("cannot brake deref/or comma needed after [exp]=exp slot declaration"); Lex(); Expression(); Expect(']'); pos = -1; if(NeedGet()) Emit2ArgsOP(_OP_GET); @@ -598,7 +594,7 @@ public: switch(_token) { case TK_STRING_LITERAL: { - _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_fs->CreateString(_lex._svalue,_lex._longstr.size()-1))); + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_fs->CreateString(_lex.View()))); Lex(); } break; @@ -618,7 +614,7 @@ public: SQObject id; SQObject constant; switch(_token) { - case TK_IDENTIFIER: id = _fs->CreateString(_lex._svalue); break; + case TK_IDENTIFIER: id = _fs->CreateString(_lex.View()); break; case TK_THIS: id = _fs->CreateString("this"); break; case TK_CONSTRUCTOR: id = _fs->CreateString("constructor"); break; } @@ -638,7 +634,7 @@ public: Expect('.'); constid = Expect(TK_IDENTIFIER); if(!_table(constant)->Get(constid,constval)) { constval.Null(); - Error(fmt::format("invalid constant [{}.{}]", _stringval(id),_stringval(constid))); + throw CompileException(fmt::format("invalid constant [{}.{}]", _stringval(id),_stringval(constid))); } } else { @@ -742,7 +738,7 @@ public: case TK_DELEGATE : DelegateExpr(); break; case '(': Lex(); CommaExpr(); Expect(')'); break; - default: Error("expression expected"); + default: throw CompileException("expression expected"); } return -1; } @@ -771,7 +767,7 @@ public: nargs++; if(_token == ','){ Lex(); - if(_token == ')') Error("expression expected, found ')'"); + if(_token == ')') throw CompileException("expression expected, found ')'"); } } Lex(); @@ -1082,13 +1078,13 @@ public: _exst._funcarg = false; PrefixedExpr(); es = PopExpState(); - if(es._deref == DEREF_NO_DEREF) Error("invalid class name"); + if(es._deref == DEREF_NO_DEREF) throw CompileException("invalid class name"); if(es._deref == DEREF_FIELD) { ClassExp(); EmitDerefOp(_OP_NEWSLOT); _fs->PopTarget(); } - else Error("cannot create a class in a local with the syntax(class )"); + else throw CompileException("cannot create a class in a local with the syntax(class )"); } SQObject ExpectScalar() { @@ -1103,7 +1099,7 @@ public: val._unVal.fFloat = _lex._fvalue; break; case TK_STRING_LITERAL: - val = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1); + val = _fs->CreateString(_lex.View()); break; case '-': Lex(); @@ -1118,12 +1114,12 @@ public: val._unVal.fFloat = -_lex._fvalue; break; default: - Error("scalar expected : integer,float"); + throw CompileException("scalar expected : integer,float"); val._type = OT_NULL; // Silent compile-warning } break; default: - Error("scalar expected : integer,float or string"); + throw CompileException("scalar expected : integer,float or string"); val._type = OT_NULL; // Silent compile-warning } Lex(); @@ -1226,9 +1222,9 @@ public: _exst._funcarg = false; PrefixedExpr(); es = PopExpState(); - if(es._deref == DEREF_NO_DEREF) Error("can't delete an expression"); + if(es._deref == DEREF_NO_DEREF) throw CompileException("can't delete an expression"); if(es._deref == DEREF_FIELD) Emit2ArgsOP(_OP_DELETE); - else Error("cannot delete a local"); + else throw CompileException("cannot delete a local"); } void PrefixIncDec(SQInteger token) { @@ -1255,10 +1251,10 @@ public: SQInteger defparams = 0; while(_token!=')') { if(_token == TK_VARPARAMS) { - if(defparams > 0) Error("function with default parameters cannot have variable number of parameters"); + if(defparams > 0) throw CompileException("function with default parameters cannot have variable number of parameters"); funcstate->_varparams = true; Lex(); - if(_token != ')') Error("expected ')'"); + if(_token != ')') throw CompileException("expected ')'"); break; } else { @@ -1271,10 +1267,10 @@ public: defparams++; } else { - if(defparams > 0) Error("expected '='"); + if(defparams > 0) throw CompileException("expected '='"); } if(_token == ',') Lex(); - else if(_token != ')') Error("expected ')' or ','"); + else if(_token != ')') throw CompileException("expected ')' or ','"); } } Expect(')'); @@ -1289,7 +1285,7 @@ public: //outers are treated as implicit local variables funcstate->AddOuterValue(paramname); if(_token == ',') Lex(); - else if(_token != ')') Error("expected ')' or ','"); + else if(_token != ')') throw CompileException("expected ')' or ','"); } Lex(); } @@ -1346,7 +1342,7 @@ private: SQVM *_vm; }; -bool Compile(SQVM *vm,SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo) +bool Compile(SQVM *vm,SQLEXREADFUNC rg, SQUserPointer up, std::string_view sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo) { SQCompiler p(vm, rg, up, sourcename, raiseerror, lineinfo); return p.Compile(out); diff --git a/src/3rdparty/squirrel/squirrel/sqcompiler.h b/src/3rdparty/squirrel/squirrel/sqcompiler.h index cdb6b1d65c..de3f2eb215 100644 --- a/src/3rdparty/squirrel/squirrel/sqcompiler.h +++ b/src/3rdparty/squirrel/squirrel/sqcompiler.h @@ -73,5 +73,5 @@ struct SQVM; using CompileException = std::runtime_error; -bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo); +bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, std::string_view sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo); #endif //_SQCOMPILER_H_ diff --git a/src/3rdparty/squirrel/squirrel/sqdebug.cpp b/src/3rdparty/squirrel/squirrel/sqdebug.cpp index 8a6f15121a..cc5c21316c 100644 --- a/src/3rdparty/squirrel/squirrel/sqdebug.cpp +++ b/src/3rdparty/squirrel/squirrel/sqdebug.cpp @@ -37,7 +37,7 @@ SQRESULT sq_stackinfos(HSQUIRRELVM v, SQInteger level, SQStackInfos *si) { SQInteger cssize = v->_callsstacksize; if (cssize > level) { - memset(si, 0, sizeof(SQStackInfos)); + *si = {}; SQVM::CallInfo &ci = v->_callsstack[cssize-level-1]; switch (type(ci._closure)) { case OT_CLOSURE:{ @@ -101,15 +101,15 @@ void SQVM::Raise_CompareError(const SQObject &o1, const SQObject &o2) void SQVM::Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger type) { - SQObjectPtr exptypes = SQString::Create(_ss(this), "", -1); + SQObjectPtr exptypes = SQString::Create(_ss(this), ""); SQInteger found = 0; for(SQInteger i=0; i<16; i++) { SQInteger mask = 0x00000001LL << i; if(typemask & (mask)) { - if(found>0) StringCat(exptypes,SQString::Create(_ss(this), "|", -1), exptypes); + if(found>0) StringCat(exptypes,SQString::Create(_ss(this), "|"), exptypes); found ++; - StringCat(exptypes,SQString::Create(_ss(this), IdType2Name((SQObjectType)mask), -1), exptypes); + StringCat(exptypes,SQString::Create(_ss(this), IdType2Name((SQObjectType)mask)), exptypes); } } Raise_Error(fmt::format("parameter {} has an invalid type '{}' ; expected: '{}'", nparam, IdType2Name((SQObjectType)type), _stringval(exptypes))); diff --git a/src/3rdparty/squirrel/squirrel/sqfuncproto.h b/src/3rdparty/squirrel/squirrel/sqfuncproto.h index b65b040b49..a2cdb94fd1 100644 --- a/src/3rdparty/squirrel/squirrel/sqfuncproto.h +++ b/src/3rdparty/squirrel/squirrel/sqfuncproto.h @@ -114,7 +114,7 @@ public: this->~SQFunctionProto(); sq_vm_free(this,size); } - const SQChar* GetLocal(SQVM *v,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop); + std::optional GetLocal(SQVM *v,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop); SQInteger GetLine(SQInstruction *curr); bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write); static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret); diff --git a/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp b/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp index 54744292a9..790805a3b5 100644 --- a/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp +++ b/src/3rdparty/squirrel/squirrel/sqfuncstate.cpp @@ -110,11 +110,6 @@ SQFuncState::SQFuncState(SQSharedState *ss,SQFuncState *parent) } -void SQFuncState::Error(const SQChar *err) -{ - throw CompileException(err); -} - #ifdef _DEBUG_DUMP void SQFuncState::Dump(SQFunctionProto *func) { @@ -234,7 +229,7 @@ SQInteger SQFuncState::GetConstant(const SQObject &cons) _nliterals++; if(_nliterals > MAX_LITERALS) { val.Null(); - Error("internal compiler error: too many literals"); + throw CompileException("internal compiler error: too many literals"); } } return _integer(val); @@ -264,7 +259,7 @@ SQInteger SQFuncState::AllocStackPos() SQInteger npos=_vlocals.size(); _vlocals.push_back(SQLocalVarInfo()); if(_vlocals.size()>((SQUnsignedInteger)_stacksize)) { - if(_stacksize>MAX_FUNC_STACKSIZE) Error("internal compiler error: too many locals"); + if(_stacksize>MAX_FUNC_STACKSIZE) throw CompileException("internal compiler error: too many locals"); _stacksize=_vlocals.size(); } return npos; @@ -499,9 +494,9 @@ void SQFuncState::AddInstruction(SQInstruction &i) _instructions.push_back(i); } -SQObject SQFuncState::CreateString(const SQChar *s,SQInteger len) +SQObject SQFuncState::CreateString(std::string_view s) { - SQObjectPtr ns(SQString::Create(_sharedstate,s,len)); + SQObjectPtr ns(SQString::Create(_sharedstate,s)); _table(_strings)->NewSlot(ns,(SQInteger)1); return std::move(ns); } @@ -539,7 +534,7 @@ SQFunctionProto *SQFuncState::BuildProto() for(SQUnsignedInteger no = 0; no < _lineinfos.size(); no++) f->_lineinfos[no] = _lineinfos[no]; for(SQUnsignedInteger no = 0; no < _defaultparams.size(); no++) f->_defaultparams[no] = _defaultparams[no]; - memcpy(f->_instructions,&_instructions[0],(size_t)_instructions.size()*sizeof(SQInstruction)); + std::copy_n(&_instructions[0], _instructions.size(), f->_instructions); f->_varparams = _varparams; diff --git a/src/3rdparty/squirrel/squirrel/sqfuncstate.h b/src/3rdparty/squirrel/squirrel/sqfuncstate.h index 739eb16261..d67f6dcc91 100644 --- a/src/3rdparty/squirrel/squirrel/sqfuncstate.h +++ b/src/3rdparty/squirrel/squirrel/sqfuncstate.h @@ -11,7 +11,6 @@ struct SQFuncState #ifdef _DEBUG_DUMP void Dump(SQFunctionProto *func); #endif - [[noreturn]] void Error(const SQChar *err); SQFuncState *PushChildState(SQSharedState *ss); void PopChildState(); void AddInstruction(SQOpcode _op,SQInteger arg0=0,SQInteger arg1=0,SQInteger arg2=0,SQInteger arg3=0){SQInstruction i(_op,arg0,arg1,arg2,arg3);AddInstruction(i);} @@ -43,7 +42,7 @@ struct SQFuncState SQInteger TopTarget(); SQInteger GetUpTarget(SQInteger n); bool IsLocal(SQUnsignedInteger stkpos); - SQObject CreateString(const SQChar *s,SQInteger len = -1); + SQObject CreateString(std::string_view s); SQObject CreateTable(); bool IsConstant(const SQObject &name,SQObject &e); SQInteger _returnexp; diff --git a/src/3rdparty/squirrel/squirrel/sqlexer.cpp b/src/3rdparty/squirrel/squirrel/sqlexer.cpp index 89eae0026e..6dadfa36d1 100644 --- a/src/3rdparty/squirrel/squirrel/sqlexer.cpp +++ b/src/3rdparty/squirrel/squirrel/sqlexer.cpp @@ -12,6 +12,7 @@ #include "sqlexer.h" #include "../../../core/utf8.hpp" +#include "../../../core/string_consumer.hpp" #include "../../../safeguards.h" @@ -84,22 +85,16 @@ SQLexer::SQLexer(SQSharedState *ss, SQLEXREADFUNC rg, SQUserPointer up) _prevtoken = -1; _curtoken = -1; - _svalue = nullptr; _nvalue = 0; _fvalue = 0; Next(); } -[[noreturn]] void SQLexer::Error(const SQChar *err) -{ - throw CompileException(err); -} - void SQLexer::Next() { char32_t t = _readf(_up); - if(t > MAX_CHAR) Error("Invalid character"); + if(t > MAX_CHAR) throw CompileException("Invalid character"); if(t != 0) { _currdata = t; return; @@ -107,7 +102,7 @@ void SQLexer::Next() _currdata = SQUIRREL_EOB; } -const SQChar *SQLexer::Tok2Str(SQInteger tok) +std::optional SQLexer::Tok2Str(SQInteger tok) { SQObjectPtr itr, key, val; SQInteger nitr; @@ -116,7 +111,7 @@ const SQChar *SQLexer::Tok2Str(SQInteger tok) if(((SQInteger)_integer(val)) == tok) return _stringval(key); } - return nullptr; + return std::nullopt; } void SQLexer::LexBlockComment() @@ -126,7 +121,7 @@ void SQLexer::LexBlockComment() switch(CUR_CHAR) { case '*': { NEXT(); if(CUR_CHAR == '/') { done = true; NEXT(); }}; continue; case '\n': _currentline++; NEXT(); continue; - case SQUIRREL_EOB: Error("missing \"*/\" in comment"); + case SQUIRREL_EOB: throw CompileException("missing \"*/\" in comment"); default: NEXT(); } } @@ -195,11 +190,11 @@ SQInteger SQLexer::Lex() SQInteger stype; NEXT(); if(CUR_CHAR != '"') - Error("string expected"); + throw CompileException("string expected"); if((stype=ReadString('"',true))!=-1) { RETURN_TOKEN(stype); } - Error("error parsing the string"); + throw CompileException("error parsing the string"); } case '"': case '\'': { @@ -207,7 +202,7 @@ SQInteger SQLexer::Lex() if((stype=ReadString(CUR_CHAR,false))!=-1){ RETURN_TOKEN(stype); } - Error("error parsing the string"); + throw CompileException("error parsing the string"); } case '{': case '}': case '(': case ')': case '[': case ']': case ';': case ',': case '?': case '^': case '~': @@ -217,7 +212,7 @@ SQInteger SQLexer::Lex() NEXT(); if (CUR_CHAR != '.'){ RETURN_TOKEN('.') } NEXT(); - if (CUR_CHAR != '.'){ Error("invalid token '..'"); } + if (CUR_CHAR != '.'){ throw CompileException("invalid token '..'"); } NEXT(); RETURN_TOKEN(TK_VARPARAMS); case '&': @@ -263,7 +258,7 @@ SQInteger SQLexer::Lex() } else { SQInteger c = CUR_CHAR; - if (iscntrl((int)c)) Error("unexpected character(control)"); + if (iscntrl((int)c)) throw CompileException("unexpected character(control)"); NEXT(); RETURN_TOKEN(c); } @@ -274,7 +269,7 @@ SQInteger SQLexer::Lex() return 0; } -SQInteger SQLexer::GetIDType(SQChar *s) +SQInteger SQLexer::GetIDType(std::string_view s) { SQObjectPtr t; if(_keywords->Get(SQString::Create(_sharedstate, s), t)) { @@ -293,10 +288,10 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) while(CUR_CHAR != ndelim) { switch(CUR_CHAR) { case SQUIRREL_EOB: - Error("unfinished string"); + throw CompileException("unfinished string"); return -1; case '\n': - if(!verbatim) Error("newline in a constant"); + if(!verbatim) throw CompileException("newline in a constant"); APPEND_CHAR(CUR_CHAR); NEXT(); _currentline++; break; @@ -308,18 +303,18 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) NEXT(); switch(CUR_CHAR) { case 'x': NEXT(); { - if(!isxdigit(CUR_CHAR)) Error("hexadecimal number expected"); + if(!isxdigit(CUR_CHAR)) throw CompileException("hexadecimal number expected"); const SQInteger maxdigits = 4; - SQChar temp[maxdigits+1]; - SQInteger n = 0; + char temp[maxdigits]; + size_t n = 0; while(isxdigit(CUR_CHAR) && n < maxdigits) { temp[n] = CUR_CHAR; n++; NEXT(); } - temp[n] = 0; - SQChar *sTemp; - APPEND_CHAR((SQChar)strtoul(temp,&sTemp,16)); + auto val = ParseInteger(std::string_view{temp, n}, 16); + if (!val.has_value()) throw CompileException("hexadecimal number expected"); + APPEND_CHAR(static_cast(*val)); } break; case 't': APPEND_CHAR('\t'); NEXT(); break; @@ -334,7 +329,7 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) case '"': APPEND_CHAR('"'); NEXT(); break; case '\'': APPEND_CHAR('\''); NEXT(); break; default: - Error("unrecognised escaper char"); + throw CompileException("unrecognised escaper char"); break; } } @@ -353,49 +348,16 @@ SQInteger SQLexer::ReadString(char32_t ndelim,bool verbatim) break; } } - TERMINATE_BUFFER(); - SQInteger len = _longstr.size()-1; if(ndelim == '\'') { - if(len == 0) Error("empty constant"); - if(len > 1) Error("constant too long"); + if(_longstr.empty()) throw CompileException("empty constant"); + if(_longstr.size() > 1) throw CompileException("constant too long"); _nvalue = _longstr[0]; return TK_INTEGER; } - _svalue = &_longstr[0]; return TK_STRING_LITERAL; } -void LexHexadecimal(const SQChar *s,SQUnsignedInteger *res) -{ - *res = 0; - while(*s != 0) - { - if(isdigit(*s)) *res = (*res)*16+((*s++)-'0'); - else if(isxdigit(*s)) *res = (*res)*16+(toupper(*s++)-'A'+10); - else { assert(0); } - } -} - -void LexInteger(const SQChar *s,SQUnsignedInteger *res) -{ - *res = 0; - while(*s != 0) - { - *res = (*res)*10+((*s++)-'0'); - } -} - -SQInteger scisodigit(SQChar c) { return c >= '0' && c <= '7'; } - -void LexOctal(const SQChar *s,SQUnsignedInteger *res) -{ - *res = 0; - while(*s != 0) - { - if(scisodigit(*s)) *res = (*res)*8+((*s++)-'0'); - else { assert(0); } - } -} +SQInteger scisodigit(char c) { return c >= '0' && c <= '7'; } SQInteger isexponent(SQInteger c) { return c == 'e' || c=='E'; } @@ -409,7 +371,6 @@ SQInteger SQLexer::ReadNumber() #define TSCIENTIFIC 4 #define TOCTAL 5 SQInteger type = TINT, firstchar = CUR_CHAR; - SQChar *sTemp; INIT_TEMP_STRING(); NEXT(); if(firstchar == '0' && (toupper(CUR_CHAR) == 'X' || scisodigit(CUR_CHAR)) ) { @@ -419,7 +380,7 @@ SQInteger SQLexer::ReadNumber() APPEND_CHAR(CUR_CHAR); NEXT(); } - if(isdigit(CUR_CHAR)) Error("invalid octal number"); + if(isdigit(CUR_CHAR)) throw CompileException("invalid octal number"); } else { NEXT(); @@ -428,7 +389,7 @@ SQInteger SQLexer::ReadNumber() APPEND_CHAR(CUR_CHAR); NEXT(); } - if(_longstr.size() > MAX_HEX_DIGITS) Error("too many digits for an Hex number"); + if(_longstr.size() > MAX_HEX_DIGITS) throw CompileException("too many digits for an Hex number"); } } else { @@ -436,7 +397,7 @@ SQInteger SQLexer::ReadNumber() while (CUR_CHAR == '.' || isdigit(CUR_CHAR) || isexponent(CUR_CHAR)) { if(CUR_CHAR == '.' || isexponent(CUR_CHAR)) type = TFLOAT; if(isexponent(CUR_CHAR)) { - if(type != TFLOAT) Error("invalid numeric format"); + if(type != TFLOAT) throw CompileException("invalid numeric format"); type = TSCIENTIFIC; APPEND_CHAR(CUR_CHAR); NEXT(); @@ -444,27 +405,29 @@ SQInteger SQLexer::ReadNumber() APPEND_CHAR(CUR_CHAR); NEXT(); } - if(!isdigit(CUR_CHAR)) Error("exponent expected"); + if(!isdigit(CUR_CHAR)) throw CompileException("exponent expected"); } APPEND_CHAR(CUR_CHAR); NEXT(); } } - TERMINATE_BUFFER(); switch(type) { case TSCIENTIFIC: - case TFLOAT: - _fvalue = (SQFloat)strtod(&_longstr[0],&sTemp); + case TFLOAT: { + std::string str{View()}; + char *sTemp; + _fvalue = (SQFloat)strtod(str.c_str(),&sTemp); return TK_FLOAT; + } case TINT: - LexInteger(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + _nvalue = ParseInteger(View(), 10).value_or(0); return TK_INTEGER; case THEX: - LexHexadecimal(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + _nvalue = ParseInteger(View(), 16).value_or(0); return TK_INTEGER; case TOCTAL: - LexOctal(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + _nvalue = ParseInteger(View(), 8).value_or(0); return TK_INTEGER; } return 0; @@ -478,10 +441,6 @@ SQInteger SQLexer::ReadID() APPEND_CHAR(CUR_CHAR); NEXT(); } while(isalnum(CUR_CHAR) || CUR_CHAR == '_'); - TERMINATE_BUFFER(); - res = GetIDType(&_longstr[0]); - if(res == TK_IDENTIFIER || res == TK_CONSTRUCTOR) { - _svalue = &_longstr[0]; - } + res = GetIDType(View()); return res; } diff --git a/src/3rdparty/squirrel/squirrel/sqlexer.h b/src/3rdparty/squirrel/squirrel/sqlexer.h index 9ce9469b24..0281e9dff8 100644 --- a/src/3rdparty/squirrel/squirrel/sqlexer.h +++ b/src/3rdparty/squirrel/squirrel/sqlexer.h @@ -6,11 +6,10 @@ struct SQLexer { ~SQLexer(); SQLexer(SQSharedState *ss,SQLEXREADFUNC rg,SQUserPointer up); - [[noreturn]] void Error(const SQChar *err); SQInteger Lex(); - const SQChar *Tok2Str(SQInteger tok); + std::optional Tok2Str(SQInteger tok); private: - SQInteger GetIDType(SQChar *s); + SQInteger GetIDType(std::string_view s); SQInteger ReadString(char32_t ndelim,bool verbatim); SQInteger ReadNumber(); void LexBlockComment(); @@ -20,21 +19,21 @@ private: SQTable *_keywords; void INIT_TEMP_STRING() { _longstr.resize(0); } void APPEND_CHAR(char32_t c); - void TERMINATE_BUFFER() { _longstr.push_back('\0'); } public: SQInteger _prevtoken; SQInteger _currentline; SQInteger _lasttokenline; SQInteger _currentcolumn; - const SQChar *_svalue; SQInteger _nvalue; SQFloat _fvalue; SQLEXREADFUNC _readf; SQUserPointer _up; char32_t _currdata; SQSharedState *_sharedstate; - sqvector _longstr; + sqvector _longstr; + + std::string_view View() const { return std::string_view(_longstr._vals, _longstr.size()); } }; #endif diff --git a/src/3rdparty/squirrel/squirrel/sqobject.cpp b/src/3rdparty/squirrel/squirrel/sqobject.cpp index a0f9c75363..9fc3ae3545 100644 --- a/src/3rdparty/squirrel/squirrel/sqobject.cpp +++ b/src/3rdparty/squirrel/squirrel/sqobject.cpp @@ -18,7 +18,7 @@ #include "../../../safeguards.h" -const SQChar *IdType2Name(SQObjectType type) +std::string_view IdType2Name(SQObjectType type) { switch(_RAW_TYPE(type)) { @@ -42,25 +42,25 @@ const SQChar *IdType2Name(SQObjectType type) case _RT_INSTANCE: return "instance"; case _RT_WEAKREF: return "weakref"; default: - return nullptr; + NOT_REACHED(); } } -const SQChar *GetTypeName(const SQObjectPtr &obj1) +std::string_view GetTypeName(const SQObjectPtr &obj1) { return IdType2Name(type(obj1)); } -SQString *SQString::Create(SQSharedState *ss,const SQChar *s,SQInteger len) +SQString *SQString::Create(SQSharedState *ss,std::string_view s) { - SQString *str=ADD_STRING(ss,s,len); + SQString *str=ss->_stringtable->Add(s); str->_sharedstate=ss; return str; } void SQString::Release() { - REMOVE_STRING(_sharedstate,this); + _sharedstate->_stringtable->Remove(this); } SQInteger SQString::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval) @@ -202,24 +202,21 @@ void SQArray::Extend(const SQArray *a){ Append(a->_values[i]); } -const SQChar* SQFunctionProto::GetLocal(SQVM *vm,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop) +std::optional SQFunctionProto::GetLocal(SQVM *vm,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop) { SQUnsignedInteger nvars=_nlocalvarinfos; - const SQChar *res=nullptr; if(nvars>=nseq){ for(SQUnsignedInteger i=0;i=nop) - { + if(_localvarinfos[i]._start_op<=nop && _localvarinfos[i]._end_op>=nop) { if(nseq==0){ vm->Push(vm->_stack[stackbase+_localvarinfos[i]._pos]); - res=_stringval(_localvarinfos[i]._name); - break; + return _stringval(_localvarinfos[i]._name); } nseq--; } } } - return res; + return std::nullopt; } SQInteger SQFunctionProto::GetLine(SQInstruction *curr) @@ -273,10 +270,13 @@ bool WriteObject(HSQUIRRELVM v,SQUserPointer up,SQWRITEFUNC write,SQObjectPtr &o { _CHECK_IO(SafeWrite(v,write,up,&type(o),sizeof(SQObjectType))); switch(type(o)){ - case OT_STRING: - _CHECK_IO(SafeWrite(v,write,up,&_string(o)->_len,sizeof(SQInteger))); - _CHECK_IO(SafeWrite(v,write,up,_stringval(o),_string(o)->_len)); + case OT_STRING: { + auto str = _string(o)->Span(); + SQInteger len = str.size(); + _CHECK_IO(SafeWrite(v,write,up,&len,sizeof(len))); + _CHECK_IO(SafeWrite(v,write,up,str.data(),len)); break; + } case OT_INTEGER: _CHECK_IO(SafeWrite(v,write,up,&_integer(o),sizeof(SQInteger)));break; case OT_FLOAT: @@ -297,11 +297,11 @@ bool ReadObject(HSQUIRRELVM v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &o) switch(t){ case OT_STRING:{ SQInteger len; - _CHECK_IO(SafeRead(v,read,up,&len,sizeof(SQInteger))); - _CHECK_IO(SafeRead(v,read,up,_ss(v)->GetScratchPad(len),len)); - o=SQString::Create(_ss(v),_ss(v)->GetScratchPad(-1),len); - } + _CHECK_IO(SafeRead(v,read,up,&len,sizeof(len))); + _CHECK_IO(SafeRead(v,read,up,_ss(v)->GetScratchPad(len).data(),len)); + o=SQString::Create(_ss(v),std::string_view(_ss(v)->GetScratchPad(-1).data(),len)); break; + } case OT_INTEGER:{ SQInteger i; _CHECK_IO(SafeRead(v,read,up,&i,sizeof(SQInteger))); o = i; break; @@ -323,7 +323,7 @@ bool ReadObject(HSQUIRRELVM v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &o) bool SQClosure::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write) { _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_HEAD)); - _CHECK_IO(WriteTag(v,write,up,sizeof(SQChar))); + _CHECK_IO(WriteTag(v,write,up,sizeof(char))); _CHECK_IO(_funcproto(_function)->Save(v,up,write)); _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_TAIL)); return true; @@ -332,7 +332,7 @@ bool SQClosure::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write) bool SQClosure::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret) { _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_HEAD)); - _CHECK_IO(CheckTag(v,read,up,sizeof(SQChar))); + _CHECK_IO(CheckTag(v,read,up,sizeof(char))); SQObjectPtr func; _CHECK_IO(SQFunctionProto::Load(v,up,read,func)); _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_TAIL)); diff --git a/src/3rdparty/squirrel/squirrel/sqobject.h b/src/3rdparty/squirrel/squirrel/sqobject.h index 3b3c516067..d9bce1732d 100644 --- a/src/3rdparty/squirrel/squirrel/sqobject.h +++ b/src/3rdparty/squirrel/squirrel/sqobject.h @@ -138,7 +138,7 @@ struct SQObjectPtr; #define _refcounted(obj) ((obj)._unVal.pRefCounted) #define _rawval(obj) ((obj)._unVal.raw) -#define _stringval(obj) (obj)._unVal.pString->_val +#define _stringval(obj) (obj)._unVal.pString->View() #define _userdataval(obj) (obj)._unVal.pUserData->_val #define tofloat(num) ((type(num)==OT_INTEGER)?(SQFloat)_integer(num):_float(num)) @@ -357,7 +357,7 @@ struct SQObjectPtr : public SQObject return *this; } private: - SQObjectPtr(const SQChar *){} //safety + SQObjectPtr(const char *) = delete; //safety }; inline void _Swap(SQObject &a,SQObject &b) @@ -449,8 +449,8 @@ struct SQDelegable : public CHAINABLE_OBJ { SQUnsignedInteger TranslateIndex(const SQObjectPtr &idx); typedef sqvector SQObjectPtrVec; typedef sqvector SQIntVec; -const SQChar *GetTypeName(const SQObjectPtr &obj1); -const SQChar *IdType2Name(SQObjectType type); +std::string_view GetTypeName(const SQObjectPtr &obj1); +std::string_view IdType2Name(SQObjectType type); diff --git a/src/3rdparty/squirrel/squirrel/sqopcodes.h b/src/3rdparty/squirrel/squirrel/sqopcodes.h index a9e4a8743c..47d0e5197a 100644 --- a/src/3rdparty/squirrel/squirrel/sqopcodes.h +++ b/src/3rdparty/squirrel/squirrel/sqopcodes.h @@ -87,7 +87,7 @@ enum SQOpcode }; struct SQInstructionDesc { - const SQChar *name; + std::string_view name; }; struct SQInstruction diff --git a/src/3rdparty/squirrel/squirrel/sqstate.cpp b/src/3rdparty/squirrel/squirrel/sqstate.cpp index 3adb4d8f98..f75c466da0 100644 --- a/src/3rdparty/squirrel/squirrel/sqstate.cpp +++ b/src/3rdparty/squirrel/squirrel/sqstate.cpp @@ -14,6 +14,7 @@ #include "sqarray.h" #include "squserdata.h" #include "sqclass.h" +#include "../../../core/string_consumer.hpp" #include "../../../safeguards.h" @@ -32,14 +33,12 @@ SQObjectPtr _minusone_((SQInteger)-1); _table(_metamethodsmap)->NewSlot(_metamethods->back(),(SQInteger)(_metamethods->size()-1)); \ } -bool CompileTypemask(SQIntVec &res,const SQChar *typemask) +bool CompileTypemask(SQIntVec &res,std::string_view typemask) { - SQInteger i = 0; - SQInteger mask = 0; - while(typemask[i] != 0) { - - switch(typemask[i]){ + StringConsumer consumer{typemask}; + while (consumer.AnyBytesLeft()) { + switch(consumer.ReadChar()){ case 'o': mask |= _RT_NULL; break; case 'i': mask |= _RT_INTEGER; break; case 'f': mask |= _RT_FLOAT; break; @@ -56,37 +55,32 @@ bool CompileTypemask(SQIntVec &res,const SQChar *typemask) case 'x': mask |= _RT_INSTANCE; break; case 'y': mask |= _RT_CLASS; break; case 'r': mask |= _RT_WEAKREF; break; - case '.': mask = -1; res.push_back(mask); i++; mask = 0; continue; - case ' ': i++; continue; //ignores spaces + case '.': mask = -1; res.push_back(mask); mask = 0; continue; + case ' ': continue; //ignores spaces default: return false; } - i++; - if(typemask[i] == '|') { - i++; - if(typemask[i] == 0) - return false; + + if(consumer.ReadCharIf('|')) { + if(!consumer.AnyBytesLeft()) return false; continue; } res.push_back(mask); mask = 0; - } return true; } -SQTable *CreateDefaultDelegate(SQSharedState *ss,SQRegFunction *funcz) +SQTable *CreateDefaultDelegate(SQSharedState *ss,const std::initializer_list &funcz) { - SQInteger i=0; SQTable *t=SQTable::Create(ss,0); - while(funcz[i].name!=nullptr){ - SQNativeClosure *nc = SQNativeClosure::Create(ss,funcz[i].f); - nc->_nparamscheck = funcz[i].nparamscheck; - nc->_name = SQString::Create(ss,funcz[i].name); - if(funcz[i].typemask && !CompileTypemask(nc->_typecheck,funcz[i].typemask)) + for (auto &func : funcz) { + SQNativeClosure *nc = SQNativeClosure::Create(ss,func.f); + nc->_nparamscheck = func.nparamscheck; + nc->_name = SQString::Create(ss,func.name); + if(func.typemask.has_value() && !CompileTypemask(nc->_typecheck,*func.typemask)) return nullptr; - t->NewSlot(SQString::Create(ss,funcz[i].name),nc); - i++; + t->NewSlot(SQString::Create(ss,func.name),nc); } return t; } @@ -97,8 +91,6 @@ SQSharedState::SQSharedState() _printfunc = nullptr; _debuginfo = false; _notifyallexceptions = false; - _scratchpad=nullptr; - _scratchpadsize=0; _collectable_free_processing = false; #ifndef NO_GARBAGE_COLLECTOR _gc_chain=nullptr; @@ -212,7 +204,6 @@ SQSharedState::~SQSharedState() sq_delete(_systemstrings,SQObjectPtrVec); sq_delete(_metamethods,SQObjectPtrVec); sq_delete(_stringtable,SQStringTable); - if(_scratchpad)SQ_FREE(_scratchpad,_scratchpadsize); } @@ -357,19 +348,16 @@ void SQCollectable::RemoveFromChain(SQCollectable **chain,SQCollectable *c) } #endif -SQChar* SQSharedState::GetScratchPad(SQInteger size) +std::span SQSharedState::GetScratchPad(SQInteger size) { SQInteger newsize; if(size>0) { - if(_scratchpadsize < size) { + if(_scratchpad.size() < static_cast(size)) { newsize = size + (size>>1); - _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize); - _scratchpadsize = newsize; - - }else if(_scratchpadsize >= (size<<5)) { - newsize = _scratchpadsize >> 1; - _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize); - _scratchpadsize = newsize; + _scratchpad.resize(newsize); + }else if(_scratchpad.size() >= static_cast(size<<5)) { + newsize = _scratchpad.size() >> 1; + _scratchpad.resize(newsize); } } return _scratchpad; @@ -550,36 +538,36 @@ void SQStringTable::AllocNodes(SQInteger size) { _numofslots = size; _strings = (SQString**)SQ_MALLOC(sizeof(SQString*)*_numofslots); - memset(_strings,0,sizeof(SQString*)*(size_t)_numofslots); + std::fill_n(_strings, _numofslots, nullptr); } -SQString *SQStringTable::Add(const SQChar *news,SQInteger len) +static const std::hash string_table_hash{}; + +SQString *SQStringTable::Add(std::string_view new_string) { - if(len<0) - len = (SQInteger)strlen(news); - SQHash h = ::_hashstr(news,(size_t)len)&(_numofslots-1); + size_t len = new_string.size(); + auto slot = string_table_hash(new_string) & (_numofslots-1); SQString *s; - for (s = _strings[h]; s; s = s->_next){ - if(s->_len == len && (!memcmp(news,s->_val,(size_t)len))) - return s; //found + for (s = _strings[slot]; s; s = s->_next){ + if(s->View() == new_string) return s; //found } SQString *t=(SQString *)SQ_MALLOC(len+sizeof(SQString)); - new (t) SQString(news, len); - t->_next = _strings[h]; - _strings[h] = t; + new (t) SQString(new_string); + t->_next = _strings[slot]; + _strings[slot] = t; _slotused++; if (_slotused > _numofslots) /* too crowded? */ Resize(_numofslots*2); return t; } -SQString::SQString(const SQChar *news, SQInteger len) +SQString::SQString(std::string_view new_string) { - memcpy(_val,news,(size_t)len); - _val[len] = '\0'; - _len = len; - _hash = ::_hashstr(news,(size_t)len); + std::ranges::copy(new_string, _val); + _val[new_string.size()] = '\0'; + _len = new_string.size(); + _hash = string_table_hash(new_string); _next = nullptr; _sharedstate = nullptr; } @@ -615,7 +603,7 @@ void SQStringTable::Remove(SQString *bs) else _strings[h] = s->_next; _slotused--; - SQInteger slen = s->_len; + size_t slen = s->View().size(); s->~SQString(); SQ_FREE(s,sizeof(SQString) + slen); return; diff --git a/src/3rdparty/squirrel/squirrel/sqstate.h b/src/3rdparty/squirrel/squirrel/sqstate.h index 547e6de47d..d98b903b05 100644 --- a/src/3rdparty/squirrel/squirrel/sqstate.h +++ b/src/3rdparty/squirrel/squirrel/sqstate.h @@ -13,7 +13,7 @@ struct SQStringTable { SQStringTable(); ~SQStringTable(); - SQString *Add(const SQChar *,SQInteger len); + SQString *Add(std::string_view str); void Remove(SQString *); private: void Resize(SQInteger size); @@ -49,9 +49,6 @@ private: RefNode **_buckets; }; -#define ADD_STRING(ss,str,len) ss->_stringtable->Add(str,len) -#define REMOVE_STRING(ss,bstr) ss->_stringtable->Remove(bstr) - struct SQObjectPtr; struct SQSharedState @@ -59,7 +56,7 @@ struct SQSharedState SQSharedState(); ~SQSharedState(); public: - SQChar* GetScratchPad(SQInteger size); + std::span GetScratchPad(SQInteger size); SQInteger GetMetaMethodIdxByName(const SQObjectPtr &name); void DelayFinalFree(SQCollectable *collectable); #ifndef NO_GARBAGE_COLLECTOR @@ -84,33 +81,32 @@ public: #endif SQObjectPtr _root_vm; SQObjectPtr _table_default_delegate; - static SQRegFunction _table_default_delegate_funcz[]; + static const std::initializer_list _table_default_delegate_funcz; SQObjectPtr _array_default_delegate; - static SQRegFunction _array_default_delegate_funcz[]; + static const std::initializer_list _array_default_delegate_funcz; SQObjectPtr _string_default_delegate; - static SQRegFunction _string_default_delegate_funcz[]; + static const std::initializer_list _string_default_delegate_funcz; SQObjectPtr _number_default_delegate; - static SQRegFunction _number_default_delegate_funcz[]; + static const std::initializer_list _number_default_delegate_funcz; SQObjectPtr _generator_default_delegate; - static SQRegFunction _generator_default_delegate_funcz[]; + static const std::initializer_list _generator_default_delegate_funcz; SQObjectPtr _closure_default_delegate; - static SQRegFunction _closure_default_delegate_funcz[]; + static const std::initializer_list _closure_default_delegate_funcz; SQObjectPtr _thread_default_delegate; - static SQRegFunction _thread_default_delegate_funcz[]; + static const std::initializer_list _thread_default_delegate_funcz; SQObjectPtr _class_default_delegate; - static SQRegFunction _class_default_delegate_funcz[]; + static const std::initializer_list _class_default_delegate_funcz; SQObjectPtr _instance_default_delegate; - static SQRegFunction _instance_default_delegate_funcz[]; + static const std::initializer_list _instance_default_delegate_funcz; SQObjectPtr _weakref_default_delegate; - static SQRegFunction _weakref_default_delegate_funcz[]; + static const std::initializer_list _weakref_default_delegate_funcz; SQCOMPILERERROR _compilererrorhandler; SQPRINTFUNCTION _printfunc; bool _debuginfo; bool _notifyallexceptions; private: - SQChar *_scratchpad; - SQInteger _scratchpadsize; + std::vector _scratchpad; }; #define _sp(s) (_sharedstate->GetScratchPad(s)) @@ -133,6 +129,6 @@ extern SQObjectPtr _false_; extern SQObjectPtr _one_; extern SQObjectPtr _minusone_; -bool CompileTypemask(SQIntVec &res,const SQChar *typemask); +bool CompileTypemask(SQIntVec &res,std::string_view typemask); #endif //_SQSTATE_H_ diff --git a/src/3rdparty/squirrel/squirrel/sqstring.h b/src/3rdparty/squirrel/squirrel/sqstring.h index 4d14e4adb4..830a71b8fa 100644 --- a/src/3rdparty/squirrel/squirrel/sqstring.h +++ b/src/3rdparty/squirrel/squirrel/sqstring.h @@ -2,29 +2,23 @@ #ifndef _SQSTRING_H_ #define _SQSTRING_H_ -inline SQHash _hashstr (const SQChar *s, size_t l) -{ - SQHash h = (SQHash)l; /* seed */ - size_t step = (l>>5)|1; /* if string is too long, don't hash all its chars */ - for (; l>=step; l-=step) - h = h ^ ((h<<5)+(h>>2)+(unsigned short)*(s++)); - return h; -} - struct SQString : public SQRefCounted { - SQString(const SQChar *news, SQInteger len); + SQString(std::string_view str); ~SQString(){} public: - static SQString *Create(SQSharedState *ss, const SQChar *, SQInteger len = -1 ); - static SQString *Create(SQSharedState *ss, const std::string &str) { return Create(ss, str.data(), str.size()); } + static SQString *Create(SQSharedState *ss, std::string_view str); SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval); void Release() override; SQSharedState *_sharedstate; SQString *_next; //chain for the string table + std::size_t _hash; + + std::string_view View() const { return std::string_view(this->_val, this->_len); } + std::span Span() { return std::span(this->_val, this->_len); } +private: SQInteger _len; - SQHash _hash; - SQChar _val[1]; + char _val[1]; }; diff --git a/src/3rdparty/squirrel/squirrel/squserdata.h b/src/3rdparty/squirrel/squirrel/squserdata.h index de75ca3c37..108c0f8f1e 100644 --- a/src/3rdparty/squirrel/squirrel/squserdata.h +++ b/src/3rdparty/squirrel/squirrel/squserdata.h @@ -31,7 +31,7 @@ struct SQUserData : SQDelegable SQInteger _size; SQRELEASEHOOK _hook; SQUserPointer _typetag; - SQChar _val[1]; + char _val[1]; }; #endif //_SQUSERDATA_H_ diff --git a/src/3rdparty/squirrel/squirrel/squtils.h b/src/3rdparty/squirrel/squirrel/squtils.h index 2c7a343638..87d0473a26 100644 --- a/src/3rdparty/squirrel/squirrel/squtils.h +++ b/src/3rdparty/squirrel/squirrel/squtils.h @@ -93,7 +93,7 @@ public: { _vals[idx].~T(); if(idx < (_size - 1)) { - memmove(static_cast(&_vals[idx]), &_vals[idx+1], sizeof(T) * (_size - (size_t)idx - 1)); + std::move(&_vals[idx + 1], &_vals[_size], &_vals[idx]); } _size--; } diff --git a/src/3rdparty/squirrel/squirrel/sqvm.cpp b/src/3rdparty/squirrel/squirrel/sqvm.cpp index bb50dc2244..1637c359f3 100644 --- a/src/3rdparty/squirrel/squirrel/sqvm.cpp +++ b/src/3rdparty/squirrel/squirrel/sqvm.cpp @@ -117,7 +117,6 @@ SQVM::SQVM(SQSharedState *ss) _in_stackoverflow = false; _ops_till_suspend = 0; _ops_till_suspend_error_threshold = INT64_MIN; - _ops_till_suspend_error_label = nullptr; _callsstack = nullptr; _callsstacksize = 0; _alloccallsstacksize = 0; @@ -198,7 +197,7 @@ bool SQVM::ObjCmp(const SQObjectPtr &o1,const SQObjectPtr &o2,SQInteger &result) SQObjectPtr res; switch(type(o1)){ case OT_STRING: - _RET_SUCCEED(strcmp(_stringval(o1),_stringval(o2))); + _RET_SUCCEED(_stringval(o1).compare(_stringval(o2))); case OT_INTEGER: /* FS#3954: wrong integer comparison */ _RET_SUCCEED((_integer(o1)<_integer(o2))?-1:(_integer(o1)==_integer(o2))?0:1); @@ -302,11 +301,7 @@ bool SQVM::StringCat(const SQObjectPtr &str,const SQObjectPtr &obj,SQObjectPtr & SQObjectPtr a, b; ToString(str, a); ToString(obj, b); - SQInteger l = _string(a)->_len , ol = _string(b)->_len; - SQChar *s = _sp(l + ol + 1); - memcpy(s, _stringval(a), (size_t)l); - memcpy(s + l, _stringval(b), (size_t)ol); - dest = SQString::Create(_ss(this), _spval, l + ol); + dest = SQString::Create(_ss(this), fmt::format("{}{}", _stringval(a), _stringval(b))); return true; } @@ -1253,7 +1248,8 @@ bool SQVM::Get(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &dest, if(fetchroot) { if(_rawval(STK(0)) == _rawval(self) && type(STK(0)) == type(self)) { - return _table(_roottable)->Get(key,dest); + if (_table(_roottable)->Get(key,dest)) return true; + return _table(_ss(this)->_consts)->Get(key,dest); } } return false; @@ -1288,9 +1284,10 @@ bool SQVM::FallBackGet(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPt case OT_STRING: if(sq_isnumeric(key)){ SQInteger n=tointeger(key); - if(abs((int)n)<_string(self)->_len){ - if(n<0)n=_string(self)->_len-n; - dest=SQInteger(_stringval(self)[n]); + std::string_view str = _stringval(self); + if(std::abs(n) < static_cast(str.size())){ + if(n<0)n=str.size()+n; + dest=SQInteger(str[n]); return true; } return false; diff --git a/src/3rdparty/squirrel/squirrel/sqvm.h b/src/3rdparty/squirrel/squirrel/sqvm.h index b083cf3bdd..34554678a5 100644 --- a/src/3rdparty/squirrel/squirrel/sqvm.h +++ b/src/3rdparty/squirrel/squirrel/sqvm.h @@ -169,7 +169,7 @@ public: SQBool _can_suspend; SQInteger _ops_till_suspend; SQInteger _ops_till_suspend_error_threshold; - const char *_ops_till_suspend_error_label; + std::string_view _ops_till_suspend_error_label; SQBool _in_stackoverflow; bool ShouldSuspend() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c2207476f8..4d7bdf075b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,6 +92,7 @@ add_files( bridge_gui.cpp bridge_map.cpp bridge_map.h + bridge_type.h build_vehicle_gui.cpp cachecheck.cpp cargo_type.h @@ -188,7 +189,6 @@ add_files( fios_gui.cpp fontcache.cpp fontcache.h - fontdetection.h framerate_gui.cpp framerate_type.h gamelog.cpp @@ -280,6 +280,10 @@ add_files( newgrf_animation_type.h newgrf_badge.cpp newgrf_badge.h + newgrf_badge_config.cpp + newgrf_badge_config.h + newgrf_badge_gui.cpp + newgrf_badge_gui.h newgrf_badge_type.h newgrf_callbacks.h newgrf_canal.cpp @@ -451,6 +455,7 @@ add_files( spritecache.cpp spritecache.h spritecache_internal.h + spritecache_type.h station.cpp station_base.h station_cmd.cpp @@ -459,6 +464,7 @@ add_files( station_gui.cpp station_gui.h station_kdtree.h + station_layout_type.h station_map.h station_type.h statusbar_gui.cpp diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp index becef23813..0422e1c8fb 100644 --- a/src/ai/ai_gui.cpp +++ b/src/ai/ai_gui.cpp @@ -86,7 +86,7 @@ static constexpr NWidgetPart _nested_ai_config_widgets[] = { /** Window definition for the configure AI window. */ static WindowDesc _ai_config_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_GAME_OPTIONS, WC_NONE, {}, _nested_ai_config_widgets @@ -144,7 +144,7 @@ struct AIConfigWindow : public Window { case WID_AIC_LIST: this->line_height = GetCharacterHeight(FS_NORMAL) + padding.height; - resize.height = this->line_height; + fill.height = resize.height = this->line_height; size.height = 8 * this->line_height; break; } @@ -220,7 +220,7 @@ struct AIConfigWindow : public Window { if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_CONTENT_END) { if (this->selected_slot == CompanyID::Invalid() || AIConfig::GetConfig(this->selected_slot) == nullptr) return; - ShowScriptTextfileWindow((TextfileType)(widget - WID_AIC_TEXTFILE), this->selected_slot); + ShowScriptTextfileWindow(this, static_cast(widget - WID_AIC_TEXTFILE), this->selected_slot); return; } @@ -252,23 +252,23 @@ struct AIConfigWindow : public Window { } case WID_AIC_LIST: { // Select a slot - this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget); + this->selected_slot = static_cast(this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget)); this->InvalidateData(); - if (click_count > 1 && IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, citymania::_fn_mod); + if (click_count > 1 && IsEditable(this->selected_slot)) ShowScriptListWindow(this->selected_slot, citymania::_fn_mod); break; } case WID_AIC_MOVE_UP: - if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot - 1))) { + if (IsEditable(this->selected_slot) && IsEditable(static_cast(this->selected_slot - 1))) { std::swap(GetGameSettings().script_config.ai[this->selected_slot], GetGameSettings().script_config.ai[this->selected_slot - 1]); - this->selected_slot = CompanyID(this->selected_slot - 1); + this->selected_slot = static_cast(this->selected_slot - 1); this->vscroll->ScrollTowards(this->selected_slot.base()); this->InvalidateData(); } break; case WID_AIC_MOVE_DOWN: - if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot + 1))) { + if (IsEditable(this->selected_slot) && IsEditable(static_cast(this->selected_slot + 1))) { std::swap(GetGameSettings().script_config.ai[this->selected_slot], GetGameSettings().script_config.ai[this->selected_slot + 1]); ++this->selected_slot; this->vscroll->ScrollTowards(this->selected_slot.base()); @@ -284,11 +284,11 @@ struct AIConfigWindow : public Window { } case WID_AIC_CHANGE: // choose other AI - if (IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, citymania::_fn_mod); + if (IsEditable(this->selected_slot)) ShowScriptListWindow(this->selected_slot, citymania::_fn_mod); break; case WID_AIC_CONFIGURE: // change the settings for an AI - ShowScriptSettingsWindow((CompanyID)this->selected_slot); + ShowScriptSettingsWindow(this->selected_slot); break; case WID_AIC_CONTENT_DOWNLOAD: @@ -322,8 +322,8 @@ struct AIConfigWindow : public Window { this->SetWidgetDisabledState(WID_AIC_INCREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MAX_COMPETITORS_INTERVAL); this->SetWidgetDisabledState(WID_AIC_CHANGE, !IsEditable(this->selected_slot)); this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == CompanyID::Invalid() || config->GetConfigList()->empty()); - this->SetWidgetDisabledState(WID_AIC_MOVE_UP, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot - 1))); - this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot + 1))); + this->SetWidgetDisabledState(WID_AIC_MOVE_UP, !IsEditable(this->selected_slot) || !IsEditable(static_cast(this->selected_slot - 1))); + this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, !IsEditable(this->selected_slot) || !IsEditable(static_cast(this->selected_slot + 1))); this->SetWidgetDisabledState(WID_AIC_OPEN_URL, this->selected_slot == CompanyID::Invalid() || config->GetInfo() == nullptr || config->GetInfo()->GetURL().empty()); for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp index 79d5ca96dd..e2d71aec1e 100644 --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -27,14 +27,14 @@ static bool CheckAPIVersion(const std::string &api_version) return std::ranges::find(AIInfo::ApiVersions, api_version) != std::end(AIInfo::ApiVersions); } -template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq_pushstring(vm, "AIInfo", -1); return 1; } +template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq_pushstring(vm, "AIInfo"); return 1; } -/* static */ void AIInfo::RegisterAPI(Squirrel *engine) +/* static */ void AIInfo::RegisterAPI(Squirrel &engine) { /* Create the AIInfo class, and add the RegisterAI function */ DefSQClass SQAIInfo("AIInfo"); SQAIInfo.PreRegister(engine); - SQAIInfo.AddConstructor(engine, "x"); + SQAIInfo.AddConstructor(engine, "x"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddSetting, "AddSetting"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddLabels, "AddLabels"); SQAIInfo.DefSQConst(engine, ScriptConfigFlags{}.base(), "CONFIG_NONE"); @@ -50,8 +50,8 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq SQAIInfo.DefSQConst(engine, ScriptConfigFlags{ScriptConfigFlag::InGame}.base(), "AICONFIG_INGAME"); SQAIInfo.PostRegister(engine); - engine->AddMethod("RegisterAI", &AIInfo::Constructor, 2, "tx"); - engine->AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, 2, "tx"); + engine.AddMethod("RegisterAI", &AIInfo::Constructor, "tx"); + engine.AddMethod("RegisterDummyAI", &AIInfo::DummyConstructor, "tx"); } /* static */ SQInteger AIInfo::Constructor(HSQUIRRELVM vm) @@ -61,11 +61,12 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq if (SQ_FAILED(sq_getinstanceup(vm, 2, &instance, nullptr)) || instance == nullptr) return sq_throwerror(vm, "Pass an instance of a child class of AIInfo to RegisterAI"); AIInfo *info = (AIInfo *)instance; - SQInteger res = ScriptInfo::Constructor(vm, info); + 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->min_loadable_version < 0) return SQ_ERROR; } else { info->min_loadable_version = info->GetVersion(); } @@ -89,7 +90,7 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq /* 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 */ - info->GetScanner()->RegisterScript(info); + info->GetScanner()->RegisterScript(std::unique_ptr{info}); return 0; } @@ -101,13 +102,13 @@ template <> SQInteger PushClassName(HSQUIRRELVM vm) { sq AIInfo *info = (AIInfo *)instance; info->api_version = *std::rbegin(AIInfo::ApiVersions); - SQInteger res = ScriptInfo::Constructor(vm, info); + SQInteger res = ScriptInfo::Constructor(vm, *info); if (res != 0) return res; /* 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 */ - static_cast(info->GetScanner())->SetDummyAI(info); + static_cast(info->GetScanner())->SetDummyAI(std::unique_ptr(info)); return 0; } @@ -124,33 +125,32 @@ bool AIInfo::CanLoadFromVersion(int version) const } -/* static */ void AILibrary::RegisterAPI(Squirrel *engine) +/* static */ void AILibrary::RegisterAPI(Squirrel &engine) { /* Create the AILibrary class, and add the RegisterLibrary function */ - engine->AddClassBegin("AILibrary"); - engine->AddClassEnd(); - engine->AddMethod("RegisterLibrary", &AILibrary::Constructor, 2, "tx"); + engine.AddClassBegin("AILibrary"); + engine.AddClassEnd(); + engine.AddMethod("RegisterLibrary", &AILibrary::Constructor, "tx"); } /* static */ SQInteger AILibrary::Constructor(HSQUIRRELVM vm) { /* Create a new library */ - AILibrary *library = new AILibrary(); + auto library = std::make_unique(); - SQInteger res = ScriptInfo::Constructor(vm, library); + SQInteger res = ScriptInfo::Constructor(vm, *library); if (res != 0) { - delete library; return res; } /* Cache the category */ if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethod(library->SQ_instance, "GetCategory", &library->category, MAX_GET_OPS)) { - delete library; return SQ_ERROR; } /* Register the Library to the base system */ - library->GetScanner()->RegisterScript(library); + ScriptScanner *scanner = library->GetScanner(); + scanner->RegisterScript(std::move(library)); return 0; } diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp index dc2e0de500..3928588114 100644 --- a/src/ai/ai_info.hpp +++ b/src/ai/ai_info.hpp @@ -23,7 +23,7 @@ public: /** * Register the functions of this class. */ - static void RegisterAPI(Squirrel *engine); + static void RegisterAPI(Squirrel &engine); /** * Create an AI, using this AIInfo as start-template. @@ -64,7 +64,7 @@ public: /** * Register the functions of this class. */ - static void RegisterAPI(Squirrel *engine); + static void RegisterAPI(Squirrel &engine); /** * Create an AI, using this AIInfo as start-template. diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 07dcd40023..03d0f89792 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -27,7 +27,7 @@ #include "table/strings.h" /* Manually include the Text glue. */ -#include "../script/api/template/template_text.hpp.sq" +#include "../script/api/template/template_text.sq.hpp" /* Convert all AI related classes to Squirrel data. */ #include "../script/api/ai/ai_includes.hpp" @@ -43,7 +43,7 @@ void AIInstance::Initialize(AIInfo *info) this->api_version = info->GetAPIVersion(); /* Register the AIController (including the "import" command) */ - SQAIController_Register(this->engine); + SQAIController_Register(*this->engine); ScriptInstance::Initialize(info->GetMainScript(), info->GetInstanceName(), _current_company); } @@ -53,7 +53,7 @@ void AIInstance::RegisterAPI() ScriptInstance::RegisterAPI(); /* Register all classes */ - SQAI_RegisterAll(this->engine); + SQAI_RegisterAll(*this->engine); if (!this->LoadCompatibilityScripts(AI_DIR, AIInfo::ApiVersions)) this->Died(); } @@ -83,7 +83,7 @@ void AIInstance::Died() void AIInstance::LoadDummyScript() { - ScriptAllocatorScope alloc_scope(this->engine); + ScriptAllocatorScope alloc_scope(this->engine.get()); Script_CreateDummy(this->engine->GetVM(), STR_ERROR_AI_NO_AI_FOUND, "AI"); } diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp index 843229e699..70e5b55148 100644 --- a/src/ai/ai_scanner.cpp +++ b/src/ai/ai_scanner.cpp @@ -22,39 +22,31 @@ #include "../safeguards.h" -AIScannerInfo::AIScannerInfo() : - ScriptScanner(), - info_dummy(nullptr) -{ -} +AIScannerInfo::AIScannerInfo() = default; +AIScannerInfo::~AIScannerInfo() = default; void AIScannerInfo::Initialize() { ScriptScanner::Initialize("AIScanner"); - ScriptAllocatorScope alloc_scope(this->engine); + ScriptAllocatorScope alloc_scope(this->engine.get()); /* Create the dummy AI */ this->main_script = "%_dummy"; Script_CreateDummyInfo(this->engine->GetVM(), "AI", "ai"); } -void AIScannerInfo::SetDummyAI(class AIInfo *info) +void AIScannerInfo::SetDummyAI(std::unique_ptr &&info) { - this->info_dummy = info; + this->info_dummy = std::move(info); } -AIScannerInfo::~AIScannerInfo() +std::string AIScannerInfo::GetScriptName(ScriptInfo &info) { - delete this->info_dummy; + return info.GetName(); } -std::string AIScannerInfo::GetScriptName(ScriptInfo *info) -{ - return info->GetName(); -} - -void AIScannerInfo::RegisterAPI(class Squirrel *engine) +void AIScannerInfo::RegisterAPI(class Squirrel &engine) { AIInfo::RegisterAPI(engine); } @@ -63,7 +55,7 @@ AIInfo *AIScannerInfo::SelectRandomAI() const { if (_game_mode == GM_MENU) { Debug(script, 0, "The intro game should not use AI, loading 'dummy' AI."); - return this->info_dummy; + return this->info_dummy.get(); } /* Filter for AIs suitable as Random AI. */ @@ -72,7 +64,7 @@ AIInfo *AIScannerInfo::SelectRandomAI() const uint num_random_ais = std::ranges::distance(random_ais); if (num_random_ais == 0) { Debug(script, 0, "No suitable AI found, loading 'dummy' AI."); - return this->info_dummy; + return this->info_dummy.get(); } /* Pick a random AI */ @@ -125,13 +117,13 @@ void AIScannerLibrary::Initialize() ScriptScanner::Initialize("AIScanner"); } -std::string AIScannerLibrary::GetScriptName(ScriptInfo *info) +std::string AIScannerLibrary::GetScriptName(ScriptInfo &info) { - AILibrary *library = static_cast(info); - return fmt::format("{}.{}", library->GetCategory(), library->GetInstanceName()); + AILibrary &library = static_cast(info); + return fmt::format("{}.{}", library.GetCategory(), library.GetInstanceName()); } -void AIScannerLibrary::RegisterAPI(class Squirrel *engine) +void AIScannerLibrary::RegisterAPI(class Squirrel &engine) { AILibrary::RegisterAPI(engine); } diff --git a/src/ai/ai_scanner.hpp b/src/ai/ai_scanner.hpp index a2c8a7280e..364e5642a9 100644 --- a/src/ai/ai_scanner.hpp +++ b/src/ai/ai_scanner.hpp @@ -37,17 +37,17 @@ public: /** * Set the Dummy AI. */ - void SetDummyAI(class AIInfo *info); + void SetDummyAI(std::unique_ptr &&info); protected: - std::string GetScriptName(ScriptInfo *info) override; - const char *GetFileName() const override { return PATHSEP "info.nut"; } + std::string GetScriptName(ScriptInfo &info) override; + std::string_view GetFileName() const override { return PATHSEP "info.nut"; } Subdirectory GetDirectory() const override { return AI_DIR; } - const char *GetScannerName() const override { return "AIs"; } - void RegisterAPI(class Squirrel *engine) override; + std::string_view GetScannerName() const override { return "AIs"; } + void RegisterAPI(class Squirrel &engine) override; private: - AIInfo *info_dummy; ///< The dummy AI. + std::unique_ptr info_dummy; ///< The dummy AI. }; class AIScannerLibrary : public ScriptScanner { @@ -63,11 +63,11 @@ public: class AILibrary *FindLibrary(const std::string &library, int version); protected: - std::string GetScriptName(ScriptInfo *info) override; - const char *GetFileName() const override { return PATHSEP "library.nut"; } + std::string GetScriptName(ScriptInfo &info) override; + std::string_view GetFileName() const override { return PATHSEP "library.nut"; } Subdirectory GetDirectory() const override { return AI_LIBRARY_DIR; } - const char *GetScannerName() const override { return "AI Libraries"; } - void RegisterAPI(class Squirrel *engine) override; + std::string_view GetScannerName() const override { return "AI Libraries"; } + void RegisterAPI(class Squirrel &engine) override; }; #endif /* AI_SCANNER_HPP */ diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 5bd49928df..f06219087f 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -48,10 +48,7 @@ void Aircraft::UpdateDeltaXY() { - this->x_offs = -1; - this->y_offs = -1; - this->x_extent = 2; - this->y_extent = 2; + this->bounds = {{-1, -1, 0}, {2, 2, 0}, {}}; switch (this->subtype) { default: NOT_REACHED(); @@ -64,21 +61,21 @@ void Aircraft::UpdateDeltaXY() case LANDING: case HELILANDING: case FLYING: - this->x_extent = 24; - this->y_extent = 24; + /* Bounds are not centred on the aircraft. */ + this->bounds.extent.x = 24; + this->bounds.extent.y = 24; break; } - this->z_extent = 5; + this->bounds.extent.z = 5; break; case AIR_SHADOW: - this->z_extent = 1; - this->x_offs = 0; - this->y_offs = 0; + this->bounds.extent.z = 1; + this->bounds.origin = {}; break; case AIR_ROTOR: - this->z_extent = 1; + this->bounds.extent.z = 1; break; } } @@ -175,7 +172,7 @@ void Aircraft::GetImage(Direction direction, EngineImageType image_type, Vehicle { uint8_t spritenum = this->spritenum; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleSprite(this, direction, image_type, result); if (result->IsValid()) return; @@ -191,7 +188,7 @@ void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteS assert(v->subtype == AIR_HELICOPTER); const Aircraft *w = v->Next()->Next(); - if (is_custom_sprite(v->spritenum)) { + if (IsCustomVehicleSpriteNum(v->spritenum)) { GetCustomRotorSprite(v, image_type, result); if (result->IsValid()) return; } @@ -205,7 +202,7 @@ static void GetAircraftIcon(EngineID engine, EngineImageType image_type, Vehicle const Engine *e = Engine::Get(engine); uint8_t spritenum = e->u.air.image_index; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleIcon(engine, DIR_W, image_type, result); if (result->IsValid()) return; @@ -1125,6 +1122,15 @@ static bool AircraftController(Aircraft *v) } if (amd.flags.Test(AirportMovingDataFlag::Land)) { + if (st->airport.blocks.Test(AirportBlock::Zeppeliner)) { + /* Zeppeliner blocked the runway, abort landing */ + v->state = FLYING; + UpdateAircraftCache(v); + SetAircraftPosition(v, gp.x, gp.y, GetAircraftFlightLevel(v)); + v->pos = v->previous_pos; + continue; + } + if (st->airport.tile == INVALID_TILE) { /* Airport has been removed, abort the landing procedure */ v->state = FLYING; @@ -1430,7 +1436,7 @@ static void AircraftLandAirplane(Aircraft *v) v->UpdateDeltaXY(); - AirportTileAnimationTrigger(st, vt, AAT_STATION_AIRPLANE_LAND); + TriggerAirportTileAnimation(st, vt, AirportAnimationTrigger::AirplaneTouchdown); if (!PlayVehicleSound(v, VSE_TOUCHDOWN)) { SndPlayVehicleFx(SND_17_SKID_PLANE, v); @@ -1668,7 +1674,7 @@ static void AircraftEventHandler_Flying(Aircraft *v, const AirportFTAClass *apc) if (current->heading == landingtype) { /* save speed before, since if AirportHasBlock is false, it resets them to 0 * we don't want that for plane in air - * hack for speed thingie */ + * hack for speed thingy */ uint16_t tcur_speed = v->cur_speed; uint16_t tsubspeed = v->subspeed; if (!AirportHasBlock(v, current, apc)) { @@ -1782,6 +1788,11 @@ static void AirportClearBlock(const Aircraft *v, const AirportFTAClass *apc) if (apc->layout[v->previous_pos].blocks != apc->layout[v->pos].blocks) { Station *st = Station::Get(v->targetairport); + if (st->airport.blocks.Test(AirportBlock::Zeppeliner) && + apc->layout[v->previous_pos].blocks == AirportBlock::RunwayIn) { + return; + } + st->airport.blocks.Reset(apc->layout[v->previous_pos].blocks); } } diff --git a/src/aircraft_gui.cpp b/src/aircraft_gui.cpp index 47a9c65a49..ed0dd72b42 100644 --- a/src/aircraft_gui.cpp +++ b/src/aircraft_gui.cpp @@ -86,7 +86,7 @@ void DrawAircraftImage(const Vehicle *v, const Rect &r, VehicleID selection, Eng int x_offs = UnScaleGUI(rect.left); int x = rtl ? r.right - width - x_offs : r.left - x_offs; /* This magic -1 offset is related to the sprite_y_offsets in build_vehicle_gui.cpp */ - int y = ScaleSpriteTrad(-1) + CenterBounds(r.top, r.bottom, 0); + int y = ScaleSpriteTrad(-1) + CentreBounds(r.top, r.bottom, 0); bool helicopter = v->subtype == AIR_HELICOPTER; int heli_offs = 0; diff --git a/src/airport.h b/src/airport.h index dd795bb943..224a9e92ae 100644 --- a/src/airport.h +++ b/src/airport.h @@ -44,7 +44,7 @@ enum AirportTypes : uint8_t { }; /** Flags for airport movement data. */ -enum AirportMovingDataFlag : uint8_t { +enum class AirportMovingDataFlag : uint8_t { NoSpeedClamp, ///< No speed restrictions. Takeoff, ///< Takeoff movement. SlowTurn, ///< Turn slowly (mostly used in the air). @@ -127,6 +127,7 @@ enum class AirportBlock : uint8_t { /* end of new blocks */ Nothing = 30, + Zeppeliner = 62, ///< Block for the zeppeliner disaster vehicle. AirportClosed = 63, ///< Dummy block for indicating a closed airport. }; using AirportBlocks = EnumBitSet; diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 7627f9b077..4e172999c6 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -21,7 +21,7 @@ #include "company_base.h" #include "station_type.h" #include "newgrf_airport.h" -#include "newgrf_badge.h" +#include "newgrf_badge_gui.h" #include "newgrf_callbacks.h" #include "dropdown_type.h" #include "dropdown_func.h" @@ -212,9 +212,9 @@ static constexpr NWidgetPart _nested_air_toolbar_widgets[] = { NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN), EndContainer(), NWidget(NWID_HORIZONTAL), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), SetSpriteTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP), - NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(), - NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetSpriteTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetToolbarMinimalSize(2), SetSpriteTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetToolbarSpacerMinimalSize(), SetFill(1, 1), EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetToolbarMinimalSize(1), SetSpriteTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC), EndContainer(), }; @@ -409,7 +409,7 @@ public: case WID_AP_AIRPORT_SPRITE: if (this->preview_sprite != 0) { Dimension d = GetSpriteSize(this->preview_sprite); - DrawSprite(this->preview_sprite, COMPANY_SPRITE_COLOUR(_local_company), CenterBounds(r.left, r.right, d.width), CenterBounds(r.top, r.bottom, d.height)); + DrawSprite(this->preview_sprite, GetCompanyPalette(_local_company), CentreBounds(r.left, r.right, d.width), CentreBounds(r.top, r.bottom, d.height)); } break; @@ -519,7 +519,7 @@ public: this->SetWidgetLoweredState(WID_AP_BTN_DONTHILIGHT, !_settings_client.gui.station_show_coverage); this->SetWidgetLoweredState(WID_AP_BTN_DOHILIGHT, _settings_client.gui.station_show_coverage); this->SetDirty(); - if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); + SndClickBeep(); this->UpdateSelectSize(); SetViewportCatchmentStation(nullptr, true); break; @@ -571,7 +571,7 @@ public: this->SelectOtherAirport(-1); } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { if (widget == WID_AP_CLASS_DROPDOWN) { _selected_airport_class = (AirportClassID)index; @@ -585,7 +585,7 @@ public: CheckRedrawStationCoverage(this); } - IntervalTimer yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) { + const IntervalTimer yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) { this->InvalidateData(); }}; }; @@ -628,7 +628,7 @@ static constexpr NWidgetPart _nested_build_airport_widgets[] = { }; static WindowDesc _build_airport_desc( - WDP_AUTO, nullptr, 0, 0, + WDP_AUTO, {}, 0, 0, WC_BUILD_STATION, WC_BUILD_TOOLBAR, WindowDefaultFlag::Construction, _nested_build_airport_widgets diff --git a/src/articulated_vehicles.cpp b/src/articulated_vehicles.cpp index 1f547b2615..ffb2fc052b 100644 --- a/src/articulated_vehicles.cpp +++ b/src/articulated_vehicles.cpp @@ -80,20 +80,18 @@ uint CountArticulatedParts(EngineID engine_type, bool purchase_window) * either, so it doesn't matter how many articulated parts there are. */ if (!Vehicle::CanAllocateItem()) return 0; - Vehicle *v = nullptr; + std::unique_ptr v; if (!purchase_window) { - v = new Vehicle(); + v = std::make_unique(); v->engine_type = engine_type; v->owner = _current_company; } uint i; for (i = 1; i < MAX_ARTICULATED_PARTS; i++) { - if (GetNextArticulatedPart(i, engine_type, v) == EngineID::Invalid()) break; + if (GetNextArticulatedPart(i, engine_type, v.get()) == EngineID::Invalid()) break; } - delete v; - return i - 1; } @@ -432,7 +430,10 @@ void AddArticulatedParts(Vehicle *first) if (flip_image) v->spritenum++; - if (v->type == VEH_TRAIN && TestVehicleBuildProbability(v, v->engine_type, BuildProbabilityType::Reversed)) SetBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION); + if (v->type == VEH_TRAIN) { + auto prob = TestVehicleBuildProbability(v, v->engine_type, BuildProbabilityType::Reversed); + if (prob.has_value()) Train::From(v)->flags.Set(VehicleRailFlag::Flipped, prob.value()); + } v->UpdatePosition(); } } diff --git a/src/autocompletion.cpp b/src/autocompletion.cpp index 1a643f8c05..e8d36406f3 100644 --- a/src/autocompletion.cpp +++ b/src/autocompletion.cpp @@ -17,9 +17,12 @@ #include "safeguards.h" +/** + * @return true, if the textbuf was updated. + */ bool AutoCompletion::AutoComplete() { - // We are pressing TAB for the first time after reset. + /* We are pressing TAB for the first time after reset. */ if (this->suggestions.empty()) { this->InitSuggestions(this->textbuf->GetText()); if (this->suggestions.empty()) { @@ -29,11 +32,11 @@ bool AutoCompletion::AutoComplete() return true; } - // We are pressing TAB again on the same text. + /* We are pressing TAB again on the same text. */ if (this->current_suggestion_index + 1 < this->suggestions.size()) { this->ApplySuggestion(prefix, this->suggestions[++this->current_suggestion_index]); } else { - // We are out of options, restore original text. + /* We are out of options, restore original text. */ this->textbuf->Assign(initial_buf); this->Reset(); } diff --git a/src/autocompletion.h b/src/autocompletion.h index 452c23e87a..03c08f5c77 100644 --- a/src/autocompletion.h +++ b/src/autocompletion.h @@ -32,7 +32,6 @@ public: } virtual ~AutoCompletion() = default; - // Returns true the textbuf was updated. bool AutoComplete(); void Reset(); diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 668360f6e7..3ff1515b01 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -185,9 +185,9 @@ static bool VerifyAutoreplaceRefitForOrders(const Vehicle *v, EngineID engine_ty CargoTypes union_refit_mask_b = GetUnionOfArticulatedRefitMasks(engine_type, false); const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; - for (const Order *o : u->Orders()) { - if (!o->IsRefit() || o->IsAutoRefit()) continue; - CargoType cargo_type = o->GetRefitCargo(); + for (const Order &o : u->Orders()) { + if (!o.IsRefit() || o.IsAutoRefit()) continue; + CargoType cargo_type = o.GetRefitCargo(); if (!HasBit(union_refit_mask_a, cargo_type)) continue; if (!HasBit(union_refit_mask_b, cargo_type)) return false; @@ -206,13 +206,12 @@ static int GetIncompatibleRefitOrderIdForAutoreplace(const Vehicle *v, EngineID { CargoTypes union_refit_mask = GetUnionOfArticulatedRefitMasks(engine_type, false); - const Order *o; const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; const OrderList *orders = u->orders; if (orders == nullptr) return -1; for (VehicleOrderID i = 0; i < orders->GetNumOrders(); i++) { - o = orders->GetOrderAt(i); + const Order *o = orders->GetOrderAt(i); if (!o->IsRefit()) continue; if (!HasBit(union_refit_mask, o->GetRefitCargo())) return i; } @@ -373,8 +372,12 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic } /* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */ - if (new_veh->type == VEH_TRAIN && HasBit(Train::From(old_veh)->flags, VRF_REVERSE_DIRECTION)) { - Command::Do(DoCommandFlag::Execute, new_veh->index, true); + if (new_veh->type == VEH_TRAIN && Train::From(old_veh)->flags.Test(VehicleRailFlag::Flipped)) { + /* Only copy the reverse state if neither old or new vehicle implements reverse-on-build probability callback. */ + if (!TestVehicleBuildProbability(old_veh, old_veh->engine_type, BuildProbabilityType::Reversed).has_value() && + !TestVehicleBuildProbability(new_veh, new_veh->engine_type, BuildProbabilityType::Reversed).has_value()) { + Command::Do(DoCommandFlag::Execute, new_veh->index, true); + } } return cost; @@ -432,7 +435,7 @@ static CommandCost CopyHeadSpecificThings(Vehicle *old_head, Vehicle *new_head, /* Last do those things which do never fail (resp. we do not care about), but which are not undo-able */ if (cost.Succeeded() && old_head != new_head && flags.Test(DoCommandFlag::Execute)) { - /* Copy other things which cannot be copied by a command and which shall not stay resetted from the build vehicle command */ + /* Copy other things which cannot be copied by a command and which shall not stay reset from the build vehicle command */ new_head->CopyVehicleConfigAndStatistics(old_head); GroupStatistics::AddProfitLastYear(new_head); diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 144e10fa12..ec25fa69e1 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -126,7 +126,7 @@ class ReplaceVehicleWindow : public Window { */ void GenerateReplaceVehList(bool draw_left) { - std::vector variants; + FlatSet variants; EngineID selected_engine = EngineID::Invalid(); VehicleType type = this->window_number; uint8_t side = draw_left ? 0 : 1; @@ -170,8 +170,7 @@ class ReplaceVehicleWindow : public Window { if (side == 1) { EngineID parent = e->info.variant_id; - while (parent != EngineID::Invalid()) { - variants.push_back(parent); + while (parent != EngineID::Invalid() && variants.insert(parent).second) { parent = Engine::Get(parent)->info.variant_id; } } @@ -320,7 +319,7 @@ public: case WID_RV_LEFT_MATRIX: case WID_RV_RIGHT_MATRIX: - resize.height = GetEngineListHeight(this->window_number); + fill.height = resize.height = GetEngineListHeight(this->window_number); size.height = (this->window_number <= VEH_ROAD ? 8 : 4) * resize.height; break; @@ -415,11 +414,11 @@ public: case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: if (const Group *g = Group::GetIfValid(this->sel_group); g != nullptr) { bool remove_wagon = g->flags.Test(GroupFlag::ReplaceWagonRemoval); - return GetString(STR_GROUP_NAME, sel_group, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + return GetString(STR_REPLACE_REMOVE_WAGON, STR_GROUP_NAME, sel_group, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); } else { const Company *c = Company::Get(_local_company); bool remove_wagon = c->settings.renew_keep_length; - return GetString(STR_GROUP_DEFAULT_TRAINS + this->window_number, std::monostate{}, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); + return GetString(STR_REPLACE_REMOVE_WAGON, STR_GROUP_DEFAULT_TRAINS + this->window_number, std::monostate{}, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); } break; @@ -574,7 +573,7 @@ public: if (g != nullptr) { Command::Post(this->sel_group, GroupFlag::ReplaceWagonRemoval, !g->flags.Test(GroupFlag::ReplaceWagonRemoval), citymania::_fn_mod); } else { - // toggle renew_keep_length + /* toggle renew_keep_length */ Command::Post("company.renew_keep_length", Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1); } break; @@ -585,8 +584,8 @@ public: this->HandleButtonClick(WID_RV_START_REPLACE); ReplaceClick_StartReplace(false); } else { - bool replacment_when_old = EngineHasReplacementWhenOldForCompany(Company::Get(_local_company), this->sel_engine[0], this->sel_group); - ShowDropDownMenu(this, _start_replace_dropdown, replacment_when_old ? 1 : 0, WID_RV_START_REPLACE, !this->replace_engines ? 1 << 1 : 0, 0); + bool replacement_when_old = EngineHasReplacementWhenOldForCompany(Company::Get(_local_company), this->sel_engine[0], this->sel_group); + ShowDropDownMenu(this, _start_replace_dropdown, replacement_when_old ? 1 : 0, WID_RV_START_REPLACE, !this->replace_engines ? 1 << 1 : 0, 0); } break; } @@ -645,7 +644,7 @@ public: } } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { switch (widget) { case WID_RV_SORT_DROPDOWN: @@ -766,7 +765,7 @@ static constexpr NWidgetPart _nested_replace_rail_vehicle_widgets[] = { NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_LEFT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(), NWidget(NWID_VERTICAL), NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_RIGHT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetStringTip(STR_REPLACE_REMOVE_WAGON, STR_REPLACE_REMOVE_WAGON_TOOLTIP), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetToolTip(STR_REPLACE_REMOVE_WAGON_TOOLTIP), SetFill(1, 0), SetResize(1, 0), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL), diff --git a/src/base_media_base.h b/src/base_media_base.h index 2c645ccfae..6286455b2d 100644 --- a/src/base_media_base.h +++ b/src/base_media_base.h @@ -16,8 +16,9 @@ #include "3rdparty/md5/md5.h" #include -/* Forward declare these; can't do 'struct X' in functions as older GCCs barf on that */ struct IniFile; +struct IniGroup; +struct IniItem; struct ContentInfo; /** Structure holding filename and MD5 information about a single file */ @@ -47,7 +48,7 @@ template struct BaseSetTraits; */ template struct BaseSet { - typedef std::unordered_map TranslatedStrings; + typedef std::unordered_map> TranslatedStrings; /** Number of files in this set */ static constexpr size_t NUM_FILES = BaseSetTraits::num_files; @@ -62,21 +63,13 @@ struct BaseSet { std::string url; ///< URL for information about the base set TranslatedStrings description; ///< Description of the base set uint32_t shortname = 0; ///< Four letter short variant of the name - uint32_t version = 0; ///< The version of this base set + std::vector version; ///< The version of this base set bool fallback = false; ///< This set is a fallback set, i.e. it should be used only as last resort std::array::NUM_FILES> files{}; ///< All files part of this set uint found_files = 0; ///< Number of the files that could be found uint valid_files = 0; ///< Number of the files that could be found and are valid - T *next = nullptr; ///< The next base set in this list - - /** Free everything we allocated */ - ~BaseSet() - { - delete this->next; - } - /** * Get the number of missing files. * @return the number @@ -96,6 +89,9 @@ struct BaseSet { return BaseSet::NUM_FILES - this->valid_files; } + void LogError(std::string_view full_filename, std::string_view detail, int level = 0) const; + const IniItem *GetMandatoryItem(std::string_view full_filename, const IniGroup &group, std::string_view name) const; + bool FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename, bool allow_empty_filename = true); void CopyCompatibleConfig([[maybe_unused]] const T &src) {} @@ -107,7 +103,7 @@ struct BaseSet { * @param isocode the isocode to search for * @return the description */ - const std::string &GetDescription(const std::string &isocode) const + const std::string &GetDescription(std::string_view isocode) const { if (!isocode.empty()) { /* First the full ISO code */ @@ -166,9 +162,9 @@ struct BaseSet { template class BaseMedia : FileScanner { protected: - static inline Tbase_set *available_sets = nullptr; ///< All available sets - static inline Tbase_set *duplicate_sets = nullptr; ///< All sets that aren't available, but needed for not downloading base sets when a newer version than the one on BaNaNaS is loaded. - static inline const Tbase_set *used_set = nullptr; ///< The currently used set + static inline std::vector> available_sets; ///< All available sets + static inline std::vector> duplicate_sets; ///< All sets that aren't available, but needed for not downloading base sets when a newer version than the one on BaNaNaS is loaded. + static inline const Tbase_set *used_set; ///< The currently used set bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override; @@ -176,7 +172,13 @@ protected: * Get the extension that is used to identify this set. * @return the extension */ - static const char *GetExtension(); + static std::string_view GetExtension(); + + /** + * Return the duplicate sets. + * @return The duplicate sets. + */ + static std::span> GetDuplicateSets() { return BaseMedia::duplicate_sets; } public: /** * Determine the graphics pack that has to be used. @@ -194,7 +196,11 @@ public: return num + fs.Scan(GetExtension(), BASESET_DIR, Tbase_set::SEARCH_IN_TARS); } - static Tbase_set *GetAvailableSets(); + /** + * Return the available sets. + * @return The available sets. + */ + static std::span> GetAvailableSets() { return BaseMedia::available_sets; } static bool SetSet(const Tbase_set *set); static bool SetSetByName(const std::string &name); @@ -219,9 +225,9 @@ public: * @param ci The content info to compare it to. * @param md5sum Should the MD5 checksum be tested as well? * @param s The list with sets. - * @return The filename of the first file of the base set, or \c nullptr if there is no match. + * @return The filename of the first file of the base set, or \c std::nullopt if there is no match. */ template -const char *TryGetBaseSetFile(const ContentInfo &ci, bool md5sum, const Tbase_set *s); +std::optional TryGetBaseSetFile(const ContentInfo &ci, bool md5sum, std::span> sets); #endif /* BASE_MEDIA_BASE_H */ diff --git a/src/base_media_func.h b/src/base_media_func.h index 2e0d644ff9..8f1fac4c26 100644 --- a/src/base_media_func.h +++ b/src/base_media_func.h @@ -14,20 +14,39 @@ #include "ini_type.h" #include "string_func.h" #include "error_func.h" +#include "core/string_consumer.hpp" +#include "3rdparty/fmt/ranges.h" extern void CheckExternalFiles(); /** - * Try to read a single piece of metadata and return false if it doesn't exist. + * Log error from reading basesets. + * @param full_filename the full filename of the loaded file + * @param detail detail log message + * @param level debug level + */ +template +void BaseSet::LogError(std::string_view full_filename, std::string_view detail, int level) const +{ + Debug(misc, level, "Loading base {}set details failed: {}", BaseSet::SET_TYPE, full_filename); + Debug(misc, level, " {}", detail); +} + +/** + * Try to read a single piece of metadata and return nullptr if it doesn't exist. + * Log error, if the data is missing. + * @param full_filename the full filename of the loaded file (for error reporting purposes) + * @param group ini group to read from * @param name the name of the item to fetch. */ -#define fetch_metadata(name) \ - item = metadata->GetItem(name); \ - if (item == nullptr || !item->value.has_value() || item->value->empty()) { \ - Debug(grf, 0, "Base {}set detail loading: {} field missing.", BaseSet::SET_TYPE, name); \ - Debug(grf, 0, " Is {} readable for the user running OpenTTD?", full_filename); \ - return false; \ - } +template +const IniItem *BaseSet::GetMandatoryItem(std::string_view full_filename, const IniGroup &group, std::string_view name) const +{ + auto *item = group.GetItem(name); + if (item != nullptr && item->value.has_value() && !item->value->empty()) return item; + this->LogError(full_filename, fmt::format("{}.{} field missing.", group.name, name)); + return nullptr; +} /** * Read the set information from a loaded ini. @@ -42,16 +61,17 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con { const IniGroup *metadata = ini.GetGroup("metadata"); if (metadata == nullptr) { - Debug(grf, 0, "Base {}set detail loading: metadata missing.", BaseSet::SET_TYPE); - Debug(grf, 0, " Is {} readable for the user running OpenTTD?", full_filename); + this->LogError(full_filename, "Is the file readable for the user running OpenTTD?"); return false; } const IniItem *item; - fetch_metadata("name"); + item = this->GetMandatoryItem(full_filename, *metadata, "name"); + if (item == nullptr) return false; this->name = *item->value; - fetch_metadata("description"); + item = this->GetMandatoryItem(full_filename, *metadata, "description"); + if (item == nullptr) return false; this->description[std::string{}] = *item->value; item = metadata->GetItem("url"); @@ -64,13 +84,24 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con this->description[titem.name.substr(12)] = titem.value.value_or(""); } - fetch_metadata("shortname"); + item = this->GetMandatoryItem(full_filename, *metadata, "shortname"); + if (item == nullptr) return false; for (uint i = 0; (*item->value)[i] != '\0' && i < 4; i++) { this->shortname |= ((uint8_t)(*item->value)[i]) << (i * 8); } - fetch_metadata("version"); - this->version = atoi(item->value->c_str()); + item = this->GetMandatoryItem(full_filename, *metadata, "version"); + if (item == nullptr) return false; + for (StringConsumer consumer{*item->value};;) { + auto value = consumer.TryReadIntegerBase(10); + bool valid = value.has_value(); + if (valid) this->version.push_back(*value); + if (valid && !consumer.AnyBytesLeft()) break; + if (!valid || !consumer.ReadIf(".")) { + this->LogError(full_filename, fmt::format("metadata.version field is invalid: {}", *item->value)); + return false; + } + } item = metadata->GetItem("fallback"); this->fallback = (item != nullptr && item->value && *item->value != "0" && *item->value != "false"); @@ -80,13 +111,18 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con const IniGroup *md5s = ini.GetGroup("md5s"); const IniGroup *origin = ini.GetGroup("origin"); auto file_names = BaseSet::GetFilenames(); + bool original_set = + std::byteswap(this->shortname) == 'TTDD' || // TTD DOS graphics, TTD DOS music + std::byteswap(this->shortname) == 'TTDW' || // TTD WIN graphics, TTD WIN music + std::byteswap(this->shortname) == 'TTDO' || // TTD sound + std::byteswap(this->shortname) == 'TTOD'; // TTO music for (uint i = 0; i < BaseSet::NUM_FILES; i++) { MD5File *file = &this->files[i]; /* Find the filename first. */ item = files != nullptr ? files->GetItem(file_names[i]) : nullptr; if (item == nullptr || (!item->value.has_value() && !allow_empty_filename)) { - Debug(grf, 0, "No {} file for: {} (in {})", BaseSet::SET_TYPE, file_names[i], full_filename); + this->LogError(full_filename, fmt::format("files.{} field missing", file_names[i])); return false; } @@ -104,34 +140,19 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con /* Then find the MD5 checksum */ item = md5s != nullptr ? md5s->GetItem(filename) : nullptr; if (item == nullptr || !item->value.has_value()) { - Debug(grf, 0, "No MD5 checksum specified for: {} (in {})", filename, full_filename); + this->LogError(full_filename, fmt::format("md5s.{} field missing", filename)); return false; } - const char *c = item->value->c_str(); - for (size_t i = 0; i < file->hash.size() * 2; i++, c++) { - uint j; - if ('0' <= *c && *c <= '9') { - j = *c - '0'; - } else if ('a' <= *c && *c <= 'f') { - j = *c - 'a' + 10; - } else if ('A' <= *c && *c <= 'F') { - j = *c - 'A' + 10; - } else { - Debug(grf, 0, "Malformed MD5 checksum specified for: {} (in {})", filename, full_filename); - return false; - } - if (i % 2 == 0) { - file->hash[i / 2] = j << 4; - } else { - file->hash[i / 2] |= j; - } + if (!ConvertHexToBytes(*item->value, file->hash)) { + this->LogError(full_filename, fmt::format("md5s.{} is malformed: {}", filename, *item->value)); + return false; } /* Then find the warning message when the file's missing */ item = origin != nullptr ? origin->GetItem(filename) : nullptr; if (item == nullptr) item = origin != nullptr ? origin->GetItem("default") : nullptr; if (item == nullptr || !item->value.has_value()) { - Debug(grf, 1, "No origin warning message specified for: {}", filename); + this->LogError(full_filename, fmt::format("origin.{} field missing", filename), 1); file->missing_warning.clear(); } else { file->missing_warning = item->value.value(); @@ -148,12 +169,14 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con break; case MD5File::CR_MISMATCH: - Debug(grf, 1, "MD5 checksum mismatch for: {} (in {})", filename, full_filename); + /* This is normal for original sample.cat, which either matches with orig_dos or orig_win. */ + this->LogError(full_filename, fmt::format("MD5 checksum mismatch for: {}", filename), original_set ? 1 : 0); this->found_files++; break; case MD5File::CR_NO_FILE: - Debug(grf, 1, "The file {} specified in {} is missing", filename, full_filename); + /* Missing files is normal for the original basesets. Use lower debug level */ + this->LogError(full_filename, fmt::format("File is missing: {}", filename), original_set ? 1 : 0); break; } } @@ -164,10 +187,9 @@ bool BaseSet::FillSetDetails(const IniFile &ini, const std::string &path, con template bool BaseMedia::AddFile(const std::string &filename, size_t basepath_length, const std::string &) { - bool ret = false; - Debug(grf, 1, "Checking {} for base {} set", filename, BaseSet::SET_TYPE); + Debug(misc, 1, "Checking {} for base {} set", filename, BaseSet::SET_TYPE); - Tbase_set *set = new Tbase_set(); + auto set = std::make_unique(); IniFile ini{}; std::string path{ filename, basepath_length }; ini.LoadFromDisk(path, BASESET_DIR); @@ -179,60 +201,44 @@ bool BaseMedia::AddFile(const std::string &filename, size_t basepath_ path.clear(); } - if (set->FillSetDetails(ini, path, filename)) { - Tbase_set *duplicate = nullptr; - for (Tbase_set *c = BaseMedia::available_sets; c != nullptr; c = c->next) { - if (c->name == set->name || c->shortname == set->shortname) { - duplicate = c; - break; - } + if (!set->FillSetDetails(ini, path, filename)) return false; + + auto existing = std::ranges::find_if(BaseMedia::available_sets, [&set](const auto &c) { return c->name == set->name || c->shortname == set->shortname; }); + if (existing != std::end(BaseMedia::available_sets)) { + /* The more complete set takes precedence over the version number. */ + if (((*existing)->valid_files == set->valid_files && (*existing)->version >= set->version) || + (*existing)->valid_files > set->valid_files) { + + Debug(misc, 1, "Not adding {} ({}) as base {} set (duplicate, {})", set->name, fmt::join(set->version, "."), + BaseSet::SET_TYPE, + (*existing)->valid_files > set->valid_files ? "fewer valid files" : "lower version"); + + duplicate_sets.push_back(std::move(set)); + return false; } - if (duplicate != nullptr) { - /* The more complete set takes precedence over the version number. */ - if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) || - duplicate->valid_files > set->valid_files) { - Debug(grf, 1, "Not adding {} ({}) as base {} set (duplicate, {})", set->name, set->version, - BaseSet::SET_TYPE, - duplicate->valid_files > set->valid_files ? "less valid files" : "lower version"); - set->next = BaseMedia::duplicate_sets; - BaseMedia::duplicate_sets = set; - } else { - Tbase_set **prev = &BaseMedia::available_sets; - while (*prev != duplicate) prev = &(*prev)->next; - *prev = set; - set->next = duplicate->next; + /* If the duplicate set is currently used (due to rescanning this can happen) + * update the currently used set to the new one. This will 'lie' about the + * version number until a new game is started which isn't a big problem */ + if (BaseMedia::used_set == existing->get()) BaseMedia::used_set = set.get(); - /* Keep baseset configuration, if compatible */ - set->CopyCompatibleConfig(*duplicate); + /* Keep baseset configuration, if compatible */ + set->CopyCompatibleConfig(**existing); - /* If the duplicate set is currently used (due to rescanning this can happen) - * update the currently used set to the new one. This will 'lie' about the - * version number until a new game is started which isn't a big problem */ - if (BaseMedia::used_set == duplicate) BaseMedia::used_set = set; + Debug(misc, 1, "Removing {} ({}) as base {} set (duplicate, {})", (*existing)->name, fmt::join((*existing)->version, "."), BaseSet::SET_TYPE, + (*existing)->valid_files < set->valid_files ? "fewer valid files" : "lower version"); - Debug(grf, 1, "Removing {} ({}) as base {} set (duplicate, {})", duplicate->name, duplicate->version, - BaseSet::SET_TYPE, - duplicate->valid_files < set->valid_files ? "less valid files" : "lower version"); - duplicate->next = BaseMedia::duplicate_sets; - BaseMedia::duplicate_sets = duplicate; - ret = true; - } - } else { - Tbase_set **last = &BaseMedia::available_sets; - while (*last != nullptr) last = &(*last)->next; + /* Existing set is worse, move it to duplicates and replace with the current set. */ + duplicate_sets.push_back(std::move(*existing)); - *last = set; - ret = true; - } - if (ret) { - Debug(grf, 1, "Adding {} ({}) as base {} set", set->name, set->version, BaseSet::SET_TYPE); - } + Debug(misc, 1, "Adding {} ({}) as base {} set", set->name, fmt::join(set->version, "."), BaseSet::SET_TYPE); + *existing = std::move(set); } else { - delete set; + Debug(grf, 1, "Adding {} ({}) as base {} set", set->name, set->version, BaseSet::SET_TYPE); + available_sets.push_back(std::move(set)); } - return ret; + return true; } /** @@ -264,9 +270,9 @@ template return SetSet(nullptr); } - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { + for (const auto &s : BaseMedia::available_sets) { if (name == s->name) { - return SetSet(s); + return SetSet(s.get()); } } return false; @@ -284,9 +290,9 @@ template return SetSet(nullptr); } - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { + for (const auto &s : BaseMedia::available_sets) { if (shortname == s->shortname) { - return SetSet(s); + return SetSet(s.get()); } } return false; @@ -300,7 +306,7 @@ template /* static */ void BaseMedia::GetSetsList(std::back_insert_iterator &output_iterator) { fmt::format_to(output_iterator, "List of {} sets:\n", BaseSet::SET_TYPE); - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { + for (const auto &s : BaseMedia::available_sets) { fmt::format_to(output_iterator, "{:>18}: {}", s->name, s->GetDescription({})); int invalid = s->GetNumInvalid(); if (invalid != 0) { @@ -319,28 +325,28 @@ template #include "network/core/tcp_content_type.h" -template const char *TryGetBaseSetFile(const ContentInfo &ci, bool md5sum, const Tbase_set *s) +template std::optional TryGetBaseSetFile(const ContentInfo &ci, bool md5sum, std::span> sets) { - for (; s != nullptr; s = s->next) { + for (const auto &s : sets) { if (s->GetNumMissing() != 0) continue; if (s->shortname != ci.unique_id) continue; - if (!md5sum) return s->files[0].filename.c_str(); + if (!md5sum) return s->files[0].filename; MD5Hash md5; for (const auto &file : s->files) { md5 ^= file.hash; } - if (md5 == ci.md5sum) return s->files[0].filename.c_str(); + if (md5 == ci.md5sum) return s->files[0].filename; } - return nullptr; + return std::nullopt; } template /* static */ bool BaseMedia::HasSet(const ContentInfo &ci, bool md5sum) { - return (TryGetBaseSetFile(ci, md5sum, BaseMedia::available_sets) != nullptr) || - (TryGetBaseSetFile(ci, md5sum, BaseMedia::duplicate_sets) != nullptr); + return TryGetBaseSetFile(ci, md5sum, BaseMedia::GetAvailableSets()).has_value() || + TryGetBaseSetFile(ci, md5sum, BaseMedia::GetDuplicateSets()).has_value(); } /** @@ -350,12 +356,9 @@ template template /* static */ int BaseMedia::GetNumSets() { - int n = 0; - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { - if (s != BaseMedia::used_set && s->GetNumMissing() != 0) continue; - n++; - } - return n; + return std::ranges::count_if(BaseMedia::GetAvailableSets(), [](const auto &set) { + return set.get() == BaseMedia::used_set || set->GetNumMissing() == 0; + }); } /** @@ -366,8 +369,8 @@ template /* static */ int BaseMedia::GetIndexOfUsedSet() { int n = 0; - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { - if (s == BaseMedia::used_set) return n; + for (const auto &s : BaseMedia::available_sets) { + if (s.get() == BaseMedia::used_set) return n; if (s->GetNumMissing() != 0) continue; n++; } @@ -381,9 +384,9 @@ template template /* static */ const Tbase_set *BaseMedia::GetSet(int index) { - for (const Tbase_set *s = BaseMedia::available_sets; s != nullptr; s = s->next) { - if (s != BaseMedia::used_set && s->GetNumMissing() != 0) continue; - if (index == 0) return s; + for (const auto &s : BaseMedia::available_sets) { + if (s.get() != BaseMedia::used_set && s->GetNumMissing() != 0) continue; + if (index == 0) return s.get(); index--; } FatalError("Base{}::GetSet(): index {} out of range", BaseSet::SET_TYPE, index); @@ -398,13 +401,3 @@ template { return BaseMedia::used_set; } - -/** - * Return the available sets. - * @return The available sets. - */ -template -/* static */ Tbase_set *BaseMedia::GetAvailableSets() -{ - return BaseMedia::available_sets; -} diff --git a/src/base_station_base.h b/src/base_station_base.h index dd6d3a5976..229c7f90aa 100644 --- a/src/base_station_base.h +++ b/src/base_station_base.h @@ -74,9 +74,9 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { TimerGameCalendar::Date build_date{}; ///< Date of construction uint16_t random_bits = 0; ///< Random bits assigned to this station - uint8_t waiting_triggers = 0; ///< Waiting triggers (NewGRF) for this station - uint8_t cached_anim_triggers = 0; ///< NOSAVE: Combined animation trigger bitmask, used to determine if trigger processing should happen. - uint8_t cached_roadstop_anim_triggers = 0; ///< NOSAVE: Combined animation trigger bitmask for road stops, used to determine if trigger processing should happen. + StationRandomTriggers waiting_random_triggers; ///< Waiting triggers (NewGRF), shared by all station parts/tiles, road stops, ... essentially useless and broken by design. + StationAnimationTriggers cached_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask, used to determine if trigger processing should happen. + StationAnimationTriggers cached_roadstop_anim_triggers; ///< NOSAVE: Combined animation trigger bitmask for road stops, used to determine if trigger processing should happen. CargoTypes cached_cargo_triggers{}; ///< NOSAVE: Combined cargo trigger bitmask CargoTypes cached_roadstop_cargo_triggers{}; ///< NOSAVE: Combined cargo trigger bitmask for road stops @@ -130,11 +130,10 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> { /** * Get the tile area for a given station type. - * @param ta tile area to fill. * @param type the type of the area + * @return The tile area. */ - virtual void GetTileArea(TileArea *ta, StationType type) const = 0; - + virtual TileArea GetTileArea(StationType type) const = 0; /** * Obtain the length of a platform @@ -244,7 +243,7 @@ struct SpecializedStation : public BaseStation { /** * Gets station with given index - * @return pointer to station with given index casted to T * + * @return pointer to station with given index cast to T * */ static inline T *Get(auto index) { diff --git a/src/blitter/32bpp_anim.cpp b/src/blitter/32bpp_anim.cpp index bb065e5aa4..bb71b0ebf9 100644 --- a/src/blitter/32bpp_anim.cpp +++ b/src/blitter/32bpp_anim.cpp @@ -27,8 +27,8 @@ inline void Blitter_32bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel { const SpriteData *src = (const SpriteData *)bp->sprite; - const Colour *src_px = (const Colour *)(src->data + src->offset[zoom][0]); - const uint16_t *src_n = (const uint16_t *)(src->data + src->offset[zoom][1]); + const Colour *src_px = reinterpret_cast(src->data + src->offset[0][zoom]); + const uint16_t *src_n = reinterpret_cast(src->data + src->offset[1][zoom]); for (uint i = bp->skip_top; i != 0; i--) { src_px = (const Colour *)((const uint8_t *)src_px + *(const uint32_t *)src_px); @@ -361,19 +361,19 @@ void Blitter_32bppAnim::DrawColourMappingRect(void *dst, int width, int height, Debug(misc, 0, "32bpp blitter doesn't know how to draw this colour table ('{}')", pal); } -void Blitter_32bppAnim::SetPixel(void *video, int x, int y, uint8_t colour) +void Blitter_32bppAnim::SetPixel(void *video, int x, int y, PixelColour colour) { - *((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour); + *((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour.p); /* Set the colour in the anim-buffer too, if we are rendering to the screen */ if (_screen_disable_anim) return; - this->anim_buf[this->ScreenToAnimOffset((uint32_t *)video) + x + y * this->anim_buf_pitch] = colour | (DEFAULT_BRIGHTNESS << 8); + this->anim_buf[this->ScreenToAnimOffset((uint32_t *)video) + x + y * this->anim_buf_pitch] = colour.p | (DEFAULT_BRIGHTNESS << 8); } -void Blitter_32bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) +void Blitter_32bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) { - const Colour c = LookupColourInPalette(colour); + const Colour c = LookupColourInPalette(colour.p); if (_screen_disable_anim) { this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [&](int x, int y) { @@ -381,7 +381,7 @@ void Blitter_32bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int }); } else { uint16_t * const offset_anim_buf = this->anim_buf + this->ScreenToAnimOffset((uint32_t *)video); - const uint16_t anim_colour = colour | (DEFAULT_BRIGHTNESS << 8); + const uint16_t anim_colour = colour.p | (DEFAULT_BRIGHTNESS << 8); this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [&](int x, int y) { *((Colour *)video + x + y * _screen.pitch) = c; offset_anim_buf[x + y * this->anim_buf_pitch] = anim_colour; @@ -389,7 +389,7 @@ void Blitter_32bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int } } -void Blitter_32bppAnim::DrawRect(void *video, int width, int height, uint8_t colour) +void Blitter_32bppAnim::DrawRect(void *video, int width, int height, PixelColour colour) { if (_screen_disable_anim) { /* This means our output is not to the screen, so we can't be doing any animation stuff, so use our parent DrawRect() */ @@ -397,7 +397,7 @@ void Blitter_32bppAnim::DrawRect(void *video, int width, int height, uint8_t col return; } - Colour colour32 = LookupColourInPalette(colour); + Colour colour32 = LookupColourInPalette(colour.p); uint16_t *anim_line = this->ScreenToAnimOffset((uint32_t *)video) + this->anim_buf; do { @@ -407,7 +407,7 @@ void Blitter_32bppAnim::DrawRect(void *video, int width, int height, uint8_t col for (int i = width; i > 0; i--) { *dst = colour32; /* Set the colour in the anim-buffer too */ - *anim = colour | (DEFAULT_BRIGHTNESS << 8); + *anim = colour.p | (DEFAULT_BRIGHTNESS << 8); dst++; anim++; } @@ -429,11 +429,11 @@ void Blitter_32bppAnim::CopyFromBuffer(void *video, const void *src, int width, Colour *dst_pal = dst; uint16_t *anim_pal = anim_line; - memcpy(static_cast(dst), usrc, width * sizeof(uint32_t)); + std::copy_n(usrc, width, reinterpret_cast(dst)); usrc += width; dst += _screen.pitch; /* Copy back the anim-buffer */ - memcpy(anim_line, usrc, width * sizeof(uint16_t)); + std::copy_n(reinterpret_cast(usrc), width, anim_line); usrc = (const uint32_t *)&((const uint16_t *)usrc)[width]; anim_line += this->anim_buf_pitch; @@ -468,11 +468,11 @@ void Blitter_32bppAnim::CopyToBuffer(const void *video, void *dst, int width, in const uint16_t *anim_line = this->ScreenToAnimOffset((const uint32_t *)video) + this->anim_buf; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; /* Copy the anim-buffer */ - memcpy(udst, anim_line, width * sizeof(uint16_t)); + std::copy_n(anim_line, width, reinterpret_cast(udst)); udst = (uint32_t *)&((uint16_t *)udst)[width]; anim_line += this->anim_buf_pitch; } @@ -498,11 +498,7 @@ void Blitter_32bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height - scroll_y; - for (; th > 0; th--) { - memcpy(dst, src, tw * sizeof(uint16_t)); - src -= this->anim_buf_pitch; - dst -= this->anim_buf_pitch; - } + Blitter::MovePixels(src, dst, tw, th, -this->anim_buf_pitch); } else { /* Calculate pointers */ dst = this->anim_buf + left + top * this->anim_buf_pitch; @@ -515,15 +511,9 @@ void Blitter_32bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt src -= scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height + scroll_y; - for (; th > 0; th--) { - memmove(dst, src, tw * sizeof(uint16_t)); - src += this->anim_buf_pitch; - dst += this->anim_buf_pitch; - } + Blitter::MovePixels(src, dst, tw, th, this->anim_buf_pitch); } Blitter_32bppBase::ScrollBuffer(video, left, top, width, height, scroll_x, scroll_y); diff --git a/src/blitter/32bpp_anim.hpp b/src/blitter/32bpp_anim.hpp index acc911ffe0..b8b89e6106 100644 --- a/src/blitter/32bpp_anim.hpp +++ b/src/blitter/32bpp_anim.hpp @@ -34,9 +34,9 @@ public: void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) override; - void SetPixel(void *video, int x, int y, uint8_t colour) override; - void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) override; - void DrawRect(void *video, int width, int height, uint8_t colour) override; + void SetPixel(void *video, int x, int y, PixelColour colour) override; + void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) override; + void DrawRect(void *video, int width, int height, PixelColour colour) override; void CopyFromBuffer(void *video, const void *src, int width, int height) override; void CopyToBuffer(const void *video, void *dst, int width, int height) override; void ScrollBuffer(void *video, int &left, int &top, int &width, int &height, int scroll_x, int scroll_y) override; diff --git a/src/blitter/32bpp_anim_sse4.cpp b/src/blitter/32bpp_anim_sse4.cpp index a7f8303d7b..ac8dd40c01 100644 --- a/src/blitter/32bpp_anim_sse4.cpp +++ b/src/blitter/32bpp_anim_sse4.cpp @@ -192,8 +192,8 @@ bmno_full_transparency: const uint m1 = (uint8_t) (mvX2 >> 16); const uint r1 = remap[m1]; if (mvX2 & 0x00FF00FF) { + /* Written so the compiler uses CMOV. */ #define CMOV_REMAP(m_colour, m_colour_init, m_src, m_m) \ - /* Written so the compiler uses CMOV. */ \ Colour m_colour = m_colour_init; \ { \ const Colour srcm = (Colour) (m_src); \ diff --git a/src/blitter/32bpp_anim_sse4.hpp b/src/blitter/32bpp_anim_sse4.hpp index cad4295a9b..c325fb806c 100644 --- a/src/blitter/32bpp_anim_sse4.hpp +++ b/src/blitter/32bpp_anim_sse4.hpp @@ -39,8 +39,10 @@ public: template void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom); void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override { - return Blitter_32bppSSE_Base::Encode(sprite, allocator); + + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override + { + return Blitter_32bppSSE_Base::Encode(sprite_type, sprite, allocator); } std::string_view GetName() override { return "32bpp-sse4-anim"; } using Blitter_32bppSSE2_Anim::LookupColourInPalette; diff --git a/src/blitter/32bpp_base.cpp b/src/blitter/32bpp_base.cpp index 58acafc35a..fdf780b982 100644 --- a/src/blitter/32bpp_base.cpp +++ b/src/blitter/32bpp_base.cpp @@ -18,22 +18,22 @@ void *Blitter_32bppBase::MoveTo(void *video, int x, int y) return (uint32_t *)video + x + y * _screen.pitch; } -void Blitter_32bppBase::SetPixel(void *video, int x, int y, uint8_t colour) +void Blitter_32bppBase::SetPixel(void *video, int x, int y, PixelColour colour) { - *((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour); + *((Colour *)video + x + y * _screen.pitch) = LookupColourInPalette(colour.p); } -void Blitter_32bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) +void Blitter_32bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) { - const Colour c = LookupColourInPalette(colour); + const Colour c = LookupColourInPalette(colour.p); this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [=](int x, int y) { *((Colour *)video + x + y * _screen.pitch) = c; }); } -void Blitter_32bppBase::DrawRect(void *video, int width, int height, uint8_t colour) +void Blitter_32bppBase::DrawRect(void *video, int width, int height, PixelColour colour) { - Colour colour32 = LookupColourInPalette(colour); + Colour colour32 = LookupColourInPalette(colour.p); do { Colour *dst = (Colour *)video; @@ -51,7 +51,7 @@ void Blitter_32bppBase::CopyFromBuffer(void *video, const void *src, int width, const uint32_t *usrc = (const uint32_t *)src; for (; height > 0; height--) { - memcpy(dst, usrc, width * sizeof(uint32_t)); + std::copy_n(usrc, width, dst); usrc += width; dst += _screen.pitch; } @@ -63,7 +63,7 @@ void Blitter_32bppBase::CopyToBuffer(const void *video, void *dst, int width, in const uint32_t *src = (const uint32_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; } @@ -75,7 +75,7 @@ void Blitter_32bppBase::CopyImageToBuffer(const void *video, void *dst, int widt const uint32_t *src = (const uint32_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += dst_pitch; } @@ -106,11 +106,7 @@ void Blitter_32bppBase::ScrollBuffer(void *video, int &left, int &top, int &widt width += scroll_x; } - for (int h = height; h > 0; h--) { - memcpy(dst, src, width * sizeof(uint32_t)); - src -= _screen.pitch; - dst -= _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, -_screen.pitch); } else { /* Calculate pointers */ dst = (uint32_t *)video + left + top * _screen.pitch; @@ -130,13 +126,7 @@ void Blitter_32bppBase::ScrollBuffer(void *video, int &left, int &top, int &widt width += scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ - for (int h = height; h > 0; h--) { - memmove(dst, src, width * sizeof(uint32_t)); - src += _screen.pitch; - dst += _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, _screen.pitch); } } diff --git a/src/blitter/32bpp_base.hpp b/src/blitter/32bpp_base.hpp index c3378b47c3..4b7e8c7c5b 100644 --- a/src/blitter/32bpp_base.hpp +++ b/src/blitter/32bpp_base.hpp @@ -19,9 +19,9 @@ class Blitter_32bppBase : public Blitter { public: uint8_t GetScreenDepth() override { return 32; } void *MoveTo(void *video, int x, int y) override; - void SetPixel(void *video, int x, int y, uint8_t colour) override; - void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) override; - void DrawRect(void *video, int width, int height, uint8_t colour) override; + void SetPixel(void *video, int x, int y, PixelColour colour) override; + void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) override; + void DrawRect(void *video, int width, int height, PixelColour colour) override; void CopyFromBuffer(void *video, const void *src, int width, int height) override; void CopyToBuffer(const void *video, void *dst, int width, int height) override; void CopyImageToBuffer(const void *video, void *dst, int width, int height, int dst_pitch) override; diff --git a/src/blitter/32bpp_optimized.cpp b/src/blitter/32bpp_optimized.cpp index c65c691bf4..6aae2f0e00 100644 --- a/src/blitter/32bpp_optimized.cpp +++ b/src/blitter/32bpp_optimized.cpp @@ -34,11 +34,11 @@ inline void Blitter_32bppOptimized::Draw(const Blitter::BlitterParams *bp, ZoomL /* src_px : each line begins with uint32_t n = 'number of bytes in this line', * then n times is the Colour struct for this line */ - const Colour *src_px = (const Colour *)(src->data + src->offset[zoom][0]); + const Colour *src_px = reinterpret_cast(src->data + src->offset[0][zoom]); /* src_n : each line begins with uint32_t n = 'number of bytes in this line', * then interleaved stream of 'm' and 'n' channels. 'm' is remap, * 'n' is number of bytes with the same alpha channel class */ - const uint16_t *src_n = (const uint16_t *)(src->data + src->offset[zoom][1]); + const uint16_t *src_n = reinterpret_cast(src->data + src->offset[1][zoom]); /* skip upper lines in src_px and src_n */ for (uint i = bp->skip_top; i != 0; i--) { @@ -319,12 +319,13 @@ void Blitter_32bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, this->Draw(bp, mode, zoom); } -template Sprite *Blitter_32bppOptimized::EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +template +Sprite *Blitter_32bppOptimized::EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { /* streams of pixels (a, r, g, b channels) * * stored in separated stream so data are always aligned on 4B boundary */ - std::array, ZOOM_LVL_END> dst_px_orig; + SpriteCollMap> dst_px_orig; /* interleaved stream of 'm' channel and 'n' channel * 'n' is number of following pixels with the same alpha channel class @@ -332,21 +333,21 @@ template Sprite *Blitter_32bppOptimized::EncodeInternal(const * * it has to be stored in one stream so fewer registers are used - * x86 has problems with register allocation even with this solution */ - std::array, ZOOM_LVL_END> dst_n_orig; + SpriteCollMap> dst_n_orig; /* lengths of streams */ - uint32_t lengths[ZOOM_LVL_END][2]; + SpriteCollMap lengths[2]; ZoomLevel zoom_min; ZoomLevel zoom_max; - if (sprite[ZOOM_LVL_MIN].type == SpriteType::Font) { - zoom_min = ZOOM_LVL_MIN; - zoom_max = ZOOM_LVL_MIN; + if (sprite_type == SpriteType::Font) { + zoom_min = ZoomLevel::Min; + zoom_max = ZoomLevel::Min; } else { zoom_min = _settings_client.gui.zoom_min; zoom_max = _settings_client.gui.zoom_max; - if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX; + if (zoom_max == zoom_min) zoom_max = ZoomLevel::Max; } for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { @@ -439,40 +440,43 @@ template Sprite *Blitter_32bppOptimized::EncodeInternal(const dst_n_ln = (uint32_t *)dst_n; } - lengths[z][0] = reinterpret_cast(dst_px_ln) - reinterpret_cast(dst_px_orig[z].get()); // all are aligned to 4B boundary - lengths[z][1] = reinterpret_cast(dst_n_ln) - reinterpret_cast(dst_n_orig[z].get()); + lengths[0][z] = reinterpret_cast(dst_px_ln) - reinterpret_cast(dst_px_orig[z].get()); // all are aligned to 4B boundary + lengths[1][z] = reinterpret_cast(dst_n_ln) - reinterpret_cast(dst_n_orig[z].get()); } uint len = 0; // total length of data for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { - len += lengths[z][0] + lengths[z][1]; + len += lengths[0][z] + lengths[1][z]; } Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + sizeof(SpriteData) + len); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + const auto &root_sprite = sprite.Root(); + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; SpriteData *dst = (SpriteData *)dest_sprite->data; - memset(dst, 0, sizeof(*dst)); + uint32_t offset = 0; for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { - dst->offset[z][0] = z == zoom_min ? 0 : lengths[z - 1][1] + dst->offset[z - 1][1]; - dst->offset[z][1] = lengths[z][0] + dst->offset[z][0]; + dst->offset[0][z] = offset; + offset += lengths[0][z]; + dst->offset[1][z] = offset; + offset += lengths[1][z]; - memcpy(dst->data + dst->offset[z][0], dst_px_orig[z].get(), lengths[z][0]); - memcpy(dst->data + dst->offset[z][1], dst_n_orig[z].get(), lengths[z][1]); + std::copy_n(reinterpret_cast(dst_px_orig[z].get()), lengths[0][z], dst->data + dst->offset[0][z]); + std::copy_n(reinterpret_cast(dst_n_orig[z].get()), lengths[1][z], dst->data + dst->offset[1][z]); } return dest_sprite; } -template Sprite *Blitter_32bppOptimized::EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); -template Sprite *Blitter_32bppOptimized::EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); +template Sprite *Blitter_32bppOptimized::EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); +template Sprite *Blitter_32bppOptimized::EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); -Sprite *Blitter_32bppOptimized::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_32bppOptimized::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { - return this->EncodeInternal(sprite, allocator); + return this->EncodeInternal(sprite_type, sprite, allocator); } diff --git a/src/blitter/32bpp_optimized.hpp b/src/blitter/32bpp_optimized.hpp index ccd7d9ac2e..c1a8a37cd2 100644 --- a/src/blitter/32bpp_optimized.hpp +++ b/src/blitter/32bpp_optimized.hpp @@ -17,12 +17,12 @@ class Blitter_32bppOptimized : public Blitter_32bppSimple { public: /** Data stored about a (single) sprite. */ struct SpriteData { - uint32_t offset[ZOOM_LVL_END][2]; ///< Offsets (from .data) to streams for different zoom levels, and the normal and remap image information. + SpriteCollMap offset[2]; ///< Offsets (from .data) to streams for different zoom levels, and the normal and remap image information. uint8_t data[]; ///< Data, all zoomlevels. }; void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "32bpp-optimized"; } @@ -30,7 +30,7 @@ public: protected: template void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom); - template Sprite *EncodeInternal(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); + template Sprite *EncodeInternal(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); }; /** Factory for the optimised 32 bpp blitter (without palette animation). */ diff --git a/src/blitter/32bpp_simple.cpp b/src/blitter/32bpp_simple.cpp index d561daba4a..e5fba1bf56 100644 --- a/src/blitter/32bpp_simple.cpp +++ b/src/blitter/32bpp_simple.cpp @@ -126,20 +126,21 @@ void Blitter_32bppSimple::DrawColourMappingRect(void *dst, int width, int height Debug(misc, 0, "32bpp blitter doesn't know how to draw this colour table ('{}')", pal); } -Sprite *Blitter_32bppSimple::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_32bppSimple::Encode(SpriteType, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { + const auto &root_sprite = sprite.Root(); Blitter_32bppSimple::Pixel *dst; - Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(sprite[ZOOM_LVL_MIN].height) * static_cast(sprite[ZOOM_LVL_MIN].width) * sizeof(*dst)); + Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(root_sprite.height) * static_cast(root_sprite.width) * sizeof(*dst)); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; - dst = (Blitter_32bppSimple::Pixel *)dest_sprite->data; - SpriteLoader::CommonPixel *src = (SpriteLoader::CommonPixel *)sprite[ZOOM_LVL_MIN].data; + dst = reinterpret_cast(dest_sprite->data); + SpriteLoader::CommonPixel *src = reinterpret_cast(root_sprite.data); - for (int i = 0; i < sprite[ZOOM_LVL_MIN].height * sprite[ZOOM_LVL_MIN].width; i++) { + for (int i = 0; i < root_sprite.height * root_sprite.width; i++) { if (src->m == 0) { dst[i].r = src->r; dst[i].g = src->g; diff --git a/src/blitter/32bpp_simple.hpp b/src/blitter/32bpp_simple.hpp index cbecf3c44b..4bacba345f 100644 --- a/src/blitter/32bpp_simple.hpp +++ b/src/blitter/32bpp_simple.hpp @@ -26,7 +26,7 @@ class Blitter_32bppSimple : public Blitter_32bppBase { public: void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "32bpp-simple"; } }; diff --git a/src/blitter/32bpp_sse2.cpp b/src/blitter/32bpp_sse2.cpp index c0ccb269c3..6fc08ca6cd 100644 --- a/src/blitter/32bpp_sse2.cpp +++ b/src/blitter/32bpp_sse2.cpp @@ -20,18 +20,18 @@ /** Instantiation of the SSE2 32bpp blitter factory. */ static FBlitter_32bppSSE2 iFBlitter_32bppSSE2; -Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_32bppSSE_Base::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { /* First uint32_t of a line = the number of transparent pixels from the left. * Second uint32_t of a line = the number of transparent pixels from the right. * Then all RGBA then all MV. */ - ZoomLevel zoom_min = ZOOM_LVL_MIN; - ZoomLevel zoom_max = ZOOM_LVL_MIN; - if (sprite[ZOOM_LVL_MIN].type != SpriteType::Font) { + ZoomLevel zoom_min = ZoomLevel::Min; + ZoomLevel zoom_max = ZoomLevel::Min; + if (sprite_type != SpriteType::Font) { zoom_min = _settings_client.gui.zoom_min; zoom_max = _settings_client.gui.zoom_max; - if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX; + if (zoom_max == zoom_min) zoom_max = ZoomLevel::Max; } /* Calculate sizes and allocate. */ @@ -39,23 +39,25 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri uint all_sprites_size = 0; for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { const SpriteLoader::Sprite *src_sprite = &sprite[z]; - sd.infos[z].sprite_width = src_sprite->width; - sd.infos[z].sprite_offset = all_sprites_size; - sd.infos[z].sprite_line_size = sizeof(Colour) * src_sprite->width + sizeof(uint32_t) * META_LENGTH; + auto &info = sd.infos[z]; + info.sprite_width = src_sprite->width; + info.sprite_offset = all_sprites_size; + info.sprite_line_size = sizeof(Colour) * src_sprite->width + sizeof(uint32_t) * META_LENGTH; - const uint rgba_size = sd.infos[z].sprite_line_size * src_sprite->height; - sd.infos[z].mv_offset = all_sprites_size + rgba_size; + const uint rgba_size = info.sprite_line_size * src_sprite->height; + info.mv_offset = all_sprites_size + rgba_size; const uint mv_size = sizeof(MapValue) * src_sprite->width * src_sprite->height; all_sprites_size += rgba_size + mv_size; } Sprite *dst_sprite = allocator.Allocate(sizeof(Sprite) + sizeof(SpriteData) + all_sprites_size); - dst_sprite->height = sprite[ZOOM_LVL_MIN].height; - dst_sprite->width = sprite[ZOOM_LVL_MIN].width; - dst_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dst_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; - memcpy(dst_sprite->data, &sd, sizeof(SpriteData)); + const auto &root_sprite = sprite.Root(); + dst_sprite->height = root_sprite.height; + dst_sprite->width = root_sprite.width; + dst_sprite->x_offs = root_sprite.x_offs; + dst_sprite->y_offs = root_sprite.y_offs; + std::copy_n(reinterpret_cast(&sd), sizeof(SpriteData), dst_sprite->data); /* Copy colours and determine flags. */ bool has_remap = false; @@ -64,8 +66,9 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri for (ZoomLevel z = zoom_min; z <= zoom_max; z++) { const SpriteLoader::Sprite *src_sprite = &sprite[z]; const SpriteLoader::CommonPixel *src = (const SpriteLoader::CommonPixel *) src_sprite->data; - Colour *dst_rgba_line = (Colour *) &dst_sprite->data[sizeof(SpriteData) + sd.infos[z].sprite_offset]; - MapValue *dst_mv = (MapValue *) &dst_sprite->data[sizeof(SpriteData) + sd.infos[z].mv_offset]; + const auto &info = sd.infos[z]; + Colour *dst_rgba_line = reinterpret_cast(&dst_sprite->data[sizeof(SpriteData) + info.sprite_offset]); + MapValue *dst_mv = reinterpret_cast(&dst_sprite->data[sizeof(SpriteData) + info.mv_offset]); for (uint y = src_sprite->height; y != 0; y--) { Colour *dst_rgba = dst_rgba_line + META_LENGTH; for (uint x = src_sprite->width; x != 0; x--) { @@ -113,7 +116,7 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri (*dst_rgba_line).data = nb_pix_transp; Colour *nb_right = dst_rgba_line + 1; - dst_rgba_line = (Colour*) ((uint8_t*) dst_rgba_line + sd.infos[z].sprite_line_size); + dst_rgba_line = reinterpret_cast(reinterpret_cast(dst_rgba_line) + info.sprite_line_size); /* Count the number of transparent pixels from the right. */ dst_rgba = dst_rgba_line - 1; @@ -132,7 +135,7 @@ Sprite *Blitter_32bppSSE_Base::Encode(const SpriteLoader::SpriteCollection &spri if (has_translucency) sd.flags.Set(SpriteFlag::Translucent); if (!has_remap) sd.flags.Set(SpriteFlag::NoRemap); if (!has_anim) sd.flags.Set(SpriteFlag::NoAnim); - memcpy(dst_sprite->data, &sd, sizeof(SpriteData)); + std::copy_n(reinterpret_cast(&sd), sizeof(SpriteData), dst_sprite->data); return dst_sprite; } diff --git a/src/blitter/32bpp_sse2.hpp b/src/blitter/32bpp_sse2.hpp index 4d2c1f10bc..383ef41c39 100644 --- a/src/blitter/32bpp_sse2.hpp +++ b/src/blitter/32bpp_sse2.hpp @@ -73,11 +73,11 @@ public: }; struct SpriteData { SpriteFlags flags{}; - std::array infos{}; + SpriteCollMap infos{}; uint8_t data[]; ///< Data, all zoomlevels. }; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator); }; /** The SSE2 32 bpp blitter (without palette animation). */ @@ -87,8 +87,9 @@ public: template void Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom); - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override { - return Blitter_32bppSSE_Base::Encode(sprite, allocator); + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override + { + return Blitter_32bppSSE_Base::Encode(sprite_type, sprite, allocator); } std::string_view GetName() override { return "32bpp-sse2"; } diff --git a/src/blitter/32bpp_sse_func.hpp b/src/blitter/32bpp_sse_func.hpp index e87b3fca41..f9475179ff 100644 --- a/src/blitter/32bpp_sse_func.hpp +++ b/src/blitter/32bpp_sse_func.hpp @@ -311,8 +311,8 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel /* Remap colours. */ if (mvX2 & 0x00FF00FF) { + /* Written so the compiler uses CMOV. */ #define CMOV_REMAP(m_colour, m_colour_init, m_src, m_m) \ - /* Written so the compiler uses CMOV. */ \ Colour m_colour = m_colour_init; \ { \ const Colour srcm = (Colour) (m_src); \ diff --git a/src/blitter/32bpp_sse_type.h b/src/blitter/32bpp_sse_type.h index c9a05e5c08..78d05e67e3 100644 --- a/src/blitter/32bpp_sse_type.h +++ b/src/blitter/32bpp_sse_type.h @@ -28,18 +28,10 @@ #endif #define META_LENGTH 2 ///< Number of uint32_t inserted before each line of pixels in a sprite. -#define MARGIN_NORMAL_THRESHOLD (zoom == ZOOM_LVL_OUT_8X ? 8 : 4) ///< Minimum width to use margins with BlitterMode::Normal. +#define MARGIN_NORMAL_THRESHOLD (zoom == ZoomLevel::Out8x ? 8 : 4) ///< Minimum width to use margins with BlitterMode::Normal. #define MARGIN_REMAP_THRESHOLD 4 ///< Minimum width to use margins with BlitterMode::ColourRemap. -#undef ALIGN - -#ifdef _MSC_VER - #define ALIGN(n) __declspec(align(n)) -#else - #define ALIGN(n) __attribute__ ((aligned (n))) -#endif - -typedef union ALIGN(16) um128i { +typedef union alignas(16) um128i { __m128i m128i; uint8_t m128i_u8[16]; uint16_t m128i_u16[8]; diff --git a/src/blitter/40bpp_anim.cpp b/src/blitter/40bpp_anim.cpp index 044747f2d1..5b83bf7b86 100644 --- a/src/blitter/40bpp_anim.cpp +++ b/src/blitter/40bpp_anim.cpp @@ -29,7 +29,7 @@ static FBlitter_40bppAnim iFBlitter_40bppAnim; static const Colour _black_colour(0, 0, 0); -void Blitter_40bppAnim::SetPixel(void *video, int x, int y, uint8_t colour) +void Blitter_40bppAnim::SetPixel(void *video, int x, int y, PixelColour colour) { if (_screen_disable_anim) { Blitter_32bppOptimized::SetPixel(video, x, y, colour); @@ -37,11 +37,11 @@ void Blitter_40bppAnim::SetPixel(void *video, int x, int y, uint8_t colour) size_t y_offset = static_cast(y) * _screen.pitch; *((Colour *)video + x + y_offset) = _black_colour; - VideoDriver::GetInstance()->GetAnimBuffer()[((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + x + y_offset] = colour; + VideoDriver::GetInstance()->GetAnimBuffer()[((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + x + y_offset] = colour.p; } } -void Blitter_40bppAnim::DrawRect(void *video, int width, int height, uint8_t colour) +void Blitter_40bppAnim::DrawRect(void *video, int width, int height, PixelColour colour) { if (_screen_disable_anim) { /* This means our output is not to the screen, so we can't be doing any animation stuff, so use our parent DrawRect() */ @@ -58,7 +58,7 @@ void Blitter_40bppAnim::DrawRect(void *video, int width, int height, uint8_t col for (int i = width; i > 0; i--) { *dst = _black_colour; - *anim = colour; + *anim = colour.p; dst++; anim++; } @@ -67,7 +67,7 @@ void Blitter_40bppAnim::DrawRect(void *video, int width, int height, uint8_t col } while (--height); } -void Blitter_40bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) +void Blitter_40bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) { if (_screen_disable_anim) { /* This means our output is not to the screen, so we can't be doing any animation stuff, so use our parent DrawRect() */ @@ -80,7 +80,7 @@ void Blitter_40bppAnim::DrawLine(void *video, int x, int y, int x2, int y2, int this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [=](int x, int y) { *((Colour *)video + x + y * _screen.pitch) = _black_colour; - *(anim + x + y * _screen.pitch) = colour; + *(anim + x + y * _screen.pitch) = colour.p; }); } @@ -98,11 +98,11 @@ inline void Blitter_40bppAnim::Draw(const Blitter::BlitterParams *bp, ZoomLevel /* src_px : each line begins with uint32_t n = 'number of bytes in this line', * then n times is the Colour struct for this line */ - const Colour *src_px = (const Colour *)(src->data + src->offset[zoom][0]); + const Colour *src_px = reinterpret_cast(src->data + src->offset[0][zoom]); /* src_n : each line begins with uint32_t n = 'number of bytes in this line', * then interleaved stream of 'm' and 'n' channels. 'm' is remap, * 'n' is number of bytes with the same alpha channel class */ - const uint16_t *src_n = (const uint16_t *)(src->data + src->offset[zoom][1]); + const uint16_t *src_n = reinterpret_cast(src->data + src->offset[1][zoom]); /* skip upper lines in src_px and src_n */ for (uint i = bp->skip_top; i != 0; i--) { @@ -390,8 +390,11 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, const uint8_t *remap = GetNonSprite(pal, SpriteType::Recolour) + 1; do { for (int i = 0; i != width; i++) { - if (*anim == 0) *udst = MakeGrey(*udst); - *anim = remap[*anim]; + if (*anim == 0) { + *udst = MakeGrey(*udst); + } else { + *anim = remap[*anim]; + } udst++; anim++; } @@ -402,7 +405,7 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, const uint8_t *remap = GetNonSprite(pal, SpriteType::Recolour) + 1; do { for (int i = 0; i != width; i++) { - *anim = remap[*anim]; + if (*anim != 0) *anim = remap[*anim]; anim++; } anim = anim - width + _screen.pitch; @@ -410,9 +413,9 @@ void Blitter_40bppAnim::DrawColourMappingRect(void *dst, int width, int height, } } -Sprite *Blitter_40bppAnim::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_40bppAnim::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { - return this->EncodeInternal(sprite, allocator); + return this->EncodeInternal(sprite_type, sprite, allocator); } @@ -428,11 +431,11 @@ void Blitter_40bppAnim::CopyFromBuffer(void *video, const void *src, int width, uint8_t *anim_line = ((uint32_t *)video - (uint32_t *)_screen.dst_ptr) + anim_buf; for (; height > 0; height--) { - memcpy(dst, usrc, width * sizeof(uint32_t)); + std::copy_n(usrc, width, dst); usrc += width; dst += _screen.pitch; /* Copy back the anim-buffer */ - memcpy(anim_line, usrc, width * sizeof(uint8_t)); + std::copy_n(reinterpret_cast(usrc), width, anim_line); usrc = (const uint32_t *)((const uint8_t *)usrc + width); anim_line += _screen.pitch; } @@ -450,11 +453,11 @@ void Blitter_40bppAnim::CopyToBuffer(const void *video, void *dst, int width, in const uint8_t *anim_line = ((const uint32_t *)video - (uint32_t *)_screen.dst_ptr) + anim_buf; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint32_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; /* Copy the anim-buffer */ - memcpy(udst, anim_line, width * sizeof(uint8_t)); + std::copy_n(anim_line, width, reinterpret_cast(udst)); udst = (uint32_t *)((uint8_t *)udst + width); anim_line += _screen.pitch; } @@ -503,11 +506,7 @@ void Blitter_40bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height - scroll_y; - for (; th > 0; th--) { - memcpy(dst, src, tw * sizeof(uint8_t)); - src -= _screen.pitch; - dst -= _screen.pitch; - } + Blitter::MovePixels(src, dst, tw, th, -_screen.pitch); } else { /* Calculate pointers */ dst = anim_buf + left + top * _screen.pitch; @@ -520,15 +519,9 @@ void Blitter_40bppAnim::ScrollBuffer(void *video, int &left, int &top, int &widt src -= scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ uint tw = width + (scroll_x >= 0 ? -scroll_x : scroll_x); uint th = height + scroll_y; - for (; th > 0; th--) { - memmove(dst, src, tw * sizeof(uint8_t)); - src += _screen.pitch; - dst += _screen.pitch; - } + Blitter::MovePixels(src, dst, tw, th, _screen.pitch); } Blitter_32bppBase::ScrollBuffer(video, left, top, width, height, scroll_x, scroll_y); diff --git a/src/blitter/40bpp_anim.hpp b/src/blitter/40bpp_anim.hpp index 385a4fe555..55e3a0664b 100644 --- a/src/blitter/40bpp_anim.hpp +++ b/src/blitter/40bpp_anim.hpp @@ -18,16 +18,16 @@ class Blitter_40bppAnim : public Blitter_32bppOptimized { public: - void SetPixel(void *video, int x, int y, uint8_t colour) override; - void DrawRect(void *video, int width, int height, uint8_t colour) override; - void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) override; + void SetPixel(void *video, int x, int y, PixelColour colour) override; + void DrawRect(void *video, int width, int height, PixelColour colour) override; + void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) override; void CopyFromBuffer(void *video, const void *src, int width, int height) override; void CopyToBuffer(const void *video, void *dst, int width, int height) override; void CopyImageToBuffer(const void *video, void *dst, int width, int height, int dst_pitch) override; void ScrollBuffer(void *video, int &left, int &top, int &width, int &height, int scroll_x, int scroll_y) override; void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; size_t BufferSize(uint width, uint height) override; Blitter::PaletteAnimation UsePaletteAnimation() override; bool NeedsAnimationBuffer() override; diff --git a/src/blitter/8bpp_base.cpp b/src/blitter/8bpp_base.cpp index a936138a1a..ae7858a17f 100644 --- a/src/blitter/8bpp_base.cpp +++ b/src/blitter/8bpp_base.cpp @@ -29,23 +29,24 @@ void *Blitter_8bppBase::MoveTo(void *video, int x, int y) return (uint8_t *)video + x + y * _screen.pitch; } -void Blitter_8bppBase::SetPixel(void *video, int x, int y, uint8_t colour) +void Blitter_8bppBase::SetPixel(void *video, int x, int y, PixelColour colour) { - *((uint8_t *)video + x + y * _screen.pitch) = colour; + *((uint8_t *)video + x + y * _screen.pitch) = colour.p; } -void Blitter_8bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) +void Blitter_8bppBase::DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) { this->DrawLineGeneric(x, y, x2, y2, screen_width, screen_height, width, dash, [=](int x, int y) { - *((uint8_t *)video + x + y * _screen.pitch) = colour; + *((uint8_t *)video + x + y * _screen.pitch) = colour.p; }); } -void Blitter_8bppBase::DrawRect(void *video, int width, int height, uint8_t colour) +void Blitter_8bppBase::DrawRect(void *video, int width, int height, PixelColour colour) { + std::byte *p = static_cast(video); do { - memset(video, colour, width); - video = (uint8_t *)video + _screen.pitch; + std::fill_n(p, width, static_cast(colour.p)); + p += _screen.pitch; } while (--height); } @@ -55,7 +56,7 @@ void Blitter_8bppBase::CopyFromBuffer(void *video, const void *src, int width, i const uint8_t *usrc = (const uint8_t *)src; for (; height > 0; height--) { - memcpy(dst, usrc, width * sizeof(uint8_t)); + std::copy_n(usrc, width, dst); usrc += width; dst += _screen.pitch; } @@ -67,7 +68,7 @@ void Blitter_8bppBase::CopyToBuffer(const void *video, void *dst, int width, int const uint8_t *src = (const uint8_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint8_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += width; } @@ -79,7 +80,7 @@ void Blitter_8bppBase::CopyImageToBuffer(const void *video, void *dst, int width const uint8_t *src = (const uint8_t *)video; for (; height > 0; height--) { - memcpy(udst, src, width * sizeof(uint8_t)); + std::copy_n(src, width, udst); src += _screen.pitch; udst += dst_pitch; } @@ -110,11 +111,7 @@ void Blitter_8bppBase::ScrollBuffer(void *video, int &left, int &top, int &width width += scroll_x; } - for (int h = height; h > 0; h--) { - memcpy(dst, src, width * sizeof(uint8_t)); - src -= _screen.pitch; - dst -= _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, -_screen.pitch); } else { /* Calculate pointers */ dst = (uint8_t *)video + left + top * _screen.pitch; @@ -134,13 +131,7 @@ void Blitter_8bppBase::ScrollBuffer(void *video, int &left, int &top, int &width width += scroll_x; } - /* the y-displacement may be 0 therefore we have to use memmove, - * because source and destination may overlap */ - for (int h = height; h > 0; h--) { - memmove(dst, src, width * sizeof(uint8_t)); - src += _screen.pitch; - dst += _screen.pitch; - } + Blitter::MovePixels(src, dst, width, height, _screen.pitch); } } diff --git a/src/blitter/8bpp_base.hpp b/src/blitter/8bpp_base.hpp index 5a76d79521..197b1f3a49 100644 --- a/src/blitter/8bpp_base.hpp +++ b/src/blitter/8bpp_base.hpp @@ -18,9 +18,9 @@ public: uint8_t GetScreenDepth() override { return 8; } void DrawColourMappingRect(void *dst, int width, int height, PaletteID pal) override; void *MoveTo(void *video, int x, int y) override; - void SetPixel(void *video, int x, int y, uint8_t colour) override; - void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash) override; - void DrawRect(void *video, int width, int height, uint8_t colour) override; + void SetPixel(void *video, int x, int y, PixelColour colour) override; + void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash) override; + void DrawRect(void *video, int width, int height, PixelColour colour) override; void CopyFromBuffer(void *video, const void *src, int width, int height) override; void CopyToBuffer(const void *video, void *dst, int width, int height) override; void CopyImageToBuffer(const void *video, void *dst, int width, int height, int dst_pitch) override; diff --git a/src/blitter/8bpp_optimized.cpp b/src/blitter/8bpp_optimized.cpp index b646dc6f89..4984ba3f7f 100644 --- a/src/blitter/8bpp_optimized.cpp +++ b/src/blitter/8bpp_optimized.cpp @@ -11,7 +11,6 @@ #include "../zoom_func.h" #include "../settings_type.h" #include "../core/math_func.hpp" -#include "../core/mem_func.hpp" #include "8bpp_optimized.hpp" #include "../safeguards.h" @@ -97,7 +96,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z } case BlitterMode::BlackRemap: - MemSetT(dst, 0, pixels); + std::fill_n(dst, pixels, 0); dst += pixels; break; @@ -113,7 +112,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z } default: - MemCpyT(dst, src, pixels); + std::copy_n(src, pixels, dst); dst += pixels; src += pixels; break; } @@ -121,7 +120,7 @@ void Blitter_8bppOptimized::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Z } } -Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_8bppOptimized::Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { /* Make memory for all zoom-levels */ uint memory = sizeof(SpriteData); @@ -129,13 +128,13 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri ZoomLevel zoom_min; ZoomLevel zoom_max; - if (sprite[ZOOM_LVL_MIN].type == SpriteType::Font) { - zoom_min = ZOOM_LVL_MIN; - zoom_max = ZOOM_LVL_MIN; + if (sprite_type == SpriteType::Font) { + zoom_min = ZoomLevel::Min; + zoom_max = ZoomLevel::Min; } else { zoom_min = _settings_client.gui.zoom_min; zoom_max = _settings_client.gui.zoom_max; - if (zoom_max == zoom_min) zoom_max = ZOOM_LVL_MAX; + if (zoom_max == zoom_min) zoom_max = ZoomLevel::Max; } for (ZoomLevel i = zoom_min; i <= zoom_max; i++) { @@ -154,13 +153,14 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri /* Make the sprites per zoom-level */ for (ZoomLevel i = zoom_min; i <= zoom_max; i++) { + const SpriteLoader::Sprite &src_orig = sprite[i]; /* Store the index table */ uint offset = dst - temp_dst->data; temp_dst->offset[i] = offset; /* cache values, because compiler can't cache it */ - int scaled_height = sprite[i].height; - int scaled_width = sprite[i].width; + int scaled_height = src_orig.height; + int scaled_width = src_orig.width; for (int y = 0; y < scaled_height; y++) { uint trans = 0; @@ -169,7 +169,7 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri uint8_t *count_dst = nullptr; /* Store the scaled image */ - const SpriteLoader::CommonPixel *src = &sprite[i].data[y * sprite[i].width]; + const SpriteLoader::CommonPixel *src = &src_orig.data[y * src_orig.width]; for (int x = 0; x < scaled_width; x++) { uint colour = src++->m; @@ -221,11 +221,12 @@ Sprite *Blitter_8bppOptimized::Encode(const SpriteLoader::SpriteCollection &spri /* Allocate the exact amount of memory we need */ Sprite *dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + size); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; - memcpy(dest_sprite->data, temp_dst, size); + const auto &root_sprite = sprite.Root(); + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; + std::copy_n(reinterpret_cast(temp_dst), size, dest_sprite->data); return dest_sprite; } diff --git a/src/blitter/8bpp_optimized.hpp b/src/blitter/8bpp_optimized.hpp index 8452111d73..240b6dbdd9 100644 --- a/src/blitter/8bpp_optimized.hpp +++ b/src/blitter/8bpp_optimized.hpp @@ -18,12 +18,12 @@ class Blitter_8bppOptimized final : public Blitter_8bppBase { public: /** Data stored about a (single) sprite. */ struct SpriteData { - uint32_t offset[ZOOM_LVL_END]; ///< Offsets (from .data) to streams for different zoom levels. + SpriteCollMap offset; ///< Offsets (from .data) to streams for different zoom levels. uint8_t data[]; ///< Data, all zoomlevels. }; void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "8bpp-optimized"; } }; diff --git a/src/blitter/8bpp_simple.cpp b/src/blitter/8bpp_simple.cpp index 03541826f2..fee3536ff5 100644 --- a/src/blitter/8bpp_simple.cpp +++ b/src/blitter/8bpp_simple.cpp @@ -62,19 +62,21 @@ void Blitter_8bppSimple::Draw(Blitter::BlitterParams *bp, BlitterMode mode, Zoom } } -Sprite *Blitter_8bppSimple::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_8bppSimple::Encode(SpriteType, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { + const auto &root_sprite = sprite.Root(); Sprite *dest_sprite; - dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(sprite[ZOOM_LVL_MIN].height) * static_cast(sprite[ZOOM_LVL_MIN].width)); + dest_sprite = allocator.Allocate(sizeof(*dest_sprite) + static_cast(root_sprite.height) * static_cast(root_sprite.width)); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; /* Copy over only the 'remap' channel, as that is what we care about in 8bpp */ - for (int i = 0; i < sprite[ZOOM_LVL_MIN].height * sprite[ZOOM_LVL_MIN].width; i++) { - dest_sprite->data[i] = sprite[ZOOM_LVL_MIN].data[i].m; + uint8_t *dst = reinterpret_cast(dest_sprite->data); + for (int i = 0; i < root_sprite.height * root_sprite.width; i++) { + dst[i] = root_sprite.data[i].m; } return dest_sprite; diff --git a/src/blitter/8bpp_simple.hpp b/src/blitter/8bpp_simple.hpp index 77dcf16a84..9398456737 100644 --- a/src/blitter/8bpp_simple.hpp +++ b/src/blitter/8bpp_simple.hpp @@ -17,7 +17,7 @@ class Blitter_8bppSimple final : public Blitter_8bppBase { public: void Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) override; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; std::string_view GetName() override { return "8bpp-simple"; } }; diff --git a/src/blitter/base.hpp b/src/blitter/base.hpp index 13943980e3..6fcf1d5739 100644 --- a/src/blitter/base.hpp +++ b/src/blitter/base.hpp @@ -96,18 +96,18 @@ public: * @param video The destination pointer (video-buffer). * @param x The x position within video-buffer. * @param y The y position within video-buffer. - * @param colour A 8bpp mapping colour. + * @param colour A pixel colour. */ - virtual void SetPixel(void *video, int x, int y, uint8_t colour) = 0; + virtual void SetPixel(void *video, int x, int y, PixelColour colour) = 0; /** * Make a single horizontal line in a single colour on the video-buffer. * @param video The destination pointer (video-buffer). * @param width The length of the line. * @param height The height of the line. - * @param colour A 8bpp mapping colour. + * @param colour A pixel colour. */ - virtual void DrawRect(void *video, int width, int height, uint8_t colour) = 0; + virtual void DrawRect(void *video, int width, int height, PixelColour colour) = 0; /** * Draw a line with a given colour. @@ -118,11 +118,11 @@ public: * @param y2 The y coordinate to where the lines goes. * @param screen_width The width of the screen you are drawing in (to avoid buffer-overflows). * @param screen_height The height of the screen you are drawing in (to avoid buffer-overflows). - * @param colour A 8bpp mapping colour. + * @param colour A pixel colour. * @param width Line width. * @param dash Length of dashes for dashed lines. 0 means solid line. */ - virtual void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8_t colour, int width, int dash = 0) = 0; + virtual void DrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, PixelColour colour, int width, int dash = 0) = 0; /** * Copy from a buffer to the screen. @@ -208,6 +208,8 @@ public: 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); + + template static void MovePixels(const T *src, T *dst, size_t width, size_t height, ptrdiff_t pitch); }; #endif /* BLITTER_BASE_HPP */ diff --git a/src/blitter/common.hpp b/src/blitter/common.hpp index eebdfc05e7..af94dadbdd 100644 --- a/src/blitter/common.hpp +++ b/src/blitter/common.hpp @@ -78,11 +78,11 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, int frac_low = dy - frac_diff / 2; int frac_high = dy + frac_diff / 2; - while (frac_low < -(dx / 2)) { + while (frac_low < -dx) { frac_low += dx; y_low -= stepy; } - while (frac_high >= dx / 2) { + while (frac_high >= dy) { frac_high -= dx; y_high += stepy; } @@ -90,13 +90,14 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, if (x1 < 0) { dash_count = (-x1) % (dash + gap); auto adjust_frac = [&](int64_t frac, int &y_bound) -> int { - frac -= ((int64_t) dy) * ((int64_t) x1); + frac -= ((int64_t) dy) * ((int64_t) (x1 + 1)); if (frac >= 0) { int quotient = frac / dx; int remainder = frac % dx; y_bound += (1 + quotient) * stepy; frac = remainder - dx; } + frac += dy; return frac; }; frac_low = adjust_frac(frac_low, y_low); @@ -140,11 +141,11 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, int frac_low = dx - frac_diff / 2; int frac_high = dx + frac_diff / 2; - while (frac_low < -(dy / 2)) { + while (frac_low < -dy) { frac_low += dy; x_low -= stepx; } - while (frac_high >= dy / 2) { + while (frac_high >= dx) { frac_high -= dy; x_high += stepx; } @@ -152,13 +153,14 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, if (y1 < 0) { dash_count = (-y1) % (dash + gap); auto adjust_frac = [&](int64_t frac, int &x_bound) -> int { - frac -= ((int64_t) dx) * ((int64_t) y1); + frac -= ((int64_t) dx) * ((int64_t) (y1 + 1)); if (frac >= 0) { int quotient = frac / dy; int remainder = frac % dy; x_bound += (1 + quotient) * stepx; frac = remainder - dy; } + frac += dx; return frac; }; frac_low = adjust_frac(frac_low, x_low); @@ -192,4 +194,24 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, } } +template +/* static */ void Blitter::MovePixels(const T *src, T *dst, size_t width, size_t height, ptrdiff_t pitch) +{ + if (src == dst) return; + + if (src < dst) { + for (size_t i = 0; i < height; ++i) { + std::move_backward(src, src + width, dst + width); + src += pitch; + dst += pitch; + } + } else { + for (size_t i = 0; i < height; ++i) { + std::move(src, src + width, dst); + src += pitch; + dst += pitch; + } + } +} + #endif /* BLITTER_COMMON_HPP */ diff --git a/src/blitter/factory.hpp b/src/blitter/factory.hpp index 0dceea90f2..a1dffdcd4a 100644 --- a/src/blitter/factory.hpp +++ b/src/blitter/factory.hpp @@ -55,7 +55,7 @@ protected: * @pre description != nullptr. * @pre There is no blitter registered with this name. */ - BlitterFactory(const char *name, const char *description, bool usable = true) : + BlitterFactory(std::string_view name, std::string_view description, bool usable = true) : name(name), description(description) { if (usable) { @@ -93,7 +93,7 @@ public: * @param name the blitter to select. * @post Sets the blitter so GetCurrentBlitter() returns it too. */ - static Blitter *SelectBlitter(const std::string_view name) + static Blitter *SelectBlitter(std::string_view name) { BlitterFactory *b = GetBlitterFactory(name); if (b == nullptr) return nullptr; @@ -109,17 +109,17 @@ public: * @param name the blitter factory to select. * @return The blitter factory, or nullptr when there isn't one with the wanted name. */ - static BlitterFactory *GetBlitterFactory(const std::string_view name) + static BlitterFactory *GetBlitterFactory(std::string_view name) { #if defined(DEDICATED) - const std::string_view default_blitter = "null"; + static const std::string_view default_blitter = "null"; #elif defined(WITH_COCOA) - const std::string_view default_blitter = "32bpp-anim"; + static const std::string_view default_blitter = "32bpp-anim"; #else - const std::string_view default_blitter = "8bpp-optimized"; + static const std::string_view default_blitter = "8bpp-optimized"; #endif if (GetBlitters().empty()) return nullptr; - const std::string_view bname = name.empty() ? default_blitter : name; + std::string_view bname = name.empty() ? default_blitter : name; for (auto &it : GetBlitters()) { BlitterFactory *b = it.second; diff --git a/src/blitter/null.cpp b/src/blitter/null.cpp index dc8b1c0ea2..41b8a719b1 100644 --- a/src/blitter/null.cpp +++ b/src/blitter/null.cpp @@ -15,15 +15,16 @@ /** Instantiation of the null blitter factory. */ static FBlitter_Null iFBlitter_Null; -Sprite *Blitter_Null::Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) +Sprite *Blitter_Null::Encode(SpriteType, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) { Sprite *dest_sprite; dest_sprite = allocator.Allocate(sizeof(*dest_sprite)); - dest_sprite->height = sprite[ZOOM_LVL_MIN].height; - dest_sprite->width = sprite[ZOOM_LVL_MIN].width; - dest_sprite->x_offs = sprite[ZOOM_LVL_MIN].x_offs; - dest_sprite->y_offs = sprite[ZOOM_LVL_MIN].y_offs; + const auto &root_sprite = sprite.Root(); + dest_sprite->height = root_sprite.height; + dest_sprite->width = root_sprite.width; + dest_sprite->x_offs = root_sprite.x_offs; + dest_sprite->y_offs = root_sprite.y_offs; return dest_sprite; } diff --git a/src/blitter/null.hpp b/src/blitter/null.hpp index 15dc722929..336cb7c770 100644 --- a/src/blitter/null.hpp +++ b/src/blitter/null.hpp @@ -18,11 +18,11 @@ public: uint8_t GetScreenDepth() override { return 0; } void Draw(Blitter::BlitterParams *, BlitterMode, ZoomLevel) override {}; void DrawColourMappingRect(void *, int, int, PaletteID) override {}; - Sprite *Encode(const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; + Sprite *Encode(SpriteType sprite_type, const SpriteLoader::SpriteCollection &sprite, SpriteAllocator &allocator) override; void *MoveTo(void *, int, int) override { return nullptr; }; - void SetPixel(void *, int, int, uint8_t) override {}; - void DrawRect(void *, int, int, uint8_t) override {}; - void DrawLine(void *, int, int, int, int, int, int, uint8_t, int, int) override {}; + void SetPixel(void *, int, int, PixelColour) override {}; + void DrawRect(void *, int, int, PixelColour) override {}; + void DrawLine(void *, int, int, int, int, int, int, PixelColour, int, int) override {}; void CopyFromBuffer(void *, const void *, int, int) override {}; void CopyToBuffer(const void *, void *, int, int) override {}; void CopyImageToBuffer(const void *, void *, int, int, int) override {}; diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index 44bce54ca8..80620f303b 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -42,7 +42,7 @@ static constexpr NWidgetPart _background_widgets[] = { * Window description for the background window to prevent smearing. */ static WindowDesc _background_desc( - WDP_MANUAL, nullptr, 0, 0, + WDP_MANUAL, {}, 0, 0, WC_BOOTSTRAP, WC_NONE, WindowDefaultFlag::NoClose, _background_widgets @@ -60,8 +60,8 @@ public: void DrawWidget(const Rect &r, WidgetID) const override { - GfxFillRect(r.left, r.top, r.right, r.bottom, 4, FILLRECT_OPAQUE); - GfxFillRect(r.left, r.top, r.right, r.bottom, 0, FILLRECT_CHECKER); + GfxFillRect(r.left, r.top, r.right, r.bottom, PixelColour{4}, FILLRECT_OPAQUE); + GfxFillRect(r.left, r.top, r.right, r.bottom, PixelColour{0}, FILLRECT_CHECKER); } }; @@ -78,7 +78,7 @@ static constexpr NWidgetPart _nested_bootstrap_errmsg_widgets[] = { /** Window description for the error window. */ static WindowDesc _bootstrap_errmsg_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_BOOTSTRAP, WC_NONE, {WindowDefaultFlag::Modal, WindowDefaultFlag::NoClose}, _nested_bootstrap_errmsg_widgets @@ -135,7 +135,7 @@ static constexpr NWidgetPart _nested_bootstrap_download_status_window_widgets[] /** Window description for the download window */ static WindowDesc _bootstrap_download_status_window_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_NETWORK_STATUS_WINDOW, WC_NONE, {WindowDefaultFlag::Modal, WindowDefaultFlag::NoClose}, _nested_bootstrap_download_status_window_widgets @@ -187,7 +187,7 @@ static constexpr NWidgetPart _bootstrap_query_widgets[] = { /** The window description for the query. */ static WindowDesc _bootstrap_query_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, WC_CONFIRM_POPUP_QUERY, WC_NONE, WindowDefaultFlag::NoClose, _bootstrap_query_widgets @@ -385,10 +385,10 @@ bool HandleBootstrap() /* Initialise the palette. The biggest step is 'faking' some recolour sprites. * This way the mauve and gray colours work and we can show the user interface. */ GfxInitPalettes(); - static const int offsets[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0x04, 0x08 }; + static const uint8_t offsets[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0x04, 0x08 }; for (Colours i = COLOUR_BEGIN; i != COLOUR_END; i++) { for (ColourShade j = SHADE_BEGIN; j < SHADE_END; j++) { - SetColourGradient(i, j, offsets[i] + j); + SetColourGradient(i, j, PixelColour(offsets[i] + j)); } } diff --git a/src/bridge.h b/src/bridge.h index c9e7200698..03485327f2 100644 --- a/src/bridge.h +++ b/src/bridge.h @@ -13,34 +13,25 @@ #include "gfx_type.h" #include "tile_cmd.h" #include "timer/timer_game_calendar.h" - -/** - * This enum is related to the definition of bridge pieces, - * which is used to determine the proper sprite table to use - * while drawing a given bridge part. - */ -enum BridgePieces : uint8_t { - BRIDGE_PIECE_NORTH = 0, - BRIDGE_PIECE_SOUTH, - BRIDGE_PIECE_INNER_NORTH, - BRIDGE_PIECE_INNER_SOUTH, - BRIDGE_PIECE_MIDDLE_ODD, - BRIDGE_PIECE_MIDDLE_EVEN, - BRIDGE_PIECE_HEAD, - NUM_BRIDGE_PIECES, -}; - -DECLARE_INCREMENT_DECREMENT_OPERATORS(BridgePieces) +#include "bridge_type.h" static const uint MAX_BRIDGES = 13; ///< Maximal number of available bridge specs. constexpr uint SPRITES_PER_BRIDGE_PIECE = 32; ///< Number of sprites there are per bridge piece. -typedef uint BridgeType; ///< Bridge spec number. +/* Container for Bridge pillar flags for each axis of each bridge middle piece. */ +using BridgeMiddlePillarFlags = std::array, NUM_BRIDGE_MIDDLE_PIECES>; /** * Struct containing information about a single bridge type */ struct BridgeSpec { + /** Internal flags about each BridgeSpec. */ + enum class ControlFlag : uint8_t { + CustomPillarFlags, ///< Bridge has set custom pillar flags. + InvalidPillarFlags, ///< Bridge pillar flags are not valid, i.e. only the tile layout has been modified. + }; + using ControlFlags = EnumBitSet; + TimerGameCalendar::Year avail_year; ///< the year where it becomes available uint8_t min_length; ///< the minimum length (not counting start and end tile) uint16_t max_length; ///< the maximum length (not counting start and end tile) @@ -52,6 +43,8 @@ struct BridgeSpec { StringID transport_name[2]; ///< description of the bridge, when built for road or rail std::vector> sprite_table; ///< table of sprites for drawing the bridge uint8_t flags; ///< bit 0 set: disable drawing of far pillars. + ControlFlags ctrl_flags{}; ///< control flags + BridgeMiddlePillarFlags pillar_flags{}; ///< bridge pillar flags. }; extern BridgeSpec _bridge[MAX_BRIDGES]; @@ -70,7 +63,7 @@ inline const BridgeSpec *GetBridgeSpec(BridgeType i) return &_bridge[i]; } -void DrawBridgeMiddle(const TileInfo *ti); +void DrawBridgeMiddle(const TileInfo *ti, BridgePillarFlags blocked_pillars); CommandCost CheckBridgeAvailability(BridgeType bridge_type, uint bridge_len, DoCommandFlags flags = {}); int CalcBridgeLenCostFactor(int x); diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index b746e8a2bd..f852562a09 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -210,7 +210,7 @@ public: sprite_dim = maxdim(sprite_dim, GetScaledSpriteSize(bridge_data.spec->sprite)); text_dim = maxdim(text_dim, GetStringBoundingBox(GetBridgeSelectString(bridge_data))); } - resize.height = std::max(sprite_dim.height, text_dim.height) + padding.height; // Max of both sizes + account for matrix edges. + fill.height = resize.height = std::max(sprite_dim.height, text_dim.height) + padding.height; // Max of both sizes + account for matrix edges. this->icon_width = sprite_dim.width; // Width of bridge icon. size.width = this->icon_width + WidgetDimensions::scaled.hsep_normal + text_dim.width + padding.width; @@ -244,7 +244,7 @@ public: for (auto it = first; it != last; ++it) { const BridgeSpec *b = it->spec; DrawSpriteIgnorePadding(b->sprite, b->pal, tr.WithWidth(this->icon_width, rtl), SA_HOR_CENTER | SA_BOTTOM); - DrawStringMultiLine(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it)); + DrawStringMultiLineWithClipping(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it)); tr = tr.Translate(0, this->resize.step_height); } break; @@ -288,7 +288,7 @@ public: } } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { if (widget == WID_BBS_DROPDOWN_CRITERIA && this->bridges.SortType() != index) { this->bridges.SetSortType(index); @@ -384,13 +384,10 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo /* only query bridge building possibility once, result is the same for all bridges! * returns CMD_ERROR on failure, and price on success */ - StringID errmsg = INVALID_STRING_ID; CommandCost ret = Command::Do(CommandFlagsToDCFlags(GetCommandFlags()) | DoCommandFlag::QueryCost, end, start, transport_type, 0, road_rail_type); GUIBridgeList bl; - if (ret.Failed()) { - errmsg = ret.GetErrorMessage(); - } else { + if (!ret.Failed()) { /* check which bridges can be built */ const uint tot_bridgedata_len = CalcBridgeLenCostFactor(bridge_len + 2); @@ -436,16 +433,12 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo } } /* give error cause if no bridges available here*/ - if (!any_available) - { - errmsg = type_check.GetErrorMessage(); - } + if (!any_available) ret = type_check; } if (!bl.empty()) { new BuildBridgeWindow(_build_bridge_desc, start, end, transport_type, road_rail_type, std::move(bl)); } else { - ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), GetEncodedString(errmsg), - WL_INFO, TileX(end) * TILE_SIZE, TileY(end) * TILE_SIZE); + ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), TileX(end) * TILE_SIZE, TileY(end) * TILE_SIZE, ret); } } diff --git a/src/bridge_type.h b/src/bridge_type.h new file mode 100644 index 0000000000..82b42b51d1 --- /dev/null +++ b/src/bridge_type.h @@ -0,0 +1,64 @@ +/* + * 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 bridge_type.h Header file for bridge types. */ + +#ifndef BRIDGE_TYPE_H +#define BRIDGE_TYPE_H + +#include "core/enum_type.hpp" + +using BridgeType = uint; ///< Bridge spec number. + +/** + * This enum is related to the definition of bridge pieces, + * which is used to determine the proper sprite table to use + * while drawing a given bridge part. + */ +enum BridgePieces : uint8_t { + BRIDGE_PIECE_NORTH = 0, + BRIDGE_PIECE_SOUTH, + BRIDGE_PIECE_INNER_NORTH, + BRIDGE_PIECE_INNER_SOUTH, + BRIDGE_PIECE_MIDDLE_ODD, + BRIDGE_PIECE_MIDDLE_EVEN, + BRIDGE_PIECE_HEAD, + NUM_BRIDGE_PIECES, +}; + +DECLARE_INCREMENT_DECREMENT_OPERATORS(BridgePieces) + +/** Number of bridge middles pieces. This is all bridge pieces except BRIDGE_PIECE_HEAD. */ +static constexpr uint NUM_BRIDGE_MIDDLE_PIECES = NUM_BRIDGE_PIECES - 1; + +/** Obstructed bridge pillars information. */ +enum class BridgePillarFlag : uint8_t { + /* Corners are in the same order as Corner enum. */ + CornerW = 0, ///< West corner is obstructed. + CornerS = 1, ///< South corner is obstructed. + CornerE = 2, ///< East corner is obstructed. + CornerN = 3, ///< North corner is obstructed. + /* Edges are in the same order as DiagDirection enum. */ + EdgeNE = 4, //< Northeast edge is obstructed. + EdgeSE = 5, //< Southeast edge is obstructed. + EdgeSW = 6, //< Southwest edge is obstructed. + EdgeNW = 7, //< Northwest edge is obstructed. +}; +using BridgePillarFlags = EnumBitSet; + +static constexpr BridgePillarFlags BRIDGEPILLARFLAGS_ALL{ + BridgePillarFlag::CornerW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerE, BridgePillarFlag::CornerN, + BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNW, +}; + +/** Information about a tile structure that may have a bridge above. */ +struct BridgeableTileInfo { + uint8_t height = 0; ///< Minimum height for a bridge above. 0 means a bridge is not allowed. + BridgePillarFlags disallowed_pillars = BRIDGEPILLARFLAGS_ALL; ///< Disallowed pillar flags for a bridge above +}; + +#endif /* BRIDGE_TYPE_H */ diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index b8daaa32c6..714784d5d0 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -19,6 +19,8 @@ #include "company_func.h" #include "vehicle_gui.h" #include "newgrf_badge.h" +#include "newgrf_badge_config.h" +#include "newgrf_badge_gui.h" #include "newgrf_engine.h" #include "newgrf_text.h" #include "group.h" @@ -75,10 +77,13 @@ static constexpr NWidgetPart _nested_build_vehicle_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_ENGINES), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetToolTip(STR_TOOLTIP_FILTER_CRITERIA), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_BV_CONFIGURE_BADGES), SetAspect(WidgetDimensions::ASPECT_UP_DOWN_BUTTON), SetResize(0, 0), SetFill(0, 1), SetSpriteTip(SPR_EXTRA_MENU, STR_BADGE_CONFIG_MENU_TOOLTIP), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY), NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BV_FILTER), SetResize(1, 0), SetFill(1, 0), SetPadding(2), SetStringTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), EndContainer(), + NWidget(NWID_VERTICAL, NWidContainerFlag{}, WID_BV_BADGE_FILTER), + EndContainer(), EndContainer(), /* Vehicle list. */ NWidget(NWID_HORIZONTAL), @@ -798,16 +803,20 @@ static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_ */ static std::optional GetNewGRFAdditionalText(EngineID engine) { - uint16_t callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr); + std::array regs100; + uint16_t callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr, regs100); if (callback == CALLBACK_FAILED || callback == 0x400) return std::nullopt; const GRFFile *grffile = Engine::Get(engine)->GetGRF(); assert(grffile != nullptr); + if (callback == 0x40F) { + return GetGRFStringWithTextStack(grffile, static_cast(regs100[0]), std::span{regs100}.subspan(1)); + } if (callback > 0x400) { ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback); return std::nullopt; } - return GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, 6); + return GetGRFStringWithTextStack(grffile, GRFSTR_MISC_GRF_TEXT + callback, regs100); } /** @@ -955,7 +964,7 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right; int sprite_width = sprite_left + sprite_right; int circle_width = std::max(GetScaledSpriteSize(SPR_CIRCLE_FOLDED).width, GetScaledSpriteSize(SPR_CIRCLE_UNFOLDED).width); - int linecolour = GetColourGradient(COLOUR_ORANGE, SHADE_NORMAL); + PixelColour linecolour = GetColourGradient(COLOUR_ORANGE, SHADE_NORMAL); auto badge_column_widths = badge_classes.GetColumnWidths(); @@ -1003,7 +1012,7 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li if (lvl < item.indent) tx += level_width; } /* Draw our node in the tree. */ - int ycentre = CenterBounds(textr.top, textr.bottom, WidgetDimensions::scaled.fullbevel.top); + int ycentre = CentreBounds(textr.top, textr.bottom, WidgetDimensions::scaled.fullbevel.top); if (!HasBit(item.level_mask, item.indent)) GfxDrawLine(tx, ir.top, tx, ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); GfxDrawLine(tx, ycentre, tx + offset - (rtl ? -1 : 1), ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); } @@ -1130,11 +1139,6 @@ void GUIEngineListAddChildren(GUIEngineList &dst, const GUIEngineList &src, Engi } } -/** Enum referring to the Hotkeys in the build vehicle window */ -enum BuildVehicleHotkeys : int32_t { - BVHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string -}; - /** GUI for building vehicles. */ struct BuildVehicleWindow : Window { VehicleType vehicle_type = VEH_INVALID; ///< Type of vehicles shown in the window. @@ -1155,9 +1159,14 @@ struct BuildVehicleWindow : Window { TestedEngineDetails te{}; ///< Tested cost and capacity after refit. GUIBadgeClasses badge_classes{}; + static constexpr int BADGE_COLUMNS = 3; ///< Number of columns available for badges (0 = left of image, 1 = between image and name, 2 = after name) + StringFilter string_filter{}; ///< Filter for vehicle name QueryString vehicle_editbox; ///< Filter editbox + std::pair badge_filters{}; ///< First and last widgets IDs of badge filters. + BadgeFilterChoices badge_filter_choices{}; + uint cm_num_hidden_engines; void SetBuyVehicleText() @@ -1319,6 +1328,12 @@ struct BuildVehicleWindow : Window { this->badge_classes = GUIBadgeClasses(static_cast(GSF_TRAINS + this->vehicle_type)); // CM calls this in constructor // this->SetCargoFilterArray(); + + auto container = this->GetWidget(WID_BV_BADGE_FILTER); + this->badge_filters = AddBadgeDropdownFilters(*container, WID_BV_BADGE_FILTER, COLOUR_GREY, static_cast(GSF_TRAINS + this->vehicle_type)); + + this->widget_lookup.clear(); + this->nested_root->FillWidgetLookup(this->widget_lookup); } /** Filter the engine list against the currently selected cargo filter */ @@ -1362,7 +1377,7 @@ struct BuildVehicleWindow : Window { /* Figure out what train EngineIDs to put in the list */ EngineID GenerateBuildTrainList(GUIEngineList &list) { - std::vector variants; + FlatSet variants; EngineID sel_id = EngineID::Invalid(); size_t num_engines = 0; this->cm_num_hidden_engines = 0; @@ -1370,6 +1385,7 @@ struct BuildVehicleWindow : Window { list.clear(); BadgeTextFilter btf(this->string_filter, GSF_TRAINS); + BadgeDropdownFilter bdf(this->badge_filter_choices); /* Make list of all available train engines and wagons. * Also check to see if the previously selected engine is still available, @@ -1386,6 +1402,8 @@ struct BuildVehicleWindow : Window { /* Filter now! So num_engines and num_wagons is valid */ if (!FilterSingleEngine(eid)) continue; + if (!bdf.Filter(e->badges)) continue; + /* Filter by name or NewGRF extra text */ if (!FilterByText(e) && !btf.Filter(e->badges)) continue; @@ -1403,8 +1421,7 @@ struct BuildVehicleWindow : Window { /* Add all parent variants of this engine to the variant list */ EngineID parent = e->info.variant_id; - while (parent != EngineID::Invalid()) { - variants.push_back(parent); + while (parent != EngineID::Invalid() && variants.insert(parent).second) { parent = Engine::Get(parent)->info.variant_id; } @@ -1577,11 +1594,10 @@ struct BuildVehicleWindow : Window { sel_id = this->FilterEngineList(); // CM change /* ensure primary engine of variant group is in list after filtering */ - std::vector variants; + FlatSet variants; for (const auto &item : this->eng_list) { EngineID parent = item.variant_id; - while (parent != EngineID::Invalid()) { - variants.push_back(parent); + while (parent != EngineID::Invalid() && variants.insert(parent).second) { parent = Engine::Get(parent)->info.variant_id; } } @@ -1627,6 +1643,12 @@ struct BuildVehicleWindow : Window { return list; } + DropDownList BuildBadgeConfigurationList() const + { + static const auto separators = {STR_BADGE_CONFIG_PREVIEW, STR_BADGE_CONFIG_NAME}; + return BuildBadgeClassConfigurationList(this->badge_classes, BADGE_COLUMNS, separators); + } + void BuildVehicle() { EngineID sel_eng = this->sel_engine; @@ -1710,6 +1732,11 @@ struct BuildVehicleWindow : Window { ShowDropDownList(this, this->BuildCargoDropDownList(), this->cargo_filter_criteria, widget); break; + case WID_BV_CONFIGURE_BADGES: + if (this->badge_classes.GetClasses().empty()) break; + ShowDropDownList(this, this->BuildBadgeConfigurationList(), -1, widget, 0, false, true); + break; + case WID_BV_SHOW_HIDE: { const Engine *e = (this->sel_engine == EngineID::Invalid()) ? nullptr : Engine::Get(this->sel_engine); if (e != nullptr) { @@ -1730,6 +1757,12 @@ struct BuildVehicleWindow : Window { } break; } + + default: + if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) { + ShowDropDownList(this, this->GetWidget(widget)->GetDropDownList(), -1, widget, 0, false); + } + break; } } @@ -1783,6 +1816,10 @@ struct BuildVehicleWindow : Window { return GetString(CM_STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + this->vehicle_type, this->cm_num_hidden_engines); default: + if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) { + return this->GetWidget(widget)->GetStringParameter(this->badge_filter_choices); + } + return this->Window::GetWidgetString(widget, stringid); } } @@ -1791,7 +1828,7 @@ struct BuildVehicleWindow : Window { { switch (widget) { case WID_BV_LIST: - resize.height = GetEngineListHeight(this->vehicle_type); + fill.height = resize.height = GetEngineListHeight(this->vehicle_type); size.height = 3 * resize.height; size.width = std::max(size.width, this->badge_classes.GetTotalColumnsWidth() + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165) + padding.width; break; @@ -1812,6 +1849,11 @@ struct BuildVehicleWindow : Window { size.width = std::max(size.width, GetDropDownListDimension(this->BuildCargoDropDownList()).width + padding.width); break; + case WID_BV_CONFIGURE_BADGES: + /* Hide the configuration button if no configurable badges are present. */ + if (this->badge_classes.GetClasses().empty()) size = {0, 0}; + break; + case WID_BV_BUILD: size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + this->vehicle_type); size = maxdim(size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_BUTTON + this->vehicle_type)); @@ -1898,7 +1940,7 @@ struct BuildVehicleWindow : Window { Command::Post(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type, this->rename_engine, *str); } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int click_result) override { switch (widget) { case WID_BV_SORT_DROPDOWN: @@ -1919,6 +1961,35 @@ struct BuildVehicleWindow : Window { this->SelectEngine(this->sel_engine); } break; + + case WID_BV_CONFIGURE_BADGES: { + bool reopen = HandleBadgeConfigurationDropDownClick(static_cast(GSF_TRAINS + this->vehicle_type), BADGE_COLUMNS, index, click_result, this->badge_filter_choices); + + this->ReInit(); + + if (reopen) { + ReplaceDropDownList(this, this->BuildBadgeConfigurationList(), -1); + } else { + this->CloseChildWindows(WC_DROPDOWN_MENU); + } + + /* We need to refresh if a filter is removed. */ + this->eng_list.ForceRebuild(); + this->SetDirty(); + break; + } + + default: + if (IsInsideMM(widget, this->badge_filters.first, this->badge_filters.second)) { + if (index < 0) { + ResetBadgeFilter(this->badge_filter_choices, this->GetWidget(widget)->GetBadgeClassID()); + } else { + SetBadgeFilter(this->badge_filter_choices, BadgeID(index)); + } + this->eng_list.ForceRebuild(); + this->SetDirty(); + } + break; } this->SetDirty(); } @@ -1936,29 +2007,8 @@ struct BuildVehicleWindow : Window { } } - EventState OnHotkey(int hotkey) override - { - switch (hotkey) { - case BVHK_FOCUS_FILTER_BOX: - this->SetFocusedWidget(WID_BV_FILTER); - SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused. - return ES_HANDLED; - - /* CityMania code start */ - case WID_BV_BUILD: - if (this->owner != _local_company) return ES_NOT_HANDLED; - return Window::OnHotkey(hotkey); - /* CityMania code end */ - - default: - return ES_NOT_HANDLED; - } - - return ES_HANDLED; - } - static inline HotkeyList hotkeys{"buildvehicle", { - Hotkey('F', "focus_filter_box", BVHK_FOCUS_FILTER_BOX), + Hotkey('F', "focus_filter_box", WID_BV_FILTER), Hotkey('R', "cm_build_vehicle", WID_BV_BUILD), }}; }; diff --git a/src/cachecheck.cpp b/src/cachecheck.cpp index 8f39f8d5ed..fb0872802b 100644 --- a/src/cachecheck.cpp +++ b/src/cachecheck.cpp @@ -74,9 +74,8 @@ void CheckCaches() for (const RoadStop *rs : RoadStop::Iterate()) { if (IsBayRoadStopTile(rs->xy)) continue; - assert(rs->GetEntry(DIAGDIR_NE) != rs->GetEntry(DIAGDIR_NW)); - rs->GetEntry(DIAGDIR_NE)->CheckIntegrity(rs); - rs->GetEntry(DIAGDIR_NW)->CheckIntegrity(rs); + rs->GetEntry(DIAGDIR_NE).CheckIntegrity(rs); + rs->GetEntry(DIAGDIR_NW).CheckIntegrity(rs); } std::vector grf_cache; diff --git a/src/cargopacket.cpp b/src/cargopacket.cpp index 5dfe57c873..38d9489490 100644 --- a/src/cargopacket.cpp +++ b/src/cargopacket.cpp @@ -410,7 +410,7 @@ void VehicleCargoList::AgeCargo() return (accepted && cp->first_station != current_station) ? MTA_DELIVER : MTA_KEEP; } else if (cargo_next == current_station) { return MTA_DELIVER; - } else if (next_station.Contains(cargo_next.base())) { + } else if (next_station.Contains(cargo_next)) { return MTA_KEEP; } else { return MTA_TRANSFER; @@ -470,7 +470,7 @@ bool VehicleCargoList::Stage(bool accepted, StationID current_station, StationID new_shares.ChangeShare(current_station, INT_MIN); StationIDStack excluded = next_station; while (!excluded.IsEmpty() && !new_shares.GetShares()->empty()) { - new_shares.ChangeShare(StationID{excluded.Pop()}, INT_MIN); + new_shares.ChangeShare(excluded.Pop(), INT_MIN); } if (new_shares.GetShares()->empty()) { cargo_next = StationID::Invalid(); @@ -743,7 +743,7 @@ uint StationCargoList::ShiftCargo(Taction action, StationIDStack next, bool incl { uint max_move = action.MaxMove(); while (!next.IsEmpty()) { - this->ShiftCargo(action, StationID{next.Pop()}); + this->ShiftCargo(action, next.Pop()); if (action.MaxMove() == 0) break; } if (include_invalid && action.MaxMove() > 0) { @@ -853,7 +853,7 @@ uint StationCargoList::Load(uint max_move, VehicleCargoList *dest, StationIDStac */ uint StationCargoList::Reroute(uint max_move, StationCargoList *dest, StationID avoid, StationID avoid2, const GoodsEntry *ge) { - return this->ShiftCargo(StationCargoReroute(this, dest, max_move, avoid, avoid2, ge), avoid.base(), false); + return this->ShiftCargo(StationCargoReroute(this, dest, max_move, avoid, avoid2, ge), avoid, false); } /* diff --git a/src/cargopacket.h b/src/cargopacket.h index f5017e2846..8b4c7b6d40 100644 --- a/src/cargopacket.h +++ b/src/cargopacket.h @@ -555,7 +555,7 @@ public: inline bool HasCargoFor(StationIDStack next) const { while (!next.IsEmpty()) { - if (this->packets.find(StationID{next.Pop()}) != this->packets.end()) return true; + if (this->packets.find(next.Pop()) != this->packets.end()) return true; } /* Packets for StationID::Invalid() can go anywhere. */ return this->packets.find(StationID::Invalid()) != this->packets.end(); @@ -571,7 +571,7 @@ public: } /** - * Returns sum of cargo still available for loading at the sation. + * Returns sum of cargo still available for loading at the station. * (i.e. not counting cargo which is already reserved for loading) * @return Cargo on board the vehicle. */ diff --git a/src/cargotype.h b/src/cargotype.h index 3ca15bfcae..444f1f5b76 100644 --- a/src/cargotype.h +++ b/src/cargotype.h @@ -74,8 +74,8 @@ static const uint TOWN_PRODUCTION_DIVISOR = 256; struct CargoSpec { CargoLabel label; ///< Unique label of the cargo type. uint8_t bitnum = INVALID_CARGO_BITNUM; ///< Cargo bit number, is #INVALID_CARGO_BITNUM for a non-used spec. - uint8_t legend_colour; - uint8_t rating_colour; + PixelColour legend_colour; + PixelColour rating_colour; uint8_t weight; ///< Weight of a single unit of this cargo type in 1/16 ton (62.5 kg). uint16_t multiplier = 0x100; ///< Capacity multiplier for vehicles. (8 fractional bits) CargoClasses classes; ///< Classes of this cargo type. @see CargoClass diff --git a/src/cheat.cpp b/src/cheat.cpp index 30adc36fbe..71546382de 100644 --- a/src/cheat.cpp +++ b/src/cheat.cpp @@ -18,5 +18,5 @@ Cheats _cheats; /** Reinitialise all the cheats. */ void InitializeCheats() { - memset(&_cheats, 0, sizeof(Cheats)); + _cheats = {}; } diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index b2994d5eee..5cbdb1f78f 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -35,6 +35,7 @@ #include "timer/timer.h" #include "timer/timer_game_calendar.h" #include "timer/timer_game_economy.h" +#include "core/string_consumer.hpp" #include "widgets/cheat_widget.h" @@ -286,7 +287,7 @@ struct CheatWindow : Window { case SLE_BOOL: { bool on = (*(bool*)ce->variable); - DrawBoolButton(button_left, y + button_y_offset, on, true); + DrawBoolButton(button_left, y + button_y_offset, COLOUR_YELLOW, COLOUR_GREY, on, true); str = GetString(ce->str, on ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF); break; } @@ -354,7 +355,7 @@ struct CheatWindow : Window { int32_t value = sd->Read(&GetGameSettings()); if (sd->IsBoolSetting()) { /* Draw checkbox for boolean-value either on/off */ - DrawBoolButton(buttons.left, buttons.top, value != 0, editable); + DrawBoolButton(buttons.left, buttons.top, COLOUR_YELLOW, COLOUR_GREY, value != 0, editable); } else if (sd->flags.Test(SettingFlag::GuiDropdown)) { /* Draw [v] button for settings of an enum-type */ DrawDropDownButton(buttons.left, buttons.top, COLOUR_YELLOW, state != 0, editable); @@ -607,12 +608,13 @@ struct CheatWindow : Window { int32_t value; if (!str->empty()) { - long long llvalue = atoll(str->c_str()); + auto llvalue = ParseInteger(*str, 10, true); + if (!llvalue.has_value()) return; /* Save the correct currency-translated value */ - if (sd->flags.Test(SettingFlag::GuiCurrency)) llvalue /= GetCurrency().rate; + if (sd->flags.Test(SettingFlag::GuiCurrency)) llvalue = *llvalue / GetCurrency().rate; - value = ClampTo(llvalue); + value = ClampTo(*llvalue); } else { value = sd->GetDefaultValue(); } @@ -621,18 +623,19 @@ struct CheatWindow : Window { } else { const CheatEntry *ce = &_cheats_ui[clicked_cheat]; int oldvalue = static_cast(ReadValue(ce->variable, ce->type)); - int value = atoi(str->c_str()); + auto value = ParseInteger(*str, 10, true); + if (!value.has_value()) return; *ce->been_used = true; - value = ce->proc(value, value - oldvalue); + value = ce->proc(*value, *value - oldvalue); - if (value != oldvalue) WriteValue(ce->variable, ce->type, static_cast(value)); + if (*value != oldvalue) WriteValue(ce->variable, ce->type, static_cast(*value)); } this->valuewindow_entry = nullptr; this->SetDirty(); } - IntervalTimer daily_interval = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [this](auto) { + const IntervalTimer daily_interval = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [this](auto) { this->SetDirty(); }}; }; diff --git a/src/cheat_type.h b/src/cheat_type.h index 8c5dc981b1..a13cab7eb2 100644 --- a/src/cheat_type.h +++ b/src/cheat_type.h @@ -14,8 +14,8 @@ * Info about each of the cheats. */ struct Cheat { - bool been_used; ///< has this cheat been used before? - bool value; ///< tells if the bool cheat is active or not + bool been_used = false; ///< has this cheat been used before? + bool value = false; ///< tells if the bool cheat is active or not }; /** @@ -24,15 +24,15 @@ struct Cheat { * Only add new entries at the end of the struct! */ struct Cheats { - Cheat magic_bulldozer; ///< dynamite industries, objects - Cheat switch_company; ///< change to another company - Cheat money; ///< get rich or poor - Cheat crossing_tunnels; ///< allow tunnels that cross each other - Cheat no_jetcrash; ///< no jet will crash on small airports anymore - Cheat change_date; ///< changes date ingame - Cheat setup_prod; ///< setup raw-material production in game - Cheat edit_max_hl; ///< edit the maximum heightlevel; this is a cheat because of the fact that it needs to reset NewGRF game state and doing so as a simple configuration breaks the expectation of many - Cheat station_rating; ///< Fix station ratings at 100% + Cheat magic_bulldozer{}; ///< dynamite industries, objects + Cheat switch_company{}; ///< change to another company + Cheat money{}; ///< get rich or poor + Cheat crossing_tunnels{}; ///< allow tunnels that cross each other + Cheat no_jetcrash{}; ///< no jet will crash on small airports anymore + Cheat change_date{}; ///< changes date ingame + Cheat setup_prod{}; ///< setup raw-material production in game + Cheat edit_max_hl{}; ///< edit the maximum heightlevel; this is a cheat because of the fact that it needs to reset NewGRF game state and doing so as a simple configuration breaks the expectation of many + Cheat station_rating{}; ///< Fix station ratings at 100% }; extern Cheats _cheats; diff --git a/src/citymania/cm_blueprint.cpp b/src/citymania/cm_blueprint.cpp index d2062394b3..438b6ddbc1 100644 --- a/src/citymania/cm_blueprint.cpp +++ b/src/citymania/cm_blueprint.cpp @@ -16,6 +16,7 @@ #include "../station_map.h" #include "../station_base.h" #include "../strings_func.h" +#include "../station_layout_type.h" #include "../tilearea_type.h" #include "../tile_map.h" #include "../tunnelbridge_map.h" @@ -25,7 +26,6 @@ extern TileHighlightData _thd; extern RailType _cur_railtype; -extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); // from rail_gui.cpp struct StationPickerSelection { @@ -255,15 +255,14 @@ std::multimap Blueprint::GetTiles(TileIndex tile add_tile(otile, ObjectTileHighlight::make_rail_depot(palette, o.u.rail.depot.ddir)); break; case Item::Type::RAIL_STATION_PART: { - std::vector layouts(o.u.rail.station_part.numtracks * o.u.rail.station_part.plat_len); - uint8_t *layout_ptr = layouts.data(); - GetStationLayout(layout_ptr, o.u.rail.station_part.numtracks, o.u.rail.station_part.plat_len, nullptr); + RailStationTileLayout stl{nullptr, o.u.rail.station_part.numtracks, o.u.rail.station_part.plat_len}; // TODO statspec + auto it = stl.begin(); + if (palette == CM_PALETTE_TINT_WHITE && can_build_station_sign.find(o.u.rail.station_part.id) == can_build_station_sign.end()) palette = CM_PALETTE_TINT_ORANGE_DEEP; IterateStation(otile, o.u.rail.station_part.axis, o.u.rail.station_part.numtracks, o.u.rail.station_part.plat_len, [&](TileIndex tile) { - uint8_t layout = *layout_ptr++; - add_tile(tile, ObjectTileHighlight::make_rail_station(palette, o.u.rail.station_part.axis, layout & ~1)); + add_tile(tile, ObjectTileHighlight::make_rail_station(palette, o.u.rail.station_part.axis, *it++)); } ); break; diff --git a/src/citymania/cm_client_list_gui.cpp b/src/citymania/cm_client_list_gui.cpp index 9d8177b1d9..d48d63ede0 100644 --- a/src/citymania/cm_client_list_gui.cpp +++ b/src/citymania/cm_client_list_gui.cpp @@ -180,7 +180,7 @@ public: // if (ci->client_id == CLIENT_ID_SERVER) colour = TC_ORANGE; DrawString(text_left, text_right, y + this->text_offset_y, name, colour); - if (icon != PAL_NONE) DrawSprite(icon, COMPANY_SPRITE_COLOUR(playas), x, y + this->icon_offset_y); + if (icon != PAL_NONE) DrawSprite(icon, GetCompanyPalette(playas), x, y + this->icon_offset_y); // DrawCompanyIcon(playas, x, y + this->icon_offset_y); y += this->line_height; } diff --git a/src/citymania/cm_colour.hpp b/src/citymania/cm_colour.hpp index 26ed3dea59..60b6e3c3a7 100644 --- a/src/citymania/cm_colour.hpp +++ b/src/citymania/cm_colour.hpp @@ -15,8 +15,8 @@ constexpr std::array IS_M_CLOSER_TO_BLACK = { true, true, true, true, constexpr std::array BLINK_COLOUR = { 141, 7, 9, 10, 11, 12, 13, 14, 2, 2, 3, 4, 5, 6, 6, 8, 22, 22, 135, 210, 2, 3, 16, 18, 29, 30, 59, 31, 69, 24, 25, 26, 37, 38, 38, 39, 104, 32, 33, 35, 77, 47, 166, 166, 49, 49, 41, 41, 42, 125, 55, 85, 86, 37, 120, 65, 69, 104, 24, 25, 78, 120, 121, 197, 60, 54, 55, 27, 85, 28, 126, 78, 79, 79, 167, 167, 167, 178, 71, 72, 94, 94, 95, 95, 95, 209, 80, 81, 101, 102, 31, 103, 69, 80, 81, 82, 101, 102, 103, 103, 69, 96, 97, 98, 36, 111, 37, 120, 121, 121, 39, 105, 79, 120, 167, 167, 167, 178, 122, 122, 61, 108, 79, 79, 167, 167, 168, 71, 133, 134, 134, 177, 177, 128, 16, 18, 142, 142, 143, 143, 143, 136, 136, 171, 150, 150, 151, 151, 152, 152, 144, 200, 154, 156, 213, 213, 212, 153, 128, 154, 155, 157, 166, 166, 180, 180, 180, 124, 35, 7, 174, 143, 176, 176, 170, 130, 131, 132, 77, 166, 166, 166, 165, 165, 180, 180, 180, 180, 62, 63, 27, 65, 53, 61, 62, 62, 55, 55, 204, 151, 151, 152, 152, 152, 198, 154, 80, 81, 83, 84, 19, 157, 156, 154, 154, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 78, 79, 167, 168, 72, 180, 180, 180, 180, 62, 62, 63, 77, 181, 29, 30, 95, 86, 152, 152, 152, 152, 152, 153, 128, 19, 161, 153, 8 }; -static inline uint8_t GetBlinkColour(uint8_t m) { - return BLINK_COLOUR[m]; +static inline PixelColour GetBlinkColour(PixelColour m) { + return PixelColour{BLINK_COLOUR[m.p]}; } static inline uint8_t GetMForRGB(uint8_t r, uint8_t g, uint8_t b) { diff --git a/src/citymania/cm_command_log.cpp b/src/citymania/cm_command_log.cpp index 0986854038..8c40b71f88 100644 --- a/src/citymania/cm_command_log.cpp +++ b/src/citymania/cm_command_log.cpp @@ -97,10 +97,10 @@ void ExecuteFakeCommands(TimerGameTick::TickCounter counter) { } -void load_replay_commands(const std::string &filename, std::function error_func) { +void load_replay_commands(std::string_view filename, std::function error_func) { _fake_commands = {}; - FILE *f = fopen(filename.c_str(), "rb"); + FILE *f = fopen(std::string(filename).c_str(), "rb"); if (f == nullptr) { error_func(fmt::format("Cannot open file `{}`: {}", filename, std::strerror(errno))); diff --git a/src/citymania/cm_command_log.hpp b/src/citymania/cm_command_log.hpp index a4700b7c83..34fae63eb9 100644 --- a/src/citymania/cm_command_log.hpp +++ b/src/citymania/cm_command_log.hpp @@ -5,7 +5,7 @@ namespace citymania { -void load_replay_commands(const std::string &filename, std::function); +void load_replay_commands(std::string_view filename, std::function); }; // namespace citymania diff --git a/src/citymania/cm_commands_gui.cpp b/src/citymania/cm_commands_gui.cpp index 68097d8449..89e31b0c60 100644 --- a/src/citymania/cm_commands_gui.cpp +++ b/src/citymania/cm_commands_gui.cpp @@ -38,14 +38,14 @@ static constexpr std::string_view BTPRO_HTTP_LOGIN = "http://openttd.btp static const std::string CFG_LOGIN_FILE = "citymania.cfg"; static const std::string CFG_LOGIN_KEY = "login"; -static const char * const NOVAPOLIS_LOGIN = "citymania_login"; -static const char * const NOVAPOLIS_PW = "citymania_pw"; -static const char * const NICE_LOGIN = "nice_login"; -static const char * const NICE_PW = "nice_pw"; -static const char * const BTPRO_LOGIN = "btpro_login"; -static const char * const BTPRO_PW = "btpro_pw"; +static constexpr std::string_view NOVAPOLIS_LOGIN = "citymania_login"; +static constexpr std::string_view NOVAPOLIS_PW = "citymania_pw"; +static constexpr std::string_view NICE_LOGIN = "nice_login"; +static constexpr std::string_view NICE_PW = "nice_pw"; +static constexpr std::string_view BTPRO_LOGIN = "btpro_login"; +static constexpr std::string_view BTPRO_PW = "btpro_pw"; -static const char * const INI_LOGIN_KEYS[] = { +static constexpr std::string_view INI_LOGIN_KEYS[] = { NOVAPOLIS_LOGIN, NOVAPOLIS_PW, NICE_LOGIN, @@ -204,7 +204,7 @@ void IniLoginInitiate(){ IniReloadLogin(); } -std::string GetLoginItem(const std::string& itemname){ +std::string GetLoginItem(std::string_view itemname){ IniGroup &group = _inilogin->GetOrCreateGroup(CFG_LOGIN_KEY); IniItem &item = group.GetOrCreateItem(itemname); return item.value.value_or(""); @@ -220,7 +220,7 @@ void IniReloadLogin() { } } -void SetLoginItem(const std::string& itemname, const std::string& value){ +void SetLoginItem(std::string_view itemname, std::string_view value){ IniGroup &group = _inilogin->GetOrCreateGroup(CFG_LOGIN_KEY); IniItem &item = group.GetOrCreateItem(itemname); item.SetValue(value); @@ -552,7 +552,7 @@ static const NWidgetPart _nested_commands_toolbar_widgets[] = { }; static WindowDesc _commands_toolbar_desc( - WDP_ALIGN_TOOLBAR, NULL, 0, 0, + WDP_ALIGN_TOOLBAR, {}, 0, 0, WC_COMMAND_TOOLBAR, WC_NONE, WDF_CONSTRUCTION, _nested_commands_toolbar_widgets, lengthof(_nested_commands_toolbar_widgets) @@ -699,15 +699,15 @@ public: { switch(widget){ case LWW_NOVAPOLIS_LOGIN: - return GetString(CM_STR_LOGIN_WINDOW_USERNAME_DISPLAY, _inilogindata[NOVAPOLISUSER]); + return GetString(CM_STR_LOGIN_WINDOW_USERNAME_DISPLAY, std::string_view(_inilogindata[NOVAPOLISUSER])); case LWW_NOVAPOLIS_PW: return GetString(CM_STR_LOGIN_WINDOW_PASSWORD_DISPLAY, (GetLoginItem(NOVAPOLIS_PW).empty() ? CM_STR_LOGIN_WINDOW_NOT_SET : CM_STR_LOGIN_WINDOW_SET)); case LWW_NICE_LOGIN: - return GetString(CM_STR_LOGIN_WINDOW_USERNAME_DISPLAY, _inilogindata[NICEUSER]); + return GetString(CM_STR_LOGIN_WINDOW_USERNAME_DISPLAY, std::string_view(_inilogindata[NICEUSER])); case LWW_NICE_PW: return GetString(CM_STR_LOGIN_WINDOW_PASSWORD_DISPLAY, (GetLoginItem(NICE_PW).empty() ? CM_STR_LOGIN_WINDOW_NOT_SET : CM_STR_LOGIN_WINDOW_SET)); case LWW_BTPRO_LOGIN: - return GetString(CM_STR_LOGIN_WINDOW_USERNAME_DISPLAY, _inilogindata[BTPROUSER]); + return GetString(CM_STR_LOGIN_WINDOW_USERNAME_DISPLAY, std::string_view(_inilogindata[BTPROUSER])); case LWW_BTPRO_PW: return GetString(CM_STR_LOGIN_WINDOW_PASSWORD_DISPLAY, (GetLoginItem(BTPRO_PW).empty() ? CM_STR_LOGIN_WINDOW_NOT_SET : CM_STR_LOGIN_WINDOW_SET)); default: @@ -839,10 +839,10 @@ static const NWidgetPart _nested_login_window_widgets[] = { }; static WindowDesc _login_window_desc( - WDP_CENTER, nullptr, 0, 0, + WDP_CENTER, {}, 0, 0, CM_WC_LOGIN_WINDOW, WC_NONE, WindowDefaultFlag::Construction, - std::span(_nested_login_window_widgets) + _nested_login_window_widgets ); void ShowLoginWindow() diff --git a/src/citymania/cm_console_cmds.cpp b/src/citymania/cm_console_cmds.cpp index 25bf9ae125..28da9faedd 100644 --- a/src/citymania/cm_console_cmds.cpp +++ b/src/citymania/cm_console_cmds.cpp @@ -19,6 +19,7 @@ #include "../town.h" #include "../train.h" #include "../tree_map.h" +#include "../core/string_consumer.hpp" #include #include @@ -26,7 +27,8 @@ #include "../safeguards.h" -bool ReadHeightMap(DetailedFileType dft, const char *filename, uint *x, uint *y, std::vector *map); +bool ReadHeightMap(DetailedFileType dft, std::string_view filename, uint *x, uint *y, std::vector *map); +extern uint32 _gfx_debug_flags; namespace citymania { @@ -35,27 +37,50 @@ uint32 _replay_last_save = 0; uint32 _replay_ticks = 0; extern uint32 _pause_countdown; -static void IConsoleHelp(const char *str) -{ +static void IConsoleHelp(std::string_view str) { IConsolePrint(CC_WARNING, "- {}", str); } -bool ConGameSpeed([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { - if (argc == 0 || argc > 2) { +template +void IConsoleError(fmt::format_string format, Args&&... args) { + IConsolePrint(CC_ERROR, fmt::format(format, std::forward(args)...)); +} + +bool ConGameSpeed(std::span argv) { + if (argv.empty() == 0 || argv.size() > 2) { IConsoleHelp("Changes game speed. Usage: 'cmgamespeed [n]'"); return true; } - _game_speed = (argc > 1 ? atoi(argv[1]) : 100); + uint new_speed = 100; + if (argv.size() > 1) { + auto t = ParseInteger(argv[1]); + if (!t.has_value()) { + IConsoleError("Invalid number '{}'", argv[1]); + return true; + } + new_speed = t.value(); + } + _game_speed = new_speed; return true; } -bool ConStep([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { - if (argc == 0 || argc > 2) { - IConsoleHelp("Advances the game for a certain amount of ticks (default 1). Usage: 'cmstep [n]'"); +bool ConStep(std::span argv) { + if (argv.empty()) { + IConsoleHelp("Advances the game for a certain number of ticks (default 1). Usage: 'cmstep [n]'"); return true; } - auto n = (argc > 1 ? atoi(argv[1]) : 1); + if (argv.size() > 2) return false; + + uint n = 1; + if (argv.size() > 1) { + auto t = ParseInteger(argv[1]); + if (!t.has_value()) { + IConsoleError("Invalid number '{}'", argv[1]); + return true; + } + n = t.value(); + } _pause_countdown = n; cmd::Pause(PauseMode::Normal, 0).post(); @@ -63,8 +88,8 @@ bool ConStep([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { return true; } -bool ConExport([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { - if (argc == 0) { +bool ConExport(std::span argv) { + if (argv.empty()) { IConsoleHelp("Exports various game data in json format to openttd.json file"); return true; } @@ -75,17 +100,17 @@ bool ConExport([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { return true; } -bool ConTreeMap([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { - if (argc == 0) { +bool ConTreeMap(std::span argv) { + if (argv.empty()) { IConsoleHelp("Loads heighmap-like file and plants trees according to it, values 0-256 ore scaled to 0-4 trees."); IConsoleHelp("Usage: 'cmtreemap '"); IConsoleHelp("Default lookup path is in scenario/heightmap in your openttd directory"); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - std::string filename = argv[1]; + std::string_view filename = std::string_view(argv[1]); if (_game_mode != GM_EDITOR) { IConsolePrint(CC_ERROR, "This command is only available in scenario editor."); @@ -106,9 +131,9 @@ bool ConTreeMap([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { #endif else { #ifdef WITH_PNG - IConsolePrint(CC_ERROR, "Unknown treemap extension {}, should be .bmp or .png.", ext.c_str()); + IConsolePrint(CC_ERROR, "Unknown treemap extension {}, should be .bmp or .png.", ext); #else - IConsolePrint(CC_ERROR, "Unknown treemap extension {}, should be .bmp (game was compiled without PNG support).", ext.c_str()); + IConsolePrint(CC_ERROR, "Unknown treemap extension {}, should be .bmp (game was compiled without PNG support).", ext); #endif return true; } @@ -116,7 +141,7 @@ bool ConTreeMap([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { uint x, y; std::vector map; - if (!ReadHeightMap(dft, filename.c_str(), &x, &y, &map)) { + if (!ReadHeightMap(dft, filename, &x, &y, &map)) { return true; } @@ -136,14 +161,14 @@ bool ConTreeMap([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { extern void (*UpdateTownGrowthRate)(Town *t); -bool ConResetTownGrowth([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { - if (argc == 0) { +bool ConResetTownGrowth(std::span argv) { + if (argv.empty()) { IConsoleHelp("Resets growth to normal for all towns."); IConsoleHelp("Usage: 'cmresettowngrowth'"); return true; } - if (argc > 1) return false; + if (argv.size() > 1) return false; for (Town *town : Town::Iterate()) { ClrBit(town->flags, TOWN_CUSTOM_GROWTH); @@ -179,34 +204,40 @@ void SetReplaySaveInterval(uint32 interval) { if (_replay_save_interval) MakeReplaySave(); } -bool ConLoadCommands([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { - if (argc == 0) { +bool ConLoadCommands(std::span argv) { + if (argv.empty()) { IConsoleHelp("Loads a file with command queue to execute"); IConsoleHelp("Usage: 'cmloadcommands '"); return true; } - if (argc > 3) return false; + if (argv.size() > 3) return false; load_replay_commands(argv[1], [](auto error) { IConsolePrint(CC_ERROR, "{}", error); }); - SetReplaySaveInterval(argc > 2 ? atoi(argv[2]) : 0); + SetReplaySaveInterval(argv.size() > 2 ? ParseInteger(argv[2]).value_or(0) : 0); return true; } -bool ConStartRecord([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { +bool ConStartRecord(std::span argv) { StartRecording(); return true; } -bool ConStopRecord([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { +bool ConStopRecord(std::span argv) { StopRecording(); return true; } -bool ConGameStats([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { +bool ConGameStats(std::span argv) { + if (argv.empty()) { + IConsoleHelp("Prints total number of vehicles"); + IConsoleHelp("Usage: 'cmgamestats'"); + return true; + } + auto num_trains = 0u; auto num_rvs = 0u; auto num_ships = 0u; @@ -232,4 +263,27 @@ bool ConGameStats([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) return true; } +// From jgrpp viewports +bool ConGfxDebug(std::span argv) { + if (argv.empty()) { + IConsolePrint(CC_HELP, "Debug: gfx flags. Usage: 'gfx_debug []'"); + IConsolePrint(CC_HELP, " 1: GDF_SHOW_WINDOW_DIRTY"); + IConsolePrint(CC_HELP, " 2: GDF_SHOW_WIDGET_DIRTY"); + IConsolePrint(CC_HELP, " 4: GDF_SHOW_RECT_DIRTY"); + return true; + } + + if (argv.size() > 2) return false; + + if (argv.size() == 1) { + IConsolePrint(CC_DEFAULT, "Gfx debug flags: {:X}", _gfx_debug_flags); + } else { + auto t = ParseInteger(argv[1], 16); + if (t.has_value()) _gfx_debug_flags = t.value(); + else IConsolePrint(CC_ERROR, "Invalid hex: '{}'", argv[1]); + } + + return true; +} + } // namespace citymania diff --git a/src/citymania/cm_console_cmds.hpp b/src/citymania/cm_console_cmds.hpp index f7571961be..d8c4993290 100644 --- a/src/citymania/cm_console_cmds.hpp +++ b/src/citymania/cm_console_cmds.hpp @@ -10,17 +10,18 @@ void SetReplaySaveInterval(uint32 interval); void CheckIntervalSave(); bool IsReplayingCommands(); -bool ConGameSpeed(uint8_t argc, char *argv[]); -bool ConStep(uint8_t argc, char *argv[]); -bool ConExport(uint8_t argc, char *argv[]); -bool ConTreeMap(uint8_t argc, char *argv[]); -bool ConResetTownGrowth(uint8_t argc, char *argv[]); -bool ConLoadCommands(uint8_t argc, char *argv[]); +bool ConGameSpeed(std::span argv); +bool ConStep(std::span argv); +bool ConExport(std::span argv); +bool ConTreeMap(std::span argv); +bool ConResetTownGrowth(std::span argv); +bool ConLoadCommands(std::span argv); void ExecuteFakeCommands(TimerGameTick::TickCounter counter); -bool ConStartRecord(uint8_t argc, char *argv[]); -bool ConStopRecord(uint8_t argc, char *argv[]); -bool ConGameStats(uint8_t argc, char *argv[]); +bool ConStartRecord(std::span argv); +bool ConStopRecord(std::span argv); +bool ConGameStats(std::span argv); +bool ConGfxDebug(std::span argv); } // namespace citymania diff --git a/src/citymania/cm_export.cpp b/src/citymania/cm_export.cpp index dc701f3e69..180ddad6b7 100644 --- a/src/citymania/cm_export.cpp +++ b/src/citymania/cm_export.cpp @@ -198,7 +198,7 @@ void WriteHouseSpecInfo(JsonWriter &j) { j.kv("name", hs->building_name); j.kv("mail_generation", hs->mail_generation); j.kv("flags", hs->building_flags.base()); - j.kv("availability", hs->building_availability); + j.kv("availability", hs->building_availability.base()); j.kv("enabled", hs->enabled); j.end_dict(); } @@ -210,11 +210,11 @@ void WriteHouseSpecInfo(JsonWriter &j) { j.kv("ground_pal", d.ground.pal); j.kv("building_sprite", d.building.sprite); j.kv("building_pal", d.building.pal); - j.kv("subtile_x", d.subtile_x); - j.kv("subtile_y", d.subtile_y); - j.kv("width", d.width); - j.kv("height", d.height); - j.kv("dz", d.dz); + j.kv("origin_x", d.origin.x); + j.kv("origin_y", d.origin.y); + j.kv("extent_x", d.extent.x); + j.kv("extent_y", d.extent.y); + j.kv("extent_z", d.extent.z); j.kv("draw_proc", d.draw_proc); j.end_dict(); } @@ -233,8 +233,8 @@ void WriteCargoSpecInfo(JsonWriter &j) { JKV(j, cs->weight); JKV(j, cs->multiplier); JKV(j, cs->is_freight); - JKV(j, cs->legend_colour); - JKV(j, cs->rating_colour); + JKV(j, cs->legend_colour.p); + JKV(j, cs->rating_colour.p); JKV(j, cs->sprite); j.ks("name", cs->name); j.ks("name_single", cs->name_single); @@ -278,7 +278,7 @@ void WritePaletteInfo(JsonWriter &j) { j.f << std::endl << "["; for (auto k = 0; k < 8; k++) { if (k != 0) j.f << ", "; - j.f << (int)GetColourGradient((Colours)i, (ColourShade)k) << " "; + j.f << GetColourGradient((Colours)i, (ColourShade)k).p << " "; } j.f << "]"; } diff --git a/src/citymania/cm_highlight.cpp b/src/citymania/cm_highlight.cpp index 11bfdbb167..98b484143c 100644 --- a/src/citymania/cm_highlight.cpp +++ b/src/citymania/cm_highlight.cpp @@ -24,6 +24,7 @@ #include "../newgrf_station.h" #include "../newgrf_industrytiles.h" #include "../sound_func.h" +#include "../station_layout_type.h" #include "../spritecache.h" #include "../strings_func.h" #include "../town.h" @@ -75,8 +76,7 @@ extern DiagDirection _road_depot_orientation; extern uint32 _realtime_tick; extern uint32 _cm_funding_layout; extern IndustryType _cm_funding_type; -extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); -extern void IndustryDrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, uint8_t rnd_colour, uint8_t stage); +extern void IndustryDrawTileLayout(const TileInfo *ti, const DrawTileSpriteSpan &dts, Colours rnd_colour, uint8_t stage); extern void SetSelectionTilesDirty(); struct RoadStopPickerSelection { @@ -588,9 +588,8 @@ void ObjectHighlight::UpdateTiles() { ).test(); auto palette = (this->cost.Succeeded() ? CM_PALETTE_TINT_WHITE : CM_PALETTE_TINT_RED_DEEP); - std::vector layouts(numtracks * plat_len); - auto layout_ptr = layouts.data(); - GetStationLayout(layout_ptr, numtracks, plat_len, nullptr); // TODO statspec + RailStationTileLayout stl{nullptr, numtracks, plat_len}; // TODO statspec + auto it = stl.begin(); auto tile_delta = (this->axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); TileIndex tile_track = this->tile; @@ -598,8 +597,7 @@ void ObjectHighlight::UpdateTiles() { TileIndex tile = tile_track; int w = plat_len; do { - uint8_t layout = *layout_ptr++; - this->AddTile(tile, ObjectTileHighlight::make_rail_station(palette, this->axis, layout & ~1)); + this->AddTile(tile, ObjectTileHighlight::make_rail_station(palette, this->axis, *it++)); tile += tile_delta; } while (--w); tile_track += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta @@ -654,7 +652,7 @@ void ObjectHighlight::UpdateTiles() { if (!as->IsAvailable() || this->airport_layout >= as->layouts.size()) break; Direction rotation = as->layouts[this->airport_layout].rotation; if (rotation == INVALID_DIR) break; - for (AirportTileTableIterator iter(as->layouts[this->airport_layout].tiles.data(), this->tile); iter != INVALID_TILE; ++iter) { + for (AirportTileTableIterator iter(as->layouts[this->airport_layout].tiles, tile); iter != INVALID_TILE; ++iter) { this->AddTile(iter, ObjectTileHighlight::make_airport_tile(palette, iter.GetStationGfx())); } break; @@ -797,8 +795,8 @@ void ObjectHighlight::MarkDirty() { MarkAllViewportsDirty( left, top, - left + UnScaleByZoom(sprite->width, ZOOM_LVL_NORMAL), - top + UnScaleByZoom(sprite->height, ZOOM_LVL_NORMAL) + left + UnScaleByZoom(sprite->width, ZoomLevel::Normal), + top + UnScaleByZoom(sprite->height, ZoomLevel::Normal) ); } if (this->type == ObjectHighlight::Type::BLUEPRINT && this->blueprint) { // TODO why && blueprint check is needed? @@ -1041,6 +1039,10 @@ void DrawTrainStationSprite(SpriteID palette, const TileInfo *ti, RailType railt DrawRailTileSeq(ti, t, TO_INVALID, total_offset, 0, palette); } +void AddSortableStationSprite(SpriteID sprite, SpriteID palette, const TileInfo *ti) { + AddSortableSpriteToDraw(sprite, palette, *ti, {{}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {}}); +} + void DrawRoadStop(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagDirection orientation, bool is_truck) { int32 total_offset = 0; const RoadTypeInfo* rti = GetRoadTypeInfo(roadtype); @@ -1057,22 +1059,22 @@ void DrawRoadStop(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagD SpriteID overlay = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); // if (overlay) DrawSprite(overlay + sprite_offset, PAL_NONE, x, y); - if (overlay) AddSortableSpriteToDraw(overlay + sprite_offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + if (overlay) AddSortableStationSprite(overlay + sprite_offset, palette, ti); } else if (RoadTypeIsTram(roadtype)) { // DrawSprite(SPR_TRAMWAY_TRAM + sprite_offset, PAL_NONE, x, y); - AddSortableSpriteToDraw(SPR_TRAMWAY_TRAM + sprite_offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + AddSortableStationSprite(SPR_TRAMWAY_TRAM + sprite_offset, palette, ti); } } else { /* Drive-in stop */ if (RoadTypeIsRoad(roadtype) && rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_ROADSTOP); // DrawSprite(, PAL_NONE, x, y); - AddSortableSpriteToDraw(ground + image, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + AddSortableStationSprite(ground + image, palette, ti); } } const DrawTileSprites *t = GetStationTileLayout(is_truck ? StationType::Truck : StationType::Bus, image); - AddSortableSpriteToDraw(t->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + AddSortableStationSprite(t->ground.sprite, palette, ti); DrawRailTileSeq(ti, t, TO_INVALID, total_offset, 0, palette); /* Draw road, tram catenary */ // DrawRoadCatenary(ti); @@ -1142,15 +1144,15 @@ void DrawRoadDepot(SpriteID palette, const TileInfo *ti, RoadType roadtype, Diag } const DrawTileSprites *dts = &_road_depot[orientation]; - AddSortableSpriteToDraw(dts->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + AddSortableStationSprite(dts->ground.sprite, palette, ti); if (default_gfx) { uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(orientation)); if (rti->UsesOverlay()) { SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); - if (ground != 0) AddSortableSpriteToDraw(ground + offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + if (ground != 0) AddSortableStationSprite(ground + offset, palette, ti); } else if (RoadTypeIsTram(roadtype)) { - AddSortableSpriteToDraw(SPR_TRAMWAY_OVERLAY + offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + AddSortableStationSprite(SPR_TRAMWAY_OVERLAY + offset, palette, ti); } } @@ -1192,7 +1194,7 @@ void DrawAirportTile(SpriteID palette, const TileInfo *ti, StationGfx gfx) { } if (t == nullptr || t->GetSequence().empty()) t = GetStationTileLayout(StationType::Airport, gfx); if (t) { - AddSortableSpriteToDraw(t->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + AddSortableStationSprite(t->ground.sprite, palette, ti); DrawRailTileSeq(ti, t, TO_INVALID, total_offset, 0, palette); } } @@ -1224,10 +1226,10 @@ struct IndustryTilePreviewScopeResolver : public IndustryTileScopeResolver { IndustryTilePreviewScopeResolver(ResolverObject &ro, Industry *industry, TileIndex tile) : IndustryTileScopeResolver{ro, industry, tile} {} - uint32 GetRandomBits() const override { return 0; }; - uint32 GetTriggers() const override { return 0; }; + uint32_t GetRandomBits() const override { return 0; }; + uint32_t GetRandomTriggers() const override { return 0; }; - uint32 GetVariable(uint8_t variable, uint32 parameter, bool &available) const override { + uint32_t GetVariable(uint8_t variable, uint32_t parameter, bool &available) const override { // Debug(misc, 0, "TILE VAR {:X} requested", variable); switch (variable) { /* Construction state of the tile: a value between 0 and 3 */ @@ -1266,9 +1268,9 @@ struct IndustriesPreviewScopeResolver : public IndustriesScopeResolver { IndustriesPreviewScopeResolver(ResolverObject &ro, TileIndex tile, Industry *industry, IndustryType type, uint32 random_bits = 0) : IndustriesScopeResolver{ro, tile, industry, type, random_bits} {} - uint32 GetRandomBits() const override { return 0; }; - uint32 GetTriggers() const override { return 0; }; - uint32 GetVariable(uint8_t variable, uint32 parameter, bool &available) const override { + uint32_t GetRandomBits() const override { return 0; }; + uint32_t GetRandomTriggers() const override { return 0; }; + uint32_t GetVariable(uint8_t variable, uint32_t parameter, bool &available) const override { // Debug(misc, 0, "UNDUSTRY VAR {:X} requested", variable); return IndustriesScopeResolver::GetVariable(variable, parameter, available); } @@ -1324,13 +1326,13 @@ bool DrawNewIndustryTile(TileInfo *ti, Industry *i, IndustryGfx gfx, const Indus // IndustryTileResolverObject object(gfx, ti->tile, i); IndustryTilePreviewResolverObject object(gfx, ti->tile, i); - const SpriteGroup *group = object.Resolve(); - if (group == nullptr || group->type != SGT_TILELAYOUT) return false; + const auto *group = object.Resolve(); + if (group == nullptr) return false; - /* Limit the building stage to the number of stages supplied. */ - const TileLayoutSpriteGroup *tlgroup = (const TileLayoutSpriteGroup *)group; - - IndustryDrawTileLayout(ti, tlgroup, i->random_colour, INDUSTRY_COMPLETED); + uint8_t stage = INDUSTRY_COMPLETED; + auto processor = group->ProcessRegisters(object, &stage); + auto dts = processor.GetLayout(); + IndustryDrawTileLayout(ti, dts, i->random_colour, stage); return true; } @@ -1379,16 +1381,7 @@ void DrawIndustryTile(SpriteID palette, const TileInfo *ti, IndustryType ind_typ /* Add industry on top of the ground? */ image = dits->building.sprite; if (image != 0) { - AddSortableSpriteToDraw(image, palette, - ti->x + dits->subtile_x, - ti->y + dits->subtile_y, - dits->width, - dits->height, - dits->dz, - ti->z, - false, - 0, 0, 0, nullptr, - true); + AddSortableSpriteToDraw(image, palette, *ti, *dits, false); } } @@ -1470,7 +1463,7 @@ void DrawSignal(SpriteID palette, const TileInfo *ti, RailType railtype, uint po sprite += type * 16 + variant * 64 + image * 2 + condition + (type > SIGTYPE_LAST_NOPBS ? 64 : 0); } - AddSortableSpriteToDraw(sprite, palette, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, GetSaveSlopeZ(x, y, track)); + AddSortableSpriteToDraw(sprite, palette, x, y, GetSaveSlopeZ(x, y, track), {{}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {}}); } // copied from tunnelbridge_cmd.cpp @@ -1495,7 +1488,7 @@ void DrawBridgeHead(SpriteID palette, const TileInfo *ti, RailType railtype, Dia if (ti->tileh == SLOPE_FLAT) base_offset += 4; // sloped bridge head psid = &GetBridgeSpriteTable(type, BRIDGE_PIECE_HEAD)[base_offset]; - AddSortableSpriteToDraw(psid->sprite, palette, ti->x, ti->y, 16, 16, ti->tileh == SLOPE_FLAT ? 0 : 8, ti->z); + AddSortableSpriteToDraw(psid->sprite, palette, ti->x, ti->y, ti->z, {{}, {16, 16, ti->tileh == SLOPE_FLAT ? 0 : 8}, {}}); // DrawAutorailSelection(ti, (ddir == DIAGDIR_SW || ddir == DIAGDIR_NE ? HT_DIR_X : HT_DIR_Y), PAL_NONE); } @@ -1513,7 +1506,7 @@ void DrawTunnelHead(SpriteID palette, const TileInfo *ti, RailType railtype, Dia } image += ddir * 2; - AddSortableSpriteToDraw(image, palette, ti->x, ti->y, 16, 16, 0, ti->z); + AddSortableSpriteToDraw(image, palette, ti->x, ti->y, ti->z, {{}, {16, 16, 0}, {}}); } void DrawSelectionPoint(SpriteID palette, const TileInfo *ti) { @@ -2172,11 +2165,11 @@ void AllocateZoningMap(uint map_size) { uint8 GetTownZone(Town *town, TileIndex tile) { auto dist = DistanceSquare(tile, town->xy); - if (dist > town->cache.squared_town_zone_radius[HZB_TOWN_EDGE]) + if (dist > town->cache.squared_town_zone_radius[(uint8_t)HouseZone::TownEdge]) return 0; uint8 z = 1; - for (HouseZonesBits i = HZB_TOWN_OUTSKIRT; i < HZB_END; i++) + for (uint8_t i = (uint8_t)HouseZone::TownOutskirt; i < (uint8_t)HouseZone::TownEnd; i++) if (dist < town->cache.squared_town_zone_radius[i]) z = (uint8)i + 1; else if (town->cache.squared_town_zone_radius[i] != 0) @@ -2185,15 +2178,15 @@ uint8 GetTownZone(Town *town, TileIndex tile) { } uint8 GetAnyTownZone(TileIndex tile) { - HouseZonesBits next_zone = HZB_BEGIN; + uint8_t next_zone = (uint8_t)HouseZone::TownEdge; uint8 z = 0; for (Town *town : Town::Iterate()) { uint dist = DistanceSquare(tile, town->xy); // town code uses <= for checking town borders (tz0) but < for other zones - while (next_zone < HZB_END + while (next_zone < (uint8_t)HouseZone::TownEnd && (town->cache.squared_town_zone_radius[next_zone] == 0 - || dist <= town->cache.squared_town_zone_radius[next_zone] - (next_zone == HZB_BEGIN ? 0 : 1)) + || dist <= town->cache.squared_town_zone_radius[next_zone] - (next_zone == (uint8_t)HouseZone::TownEdge ? 0 : 1)) ) { if (town->cache.squared_town_zone_radius[next_zone] != 0) z = (uint8)next_zone + 1; next_zone++; @@ -2203,7 +2196,7 @@ uint8 GetAnyTownZone(TileIndex tile) { } void UpdateTownZoning(Town *town, uint32 prev_edge) { - auto edge = town->cache.squared_town_zone_radius[HZB_TOWN_EDGE]; + auto edge = town->cache.squared_town_zone_radius[(uint8_t)HouseZone::TownEdge]; if (prev_edge && edge == prev_edge) return; diff --git a/src/citymania/cm_hotkeys.hpp b/src/citymania/cm_hotkeys.hpp index 673706096c..0132208297 100644 --- a/src/citymania/cm_hotkeys.hpp +++ b/src/citymania/cm_hotkeys.hpp @@ -27,7 +27,6 @@ ToolRemoveMode RoadToolbar_GetRemoveMode(int widget); void RoadToolbar_UpdateOptionWidgetStatus(Window *w, int widget, bool remove_active, bool is_road); bool RoadToolbar_RemoveModChanged(Window *w, bool remove_active, bool button_clicked, bool is_road); -void CountEffectiveAction(); void ResetEffectiveActionCounter(); std::pair GetEPM(); bool ChooseSignalDragBehaviour(); diff --git a/src/citymania/cm_minimap.cpp b/src/citymania/cm_minimap.cpp index dcc37f32fa..44020df708 100644 --- a/src/citymania/cm_minimap.cpp +++ b/src/citymania/cm_minimap.cpp @@ -62,16 +62,16 @@ static const int NUM_NO_COMPANY_ENTRIES = 4; ///< Number of entries in the owner #define MK(a, b) {a, b, IT_INVALID, 0, CompanyID::Invalid(), true, false, false} /** Macro for a height legend entry with configurable colour. */ -#define MC(col_break) {0, STR_TINY_BLACK_HEIGHT, IT_INVALID, 0, CompanyID::Invalid(), true, false, col_break} +#define MC(col_break) {{}, STR_TINY_BLACK_HEIGHT, IT_INVALID, 0, CompanyID::Invalid(), true, false, col_break} /** Macro for non-company owned property entry of LegendAndColour */ #define MO(a, b) {a, b, IT_INVALID, 0, CompanyID::Invalid(), true, false, false} /** Macro used for forcing a rebuild of the owner legend the first time it is used. */ -#define MOEND() {0, 0, IT_INVALID, 0, OWNER_NONE, true, true, false} +#define MOEND() {{}, STR_NULL, IT_INVALID, 0, OWNER_NONE, true, true, false} /** Macro for end of list marker in arrays of LegendAndColour */ -#define MKEND() {0, STR_NULL, IT_INVALID, 0, CompanyID::Invalid(), true, true, false} +#define MKEND() {{}, STR_NULL, IT_INVALID, 0, CompanyID::Invalid(), true, true, false} /** * Macro for break marker in arrays of LegendAndColour. @@ -148,7 +148,7 @@ static const LegendAndColour _legend_vegetation[] = { static LegendAndColour _legend_land_owners[NUM_NO_COMPANY_ENTRIES + MAX_COMPANIES + 1] = { MO(PC_WATER, STR_SMALLMAP_LEGENDA_WATER), - MO(0x00, STR_SMALLMAP_LEGENDA_NO_OWNER), // This colour will vary depending on settings. + MO({}, STR_SMALLMAP_LEGENDA_NO_OWNER), // This colour will vary depending on settings. MO(PC_DARK_RED, STR_SMALLMAP_LEGENDA_TOWNS), MO(PC_DARK_GREY, STR_SMALLMAP_LEGENDA_INDUSTRIES), /* The legend will be terminated the first time it is used. */ @@ -179,7 +179,7 @@ static IndustryType _smallmap_industry_highlight = IT_INVALID; static bool _smallmap_industry_highlight_state; /** For connecting company ID to position in owner list (small map legend) */ -static ReferenceThroughBaseContainer> _company_to_list_pos; +static TypedIndexContainer, CompanyID> _company_to_list_pos; // TODO may have similar class in bulid overlays @@ -310,7 +310,7 @@ void BuildIndustriesLegend() void BuildLinkStatsLegend() { /* Clear the legend */ - memset(_legend_linkstats, 0, sizeof(_legend_linkstats)); + std::fill(std::begin(_legend_linkstats), std::end(_legend_linkstats), LegendAndColour{}); uint i = 0; for (; i < _sorted_cargo_specs.size(); ++i) { @@ -350,19 +350,19 @@ static const LegendAndColour * const _legend_table[] = { #define MKCOLOUR(x) TO_LE32(x) -#define MKCOLOUR_XXXX(x) (MKCOLOUR(0x01010101) * (uint)(x)) -#define MKCOLOUR_X0X0(x) (MKCOLOUR(0x01000100) * (uint)(x)) -#define MKCOLOUR_0X0X(x) (MKCOLOUR(0x00010001) * (uint)(x)) -#define MKCOLOUR_0XX0(x) (MKCOLOUR(0x00010100) * (uint)(x)) -#define MKCOLOUR_X00X(x) (MKCOLOUR(0x01000001) * (uint)(x)) +#define MKCOLOUR_XXXX(x) (MKCOLOUR(0x01010101) * (uint)(x.p)) +#define MKCOLOUR_X0X0(x) (MKCOLOUR(0x01000100) * (uint)(x.p)) +#define MKCOLOUR_0X0X(x) (MKCOLOUR(0x00010001) * (uint)(x.p)) +#define MKCOLOUR_0XX0(x) (MKCOLOUR(0x00010100) * (uint)(x.p)) +#define MKCOLOUR_X00X(x) (MKCOLOUR(0x01000001) * (uint)(x.p)) #define MKCOLOUR_XYXY(x, y) (MKCOLOUR_X0X0(x) | MKCOLOUR_0X0X(y)) #define MKCOLOUR_XYYX(x, y) (MKCOLOUR_X00X(x) | MKCOLOUR_0XX0(y)) -#define MKCOLOUR_0000 MKCOLOUR_XXXX(0x00) -#define MKCOLOUR_0FF0 MKCOLOUR_0XX0(0xFF) -#define MKCOLOUR_F00F MKCOLOUR_X00X(0xFF) -#define MKCOLOUR_FFFF MKCOLOUR_XXXX(0xFF) +#define MKCOLOUR_0000 MKCOLOUR_XXXX(PixelColour{0x00}) +#define MKCOLOUR_0FF0 MKCOLOUR_0XX0(PixelColour{0xFF}) +#define MKCOLOUR_F00F MKCOLOUR_X00X(PixelColour{0xFF}) +#define MKCOLOUR_FFFF MKCOLOUR_XXXX(PixelColour{0xFF}) #include "../table/heightmap_colours.h" @@ -375,10 +375,10 @@ struct SmallMapColourScheme { /** Available colour schemes for height maps. */ static SmallMapColourScheme _heightmap_schemes[] = { - {{}, _green_map_heights, MKCOLOUR_XXXX(0x54)}, ///< Green colour scheme. - {{}, _dark_green_map_heights, MKCOLOUR_XXXX(0x62)}, ///< Dark green colour scheme. - {{}, _violet_map_heights, MKCOLOUR_XXXX(0x81)}, ///< Violet colour scheme. - {{}, citymania::_yellow_map_heights, MKCOLOUR_XXXX(0xC1)}, + {{}, _green_map_heights, MKCOLOUR_XXXX(PixelColour{0x54})}, ///< Green colour scheme. + {{}, _dark_green_map_heights, MKCOLOUR_XXXX(PixelColour{0x62})}, ///< Dark green colour scheme. + {{}, _violet_map_heights, MKCOLOUR_XXXX(PixelColour{0x81})}, ///< Violet colour scheme. + {{}, citymania::_yellow_map_heights, MKCOLOUR_XXXX(PixelColour{0xC1})}, }; /** @@ -426,7 +426,7 @@ void BuildLandLegend() _legend_land_contours[i].col_break = j % rows == 0; _legend_land_contours[i].end = false; _legend_land_contours[i].height = j * delta; - _legend_land_contours[i].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[j * delta]; + _legend_land_contours[i].colour = PixelColour{static_cast(_heightmap_schemes[_settings_client.gui.smallmap_land_colour].height_colours[_legend_land_contours[i].height])}; j++; } _legend_land_contours[i].end = true; @@ -437,7 +437,7 @@ void BuildLandLegend() */ void BuildOwnerLegend() { - _legend_land_owners[1].colour = _heightmap_schemes[_settings_client.gui.smallmap_land_colour].default_colour; + _legend_land_owners[1].colour = PixelColour{static_cast(_heightmap_schemes[_settings_client.gui.smallmap_land_colour].default_colour)}; int i = NUM_NO_COMPANY_ENTRIES; for (const Company *c : Company::Iterate()) { @@ -458,11 +458,11 @@ void BuildOwnerLegend() } struct AndOr { - uint32 mor; - uint32 mand; + uint32_t mor; + uint32_t mand; }; -static inline uint32 ApplyMask(uint32 colour, const AndOr *mask) +static inline uint32_t ApplyMask(uint32_t colour, const AndOr *mask) { return (colour & mask->mand) | mask->mor; } @@ -723,7 +723,7 @@ static inline uint32 CM_GetSmallMapIMBAPixels(TileIndex tile, TileType t) } /** Vehicle colours in #SMT_VEHICLES mode. Indexed by #VehicleType. */ -static const uint8_t _vehicle_type_colours[6] = { +static const PixelColour _vehicle_type_colours[6] = { PC_RED, PC_YELLOW, PC_LIGHT_BLUE, PC_WHITE, PC_BLACK, PC_RED }; @@ -783,7 +783,7 @@ void SmallMapWindow::SetZoomLevel(ZoomLevelChange change, const Point *zoom_pt) switch (change) { case ZLC_INITIALIZE: cur_index = - 1; // Definitely different from new_index. - new_index = Clamp((int)ZOOM_LVL_GUI + 2, MIN_ZOOM_INDEX, MAX_ZOOM_INDEX); + new_index = Clamp((int)_gui_zoom + 2, MIN_ZOOM_INDEX, MAX_ZOOM_INDEX); tile.x = tile.y = 0; break; @@ -855,7 +855,7 @@ inline uint32 SmallMapWindow::GetTileColours(const TileArea &ta) const if (type == _smallmap_industry_highlight && _smallmap_industry_highlight_state) { return MKCOLOUR_XXXX(GetBlinkColour(map_colour)); } else { - return map_colour * 0x01010101; + return map_colour.p * 0x01010101; } } /* Otherwise make it disappear */ @@ -948,7 +948,7 @@ void SmallMapWindow::DrawSmallMapColumn(void *dst, uint xc, uint yc, int pitch, int idx = std::max(0, -start_pos); if (y >= 0 && y < end_y) { for (int pos = std::max(0, start_pos); pos < end_pos; pos++) { - blitter->SetPixel(dst, idx, 0, val8[idx]); + blitter->SetPixel(dst, idx, 0, PixelColour{val8[idx]}); idx++; } } @@ -961,7 +961,7 @@ void SmallMapWindow::DrawSmallMapColumn(void *dst, uint xc, uint yc, int pitch, int j = hidden_mod; int x = hidden_x; for (int pos = std::max(0, start_pos); pos < end_pos; pos++) { - blitter->SetPixel(ndst, x, 0, val8[idx]); + blitter->SetPixel(ndst, x, 0, PixelColour{val8[idx]}); j++; x++; if (j == this->ui_zoom) { @@ -997,9 +997,8 @@ void SmallMapWindow::DrawVehicles(const DrawPixelInfo *dpi, Blitter *blitter) co if (!IsInsideMM(y, -this->ui_zoom + 1, dpi->height)) continue; // y is out of bounds. /* Calculate pointer to pixel and the colour */ - uint8_t colour = (this->map_type == SMT_VEHICLES) ? _vehicle_type_colours[v->type] : PC_WHITE; + PixelColour colour = (this->map_type == SMT_VEHICLES) ? _vehicle_type_colours[v->type] : PC_WHITE; - /* And draw either one or two pixels depending on clipping */ auto min_i = std::max(0, -y); auto max_i = std::min(this->ui_zoom, dpi->height - y); auto min_j = std::max(0, -x); @@ -1426,7 +1425,7 @@ std::string SmallMapWindow::GetWidgetString(WidgetID widget, StringID stringid) i = 1; } - uint8 legend_colour = tbl->colour; + PixelColour legend_colour = tbl->colour; std::array params{}; @@ -1758,16 +1757,15 @@ int SmallMapWindow::GetPositionOnLegend(Point pt) return true; } -/* virtual */ void SmallMapWindow::OnMouseWheel(int wheel) +/* virtual */ void SmallMapWindow::OnMouseWheel(int wheel, WidgetID widget) { - if (_settings_client.gui.scrollwheel_scrolling != 2) { + if (widget != WID_SM_MAP) return; + if (_settings_client.gui.scrollwheel_scrolling != SWS_OFF) { const NWidgetBase *wid = this->GetWidget(WID_SM_MAP); int cursor_x = _cursor.pos.x - this->left - wid->pos_x; int cursor_y = _cursor.pos.y - this->top - wid->pos_y; - if (IsInsideMM(cursor_x, 0, wid->current_x) && IsInsideMM(cursor_y, 0, wid->current_y)) { - Point pt = {cursor_x, cursor_y}; - this->SetZoomLevel((wheel < 0) ? ZLC_ZOOM_IN : ZLC_ZOOM_OUT, &pt); - } + Point pt = {cursor_x, cursor_y}; + this->SetZoomLevel((wheel < 0) ? ZLC_ZOOM_IN : ZLC_ZOOM_OUT, &pt); } } diff --git a/src/citymania/cm_minimap.hpp b/src/citymania/cm_minimap.hpp index b843371de8..7c2691f9aa 100644 --- a/src/citymania/cm_minimap.hpp +++ b/src/citymania/cm_minimap.hpp @@ -57,7 +57,7 @@ void minimap_init_industries(); class NWidgetSmallmapDisplay; struct LegendAndColour { - uint8_t colour; ///< Colour of the item on the map. + PixelColour colour; ///< Colour of the item on the map. StringID legend; ///< String corresponding to the coloured item. IndustryType type; ///< Type of industry. Only valid for industry entries. uint8_t height; ///< Height in tiles. Only valid for height legend entries. @@ -243,7 +243,7 @@ public: void OnClick(Point pt, int widget, int click_count) override; void OnInvalidateData(int data = 0, bool gui_scope = true) override; bool OnRightClick(Point pt, int widget) override; - void OnMouseWheel(int wheel) override; + void OnMouseWheel(int wheel, WidgetID widget) override; // void OnHundredthTick() override; void OnScroll(Point delta) override; void OnMouseOver(Point pt, int widget) override; diff --git a/src/citymania/cm_misc_gui.cpp b/src/citymania/cm_misc_gui.cpp index 9f484e3a97..257a604e2d 100644 --- a/src/citymania/cm_misc_gui.cpp +++ b/src/citymania/cm_misc_gui.cpp @@ -27,7 +27,7 @@ static const NWidgetPart _nested_land_info_widgets[] = { }; static WindowDesc _land_info_desc( - WDP_MANUAL, nullptr, 0, 0, + WDP_MANUAL, {}, 0, 0, WC_LAND_INFO, WC_NONE, {}, _nested_land_info_widgets diff --git a/src/citymania/cm_station_gui.cpp b/src/citymania/cm_station_gui.cpp index 26a7951d2b..3b586ddfd9 100644 --- a/src/citymania/cm_station_gui.cpp +++ b/src/citymania/cm_station_gui.cpp @@ -29,6 +29,7 @@ #include "../town.h" #include "../viewport_func.h" #include "../viewport_kdtree.h" +#include "../vehicle_base.h" #include "../window_func.h" // SetWindowDirty #include "../window_gui.h" #include "../zoom_type.h" @@ -80,6 +81,29 @@ extern uint8_t _selected_airport_layout; ///< selected airport layout n namespace citymania { +// Copy of GetOrderDistance but returning both squared dist and manhattan +std::pair GetOrderDistances(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth) +{ + assert(v->orders != nullptr); + const OrderList &orderlist = *v->orders; + auto orders = orderlist.GetOrders(); + + if (orders[cur].IsType(OT_CONDITIONAL)) { + if (conditional_depth > v->GetNumOrders()) return {0, 0}; + + conditional_depth++; + + auto dist1 = GetOrderDistances(prev, orders[cur].GetConditionSkipToOrder(), v, conditional_depth); + auto dist2 = GetOrderDistances(prev, orderlist.GetNext(cur), v, conditional_depth); + return {std::max(dist1.first, dist2.first), std::max(dist1.second, dist2.second)}; + } + + TileIndex prev_tile = orders[prev].GetLocation(v, true); + TileIndex cur_tile = orders[cur].GetLocation(v, true); + if (prev_tile == INVALID_TILE || cur_tile == INVALID_TILE) return {0, 0}; + return {DistanceSquare(prev_tile, cur_tile), DistanceManhattan(prev_tile, cur_tile)}; +} + bool UseImprovedStationJoin() { return _settings_client.gui.cm_use_improved_station_join && _settings_game.station.distant_join_stations; } diff --git a/src/citymania/cm_station_gui.hpp b/src/citymania/cm_station_gui.hpp index 48a7e2c3ee..8668ae174a 100644 --- a/src/citymania/cm_station_gui.hpp +++ b/src/citymania/cm_station_gui.hpp @@ -15,6 +15,8 @@ namespace citymania { +std::pair GetOrderDistances(VehicleOrderID prev, VehicleOrderID cur, const Vehicle *v, int conditional_depth = 0); + const DiagDirection DEPOTDIR_AUTO = DIAGDIR_END; const DiagDirection STATIONDIR_X = DIAGDIR_END; const DiagDirection STATIONDIR_Y = (DiagDirection)((uint)DIAGDIR_END + 1); diff --git a/src/citymania/cm_tooltips.cpp b/src/citymania/cm_tooltips.cpp index 8e28d433c2..a8357015b5 100644 --- a/src/citymania/cm_tooltips.cpp +++ b/src/citymania/cm_tooltips.cpp @@ -30,7 +30,7 @@ static const NWidgetPart _nested_land_tooltips_widgets[] = { }; static WindowDesc _land_tooltips_desc( - WDP_MANUAL, nullptr, 0, 0, + WDP_MANUAL, {}, 0, 0, CM_WC_LAND_TOOLTIPS, WC_NONE, {}, _nested_land_tooltips_widgets @@ -272,7 +272,7 @@ static const NWidgetPart _nested_station_rating_tooltip_widgets[] = { }; static WindowDesc _station_rating_tooltip_desc( - WDP_MANUAL, nullptr, 0, 0, + WDP_MANUAL, {}, 0, 0, WC_STATION_RATING_TOOLTIP, WC_NONE, {}, _nested_station_rating_tooltip_widgets diff --git a/src/citymania/cm_town_gui.cpp b/src/citymania/cm_town_gui.cpp index cd6c3e9596..3245f9468a 100644 --- a/src/citymania/cm_town_gui.cpp +++ b/src/citymania/cm_town_gui.cpp @@ -304,7 +304,7 @@ public: switch(widget) { case WID_CB_CARGO_NAME: { // FIXME scaling - GfxFillRect(tr.left, tr.top + 1, tr.left + 8, tr.top + 6, 0); + GfxFillRect(tr.left, tr.top + 1, tr.left + 8, tr.top + 6, PC_BLACK); GfxFillRect(tr.left + 1, tr.top + 2, tr.left + 7, tr.top + 5, cargos->legend_colour); DrawString(tr.Shrink(ScaleGUITrad(14), 0, 0, 0), GetString(CM_STR_TOWN_CB_CARGO_NAME, cargos->name)); @@ -412,7 +412,7 @@ public: static inline HotkeyList hotkeys{"cm_town_cb_view", { Hotkey((uint16)0, "location", WID_TV_CENTER_VIEW), Hotkey((uint16)0, "local_authority", WID_TV_SHOW_AUTHORITY), - Hotkey((uint16)0, "cb_window", WID_TV_CB), + Hotkey((uint16)0, "cb_window", CM_WID_TV_CB), Hotkey(WKC_CTRL | 'S', "build_statue", HK_STATUE + 0x80), }}; }; diff --git a/src/citymania/cm_watch_gui.cpp b/src/citymania/cm_watch_gui.cpp index 96ba29dc78..25d9734335 100644 --- a/src/citymania/cm_watch_gui.cpp +++ b/src/citymania/cm_watch_gui.cpp @@ -273,7 +273,7 @@ WatchCompany::WatchCompany(WindowDesc &desc, int window_number, CompanyID compan } /* Init the viewport area */ NWidgetViewport *nvp = this->GetWidget(EWW_WATCH); - nvp->InitializeViewport(this, (TileIndex)0, ScaleZoomGUI(ZOOM_LVL_NORMAL)); + nvp->InitializeViewport(this, (TileIndex)0, ScaleZoomGUI(ZoomLevel::Normal)); Point pt; /* the main window with the main view */ @@ -338,7 +338,7 @@ void WatchCompany::DrawWidget(const Rect &r, int widget) const int offset = (cid == this->watched_company) ? 1 : 0; auto icon = (company_activity[cid] > 0 ? CM_SPR_COMPANY_ICON : CM_SPR_COMPANY_ICON_AFK); Dimension sprite_size = GetSpriteSize(icon); - DrawSprite(icon, COMPANY_SPRITE_COLOUR(cid), (r.left + r.right - sprite_size.width) / 2 + offset, (r.top + r.bottom - sprite_size.height) / 2 + offset); + DrawSprite(icon, GetCompanyPalette(cid), (r.left + r.right - sprite_size.width) / 2 + offset, (r.top + r.bottom - sprite_size.height) / 2 + offset); } return; } @@ -375,8 +375,9 @@ void WatchCompany::OnScroll(Point delta) this->viewport->dest_scrollpos_y = this->viewport->scrollpos_y; } -void WatchCompany::OnMouseWheel( int wheel ) +void WatchCompany::OnMouseWheel(int wheel, WidgetID widget) { + if (widget != EWW_WATCH) return; ZoomInOrOutToCursorWindow(wheel < 0, this); } diff --git a/src/citymania/cm_watch_gui.hpp b/src/citymania/cm_watch_gui.hpp index f37e0a4dc6..9cb73de1bf 100644 --- a/src/citymania/cm_watch_gui.hpp +++ b/src/citymania/cm_watch_gui.hpp @@ -61,8 +61,8 @@ class WatchCompany : public Window { protected: CompanyID watched_company; // Company ID beeing watched. - ReferenceThroughBaseContainer> company_activity; // int array for activity blot. - ReferenceThroughBaseContainer> company_count_client; // company client count. + TypedIndexContainer, CompanyID> company_activity; // int array for activity blot. + TypedIndexContainer, CompanyID> company_count_client; // company client count. std::string company_name; // company name for title display std::string client_name; @@ -80,7 +80,7 @@ public: void OnClick(Point pt, int widget, int click_count) override; void OnResize() override; void OnScroll(Point delta) override; - void OnMouseWheel(int wheel) override; + void OnMouseWheel(int wheel, WidgetID widget) override; void OnInvalidateData(int data, bool gui_scope) override; std::string GetWidgetString(WidgetID widget, StringID stringid) const override; void OnRealtimeTick([[maybe_unused]] uint delta_ms) override; diff --git a/src/citymania/cm_zoning_cmd.cpp b/src/citymania/cm_zoning_cmd.cpp index af6541b94a..e69602bb31 100644 --- a/src/citymania/cm_zoning_cmd.cpp +++ b/src/citymania/cm_zoning_cmd.cpp @@ -122,7 +122,7 @@ const uint8_t _tileh_to_sprite[32] = { void DrawZoningSprites(SpriteID image, SpriteID colour, const TileInfo *ti) { if (colour != INVALID_SPRITE_ID){ - AddSortableSpriteToDraw(image + _tileh_to_sprite[ti->tileh], colour, ti->x, ti->y, 0x10, 0x10, 1, ti->z + 7); + AddSortableSpriteToDraw(image + _tileh_to_sprite[ti->tileh], colour, ti->x, ti->y, ti->z + 7, {{}, {0x10, 0x10, 1}, {}}); } } @@ -243,26 +243,26 @@ SpriteID TileZoneCheckUnservedIndustriesEvaluation(TileIndex tile) { //Check which town zone tile belongs to. SpriteID TileZoneCheckTownZones(TileIndex tile) { - HouseZonesBits next_zone = HZB_BEGIN, tz = HZB_END; + uint8_t next_zone = 0, tz = (uint8_t)HouseZone::TownEnd; for (Town *town : Town::Iterate()) { uint dist = DistanceSquare(tile, town->xy); // town code uses <= for checking town borders (tz0) but < for other zones - while (next_zone < HZB_END + while (next_zone < (uint8_t)HouseZone::TownEnd && (town->cache.squared_town_zone_radius[next_zone] == 0 - || dist <= town->cache.squared_town_zone_radius[next_zone] - (next_zone == HZB_BEGIN ? 0 : 1)) + || dist <= town->cache.squared_town_zone_radius[next_zone] - (next_zone == (uint8_t)HouseZone::TownEdge ? 0 : 1)) ){ if(town->cache.squared_town_zone_radius[next_zone] != 0) tz = next_zone; next_zone++; } } - switch (tz) { - case HZB_TOWN_EDGE: return CM_SPR_PALETTE_ZONING_LIGHT_BLUE; // Tz0 - case HZB_TOWN_OUTSKIRT: return CM_SPR_PALETTE_ZONING_RED; // Tz1 - case HZB_TOWN_OUTER_SUBURB: return CM_SPR_PALETTE_ZONING_YELLOW; // Tz2 - case HZB_TOWN_INNER_SUBURB: return CM_SPR_PALETTE_ZONING_GREEN; // Tz3 - case HZB_TOWN_CENTRE: return CM_SPR_PALETTE_ZONING_WHITE; // Tz4 - center + switch ((HouseZone)tz) { + case HouseZone::TownEdge: return CM_SPR_PALETTE_ZONING_LIGHT_BLUE; // Tz0 + case HouseZone::TownOutskirt: return CM_SPR_PALETTE_ZONING_RED; // Tz1 + case HouseZone::TownOuterSuburb: return CM_SPR_PALETTE_ZONING_YELLOW; // Tz2 + case HouseZone::TownInnerSuburb: return CM_SPR_PALETTE_ZONING_GREEN; // Tz3 + case HouseZone::TownCentre: return CM_SPR_PALETTE_ZONING_WHITE; // Tz4 - center default: return INVALID_SPRITE_ID; // no town } return INVALID_SPRITE_ID; diff --git a/src/citymania/generated/cm_gen_commands.cpp b/src/citymania/generated/cm_gen_commands.cpp index 25fd1af6d0..9b5232d5f1 100644 --- a/src/citymania/generated/cm_gen_commands.cpp +++ b/src/citymania/generated/cm_gen_commands.cpp @@ -40,7 +40,6 @@ static constexpr auto _callback_tuple = std::make_tuple( &CcBuildRailTunnel, &CcFoundRandomTown, &CcFoundTown, - &CcBuildIndustry, &CcBuildBridge, &CcCreateGroup, &CcAddVehicleNewGroup, @@ -955,12 +954,12 @@ CommandCost RenamePresident::_do(DoCommandFlags flags) { } Commands SetCompanyManagerFace::get_command() { return CMD_SET_COMPANY_MANAGER_FACE; } -static constexpr auto _SetCompanyManagerFace_dispatch = MakeDispatchTable(); +static constexpr auto _SetCompanyManagerFace_dispatch = MakeDispatchTable(); bool SetCompanyManagerFace::_post(::CommandCallback *callback) { - return _SetCompanyManagerFace_dispatch[FindCallbackIndex(callback)](this->error, this->cmf); + return _SetCompanyManagerFace_dispatch[FindCallbackIndex(callback)](this->error, this->style, this->bits); } CommandCost SetCompanyManagerFace::_do(DoCommandFlags flags) { - return (::Command::Do(flags, cmf)); + return (::Command::Do(flags, style, bits)); } Commands SetCompanyColour::get_command() { return CMD_SET_COMPANY_COLOUR; } @@ -1054,12 +1053,12 @@ CommandCost TownSetText::_do(DoCommandFlags flags) { } Commands ExpandTown::get_command() { return CMD_EXPAND_TOWN; } -static constexpr auto _ExpandTown_dispatch = MakeDispatchTable(); +static constexpr auto _ExpandTown_dispatch = MakeDispatchTable(); bool ExpandTown::_post(::CommandCallback *callback) { - return _ExpandTown_dispatch[FindCallbackIndex(callback)](this->error, this->town_id, this->grow_amount); + return _ExpandTown_dispatch[FindCallbackIndex(callback)](this->error, this->town_id, this->grow_amount, this->modes); } CommandCost ExpandTown::_do(DoCommandFlags flags) { - return (::Command::Do(flags, town_id, grow_amount)); + return (::Command::Do(flags, town_id, grow_amount, modes)); } Commands DeleteTown::get_command() { return CMD_DELETE_TOWN; } diff --git a/src/citymania/generated/cm_gen_commands.hpp b/src/citymania/generated/cm_gen_commands.hpp index 89ddf2ed3a..adcbe0a86d 100644 --- a/src/citymania/generated/cm_gen_commands.hpp +++ b/src/citymania/generated/cm_gen_commands.hpp @@ -1493,10 +1493,11 @@ public: class SetCompanyManagerFace: public Command { public: - CompanyManagerFace cmf; + uint style; + uint32_t bits; - SetCompanyManagerFace(CompanyManagerFace cmf) - :cmf{cmf} {} + SetCompanyManagerFace(uint style, uint32_t bits) + :style{style}, bits{bits} {} ~SetCompanyManagerFace() override {} bool _post(::CommandCallback * callback) override; @@ -1658,9 +1659,10 @@ class ExpandTown: public Command { public: TownID town_id; uint32_t grow_amount; + TownExpandModes modes; - ExpandTown(TownID town_id, uint32_t grow_amount) - :town_id{town_id}, grow_amount{grow_amount} {} + ExpandTown(TownID town_id, uint32_t grow_amount, TownExpandModes modes) + :town_id{town_id}, grow_amount{grow_amount}, modes{modes} {} ~ExpandTown() override {} bool _post(::CommandCallback * callback) override; diff --git a/src/clear_cmd.cpp b/src/clear_cmd.cpp index 04f52cfdee..a9dc5c567f 100644 --- a/src/clear_cmd.cpp +++ b/src/clear_cmd.cpp @@ -69,36 +69,45 @@ static void DrawClearLandFence(const TileInfo *ti) /* combine fences into one sprite object */ StartSpriteCombine(); - int maxz = GetSlopeMaxPixelZ(ti->tileh); + SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, 4}, {}}; + + bounds.extent.z += GetSlopeMaxPixelZ(ti->tileh); uint fence_nw = GetFence(ti->tile, DIAGDIR_NW); if (fence_nw != 0) { - int z = GetSlopePixelZInCorner(ti->tileh, CORNER_W); + bounds.offset.x = 0; + bounds.offset.y = -static_cast(TILE_SIZE); + bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_W); SpriteID sprite = _clear_land_fence_sprites[fence_nw - 1] + _fence_mod_by_tileh_nw[ti->tileh]; - AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y - 16, 16, 32, maxz - z + 4, ti->z + z, false, 0, 16, -z); + AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false); } uint fence_ne = GetFence(ti->tile, DIAGDIR_NE); if (fence_ne != 0) { - int z = GetSlopePixelZInCorner(ti->tileh, CORNER_E); + bounds.offset.x = -static_cast(TILE_SIZE); + bounds.offset.y = 0; + bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_E); SpriteID sprite = _clear_land_fence_sprites[fence_ne - 1] + _fence_mod_by_tileh_ne[ti->tileh]; - AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x - 16, ti->y, 32, 16, maxz - z + 4, ti->z + z, false, 16, 0, -z); + AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false); } uint fence_sw = GetFence(ti->tile, DIAGDIR_SW); uint fence_se = GetFence(ti->tile, DIAGDIR_SE); if (fence_sw != 0 || fence_se != 0) { - int z = GetSlopePixelZInCorner(ti->tileh, CORNER_S); + bounds.offset.x = 0; + bounds.offset.y = 0; + bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_S); if (fence_sw != 0) { SpriteID sprite = _clear_land_fence_sprites[fence_sw - 1] + _fence_mod_by_tileh_sw[ti->tileh]; - AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y, 16, 16, maxz - z + 4, ti->z + z, false, 0, 0, -z); + AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false); } if (fence_se != 0) { SpriteID sprite = _clear_land_fence_sprites[fence_se - 1] + _fence_mod_by_tileh_se[ti->tileh]; - AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y, 16, 16, maxz - z + 4, ti->z + z, false, 0, 0, -z); + AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false); + } } EndSpriteCombine(); @@ -143,7 +152,7 @@ static void DrawTile_Clear(TileInfo *ti) break; } - DrawBridgeMiddle(ti); + DrawBridgeMiddle(ti, {}); } static int GetSlopePixelZ_Clear(TileIndex tile, uint x, uint y, bool) @@ -381,6 +390,12 @@ static CommandCost TerraformTile_Clear(TileIndex tile, DoCommandFlags flags, int return Command::Do(flags, tile); } +static CommandCost CheckBuildAbove_Clear(TileIndex, DoCommandFlags, Axis, int) +{ + /* Can always build above clear tiles. */ + return CommandCost(); +} + extern const TileTypeProcs _tile_type_clear_procs = { DrawTile_Clear, ///< draw_tile_proc GetSlopePixelZ_Clear, ///< get_slope_z_proc @@ -396,4 +411,5 @@ extern const TileTypeProcs _tile_type_clear_procs = { nullptr, ///< vehicle_enter_tile_proc GetFoundation_Clear, ///< get_foundation_proc TerraformTile_Clear, ///< terraform_tile_proc + CheckBuildAbove_Clear, // check_build_above_proc }; diff --git a/src/command.cpp b/src/command.cpp index 1e50017e03..3969f005b7 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -80,7 +80,7 @@ int RecursiveCommandCounter::_counter = 0; * the #CommandFlag::Auto, #CommandFlag::Offline and #CommandFlag::Server values. */ struct CommandInfo { - const char *name; ///< A human readable name for the procedure + std::string_view name; ///< A human readable name for the procedure CommandFlags flags; ///< The (command) flags to that apply to this command CommandType type; ///< The type of command. }; @@ -135,7 +135,7 @@ CommandFlags GetCommandFlags(Commands cmd) * @param cmd The integer value of the command * @return The name for this command */ -const char *GetCommandName(Commands cmd) +std::string_view GetCommandName(Commands cmd) { assert(IsValidCommand(cmd)); diff --git a/src/command_func.h b/src/command_func.h index 09d93b5558..d195b3a0ee 100644 --- a/src/command_func.h +++ b/src/command_func.h @@ -42,7 +42,7 @@ void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *cal bool IsValidCommand(Commands cmd); CommandFlags GetCommandFlags(Commands cmd); -const char *GetCommandName(Commands cmd); +std::string_view GetCommandName(Commands cmd); bool IsCommandAllowedWhilePaused(Commands cmd); template diff --git a/src/command_type.h b/src/command_type.h index de960e3e54..a5fcbbdac2 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -26,7 +26,7 @@ struct GRFFile; class CommandCost { Money cost; ///< The cost of this action StringID message; ///< Warning message for when success is unset - ExpensesType expense_type; ///< the type of expence as shown on the finances view + ExpensesType expense_type; ///< the type of expense as shown on the finances view bool success; ///< Whether the command went fine up to this moment Owner owner = CompanyID::Invalid(); ///< Originator owner of error. StringID extra_message = INVALID_STRING_ID; ///< Additional warning message for when success is unset @@ -474,7 +474,7 @@ template struct CommandTraits; static constexpr auto &proc = proc_; \ static constexpr CommandFlags flags = flags_; \ static constexpr CommandType type = type_; \ - static inline constexpr const char *name = #proc_; \ + static inline constexpr std::string_view name = #proc_; \ }; /** Storage buffer for serialized command data. */ diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index ca7db187ac..deb80fc3e1 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -28,6 +28,7 @@ #include "sound_func.h" #include "rail.h" #include "core/pool_func.hpp" +#include "core/string_consumer.hpp" #include "settings_func.h" #include "vehicle_base.h" #include "vehicle_func.h" @@ -39,15 +40,18 @@ #include "timer/timer.h" #include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" +#include "timer/timer_window.h" #include "widgets/statusbar_widget.h" #include "table/strings.h" +#include "table/company_face.h" + + #include "cargo_type.h" #include "citymania/cm_hotkeys.hpp" #include "citymania/cm_console_cmds.hpp" - #include "safeguards.h" void ClearEnginesHiddenFlagOfCompany(CompanyID cid); @@ -55,8 +59,8 @@ void UpdateObjectColours(const Company *c); CompanyID _local_company; ///< Company controlled by the human player at this client. Can also be #COMPANY_SPECTATOR. CompanyID _current_company; ///< Company currently doing an action. -ReferenceThroughBaseContainer> _company_colours; ///< NOSAVE: can be determined from company structs. -CompanyManagerFace _company_manager_face; ///< for company manager face storage in openttd.cfg +TypedIndexContainer, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs. +std::string _company_manager_face; ///< for company manager face storage in openttd.cfg uint _cur_company_tick_index; ///< used to generate a name for one company that doesn't have a name yet per tick CompanyPool _company_pool("Company"); ///< Pool of companies. @@ -147,6 +151,8 @@ void SetLocalCompany(CompanyID new_company) MarkWholeScreenDirty(); InvalidateWindowClassesData(WC_SIGN_LIST, -1); InvalidateWindowClassesData(WC_GOALS_LIST); + InvalidateWindowClassesData(WC_COMPANY_COLOUR, -1); + ResetVehicleColourMap(); } /** @@ -156,8 +162,18 @@ void SetLocalCompany(CompanyID new_company) */ TextColour GetDrawStringCompanyColour(CompanyID company) { - if (!Company::IsValidID(company)) return (TextColour)GetColourGradient(COLOUR_WHITE, SHADE_NORMAL) | TC_IS_PALETTE_COLOUR; - return (TextColour)GetColourGradient(_company_colours[company], SHADE_NORMAL) | TC_IS_PALETTE_COLOUR; + if (!Company::IsValidID(company)) return GetColourGradient(COLOUR_WHITE, SHADE_NORMAL).ToTextColour(); + return GetColourGradient(_company_colours[company], SHADE_NORMAL).ToTextColour(); +} + +/** + * Get the palette for recolouring with a company colour. + * @param company Company to get the colour of. + * @return Palette for recolouring. + */ +PaletteID GetCompanyPalette(CompanyID company) +{ + return GetColourPalette(_company_colours[company]); } /** @@ -168,7 +184,7 @@ TextColour GetDrawStringCompanyColour(CompanyID company) */ void DrawCompanyIcon(CompanyID c, int x, int y) { - DrawSprite(SPR_COMPANY_ICON, COMPANY_SPRITE_COLOUR(c), x, y); + DrawSprite(SPR_COMPANY_ICON, GetCompanyPalette(c), x, y); } /** @@ -179,41 +195,49 @@ void DrawCompanyIcon(CompanyID c, int x, int y) */ static bool IsValidCompanyManagerFace(CompanyManagerFace cmf) { - if (!AreCompanyManagerFaceBitsValid(cmf, CMFV_GEN_ETHN, GE_WM)) return false; + if (cmf.style >= GetNumCompanyManagerFaceStyles()) return false; - GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM); - bool has_moustache = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0; - bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0; - bool has_glasses = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0; - - if (!AreCompanyManagerFaceBitsValid(cmf, CMFV_EYE_COLOUR, ge)) return false; - for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) { - switch (cmfv) { - case CMFV_MOUSTACHE: if (!has_moustache) continue; break; - case CMFV_LIPS: - case CMFV_NOSE: if (has_moustache) continue; break; - case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break; - case CMFV_GLASSES: if (!has_glasses) continue; break; - default: break; - } - if (!AreCompanyManagerFaceBitsValid(cmf, cmfv, ge)) return false; + /* Test if each enabled part is valid. */ + FaceVars vars = GetCompanyManagerFaceVars(cmf.style); + for (uint var : SetBitIterator(GetActiveFaceVars(cmf, vars))) { + if (!vars[var].IsValid(cmf)) return false; } return true; } +static CompanyMask _dirty_company_finances{}; ///< Bitmask of company finances that should be marked dirty. + /** - * Refresh all windows owned by a company. + * Mark all finance windows owned by a company as needing a refresh. + * The actual refresh is deferred until the end of the gameloop to reduce duplicated work. * @param company Company that changed, and needs its windows refreshed. */ void InvalidateCompanyWindows(const Company *company) { CompanyID cid = company->index; - - if (cid == _local_company) SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_RIGHT); - SetWindowDirty(WC_FINANCES, cid); + _dirty_company_finances.Set(cid); } +/** + * Refresh all company finance windows previously marked dirty. + */ +static const IntervalTimer invalidate_company_windows_interval(std::chrono::milliseconds(1), [](auto) { + for (CompanyID cid : _dirty_company_finances) { + if (cid == _local_company) SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_RIGHT); + Window *w = FindWindowById(WC_FINANCES, cid); + if (w != nullptr) { + w->SetWidgetDirty(WID_CF_EXPS_PRICE3); + w->SetWidgetDirty(WID_CF_OWN_VALUE); + w->SetWidgetDirty(WID_CF_LOAN_VALUE); + w->SetWidgetDirty(WID_CF_BALANCE_VALUE); + w->SetWidgetDirty(WID_CF_MAXLOAN_VALUE); + } + SetWindowWidgetDirty(WC_COMPANY, cid, WID_C_DESC_COMPANY_VALUE); + } + _dirty_company_finances.Reset(); +}); + /** * Get the amount of money that a company has available, or INT64_MAX * if there is no such valid company. @@ -611,11 +635,15 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid( /* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face. * In a network game, we'll choose the favorite face later in CmdCompanyCtrl to sync it to all clients. */ - if (_company_manager_face != 0 && !is_ai && !_networking) { - c->face = _company_manager_face; - } else { - RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, _random); + bool randomise_face = true; + if (!_company_manager_face.empty() && !is_ai && !_networking) { + auto cmf = ParseCompanyManagerFaceCode(_company_manager_face); + if (cmf.has_value()) { + randomise_face = false; + c->face = std::move(*cmf); + } } + if (randomise_face) RandomiseCompanyManagerFace(c->face, _random); SetDefaultCompanySettings(c->index); ClearEnginesHiddenFlagOfCompany(c->index); @@ -790,7 +818,7 @@ void OnTick_Companies() * A year has passed, update the economic data of all companies, and perhaps show the * financial overview window of the local company. */ -static IntervalTimer _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto) +static const IntervalTimer _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto) { /* Copy statistics */ for (Company *c : Company::Iterate()) { @@ -904,7 +932,12 @@ CommandCost CmdCompanyCtrl(DoCommandFlags flags, CompanyCtrlAction cca, CompanyI * its configuration and we are currently in the execution of a command, we have * to circumvent the normal ::Post logic for commands and just send the command. */ - if (_company_manager_face != 0) Command::SendNet(STR_NULL, c->index, _company_manager_face); + if (!_company_manager_face.empty()) { + auto cmf = ParseCompanyManagerFaceCode(_company_manager_face); + if (cmf.has_value()) { + Command::SendNet(STR_NULL, c->index, cmf->bits, cmf->style); + } + } /* Now that we have a new company, broadcast our company settings to * all clients so everything is in sync */ @@ -1028,15 +1061,20 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc /** * Change the company manager's face. * @param flags operation to perform - * @param cmf face bitmasked + * @param bits The bits of company manager face. + * @param style The style of the company manager face. * @return the cost of this operation or an error */ -CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, CompanyManagerFace cmf) +CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint32_t bits, uint style) { - if (!IsValidCompanyManagerFace(cmf)) return CMD_ERROR; + CompanyManagerFace tmp_face{style, bits, {}}; + if (!IsValidCompanyManagerFace(tmp_face)) return CMD_ERROR; if (flags.Test(DoCommandFlag::Execute)) { - Company::Get(_current_company)->face = cmf; + CompanyManagerFace &cmf = Company::Get(_current_company)->face; + SetCompanyManagerFaceStyle(cmf, style); + cmf.bits = tmp_face.bits; + MarkWholeScreenDirty(); } return CommandCost(); @@ -1355,3 +1393,157 @@ CompanyID GetFirstPlayableCompanyID() return CompanyID::Begin(); } + +static std::vector _faces; ///< All company manager face styles. + +/** + * Reset company manager face styles to default. + */ +void ResetFaces() +{ + _faces.clear(); + _faces.assign(std::begin(_original_faces), std::end(_original_faces)); +} + +/** + * Get the number of company manager face styles. + * @return Number of face styles. + */ +uint GetNumCompanyManagerFaceStyles() +{ + return static_cast(std::size(_faces)); +} + +/** + * Get the definition of a company manager face style. + * @param style_index Face style to get. + * @return Definition of face style. + */ +const FaceSpec *GetCompanyManagerFaceSpec(uint style_index) +{ + if (style_index < GetNumCompanyManagerFaceStyles()) return &_faces[style_index]; + return nullptr; +} + +/** + * Find a company manager face style by label. + * @param label Label to find. + * @return Index of face style if label is found, otherwise std::nullopt. + */ +std::optional FindCompanyManagerFaceLabel(std::string_view label) +{ + auto it = std::ranges::find(_faces, label, &FaceSpec::label); + if (it == std::end(_faces)) return std::nullopt; + + return static_cast(std::distance(std::begin(_faces), it)); +} + +/** + * Get the face variables for a face style. + * @param style_index Face style to get variables for. + * @return Variables for the face style. + */ +FaceVars GetCompanyManagerFaceVars(uint style) +{ + const FaceSpec *spec = GetCompanyManagerFaceSpec(style); + if (spec == nullptr) return {}; + return spec->GetFaceVars(); +} + +/** + * Set a company face style. + * Changes both the style index and the label. + * @param cmf The CompanyManagerFace to change. + * @param style The style to set. + */ +void SetCompanyManagerFaceStyle(CompanyManagerFace &cmf, uint style) +{ + const FaceSpec *spec = GetCompanyManagerFaceSpec(style); + assert(spec != nullptr); + + cmf.style = style; + cmf.style_label = spec->label; +} + +/** + * Completely randomise a company manager face, including style. + * @note randomizer should passed be appropriate for server-side or client-side usage. + * @param cmf The CompanyManagerFace to randomise. + * @param randomizer The randomizer to use. + */ +void RandomiseCompanyManagerFace(CompanyManagerFace &cmf, Randomizer &randomizer) +{ + SetCompanyManagerFaceStyle(cmf, randomizer.Next(GetNumCompanyManagerFaceStyles())); + RandomiseCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style), randomizer); +} + +/** + * Mask company manager face bits to ensure they are all within range. + * @note Does not update the CompanyManagerFace itself. Unused bits are cleared. + * @param cmf The CompanyManagerFace. + * @param style The face variables. + * @return The masked face bits. + */ +uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars) +{ + CompanyManagerFace face{}; + + for (auto var : SetBitIterator(GetActiveFaceVars(cmf, vars))) { + vars[var].SetBits(face, vars[var].GetBits(cmf)); + } + + return face.bits; +} + +/** + * Get a face code representation of a company manager face. + * @param cmf The company manager face. + * @return String containing face code. + */ +std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf) +{ + uint32_t masked_face_bits = MaskCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style)); + return fmt::format("{}:{}", cmf.style_label, masked_face_bits); +} + +/** + * Parse a face code into a company manager face. + * @param str Face code to parse. + * @return Company manager face, or std::nullopt if it could not be parsed. + */ +std::optional ParseCompanyManagerFaceCode(std::string_view str) +{ + if (str.empty()) return std::nullopt; + + CompanyManagerFace cmf; + StringConsumer consumer{str}; + if (consumer.FindChar(':') != StringConsumer::npos) { + auto label = consumer.ReadUntilChar(':', StringConsumer::SKIP_ONE_SEPARATOR); + + /* Read numeric part and ensure it's valid. */ + auto bits = consumer.TryReadIntegerBase(10, true); + if (!bits.has_value() || consumer.AnyBytesLeft()) return std::nullopt; + + /* Ensure style label is valid. */ + auto style = FindCompanyManagerFaceLabel(label); + if (!style.has_value()) return std::nullopt; + + SetCompanyManagerFaceStyle(cmf, *style); + cmf.bits = *bits; + } else { + /* No ':' included, treat as numeric-only. This allows old-style codes to be entered. */ + auto bits = ParseInteger(str, 10, true); + if (!bits.has_value()) return std::nullopt; + + /* Old codes use bits 0..1 to represent face style. These map directly to the default face styles. */ + SetCompanyManagerFaceStyle(cmf, GB(*bits, 0, 2)); + cmf.bits = *bits; + } + + /* Force the face bits to be valid. */ + FaceVars vars = GetCompanyManagerFaceVars(cmf.style); + ScaleAllCompanyManagerFaceBits(cmf, vars); + cmf.bits = MaskCompanyManagerFaceBits(cmf, vars); + + return cmf; +} diff --git a/src/company_cmd.h b/src/company_cmd.h index 8359d5e07f..eb30f9b361 100644 --- a/src/company_cmd.h +++ b/src/company_cmd.h @@ -22,7 +22,7 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc CommandCost CmdGiveMoney(DoCommandFlags flags, Money money, CompanyID dest_company); CommandCost CmdRenameCompany(DoCommandFlags flags, const std::string &text); CommandCost CmdRenamePresident(DoCommandFlags flags, const std::string &text); -CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, CompanyManagerFace cmf); +CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint style, uint32_t bits); CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool primary, Colours colour); DEF_CMD_TRAIT(CMD_COMPANY_CTRL, CmdCompanyCtrl, CommandFlags({CommandFlag::Spectator, CommandFlag::ClientID, CommandFlag::NoEst}), CMDT_SERVER_SETTING) diff --git a/src/company_func.h b/src/company_func.h index 870a292424..3a68556f37 100644 --- a/src/company_func.h +++ b/src/company_func.h @@ -36,8 +36,9 @@ CommandCost CheckTileOwnership(TileIndex tile); extern CompanyID _local_company; extern CompanyID _current_company; -extern ReferenceThroughBaseContainer> _company_colours; -extern CompanyManagerFace _company_manager_face; +extern TypedIndexContainer, CompanyID> _company_colours; +extern std::string _company_manager_face; +PaletteID GetCompanyPalette(CompanyID company); /** * Is the current company the local company? diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 686d92a410..d1a8386328 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -11,6 +11,7 @@ #include "currency.h" #include "error.h" #include "gui.h" +#include "settings_gui.h" #include "window_gui.h" #include "textbuf_gui.h" #include "viewport_func.h" @@ -45,6 +46,7 @@ #include "object_cmd.h" #include "timer/timer.h" #include "timer/timer_window.h" +#include "core/string_consumer.hpp" #include "widgets/company_widget.h" @@ -542,7 +544,7 @@ struct CompanyFinancesWindow : Window { * Check on a regular interval if the maximum amount of money has changed. * If it has, rescale the window to fit the new amount. */ - IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { + const IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { const Company *c = Company::Get(this->window_number); if (c->money > CompanyFinancesWindow::max_money) { CompanyFinancesWindow::max_money = std::max(c->money * 2, CompanyFinancesWindow::max_money * 4); @@ -592,7 +594,8 @@ static const LiveryClass _livery_class[LS_END] = { template class DropDownListColourItem : public DropDownIcon> { public: - DropDownListColourItem(int colour, bool masked) : DropDownIcon>(TSprite, GENERAL_SPRITE_COLOUR(colour % COLOUR_END), GetString(colour < COLOUR_END ? (STR_COLOUR_DARK_BLUE + colour) : STR_COLOUR_DEFAULT), colour, masked) + DropDownListColourItem(int colour, bool masked) : + DropDownIcon>(TSprite, GetColourPalette(static_cast(colour % COLOUR_END)), GetString(colour < COLOUR_END ? (STR_COLOUR_DARK_BLUE + colour) : STR_COLOUR_DEFAULT), colour, masked) { } }; @@ -751,13 +754,6 @@ public: d = maxdim(d, GetStringBoundingBox(STR_LIVERY_DEFAULT + scheme)); } - /* And group names */ - for (const Group *g : Group::Iterate()) { - if (g->owner == this->window_number) { - d = maxdim(d, GetStringBoundingBox(GetString(STR_GROUP_NAME, g->index))); - } - } - size.width = std::max(size.width, 5 + d.width + padding.width); break; } @@ -769,7 +765,7 @@ public: size.height = 5 * this->line_height; resize.width = 1; - resize.height = this->line_height; + fill.height = resize.height = this->line_height; break; } @@ -881,12 +877,12 @@ public: DrawString(sch.left + (rtl ? 0 : indent), sch.right - (rtl ? indent : 0), y + text_offs, str, is_selected ? TC_WHITE : TC_BLACK); /* Text below the first dropdown. */ - DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(livery.colour1), pri_squ.left, y + square_offs); + DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour1), pri_squ.left, y + square_offs); DrawString(pri.left, pri.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 0)) ? STR_COLOUR_DARK_BLUE + livery.colour1 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); /* Text below the second dropdown. */ if (sec.right > sec.left) { // Second dropdown has non-zero size. - DrawSprite(SPR_SQUARE, GENERAL_SPRITE_COLOUR(livery.colour2), sec_squ.left, y + square_offs); + DrawSprite(SPR_SQUARE, GetColourPalette(livery.colour2), sec_squ.left, y + square_offs); DrawString(sec.left, sec.right, y + text_offs, (is_default_scheme || HasBit(livery.in_use, 1)) ? STR_COLOUR_DARK_BLUE + livery.colour2 : STR_COLOUR_DEFAULT, is_selected ? TC_WHITE : TC_GOLD); } @@ -1000,7 +996,7 @@ public: this->vscroll->SetCapacityFromWidget(this, WID_SCL_MATRIX); } - void OnDropdownSelect(WidgetID widget, int index) override + void OnDropdownSelect(WidgetID widget, int index, int) override { bool local = this->window_number == _local_company; if (!local) return; @@ -1078,15 +1074,15 @@ static constexpr NWidgetPart _nested_select_company_livery_widgets[] = { NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), NWidget(NWID_HORIZONTAL), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_GENERAL), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_COMPANY_GENERAL, STR_LIVERY_GENERAL_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_RAIL), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRAINLIST, STR_LIVERY_TRAIN_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_RAIL), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_TRAIN, STR_LIVERY_TRAIN_GROUP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_ROAD), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_ROADVEH, STR_LIVERY_ROAD_VEHICLE_GROUP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_SHIP), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_SHIP, STR_LIVERY_SHIP_GROUP_TOOLTIP), - NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_AIRCRAFT), SetMinimalSize(22, 22), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_AIRCRAFT, STR_LIVERY_AIRCRAFT_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_GENERAL), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_COMPANY_GENERAL, STR_LIVERY_GENERAL_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_RAIL), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRAINLIST, STR_LIVERY_TRAIN_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_ROAD), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_TRUCKLIST, STR_LIVERY_ROAD_VEHICLE_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_SHIP), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_SHIPLIST, STR_LIVERY_SHIP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_CLASS_AIRCRAFT), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_IMG_AIRPLANESLIST, STR_LIVERY_AIRCRAFT_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_RAIL), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_TRAIN, STR_LIVERY_TRAIN_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_ROAD), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_ROADVEH, STR_LIVERY_ROAD_VEHICLE_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_SHIP), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_SHIP, STR_LIVERY_SHIP_GROUP_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCL_GROUPS_AIRCRAFT), SetToolbarMinimalSize(1), SetFill(0, 1), SetSpriteTip(SPR_GROUP_LIVERY_AIRCRAFT, STR_LIVERY_AIRCRAFT_GROUP_TOOLTIP), NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 0), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL), @@ -1124,45 +1120,46 @@ void ShowCompanyLiveryWindow(CompanyID company, GroupID group) * @param colour the (background) colour of the gradient * @param r position to draw the face */ -void DrawCompanyManagerFace(CompanyManagerFace cmf, Colours colour, const Rect &r) +void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r) { - GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM); - /* Determine offset from centre of drawing rect. */ Dimension d = GetSpriteSize(SPR_GRADIENT); - int x = CenterBounds(r.left, r.right, d.width); - int y = CenterBounds(r.top, r.bottom, d.height); + int x = CentreBounds(r.left, r.right, d.width); + int y = CentreBounds(r.top, r.bottom, d.height); - bool has_moustache = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0; - bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0; - bool has_glasses = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0; - PaletteID pal; + FaceVars vars = GetCompanyManagerFaceVars(cmf.style); - /* Modify eye colour palette only if 2 or more valid values exist */ - if (_cmf_info[CMFV_EYE_COLOUR].valid_values[ge] < 2) { - pal = PAL_NONE; - } else { - switch (GetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR, ge)) { + /* First determine which parts are enabled. */ + uint64_t active_vars = GetActiveFaceVars(cmf, vars); + + std::unordered_map palettes; + + /* Second, get palettes. */ + for (auto var : SetBitIterator(active_vars)) { + if (vars[var].type != FaceVarType::Palette) continue; + + PaletteID pal = PAL_NONE; + switch (vars[var].GetBits(cmf)) { default: NOT_REACHED(); case 0: pal = PALETTE_TO_BROWN; break; case 1: pal = PALETTE_TO_BLUE; break; case 2: pal = PALETTE_TO_GREEN; break; } + for (uint8_t affected_var : SetBitIterator(std::get(vars[var].data))) { + palettes[affected_var] = pal; + } } /* Draw the gradient (background) */ - DrawSprite(SPR_GRADIENT, GENERAL_SPRITE_COLOUR(colour), x, y); + DrawSprite(SPR_GRADIENT, GetColourPalette(colour), x, y); - for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) { - switch (cmfv) { - case CMFV_MOUSTACHE: if (!has_moustache) continue; break; - case CMFV_LIPS: - case CMFV_NOSE: if (has_moustache) continue; break; - case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break; - case CMFV_GLASSES: if (!has_glasses) continue; break; - default: break; - } - DrawSprite(GetCompanyManagerFaceSprite(cmf, cmfv, ge), (cmfv == CMFV_EYEBROWS) ? pal : PAL_NONE, x, y); + /* Thirdly, draw sprites. */ + for (auto var : SetBitIterator(active_vars)) { + if (vars[var].type != FaceVarType::Sprite) continue; + + auto it = palettes.find(var); + PaletteID pal = (it == std::end(palettes)) ? PAL_NONE : it->second; + DrawSprite(vars[var].GetSprite(cmf), pal, x, y); } } @@ -1173,153 +1170,40 @@ static constexpr NWidgetPart _nested_select_company_manager_face_widgets[] = { NWidget(WWT_CAPTION, COLOUR_GREY, WID_SCMF_CAPTION), SetStringTip(STR_FACE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL), SetSpriteTip(SPR_LARGE_SMALL_WINDOW, STR_FACE_ADVANCED_TOOLTIP), SetAspect(WidgetDimensions::ASPECT_TOGGLE_SIZE), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_SCMF_SELECT_FACE), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPadding(2), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_SCMF_SELECT_FACE), /* Left side */ - NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), - NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCMF_FACE), SetMinimalSize(92, 119), SetFill(1, 0), - EndContainer(), + NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPadding(4), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCMF_FACE), SetMinimalSize(92, 119), SetFill(1, 0), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_RANDOM_NEW_FACE), SetFill(1, 0), SetStringTip(STR_FACE_NEW_FACE_BUTTON, STR_FACE_NEW_FACE_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON), SetFill(1, 0), SetStringTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_LOADSAVE), // Load/number/save buttons under the portrait in the advanced view. - NWidget(NWID_VERTICAL), SetPIP(0, 0, 0), SetPIPRatio(1, 0, 1), + NWidget(NWID_VERTICAL), + NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_LOAD), SetFill(1, 0), SetStringTip(STR_FACE_LOAD, STR_FACE_LOAD_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_FACECODE), SetFill(1, 0), SetStringTip(STR_FACE_FACECODE, STR_FACE_FACECODE_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_SAVE), SetFill(1, 0), SetStringTip(STR_FACE_SAVE, STR_FACE_SAVE_TOOLTIP), EndContainer(), EndContainer(), EndContainer(), - /* Right side */ - NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON), SetFill(1, 0), SetStringTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP), - NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_MALEFEMALE), // Simple male/female face setting. - NWidget(NWID_VERTICAL), SetPIPRatio(1, 0, 1), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_MALE), SetFill(1, 0), SetStringTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_FEMALE), SetFill(1, 0), SetStringTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_PARTS), // Advanced face parts setting. - NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), - NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_MALE2), SetFill(1, 0), SetStringTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_FEMALE2), SetFill(1, 0), SetStringTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP), - EndContainer(), - NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_ETHNICITY_EUR), SetFill(1, 0), SetStringTip(STR_FACE_EUROPEAN, STR_FACE_EUROPEAN_TOOLTIP), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_ETHNICITY_AFR), SetFill(1, 0), SetStringTip(STR_FACE_AFRICAN, STR_FACE_AFRICAN_TOOLTIP), - EndContainer(), - NWidget(NWID_VERTICAL), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_EYECOLOUR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAS_MOUSTACHE_EARRING), SetToolTip(STR_FACE_MOUSTACHE_EARRING_TOOLTIP), SetTextStyle(TC_WHITE), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAS_GLASSES_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_GLASSES), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAS_GLASSES), SetToolTip(STR_FACE_GLASSES_TOOLTIP), SetTextStyle(TC_WHITE), - EndContainer(), - EndContainer(), - NWidget(NWID_VERTICAL), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAIR_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_HAIR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_HAIR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_HAIR_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAIR), SetToolTip(STR_FACE_HAIR_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_HAIR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_HAIR_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_EYEBROWS_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_EYEBROWS), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYEBROWS_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_EYEBROWS_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_EYEBROWS), SetToolTip(STR_FACE_EYEBROWS_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYEBROWS_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_EYEBROWS_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_EYECOLOUR_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_EYECOLOUR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_EYECOLOUR_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR), SetToolTip(STR_FACE_EYECOLOUR_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_EYECOLOUR_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_GLASSES_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_GLASSES), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_GLASSES_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_GLASSES_TOOLTIP_2), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_GLASSES), SetToolTip(STR_FACE_GLASSES_TOOLTIP_2), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_GLASSES_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_GLASSES_TOOLTIP_2), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_NOSE_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_NOSE), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_NOSE_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_NOSE_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_NOSE), SetToolTip(STR_FACE_NOSE_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_NOSE_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_NOSE_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_LIPS_MOUSTACHE_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_MOUSTACHE), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE), SetToolTip(STR_FACE_LIPS_MOUSTACHE_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_CHIN_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_CHIN), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_CHIN_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_CHIN_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CHIN), SetToolTip(STR_FACE_CHIN_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_CHIN_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_CHIN_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_JACKET_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_JACKET), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_JACKET_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_JACKET_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_JACKET), SetToolTip(STR_FACE_JACKET_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_JACKET_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_JACKET_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_COLLAR_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_COLLAR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_COLLAR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_COLLAR_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_COLLAR), SetToolTip(STR_FACE_COLLAR_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_COLLAR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_COLLAR_TOOLTIP), - EndContainer(), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), - NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_TIE_EARRING_TEXT), SetFill(1, 0), - SetStringTip(STR_FACE_EARRING), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(NWID_HORIZONTAL), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_TIE_EARRING_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING), SetToolTip(STR_FACE_TIE_EARRING_TOOLTIP), SetTextStyle(TC_WHITE), - NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_TIE_EARRING_TOOLTIP), - EndContainer(), - EndContainer(), - EndContainer(), - EndContainer(), + EndContainer(), + /* Right side */ + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_PARTS), // Advanced face parts setting. + NWidget(NWID_VERTICAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCMF_STYLE), SetResize(1, 0), SetFill(1, 0), SetMatrixDataTip(1, 1), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCMF_PARTS), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0), SetScrollbar(WID_SCMF_PARTS_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCMF_PARTS_SCROLLBAR), EndContainer(), EndContainer(), EndContainer(), EndContainer(), NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CANCEL), SetFill(1, 0), SetStringTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_ACCEPT), SetFill(1, 0), SetStringTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CANCEL), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_ACCEPT), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_RESIZE), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), EndContainer(), }; @@ -1328,43 +1212,34 @@ class SelectCompanyManagerFaceWindow : public Window { CompanyManagerFace face{}; ///< company manager face bits bool advanced = false; ///< advanced company manager face selection window + uint selected_var = UINT_MAX; ///< Currently selected face variable. `UINT_MAX` for none, `UINT_MAX - 1` means style is clicked instead. + uint click_state = 0; ///< Click state on selected face variable. + int line_height = 0; ///< Height of each face variable row. - GenderEthnicity ge{}; ///< Gender and ethnicity. - bool is_female = false; ///< Female face. - bool is_moust_male = false; ///< Male face with a moustache. - - Dimension yesno_dim{}; ///< Dimension of a yes/no button of a part in the advanced face window. - Dimension number_dim{}; ///< Dimension of a number widget of a part in the advanced face window. + std::vector face_vars; ///< Visible face variables. /** - * Get the string for the value of face control buttons. - * - * @param widget_index index of this widget in the window - * @param stringid formatting string for the button. - * @param val the value which will be displayed - * @param is_bool_widget is it a bool button + * Make face bits valid and update visible face variables. */ - std::string GetFaceString(WidgetID widget_index, uint8_t val, bool is_bool_widget) const - { - const NWidgetCore *nwi_widget = this->GetWidget(widget_index); - if (nwi_widget->IsDisabled()) return {}; - - /* If it a bool button write yes or no. */ - if (is_bool_widget) return GetString((val != 0) ? STR_FACE_YES : STR_FACE_NO); - - /* Else write the value + 1. */ - return GetString(STR_JUST_INT, val + 1); - } - void UpdateData() { - this->ge = (GenderEthnicity)GB(this->face, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // get the gender and ethnicity - this->is_female = HasBit(this->ge, GENDER_FEMALE); // get the gender: 0 == male and 1 == female - this->is_moust_male = !is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_MOUSTACHE, this->ge) != 0; // is a male face with moustache + FaceVars vars = GetCompanyManagerFaceVars(this->face.style); + ScaleAllCompanyManagerFaceBits(this->face, vars); - this->GetWidget(WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT)->SetString(this->is_female ? STR_FACE_EARRING : STR_FACE_MOUSTACHE); - this->GetWidget(WID_SCMF_TIE_EARRING_TEXT)->SetString(this->is_female ? STR_FACE_EARRING : STR_FACE_TIE); - this->GetWidget(WID_SCMF_LIPS_MOUSTACHE_TEXT)->SetString(this->is_moust_male ? STR_FACE_MOUSTACHE : STR_FACE_LIPS); + uint64_t active_vars = GetActiveFaceVars(this->face, vars); + /* Exclude active parts which have no string. */ + for (auto var : SetBitIterator(active_vars)) { + if (vars[var].name == STR_NULL) ClrBit(active_vars, var); + } + + /* Rebuild the sorted list of face variable pointers. */ + this->face_vars.clear(); + for (auto var : SetBitIterator(active_vars)) { + this->face_vars.emplace_back(&vars[var]); + } + std::ranges::sort(this->face_vars, std::less{}, &FaceVar::position); + + this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->SetCount(std::size(this->face_vars)); } public: @@ -1380,16 +1255,20 @@ public: this->UpdateData(); } + void OnInit() override + { + this->line_height = std::max(SETTING_BUTTON_HEIGHT, GetCharacterHeight(FS_NORMAL)) + WidgetDimensions::scaled.matrix.Vertical(); + } + /** * Select planes to display to the user with the #NWID_SELECTION widgets #WID_SCMF_SEL_LOADSAVE, #WID_SCMF_SEL_MALEFEMALE, and #WID_SCMF_SEL_PARTS. * @param advanced Display advanced face management window. */ void SelectDisplayPlanes(bool advanced) { - this->GetWidget(WID_SCMF_SEL_LOADSAVE)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE); + this->GetWidget(WID_SCMF_SEL_LOADSAVE)->SetDisplayedPlane(advanced ? 0 : SZSP_HORIZONTAL); this->GetWidget(WID_SCMF_SEL_PARTS)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE); - this->GetWidget(WID_SCMF_SEL_MALEFEMALE)->SetDisplayedPlane(advanced ? SZSP_NONE : 0); - this->GetWidget(WID_SCMF_RANDOM_NEW_FACE)->SetString(advanced ? STR_FACE_RANDOM : STR_FACE_NEW_FACE_BUTTON); + this->GetWidget(WID_SCMF_SEL_RESIZE)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE); NWidgetCore *wi = this->GetWidget(WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON); if (advanced) { @@ -1399,185 +1278,99 @@ public: } } - void OnInit() override + static StringID GetLongestString(StringID a, StringID b) { - /* Size of the boolean yes/no button. */ - Dimension yesno_dim = maxdim(GetStringBoundingBox(STR_FACE_YES), GetStringBoundingBox(STR_FACE_NO)); - yesno_dim.width += WidgetDimensions::scaled.framerect.Horizontal(); - yesno_dim.height += WidgetDimensions::scaled.framerect.Vertical(); - /* Size of the number button + arrows. */ - Dimension number_dim = {0, 0}; - for (int val = 1; val <= 12; val++) { - number_dim = maxdim(number_dim, GetStringBoundingBox(GetString(STR_JUST_INT, val))); - } - uint arrows_width = GetSpriteSize(SPR_ARROW_LEFT).width + GetSpriteSize(SPR_ARROW_RIGHT).width + 2 * (WidgetDimensions::scaled.imgbtn.Horizontal()); - number_dim.width += WidgetDimensions::scaled.framerect.Horizontal() + arrows_width; - number_dim.height += WidgetDimensions::scaled.framerect.Vertical(); - /* Compute width of both buttons. */ - yesno_dim.width = std::max(yesno_dim.width, number_dim.width); - number_dim.width = yesno_dim.width - arrows_width; + return GetStringBoundingBox(a).width > GetStringBoundingBox(b).width ? a : b; + } - this->yesno_dim = yesno_dim; - this->number_dim = number_dim; + static uint GetMaximumFacePartsWidth() + { + StringID yes_no = GetLongestString(STR_FACE_YES, STR_FACE_NO); + + uint width = 0; + for (uint style_index = 0; style_index != GetNumCompanyManagerFaceStyles(); ++style_index) { + FaceVars vars = GetCompanyManagerFaceVars(style_index); + for (const auto &info : vars) { + if (info.name == STR_NULL) continue; + if (info.type == FaceVarType::Toggle) { + width = std::max(width, GetStringBoundingBox(GetString(STR_FACE_SETTING_TOGGLE, info.name, yes_no)).width); + } else { + uint64_t max_digits = GetParamMaxValue(info.valid_values); + width = std::max(width, GetStringBoundingBox(GetString(STR_FACE_SETTING_NUMERIC, info.name, max_digits, max_digits)).width); + } + } + } + + /* Include width of button and spacing. */ + width += SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide + WidgetDimensions::scaled.frametext.Horizontal(); + return width; } void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { switch (widget) { - case WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT: - size = maxdim(size, GetStringBoundingBox(STR_FACE_EARRING)); - size = maxdim(size, GetStringBoundingBox(STR_FACE_MOUSTACHE)); - break; - - case WID_SCMF_TIE_EARRING_TEXT: - size = maxdim(size, GetStringBoundingBox(STR_FACE_EARRING)); - size = maxdim(size, GetStringBoundingBox(STR_FACE_TIE)); - break; - - case WID_SCMF_LIPS_MOUSTACHE_TEXT: - size = maxdim(size, GetStringBoundingBox(STR_FACE_LIPS)); - size = maxdim(size, GetStringBoundingBox(STR_FACE_MOUSTACHE)); - break; - case WID_SCMF_FACE: size = maxdim(size, GetScaledSpriteSize(SPR_GRADIENT)); break; - case WID_SCMF_HAS_MOUSTACHE_EARRING: - case WID_SCMF_HAS_GLASSES: - size = this->yesno_dim; + case WID_SCMF_STYLE: + size.height = this->line_height; break; - case WID_SCMF_EYECOLOUR: - case WID_SCMF_CHIN: - case WID_SCMF_EYEBROWS: - case WID_SCMF_LIPS_MOUSTACHE: - case WID_SCMF_NOSE: - case WID_SCMF_HAIR: - case WID_SCMF_JACKET: - case WID_SCMF_COLLAR: - case WID_SCMF_TIE_EARRING: - case WID_SCMF_GLASSES: - size = this->number_dim; + case WID_SCMF_PARTS: + fill.height = resize.height = this->line_height; + size.width = GetMaximumFacePartsWidth(); + size.height = resize.height * 5; break; } } - void OnPaint() override - { - /* lower the non-selected gender button */ - this->SetWidgetsLoweredState(!this->is_female, WID_SCMF_MALE, WID_SCMF_MALE2); - this->SetWidgetsLoweredState( this->is_female, WID_SCMF_FEMALE, WID_SCMF_FEMALE2); - - /* advanced company manager face selection window */ - - /* lower the non-selected ethnicity button */ - this->SetWidgetLoweredState(WID_SCMF_ETHNICITY_EUR, !HasBit(this->ge, ETHNICITY_BLACK)); - this->SetWidgetLoweredState(WID_SCMF_ETHNICITY_AFR, HasBit(this->ge, ETHNICITY_BLACK)); - - - /* Disable dynamically the widgets which CompanyManagerFaceVariable has less than 2 options - * (or in other words you haven't any choice). - * If the widgets depend on a HAS-variable and this is false the widgets will be disabled, too. */ - - /* Eye colour buttons */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_EYE_COLOUR].valid_values[this->ge] < 2, - WID_SCMF_EYECOLOUR, WID_SCMF_EYECOLOUR_L, WID_SCMF_EYECOLOUR_R); - - /* Chin buttons */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_CHIN].valid_values[this->ge] < 2, - WID_SCMF_CHIN, WID_SCMF_CHIN_L, WID_SCMF_CHIN_R); - - /* Eyebrows buttons */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_EYEBROWS].valid_values[this->ge] < 2, - WID_SCMF_EYEBROWS, WID_SCMF_EYEBROWS_L, WID_SCMF_EYEBROWS_R); - - /* Lips or (if it a male face with a moustache) moustache buttons */ - this->SetWidgetsDisabledState(_cmf_info[this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS].valid_values[this->ge] < 2, - WID_SCMF_LIPS_MOUSTACHE, WID_SCMF_LIPS_MOUSTACHE_L, WID_SCMF_LIPS_MOUSTACHE_R); - - /* Nose buttons | male faces with moustache haven't any nose options */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_NOSE].valid_values[this->ge] < 2 || this->is_moust_male, - WID_SCMF_NOSE, WID_SCMF_NOSE_L, WID_SCMF_NOSE_R); - - /* Hair buttons */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_HAIR].valid_values[this->ge] < 2, - WID_SCMF_HAIR, WID_SCMF_HAIR_L, WID_SCMF_HAIR_R); - - /* Jacket buttons */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_JACKET].valid_values[this->ge] < 2, - WID_SCMF_JACKET, WID_SCMF_JACKET_L, WID_SCMF_JACKET_R); - - /* Collar buttons */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_COLLAR].valid_values[this->ge] < 2, - WID_SCMF_COLLAR, WID_SCMF_COLLAR_L, WID_SCMF_COLLAR_R); - - /* Tie/earring buttons | female faces without earring haven't any earring options */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_TIE_EARRING].valid_values[this->ge] < 2 || - (this->is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_TIE_EARRING, this->ge) == 0), - WID_SCMF_TIE_EARRING, WID_SCMF_TIE_EARRING_L, WID_SCMF_TIE_EARRING_R); - - /* Glasses buttons | faces without glasses haven't any glasses options */ - this->SetWidgetsDisabledState(_cmf_info[CMFV_GLASSES].valid_values[this->ge] < 2 || GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge) == 0, - WID_SCMF_GLASSES, WID_SCMF_GLASSES_L, WID_SCMF_GLASSES_R); - - this->DrawWidgets(); - } - - std::string GetWidgetString(WidgetID widget, StringID stringid) const override - { - switch (widget) { - case WID_SCMF_HAS_MOUSTACHE_EARRING: { - CompanyManagerFaceVariable facepart = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE; - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, facepart, this->ge), true); - } - - case WID_SCMF_TIE_EARRING: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_TIE_EARRING, this->ge), false); - - case WID_SCMF_LIPS_MOUSTACHE: { - CompanyManagerFaceVariable facepart = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS; - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, facepart, this->ge), false); - } - - case WID_SCMF_HAS_GLASSES: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge), true ); - - case WID_SCMF_HAIR: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_HAIR, this->ge), false); - - case WID_SCMF_EYEBROWS: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_EYEBROWS, this->ge), false); - - case WID_SCMF_EYECOLOUR: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_EYE_COLOUR, this->ge), false); - - case WID_SCMF_GLASSES: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_GLASSES, this->ge), false); - - case WID_SCMF_NOSE: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_NOSE, this->ge), false); - - case WID_SCMF_CHIN: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_CHIN, this->ge), false); - - case WID_SCMF_JACKET: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_JACKET, this->ge), false); - - case WID_SCMF_COLLAR: - return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_COLLAR, this->ge), false); - - default: - return this->Window::GetWidgetString(widget, stringid); - } - } - void DrawWidget(const Rect &r, WidgetID widget) const override { switch (widget) { case WID_SCMF_FACE: DrawCompanyManagerFace(this->face, Company::Get(this->window_number)->colour, r); break; + + case WID_SCMF_STYLE: { + Rect ir = r.Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero).WithHeight(this->line_height); + bool rtl = _current_text_dir == TD_RTL; + + Rect br = ir.CentreTo(ir.Width(), SETTING_BUTTON_HEIGHT).WithWidth(SETTING_BUTTON_WIDTH, rtl); + Rect tr = ir.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix).CentreTo(ir.Width(), GetCharacterHeight(FS_NORMAL)).Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl); + + DrawArrowButtons(br.left, br.top, COLOUR_YELLOW, this->selected_var == UINT_MAX - 1 ? this->click_state : 0, true, true); + DrawString(tr, GetString(STR_FACE_SETTING_NUMERIC, STR_FACE_STYLE, this->face.style + 1, GetNumCompanyManagerFaceStyles()), TC_WHITE); + break; + } + + case WID_SCMF_PARTS: { + Rect ir = r.Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero).WithHeight(this->line_height); + bool rtl = _current_text_dir == TD_RTL; + + FaceVars vars = GetCompanyManagerFaceVars(this->face.style); + + auto [first, last] = this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->GetVisibleRangeIterators(this->face_vars); + for (auto it = first; it != last; ++it) { + const uint8_t var = static_cast(*it - vars.data()); + const FaceVar &facevar = **it; + + Rect br = ir.CentreTo(ir.Width(), SETTING_BUTTON_HEIGHT).WithWidth(SETTING_BUTTON_WIDTH, rtl); + Rect tr = ir.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix).CentreTo(ir.Width(), GetCharacterHeight(FS_NORMAL)).Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl); + + uint val = vars[var].GetBits(this->face); + if (facevar.type == FaceVarType::Toggle) { + DrawBoolButton(br.left, br.top, COLOUR_YELLOW, COLOUR_GREY, val == 1, true); + DrawString(tr, GetString(STR_FACE_SETTING_TOGGLE, facevar.name, val == 1 ? STR_FACE_YES : STR_FACE_NO), TC_WHITE); + } else { + DrawArrowButtons(br.left, br.top, COLOUR_YELLOW, this->selected_var == var ? this->click_state : 0, true, true); + DrawString(tr, GetString(STR_FACE_SETTING_NUMERIC, facevar.name, val + 1, facevar.valid_values), TC_WHITE); + } + + ir = ir.Translate(0, this->line_height); + } + break; + } } } @@ -1594,7 +1387,7 @@ public: /* OK button */ case WID_SCMF_ACCEPT: - Command::Post(this->face); + Command::Post(this->face.bits, this->face.style); [[fallthrough]]; /* Cancel button */ @@ -1603,99 +1396,119 @@ public: break; /* Load button */ - case WID_SCMF_LOAD: - this->face = _company_manager_face; - ScaleAllCompanyManagerFaceBits(this->face); + case WID_SCMF_LOAD: { + auto cmf = ParseCompanyManagerFaceCode(_company_manager_face); + if (cmf.has_value()) this->face = *cmf; ShowErrorMessage(GetEncodedString(STR_FACE_LOAD_DONE), {}, WL_INFO); this->UpdateData(); this->SetDirty(); break; + } /* 'Company manager face number' button, view and/or set company manager face number */ case WID_SCMF_FACECODE: - ShowQueryString(GetString(STR_JUST_INT, this->face), STR_FACE_FACECODE_CAPTION, 10 + 1, this, CS_NUMERAL, {}); + ShowQueryString(FormatCompanyManagerFaceCode(this->face), STR_FACE_FACECODE_CAPTION, 128, this, CS_ALPHANUMERAL, {}); break; /* Save button */ case WID_SCMF_SAVE: - _company_manager_face = this->face; + _company_manager_face = FormatCompanyManagerFaceCode(this->face); ShowErrorMessage(GetEncodedString(STR_FACE_SAVE_DONE), {}, WL_INFO); break; - /* Toggle gender (male/female) button */ - case WID_SCMF_MALE: - case WID_SCMF_FEMALE: - case WID_SCMF_MALE2: - case WID_SCMF_FEMALE2: - SetCompanyManagerFaceBits(this->face, CMFV_GENDER, this->ge, (widget == WID_SCMF_FEMALE || widget == WID_SCMF_FEMALE2)); - ScaleAllCompanyManagerFaceBits(this->face); - this->UpdateData(); - this->SetDirty(); - break; - /* Randomize face button */ case WID_SCMF_RANDOM_NEW_FACE: - RandomCompanyManagerFaceBits(this->face, this->ge, this->advanced, _interactive_random); + RandomiseCompanyManagerFace(this->face, _interactive_random); this->UpdateData(); this->SetDirty(); break; - /* Toggle ethnicity (european/african) button */ - case WID_SCMF_ETHNICITY_EUR: - case WID_SCMF_ETHNICITY_AFR: - SetCompanyManagerFaceBits(this->face, CMFV_ETHNICITY, this->ge, widget - WID_SCMF_ETHNICITY_EUR); - ScaleAllCompanyManagerFaceBits(this->face); - this->UpdateData(); - this->SetDirty(); - break; + case WID_SCMF_STYLE: { + bool rtl = _current_text_dir == TD_RTL; + Rect ir = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero); + Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl); - default: - /* Here all buttons from WID_SCMF_HAS_MOUSTACHE_EARRING to WID_SCMF_GLASSES_R are handled. - * First it checks which CompanyManagerFaceVariable is being changed, and then either - * a: invert the value for boolean variables, or - * b: it checks inside of IncreaseCompanyManagerFaceBits() if a left (_L) butten is pressed and then decrease else increase the variable */ - if (widget >= WID_SCMF_HAS_MOUSTACHE_EARRING && widget <= WID_SCMF_GLASSES_R) { - CompanyManagerFaceVariable cmfv; // which CompanyManagerFaceVariable shall be edited - - if (widget < WID_SCMF_EYECOLOUR_L) { // Bool buttons - switch (widget - WID_SCMF_HAS_MOUSTACHE_EARRING) { - default: NOT_REACHED(); - case 0: cmfv = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE; break; // Has earring/moustache button - case 1: cmfv = CMFV_HAS_GLASSES; break; // Has glasses button - } - SetCompanyManagerFaceBits(this->face, cmfv, this->ge, !GetCompanyManagerFaceBits(this->face, cmfv, this->ge)); - ScaleAllCompanyManagerFaceBits(this->face); - } else { // Value buttons - switch ((widget - WID_SCMF_EYECOLOUR_L) / 3) { - default: NOT_REACHED(); - case 0: cmfv = CMFV_EYE_COLOUR; break; // Eye colour buttons - case 1: cmfv = CMFV_CHIN; break; // Chin buttons - case 2: cmfv = CMFV_EYEBROWS; break; // Eyebrows buttons - case 3: cmfv = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS; break; // Moustache or lips buttons - case 4: cmfv = CMFV_NOSE; break; // Nose buttons - case 5: cmfv = CMFV_HAIR; break; // Hair buttons - case 6: cmfv = CMFV_JACKET; break; // Jacket buttons - case 7: cmfv = CMFV_COLLAR; break; // Collar buttons - case 8: cmfv = CMFV_TIE_EARRING; break; // Tie/earring buttons - case 9: cmfv = CMFV_GLASSES; break; // Glasses buttons - } - /* 0 == left (_L), 1 == middle or 2 == right (_R) - button click */ - IncreaseCompanyManagerFaceBits(this->face, cmfv, this->ge, (((widget - WID_SCMF_EYECOLOUR_L) % 3) != 0) ? 1 : -1); - } - this->UpdateData(); - this->SetDirty(); + uint num_styles = GetNumCompanyManagerFaceStyles(); + this->selected_var = UINT_MAX - 1; + if (IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH / 2)) { + SetCompanyManagerFaceStyle(this->face, (this->face.style + num_styles - 1) % num_styles); + this->click_state = 1; + } else if (IsInsideBS(pt.x, br.left + SETTING_BUTTON_WIDTH / 2, SETTING_BUTTON_WIDTH / 2)) { + SetCompanyManagerFaceStyle(this->face, (this->face.style + 1) % num_styles); + this->click_state = 2; } + + this->UpdateData(); + this->SetTimeout(); + this->SetDirty(); break; + } + + case WID_SCMF_PARTS: { + bool rtl = _current_text_dir == TD_RTL; + Rect ir = this->GetWidget(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero); + Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl); + + this->selected_var = UINT_MAX; + + FaceVars vars = GetCompanyManagerFaceVars(this->face.style); + auto it = this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->GetScrolledItemFromWidget(this->face_vars, pt.y, this, widget, 0, this->line_height); + if (it == std::end(this->face_vars)) break; + + this->selected_var = static_cast(*it - vars.data()); + const auto &facevar = **it; + + if (facevar.type == FaceVarType::Toggle) { + if (!IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH)) break; + facevar.ChangeBits(this->face, 1); + this->UpdateData(); + } else { + if (IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH / 2)) { + facevar.ChangeBits(this->face, -1); + this->click_state = 1; + } else if (IsInsideBS(pt.x, br.left + SETTING_BUTTON_WIDTH / 2, SETTING_BUTTON_WIDTH / 2)) { + facevar.ChangeBits(this->face, 1); + this->click_state = 2; + } else { + break; + } + } + + this->SetTimeout(); + this->SetDirty(); + break; + } } } + void OnResize() override + { + if (auto *wid = this->GetWidget(WID_SCMF_PARTS); wid != nullptr) { + /* Workaround for automatic widget sizing ignoring resize steps. Manually ensure parts matrix is a + * multiple of its resize step. This trick only works here as the window itself is not resizable. */ + if (wid->UpdateVerticalSize((wid->current_y + wid->resize_y - 1) / wid->resize_y * wid->resize_y)) { + this->ReInit(); + return; + } + } + + this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->SetCapacityFromWidget(this, WID_SCMF_PARTS); + } + + void OnTimeout() override + { + this->click_state = 0; + this->selected_var = UINT_MAX; + this->SetDirty(); + } + void OnQueryTextFinished(std::optional str) override { if (!str.has_value()) return; - /* Set a new company manager face number */ - if (!str->empty()) { - this->face = std::strtoul(str->c_str(), nullptr, 10); - ScaleAllCompanyManagerFaceBits(this->face); + /* Parse new company manager face number */ + auto cmf = ParseCompanyManagerFaceCode(*str); + if (cmf.has_value()) { + this->face = *cmf; ShowErrorMessage(GetEncodedString(STR_FACE_FACECODE_SET), {}, WL_INFO); this->UpdateData(); this->SetDirty(); @@ -1707,7 +1520,7 @@ public: /** Company manager face selection window description */ static WindowDesc _select_company_manager_face_desc( - WDP_AUTO, nullptr, 0, 0, + WDP_AUTO, {}, 0, 0, WC_COMPANY_MANAGER_FACE, WC_NONE, WindowDefaultFlag::Construction, _nested_select_company_manager_face_widgets @@ -1731,34 +1544,16 @@ static constexpr NWidgetPart _nested_company_infrastructure_widgets[] = { NWidget(WWT_CLOSEBOX, COLOUR_GREY), NWidget(WWT_CAPTION, COLOUR_GREY, WID_CI_CAPTION), NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY), - NWidget(NWID_VERTICAL), SetPadding(WidgetDimensions::unscaled.framerect), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_RAIL_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_RAIL_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_ROAD_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_ROAD_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TRAM_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TRAM_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_WATER_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_WATER_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_STATION_DESC), SetMinimalTextLines(3, 0), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_STATION_COUNT), SetMinimalTextLines(3, 0), SetFill(0, 1), - EndContainer(), - NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TOTAL_DESC), SetFill(1, 0), - NWidget(WWT_EMPTY, INVALID_COLOUR, WID_CI_TOTAL), SetFill(0, 1), - EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_CI_LIST), SetFill(1, 1), SetResize(0, 1), + SetMinimalTextLines(5, WidgetDimensions::unscaled.framerect.Vertical()), SetScrollbar(WID_CI_SCROLLBAR), + EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_CI_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), EndContainer(), EndContainer(), }; @@ -1768,70 +1563,127 @@ static constexpr NWidgetPart _nested_company_infrastructure_widgets[] = { */ struct CompanyInfrastructureWindow : Window { - RailTypes railtypes{}; ///< Valid railtypes. - RoadTypes roadtypes{}; ///< Valid roadtypes. + enum class InfrastructureItemType : uint8_t { + Header, ///< Section header. + Spacer, ///< Spacer + Value, ///< Label with values. + Total, ///< Total cost. + }; - uint total_width = 0; ///< String width of the total cost line. + struct InfrastructureItem { + InfrastructureItemType type; + StringID label; + uint count; + Money cost; + }; + + uint count_width = 0; + uint cost_width = 0; + + mutable std::vector list; CompanyInfrastructureWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc) { - this->UpdateRailRoadTypes(); - this->InitNested(window_number); this->owner = this->window_number; } - void UpdateRailRoadTypes() + void OnInit() override { - this->railtypes = {}; - this->roadtypes = {}; - - /* Find the used railtypes. */ - for (const Engine *e : Engine::IterateType(VEH_TRAIN)) { - if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue; - - this->railtypes.Set(GetRailTypeInfo(e->u.rail.railtype)->introduces_railtypes); - } - - /* Get the date introduced railtypes as well. */ - this->railtypes = AddDateIntroducedRailTypes(this->railtypes, CalendarTime::MAX_DATE); - this->railtypes.Reset(_railtypes_hidden_mask); - - /* Find the used roadtypes. */ - for (const Engine *e : Engine::IterateType(VEH_ROAD)) { - if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue; - - this->roadtypes.Set(GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes); - } - - /* Get the date introduced roadtypes as well. */ - this->roadtypes = AddDateIntroducedRoadTypes(this->roadtypes, CalendarTime::MAX_DATE); - this->roadtypes.Reset(_roadtypes_hidden_mask); + this->UpdateInfrastructureList(); } - /** Get total infrastructure maintenance cost. */ - Money GetTotalMaintenanceCost() const + void UpdateInfrastructureList() { - const Company *c = Company::Get(this->window_number); - Money total; + this->list.clear(); - uint32_t rail_total = c->infrastructure.GetRailTotal(); - for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) { - if (this->railtypes.Test(rt)) total += RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total); - } - total += SignalMaintenanceCost(c->infrastructure.signal); + const Company *c = Company::GetIfValid(this->window_number); + if (c == nullptr) return; - uint32_t road_total = c->infrastructure.GetRoadTotal(); - uint32_t tram_total = c->infrastructure.GetTramTotal(); - for (RoadType rt = ROADTYPE_BEGIN; rt != ROADTYPE_END; rt++) { - if (this->roadtypes.Test(rt)) total += RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total); + Money total_monthly_cost = 0; + + if (uint32_t rail_total = c->infrastructure.GetRailTotal(); rail_total > 0) { + /* Rail types and signals. */ + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT); + + for (const RailType &rt : _sorted_railtypes) { + if (c->infrastructure.rail[rt] == 0) continue; + Money monthly_cost = RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, GetRailTypeInfo(rt)->strings.name, c->infrastructure.rail[rt], monthly_cost); + } + + if (c->infrastructure.signal > 0) { + Money monthly_cost = SignalMaintenanceCost(c->infrastructure.signal); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS, c->infrastructure.signal, monthly_cost); + } } - total += CanalMaintenanceCost(c->infrastructure.water); - total += StationMaintenanceCost(c->infrastructure.station); - total += AirportMaintenanceCost(c->index); + if (uint32_t road_total = c->infrastructure.GetRoadTotal(); road_total > 0) { + /* Road types. */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT); - return total; + for (const RoadType &rt : _sorted_roadtypes) { + if (!RoadTypeIsRoad(rt)) continue; + if (c->infrastructure.road[rt] == 0) continue; + Money monthly_cost = RoadMaintenanceCost(rt, c->infrastructure.road[rt], road_total); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, GetRoadTypeInfo(rt)->strings.name, c->infrastructure.road[rt], monthly_cost); + } + } + + if (uint32_t tram_total = c->infrastructure.GetTramTotal(); tram_total > 0) { + /* Tram types. */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT); + + for (const RoadType &rt : _sorted_roadtypes) { + if (!RoadTypeIsTram(rt)) continue; + if (c->infrastructure.road[rt] == 0) continue; + Money monthly_cost = RoadMaintenanceCost(rt, c->infrastructure.road[rt], tram_total); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, GetRoadTypeInfo(rt)->strings.name, c->infrastructure.road[rt], monthly_cost); + } + } + + if (c->infrastructure.water > 0) { + /* Canals, locks, and ship depots (docks are counted as stations). */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT); + + Money monthly_cost = CanalMaintenanceCost(c->infrastructure.water); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS, c->infrastructure.water, monthly_cost); + } + + if (Money airport_cost = AirportMaintenanceCost(c->index); airport_cost > 0 || c->infrastructure.station > 0) { + /* Stations and airports. */ + if (!this->list.empty()) this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Header, STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT); + + if (c->infrastructure.station > 0) { + Money monthly_cost = StationMaintenanceCost(c->infrastructure.station); + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS, c->infrastructure.station, monthly_cost); + } + + if (airport_cost > 0) { + Money monthly_cost = airport_cost; + total_monthly_cost += monthly_cost; + this->list.emplace_back(InfrastructureItemType::Value, STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS, c->infrastructure.airport, monthly_cost); + } + } + + if (_settings_game.economy.infrastructure_maintenance) { + /* Total monthly maintenance cost. */ + this->list.emplace_back(InfrastructureItemType::Spacer); + this->list.emplace_back(InfrastructureItemType::Total, STR_NULL, 0, total_monthly_cost); + } + + /* Update scrollbar. */ + this->GetScrollbar(WID_CI_SCROLLBAR)->SetCount(std::size(list)); } std::string GetWidgetString(WidgetID widget, StringID stringid) const override @@ -1845,230 +1697,122 @@ struct CompanyInfrastructureWindow : Window } } - void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override + void FindWindowPlacementAndResize(int def_width, int def_height, bool allow_resize) override { - const Company *c = Company::Get(this->window_number); - - switch (widget) { - case WID_CI_RAIL_DESC: { - uint lines = 1; // Starts at 1 because a line is also required for the section title - - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT).width + padding.width); - - for (const auto &rt : _sorted_railtypes) { - if (this->railtypes.Test(rt)) { - lines++; - size.width = std::max(size.width, GetStringBoundingBox(GetRailTypeInfo(rt)->strings.name).width + padding.width + WidgetDimensions::scaled.hsep_indent); - } - } - if (this->railtypes.Any()) { - lines++; - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - } - - size.height = std::max(size.height, lines * GetCharacterHeight(FS_NORMAL)); - break; - } - - case WID_CI_ROAD_DESC: - case WID_CI_TRAM_DESC: { - uint lines = 1; // Starts at 1 because a line is also required for the section title - - size.width = std::max(size.width, GetStringBoundingBox(widget == WID_CI_ROAD_DESC ? STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT : STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT).width + padding.width); - - for (const auto &rt : _sorted_roadtypes) { - if (this->roadtypes.Test(rt) && RoadTypeIsRoad(rt) == (widget == WID_CI_ROAD_DESC)) { - lines++; - size.width = std::max(size.width, GetStringBoundingBox(GetRoadTypeInfo(rt)->strings.name).width + padding.width + WidgetDimensions::scaled.hsep_indent); - } - } - - size.height = std::max(size.height, lines * GetCharacterHeight(FS_NORMAL)); - break; - } - - case WID_CI_WATER_DESC: - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT).width + padding.width); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - break; - - case WID_CI_STATION_DESC: - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT).width + padding.width); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS).width + padding.width + WidgetDimensions::scaled.hsep_indent); - break; - - case WID_CI_RAIL_COUNT: - case WID_CI_ROAD_COUNT: - case WID_CI_TRAM_COUNT: - case WID_CI_WATER_COUNT: - case WID_CI_STATION_COUNT: - case WID_CI_TOTAL: { - /* Find the maximum count that is displayed. */ - uint32_t max_val = 1000; // Some random number to reserve enough space. - Money max_cost = 10000; // Some random number to reserve enough space. - uint32_t rail_total = c->infrastructure.GetRailTotal(); - for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) { - max_val = std::max(max_val, c->infrastructure.rail[rt]); - max_cost = std::max(max_cost, RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total)); - } - max_val = std::max(max_val, c->infrastructure.signal); - max_cost = std::max(max_cost, SignalMaintenanceCost(c->infrastructure.signal)); - uint32_t road_total = c->infrastructure.GetRoadTotal(); - uint32_t tram_total = c->infrastructure.GetTramTotal(); - for (RoadType rt = ROADTYPE_BEGIN; rt < ROADTYPE_END; rt++) { - max_val = std::max(max_val, c->infrastructure.road[rt]); - max_cost = std::max(max_cost, RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total)); - - } - max_val = std::max(max_val, c->infrastructure.water); - max_cost = std::max(max_cost, CanalMaintenanceCost(c->infrastructure.water)); - max_val = std::max(max_val, c->infrastructure.station); - max_cost = std::max(max_cost, StationMaintenanceCost(c->infrastructure.station)); - max_val = std::max(max_val, c->infrastructure.airport); - max_cost = std::max(max_cost, AirportMaintenanceCost(c->index)); - - uint count_width = GetStringBoundingBox(GetString(STR_JUST_COMMA, GetParamMaxValue(max_val))).width + WidgetDimensions::scaled.hsep_indent; // Reserve some wiggle room - - if (_settings_game.economy.infrastructure_maintenance) { - StringID str_total = TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR; - /* Convert to per year */ - this->total_width = GetStringBoundingBox(GetString(str_total, GetParamMaxValue(this->GetTotalMaintenanceCost() * 12))).width + WidgetDimensions::scaled.hsep_indent * 2; - size.width = std::max(size.width, this->total_width); - - /* Convert to per year */ - count_width += std::max(this->total_width, GetStringBoundingBox(GetString(str_total, GetParamMaxValue(max_cost * 12))).width); - } - - size.width = std::max(size.width, count_width); - - /* Set height of the total line. */ - if (widget == WID_CI_TOTAL) { - size.height = _settings_game.economy.infrastructure_maintenance ? std::max(size.height, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FS_NORMAL)) : 0; - } - break; - } + if (def_height == 0) { + /* Try to open the window with the exact required rows, but clamp to a reasonable limit. */ + int rows = (this->GetWidget(WID_CI_LIST)->current_y - WidgetDimensions::scaled.framerect.Vertical()) / GetCharacterHeight(FS_NORMAL); + int delta = std::min(20, static_cast(std::size(this->list))) - rows; + def_height = this->height + delta * GetCharacterHeight(FS_NORMAL); } + + this->Window::FindWindowPlacementAndResize(def_width, def_height, allow_resize); } - /** - * Helper for drawing the counts line. - * @param r The bounds to draw in. - * @param y The y position to draw at. - * @param count The count to show on this line. - * @param monthly_cost The monthly costs. - */ - void DrawCountLine(const Rect &r, int &y, int count, Money monthly_cost) const + void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override { - Rect cr = r.Indent(this->total_width, _current_text_dir == TD_RTL); - DrawString(cr.left, cr.right, y += GetCharacterHeight(FS_NORMAL), GetString(STR_JUST_COMMA, count), TC_WHITE, SA_RIGHT | SA_FORCE); + if (widget != WID_CI_LIST) return; + + uint max_count = 1000; // Some random number to reserve minimum space. + Money max_cost = 1000000; // Some random number to reserve minimum space. + + /* List of headers that might be used. */ + static constexpr StringID header_strings[] = { + STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT, + STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT, + }; + /* List of labels that might be used. */ + static constexpr StringID label_strings[] = { + STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS, + STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS, + STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS, + STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS, + }; + + uint max_header_width = GetStringListWidth(header_strings); + uint max_label_width = GetStringListWidth(label_strings); + + /* Include width of all possible rail and road types. */ + for (const RailType &rt : _sorted_railtypes) max_label_width = std::max(max_label_width, GetStringBoundingBox(GetRailTypeInfo(rt)->strings.name).width); + for (const RoadType &rt : _sorted_roadtypes) max_label_width = std::max(max_label_width, GetStringBoundingBox(GetRoadTypeInfo(rt)->strings.name).width); + + for (const InfrastructureItem &entry : this->list) { + max_count = std::max(max_count, entry.count); + max_cost = std::max(max_cost, entry.cost * 12); + } + + max_label_width += WidgetDimensions::scaled.hsep_indent; + this->count_width = GetStringBoundingBox(GetString(STR_JUST_COMMA, max_count)).width; if (_settings_game.economy.infrastructure_maintenance) { - Rect tr = r.WithWidth(this->total_width, _current_text_dir == TD_RTL); - DrawString(tr.left, tr.right, y, - GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, monthly_cost * 12), - TC_FROMSTRING, SA_RIGHT | SA_FORCE); + this->cost_width = GetStringBoundingBox(GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, max_cost)).width; + } else { + this->cost_width = 0; } + + size.width = max_label_width + WidgetDimensions::scaled.hsep_wide + this->count_width + WidgetDimensions::scaled.hsep_wide + this->cost_width; + size.width = std::max(size.width, max_header_width) + WidgetDimensions::scaled.framerect.Horizontal(); + + fill.height = resize.height = GetCharacterHeight(FS_NORMAL); } void DrawWidget(const Rect &r, WidgetID widget) const override { - const Company *c = Company::Get(this->window_number); + if (widget != WID_CI_LIST) return; - int y = r.top; + bool rtl = _current_text_dir == TD_RTL; // We allocate space from end-to-start so the label fills. + int line_height = GetCharacterHeight(FS_NORMAL); - Rect ir = r.Indent(WidgetDimensions::scaled.hsep_indent, _current_text_dir == TD_RTL); - switch (widget) { - case WID_CI_RAIL_DESC: - DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_RAIL_SECT); + Rect ir = r.Shrink(WidgetDimensions::scaled.framerect); + Rect countr = ir.WithWidth(this->count_width, !rtl); + Rect costr = ir.Indent(this->count_width + WidgetDimensions::scaled.hsep_wide, !rtl).WithWidth(this->cost_width, !rtl); + Rect labelr = ir.Indent(this->count_width + WidgetDimensions::scaled.hsep_wide + this->cost_width + WidgetDimensions::scaled.hsep_wide, !rtl); - if (this->railtypes.Any()) { - /* Draw name of each valid railtype. */ - for (const auto &rt : _sorted_railtypes) { - if (this->railtypes.Test(rt)) { - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), GetRailTypeInfo(rt)->strings.name, TC_WHITE); - } + auto [first, last] = this->GetScrollbar(WID_CI_SCROLLBAR)->GetVisibleRangeIterators(this->list); + for (auto it = first; it != last; ++it) { + switch (it->type) { + case InfrastructureItemType::Header: + /* Header is allowed to fill the window's width. */ + DrawString(ir.left, ir.right, labelr.top, GetString(it->label), TC_ORANGE); + break; + + case InfrastructureItemType::Spacer: + break; + + case InfrastructureItemType::Total: + /* Draw line in the spacer above the total. */ + GfxFillRect(costr.Translate(0, -WidgetDimensions::scaled.vsep_normal).WithHeight(WidgetDimensions::scaled.fullbevel.top), PC_WHITE); + DrawString(costr, GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, it->cost * 12), TC_BLACK, SA_RIGHT | SA_FORCE); + break; + + case InfrastructureItemType::Value: + DrawString(labelr.Indent(WidgetDimensions::scaled.hsep_indent, rtl), GetString(it->label), TC_WHITE); + DrawString(countr, GetString(STR_JUST_COMMA, it->count), TC_WHITE, SA_RIGHT | SA_FORCE); + if (_settings_game.economy.infrastructure_maintenance) { + DrawString(costr, GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, it->cost * 12), TC_BLACK, SA_RIGHT | SA_FORCE); } - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_SIGNALS); - } else { - /* No valid railtype. */ - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_VIEW_INFRASTRUCTURE_NONE); - } - - break; - - case WID_CI_RAIL_COUNT: { - /* Draw infrastructure count for each valid railtype. */ - uint32_t rail_total = c->infrastructure.GetRailTotal(); - for (const auto &rt : _sorted_railtypes) { - if (this->railtypes.Test(rt)) { - this->DrawCountLine(r, y, c->infrastructure.rail[rt], RailMaintenanceCost(rt, c->infrastructure.rail[rt], rail_total)); - } - } - if (this->railtypes.Any()) { - this->DrawCountLine(r, y, c->infrastructure.signal, SignalMaintenanceCost(c->infrastructure.signal)); - } - break; + break; } - case WID_CI_ROAD_DESC: - case WID_CI_TRAM_DESC: { - DrawString(r.left, r.right, y, widget == WID_CI_ROAD_DESC ? STR_COMPANY_INFRASTRUCTURE_VIEW_ROAD_SECT : STR_COMPANY_INFRASTRUCTURE_VIEW_TRAM_SECT); - - /* Draw name of each valid roadtype. */ - for (const auto &rt : _sorted_roadtypes) { - if (this->roadtypes.Test(rt) && RoadTypeIsRoad(rt) == (widget == WID_CI_ROAD_DESC)) { - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), GetRoadTypeInfo(rt)->strings.name, TC_WHITE); - } - } - - break; - } - - case WID_CI_ROAD_COUNT: - case WID_CI_TRAM_COUNT: { - uint32_t road_tram_total = widget == WID_CI_ROAD_COUNT ? c->infrastructure.GetRoadTotal() : c->infrastructure.GetTramTotal(); - for (const auto &rt : _sorted_roadtypes) { - if (this->roadtypes.Test(rt) && RoadTypeIsRoad(rt) == (widget == WID_CI_ROAD_COUNT)) { - this->DrawCountLine(r, y, c->infrastructure.road[rt], RoadMaintenanceCost(rt, c->infrastructure.road[rt], road_tram_total)); - } - } - break; - } - - case WID_CI_WATER_DESC: - DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS); - break; - - case WID_CI_WATER_COUNT: - this->DrawCountLine(r, y, c->infrastructure.water, CanalMaintenanceCost(c->infrastructure.water)); - break; - - case WID_CI_TOTAL: - if (_settings_game.economy.infrastructure_maintenance) { - Rect tr = r.WithWidth(this->total_width, _current_text_dir == TD_RTL); - GfxFillRect(tr.left, y, tr.right, y + WidgetDimensions::scaled.bevel.top - 1, PC_WHITE); - y += WidgetDimensions::scaled.vsep_normal; - DrawString(tr.left, tr.right, y, - GetString(TimerGameEconomy::UsingWallclockUnits() ? STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD : STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR, this->GetTotalMaintenanceCost() * 12), - TC_FROMSTRING, SA_RIGHT | SA_FORCE); - } - break; - - case WID_CI_STATION_DESC: - DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS); - DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS); - break; - - case WID_CI_STATION_COUNT: - this->DrawCountLine(r, y, c->infrastructure.station, StationMaintenanceCost(c->infrastructure.station)); - this->DrawCountLine(r, y, c->infrastructure.airport, AirportMaintenanceCost(c->index)); - break; + labelr.top += line_height; + countr.top += line_height; + costr.top += line_height; } } + const IntervalTimer redraw_interval = {std::chrono::seconds(1), [this](auto) { + this->UpdateInfrastructureList(); + this->SetWidgetDirty(WID_CI_LIST); + }}; + + void OnResize() override + { + this->GetScrollbar(WID_CI_SCROLLBAR)->SetCapacityFromWidget(this, WID_CI_LIST, WidgetDimensions::scaled.framerect.top); + } + /** * Some data on this window has become invalid. * @param data Information about the changed data. @@ -2078,7 +1822,6 @@ struct CompanyInfrastructureWindow : Window { if (!gui_scope) return; - this->UpdateRailRoadTypes(); this->ReInit(); } }; @@ -2408,7 +2151,7 @@ struct CompanyWindow : Window Point offset; Dimension d = GetSpriteSize(SPR_VEH_BUS_SW_VIEW, &offset); d.height -= offset.y; - DrawSprite(SPR_VEH_BUS_SW_VIEW, COMPANY_SPRITE_COLOUR(c->index), r.left - offset.x, CenterBounds(r.top, r.bottom, d.height) - offset.y); + DrawSprite(SPR_VEH_BUS_SW_VIEW, GetCompanyPalette(c->index), r.left - offset.x, CentreBounds(r.top, r.bottom, d.height) - offset.y); break; } @@ -2556,7 +2299,7 @@ struct CompanyWindow : Window } /** Redraw the window on a regular interval. */ - IntervalTimer redraw_interval = {std::chrono::seconds(3), [this](auto) { + const IntervalTimer redraw_interval = {std::chrono::seconds(3), [this](auto) { this->SetDirty(); }}; @@ -2581,7 +2324,9 @@ struct CompanyWindow : Window default: NOT_REACHED(); case WID_C_GIVE_MONEY: { - Money money = std::strtoull(str->c_str(), nullptr, 10) / GetCurrency().rate; + auto value = ParseInteger(*str, 10, true); + if (!value.has_value()) return; + Money money = *value / GetCurrency().rate; Command::Post(STR_ERROR_CAN_T_GIVE_MONEY, money, this->window_number); break; } @@ -2700,7 +2445,7 @@ struct BuyCompanyWindow : Window { /** * Check on a regular interval if the company value has changed. */ - IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { + const IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { /* Value can't change when in bankruptcy. */ if (!this->hostile_takeover) return; @@ -2737,7 +2482,7 @@ static constexpr NWidgetPart _nested_buy_company_widgets[] = { }; static WindowDesc _buy_company_desc( - WDP_AUTO, nullptr, 0, 0, + WDP_AUTO, {}, 0, 0, WC_BUY_COMPANY, WC_NONE, WindowDefaultFlag::Construction, _nested_buy_company_widgets diff --git a/src/company_manager_face.h b/src/company_manager_face.h index 02544cf4a2..663da09a11 100644 --- a/src/company_manager_face.h +++ b/src/company_manager_face.h @@ -12,158 +12,142 @@ #include "core/random_func.hpp" #include "core/bitmath_func.hpp" -#include "table/sprites.h" +#include "strings_type.h" #include "company_type.h" +#include "gfx_type.h" -/** The gender/race combinations that we have faces for */ -enum GenderEthnicity : uint8_t { - GENDER_FEMALE = 0, ///< This bit set means a female, otherwise male - ETHNICITY_BLACK = 1, ///< This bit set means black, otherwise white +#include "table/strings.h" - GE_WM = 0, ///< A male of Caucasian origin (white) - GE_WF = 1 << GENDER_FEMALE, ///< A female of Caucasian origin (white) - GE_BM = 1 << ETHNICITY_BLACK, ///< A male of African origin (black) - GE_BF = 1 << ETHNICITY_BLACK | 1 << GENDER_FEMALE, ///< A female of African origin (black) - GE_END, +enum class FaceVarType : uint8_t { + Sprite, + Palette, + Toggle, }; -DECLARE_ENUM_AS_BIT_SET(GenderEthnicity) ///< See GenderRace as a bitset - -/** Bitgroups of the CompanyManagerFace variable */ -enum CompanyManagerFaceVariable : uint8_t { - CMFV_GENDER, - CMFV_ETHNICITY, - CMFV_GEN_ETHN, - CMFV_HAS_MOUSTACHE, - CMFV_HAS_TIE_EARRING, - CMFV_HAS_GLASSES, - CMFV_EYE_COLOUR, - CMFV_CHEEKS, - CMFV_CHIN, - CMFV_EYEBROWS, - CMFV_MOUSTACHE, - CMFV_LIPS, - CMFV_NOSE, - CMFV_HAIR, - CMFV_COLLAR, - CMFV_JACKET, - CMFV_TIE_EARRING, - CMFV_GLASSES, - CMFV_END, -}; -DECLARE_INCREMENT_DECREMENT_OPERATORS(CompanyManagerFaceVariable) /** Information about the valid values of CompanyManagerFace bitgroups as well as the sprites to draw */ -struct CompanyManagerFaceBitsInfo { - uint8_t offset; ///< Offset in bits into the CompanyManagerFace - uint8_t length; ///< Number of bits used in the CompanyManagerFace - uint8_t valid_values[GE_END]; ///< The number of valid values per gender/ethnicity - SpriteID first_sprite[GE_END]; ///< The first sprite per gender/ethnicity -}; +struct FaceVar { + FaceVarType type; + uint8_t position; ///< Position in UI. + uint8_t offset; ///< Offset in bits into the CompanyManagerFace + uint8_t length; ///< Number of bits used in the CompanyManagerFace + uint8_t valid_values; ///< The number of valid values + std::variant> data; ///< The first sprite + StringID name = STR_NULL; -/** Lookup table for indices into the CompanyManagerFace, valid ranges and sprites */ -static const CompanyManagerFaceBitsInfo _cmf_info[] = { - /* Index off len WM WF BM BF WM WF BM BF - * CMFV_GENDER */ { 0, 1, { 2, 2, 2, 2 }, { 0, 0, 0, 0 } }, ///< 0 = male, 1 = female - /* CMFV_ETHNICITY */ { 1, 2, { 2, 2, 2, 2 }, { 0, 0, 0, 0 } }, ///< 0 = (Western-)Caucasian, 1 = African(-American)/Black - /* CMFV_GEN_ETHN */ { 0, 3, { 4, 4, 4, 4 }, { 0, 0, 0, 0 } }, ///< Shortcut to get/set gender _and_ ethnicity - /* CMFV_HAS_MOUSTACHE */ { 3, 1, { 2, 0, 2, 0 }, { 0, 0, 0, 0 } }, ///< Females do not have a moustache - /* CMFV_HAS_TIE_EARRING */ { 3, 1, { 0, 2, 0, 2 }, { 0, 0, 0, 0 } }, ///< Draw the earring for females or not. For males the tie is always drawn. - /* CMFV_HAS_GLASSES */ { 4, 1, { 2, 2, 2, 2 }, { 0, 0, 0, 0 } }, ///< Whether to draw glasses or not - /* CMFV_EYE_COLOUR */ { 5, 2, { 3, 3, 1, 1 }, { 0, 0, 0, 0 } }, ///< Palette modification - /* CMFV_CHEEKS */ { 0, 0, { 1, 1, 1, 1 }, { 0x325, 0x326, 0x390, 0x3B0 } }, ///< Cheeks are only indexed by their gender/ethnicity - /* CMFV_CHIN */ { 7, 2, { 4, 1, 2, 2 }, { 0x327, 0x327, 0x391, 0x3B1 } }, - /* CMFV_EYEBROWS */ { 9, 4, { 12, 16, 11, 16 }, { 0x32B, 0x337, 0x39A, 0x3B8 } }, - /* CMFV_MOUSTACHE */ { 13, 2, { 3, 0, 3, 0 }, { 0x367, 0, 0x397, 0 } }, ///< Depends on CMFV_HAS_MOUSTACHE - /* CMFV_LIPS */ { 13, 4, { 12, 10, 9, 9 }, { 0x35B, 0x351, 0x3A5, 0x3C8 } }, ///< Depends on !CMFV_HAS_MOUSTACHE - /* CMFV_NOSE */ { 17, 3, { 8, 4, 4, 5 }, { 0x349, 0x34C, 0x393, 0x3B3 } }, ///< Depends on !CMFV_HAS_MOUSTACHE - /* CMFV_HAIR */ { 20, 4, { 9, 5, 5, 5 }, { 0x382, 0x38B, 0x3D4, 0x3D9 } }, - /* CMFV_COLLAR */ { 26, 2, { 4, 4, 4, 4 }, { 0x36E, 0x37B, 0x36E, 0x37B } }, - /* CMFV_JACKET */ { 24, 2, { 3, 3, 3, 3 }, { 0x36B, 0x378, 0x36B, 0x378 } }, - /* CMFV_TIE_EARRING */ { 28, 3, { 6, 3, 6, 3 }, { 0x372, 0x37F, 0x372, 0x3D1 } }, ///< Depends on CMFV_HAS_TIE_EARRING - /* CMFV_GLASSES */ { 31, 1, { 2, 2, 2, 2 }, { 0x347, 0x347, 0x3AE, 0x3AE } } ///< Depends on CMFV_HAS_GLASSES -}; -/** Make sure the table's size is right. */ -static_assert(lengthof(_cmf_info) == CMFV_END); - -/** - * Gets the company manager's face bits for the given company manager's face variable - * @param cmf the face to extract the bits from - * @param cmfv the face variable to get the data of - * @param ge the gender and ethnicity of the face - * @pre _cmf_info[cmfv].valid_values[ge] != 0 - * @return the requested bits - */ -inline uint GetCompanyManagerFaceBits(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, [[maybe_unused]] GenderEthnicity ge) -{ - assert(_cmf_info[cmfv].valid_values[ge] != 0); - - return GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length); -} - -/** - * Sets the company manager's face bits for the given company manager's face variable - * @param cmf the face to write the bits to - * @param cmfv the face variable to write the data of - * @param ge the gender and ethnicity of the face - * @param val the new value - * @pre val < _cmf_info[cmfv].valid_values[ge] - */ -inline void SetCompanyManagerFaceBits(CompanyManagerFace &cmf, CompanyManagerFaceVariable cmfv, [[maybe_unused]] GenderEthnicity ge, uint val) -{ - assert(val < _cmf_info[cmfv].valid_values[ge]); - - SB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length, val); -} - -/** - * Increase/Decrease the company manager's face variable by the given amount. - * The value wraps around to stay in the valid range. - * - * @param cmf the company manager face to write the bits to - * @param cmfv the company manager face variable to write the data of - * @param ge the gender and ethnicity of the company manager's face - * @param amount the amount which change the value - * - * @pre 0 <= val < _cmf_info[cmfv].valid_values[ge] - */ -inline void IncreaseCompanyManagerFaceBits(CompanyManagerFace &cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge, int8_t amount) -{ - int8_t val = GetCompanyManagerFaceBits(cmf, cmfv, ge) + amount; // the new value for the cmfv - - /* scales the new value to the correct scope */ - while (val < 0) { - val += _cmf_info[cmfv].valid_values[ge]; + /** + * Gets the company manager's face bits. + * @param cmf The face to extract the bits from. + * @return the requested bits + */ + inline uint GetBits(const CompanyManagerFace &cmf) const + { + return GB(cmf.bits, this->offset, this->length); } - val %= _cmf_info[cmfv].valid_values[ge]; - SetCompanyManagerFaceBits(cmf, cmfv, ge, val); // save the new value -} + /** + * Sets the company manager's face bits. + * @param cmf The face to write the bits to. + * @param val The new value. + */ + inline void SetBits(CompanyManagerFace &cmf, uint val) const + { + SB(cmf.bits, this->offset, this->length, val); + } + + /** + * Increase/Decrease the company manager's face variable by the given amount. + * The value wraps around to stay in the valid range. + * @param cmf The face to write the bits to. + * @param amount the amount to change the value + */ + inline void ChangeBits(CompanyManagerFace &cmf, int8_t amount) const + { + int8_t val = this->GetBits(cmf) + amount; // the new value for the cmfv + + /* scales the new value to the correct scope */ + while (val < 0) { + val += this->valid_values; + } + val %= this->valid_values; + + this->SetBits(cmf, val); // save the new value + } + + /** + * Checks whether the company manager's face bits have a valid range + * @param cmf The face to check. + * @return true if and only if the bits are valid + */ + inline bool IsValid(const CompanyManagerFace &cmf) const + { + return GB(cmf.bits, this->offset, this->length) < this->valid_values; + } + + /** + * Scales a company manager's face bits variable to the correct scope + * @param vars The face variables of the face style. + * @pre val < (1U << length), i.e. val has a value of 0..2^(bits used for this variable)-1 + * @return the scaled value + */ + inline uint ScaleBits(uint val) const + { + assert(val < (1U << this->length)); + return (val * this->valid_values) >> this->length; + } + + /** + * Gets the sprite to draw. + * @param cmf The face to extract the data from + * @pre vars[var].type == FaceVarType::Sprite. + * @return sprite to draw + */ + inline SpriteID GetSprite(const CompanyManagerFace &cmf) const + { + assert(this->type == FaceVarType::Sprite); + return std::get(this->data) + this->GetBits(cmf); + } +}; + +using FaceVars = std::span; + +struct FaceSpec { + std::string label; + std::variant> face_vars; + + inline FaceVars GetFaceVars() const + { + struct visitor { + FaceVars operator()(FaceVars vars) const { return vars; } + FaceVars operator()(const std::vector &vars) const { return vars; } + }; + return std::visit(visitor{}, this->face_vars); + } +}; + +void ResetFaces(); +uint GetNumCompanyManagerFaceStyles(); +std::optional FindCompanyManagerFaceLabel(std::string_view label); +const FaceSpec *GetCompanyManagerFaceSpec(uint style_index); +FaceVars GetCompanyManagerFaceVars(uint style_index); /** - * Checks whether the company manager's face bits have a valid range - * @param cmf the face to extract the bits from - * @param cmfv the face variable to get the data of - * @param ge the gender and ethnicity of the face - * @return true if and only if the bits are valid + * Get a bitmask of currently active face variables. + * Face variables can be inactive due to toggles in the face variables. + * @param cmf The company manager face. + * @param vars The face variables of the face. + * @return Currently active face variables for the face. */ -inline bool AreCompanyManagerFaceBitsValid(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge) +inline uint64_t GetActiveFaceVars(const CompanyManagerFace &cmf, FaceVars vars) { - return GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length) < _cmf_info[cmfv].valid_values[ge]; -} + uint64_t active_vars = (1ULL << std::size(vars)) - 1ULL; -/** - * Scales a company manager's face bits variable to the correct scope - * @param cmfv the face variable to write the data of - * @param ge the gender and ethnicity of the face - * @param val the to value to scale - * @pre val < (1U << _cmf_info[cmfv].length), i.e. val has a value of 0..2^(bits used for this variable)-1 - * @return the scaled value - */ -inline uint ScaleCompanyManagerFaceValue(CompanyManagerFaceVariable cmfv, GenderEthnicity ge, uint val) -{ - assert(val < (1U << _cmf_info[cmfv].length)); + for (const auto &info : vars) { + if (info.type != FaceVarType::Toggle) continue; + const auto &[off, on] = std::get>(info.data); + active_vars &= ~(HasBit(cmf.bits, info.offset) ? on : off); + } - return (val * _cmf_info[cmfv].valid_values[ge]) >> _cmf_info[cmfv].length; + return active_vars; } /** @@ -171,69 +155,31 @@ inline uint ScaleCompanyManagerFaceValue(CompanyManagerFaceVariable cmfv, Gender * * @param cmf the company manager's face to write the bits to */ -inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf) +inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf, FaceVars vars) { - IncreaseCompanyManagerFaceBits(cmf, CMFV_ETHNICITY, GE_WM, 0); // scales the ethnicity - - GenderEthnicity ge = (GenderEthnicity)GB(cmf, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // gender & ethnicity of the face - - /* Is a male face with moustache. Need to reduce CPU load in the loop. */ - bool is_moust_male = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0; - - for (CompanyManagerFaceVariable cmfv = CMFV_EYE_COLOUR; cmfv < CMFV_END; cmfv++) { // scales all other variables - - /* The moustache variable will be scaled only if it is a male face with has a moustache */ - if (cmfv != CMFV_MOUSTACHE || is_moust_male) { - IncreaseCompanyManagerFaceBits(cmf, cmfv, ge, 0); - } + for (auto var : SetBitIterator(GetActiveFaceVars(cmf, vars))) { + vars[var].ChangeBits(cmf, 0); } } /** - * Make a random new face. - * If it is for the advanced company manager's face window then the new face have the same gender - * and ethnicity as the old one, else the gender is equal and the ethnicity is random. - * - * @param cmf the company manager's face to write the bits to - * @param ge the gender and ethnicity of the old company manager's face - * @param adv if it for the advanced company manager's face window - * @param randomizer the source of random to use for creating the manager face - * - * @pre scale 'ge' to a valid gender/ethnicity combination + * Make a random new face without changing the face style. + * @param cmf The company manager's face to write the bits to + * @param vars The face variables. + * @param randomizer The source of random to use for creating the manager face */ -inline void RandomCompanyManagerFaceBits(CompanyManagerFace &cmf, GenderEthnicity ge, bool adv, Randomizer &randomizer) +inline void RandomiseCompanyManagerFaceBits(CompanyManagerFace &cmf, FaceVars vars, Randomizer &randomizer) { - cmf = randomizer.Next(); // random all company manager's face bits - - /* scale ge: 0 == GE_WM, 1 == GE_WF, 2 == GE_BM, 3 == GE_BF (and maybe in future: ...) */ - ge = (GenderEthnicity)((uint)ge % GE_END); - - /* set the gender (and ethnicity) for the new company manager's face */ - if (adv) { - SetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, ge, ge); - } else { - SetCompanyManagerFaceBits(cmf, CMFV_GENDER, ge, HasBit(ge, GENDER_FEMALE)); - } - - /* scales all company manager's face bits to the correct scope */ - ScaleAllCompanyManagerFaceBits(cmf); + cmf.bits = randomizer.Next(); + ScaleAllCompanyManagerFaceBits(cmf, vars); } -/** - * Gets the sprite to draw for the given company manager's face variable - * @param cmf the face to extract the data from - * @param cmfv the face variable to get the sprite of - * @param ge the gender and ethnicity of the face - * @pre _cmf_info[cmfv].valid_values[ge] != 0 - * @return sprite to draw - */ -inline SpriteID GetCompanyManagerFaceSprite(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge) -{ - assert(_cmf_info[cmfv].valid_values[ge] != 0); +void SetCompanyManagerFaceStyle(CompanyManagerFace &cmf, uint style); +void RandomiseCompanyManagerFace(CompanyManagerFace &cmf, Randomizer &randomizer); +uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars); +std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf); +std::optional ParseCompanyManagerFaceCode(std::string_view str); - return _cmf_info[cmfv].first_sprite[ge] + GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length); -} - -void DrawCompanyManagerFace(CompanyManagerFace face, Colours colour, const Rect &r); +void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r); #endif /* COMPANY_MANAGER_FACE_H */ diff --git a/src/company_type.h b/src/company_type.h index 17ce117399..fe984a245d 100644 --- a/src/company_type.h +++ b/src/company_type.h @@ -49,7 +49,12 @@ public: }; struct Company; -typedef uint32_t CompanyManagerFace; ///< Company manager face bits, info see in company_manager_face.h + +struct CompanyManagerFace { + uint style = 0; ///< Company manager face style. + uint32_t bits = 0; ///< Company manager face bits, meaning is dependent on style. + std::string style_label; ///< Face style label. +}; /** The reason why the company was removed. */ enum CompanyRemoveReason : uint8_t { diff --git a/src/console.cpp b/src/console.cpp index 675b8bbcb5..e94f2acb9d 100644 --- a/src/console.cpp +++ b/src/console.cpp @@ -8,6 +8,8 @@ /** @file console.cpp Handling of the in-game console. */ #include "stdafx.h" +#include "core/string_builder.hpp" +#include "core/string_consumer.hpp" #include "console_internal.h" #include "network/network.h" #include "network/network_func.h" @@ -18,7 +20,6 @@ #include "safeguards.h" -static const uint ICON_TOKEN_COUNT = 20; ///< Maximum number of tokens in one command static const uint ICON_MAX_RECURSE = 10; ///< Maximum number of recursion /* console parser */ @@ -117,30 +118,6 @@ void IConsolePrint(TextColour colour_code, const std::string &string) IConsoleGUIPrint(colour_code, str); } -/** - * Change a string into its number representation. Supports - * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false' - * @param *value the variable a successful conversion will be put in - * @param *arg the string to be converted - * @return Return true on success or false on failure - */ -bool GetArgumentInteger(uint32_t *value, const char *arg) -{ - char *endptr; - - if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) { - *value = 1; - return true; - } - if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) { - *value = 0; - return true; - } - - *value = std::strtoul(arg, &endptr, 0); - return arg != endptr; -} - /** * Creates a copy of a string with underscores removed from it * @param name String to remove the underscores from. @@ -179,7 +156,7 @@ static std::string RemoveUnderscores(std::string name) * @param name name of the alias that will be used * @param cmd name of the command that 'name' will be alias of */ -/* static */ void IConsole::AliasRegister(const std::string &name, const std::string &cmd) +/* static */ void IConsole::AliasRegister(const std::string &name, std::string_view cmd) { auto result = IConsole::Aliases().try_emplace(RemoveUnderscores(name), name, cmd); if (!result.second) IConsolePrint(CC_ERROR, "An alias with the name '{}' already exists.", name); @@ -200,14 +177,12 @@ static std::string RemoveUnderscores(std::string name) /** * An alias is just another name for a command, or for more commands * Execute it as well. - * @param *alias is the alias of the command - * @param tokencount the number of parameters passed - * @param *tokens are the parameters given to the original command (0 is the first param) + * @param alias is the alias of the command + * @param tokens are the parameters given to the original command (0 is the first param) + * @param recurse_count the number of re-entrant calls to this function */ -static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count) +static void IConsoleAliasExec(const IConsoleAlias *alias, std::span tokens, uint recurse_count) { - std::string alias_buffer; - Debug(console, 6, "Requested command is an alias; parsing..."); if (recurse_count > ICON_MAX_RECURSE) { @@ -215,72 +190,75 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, ch return; } - for (const char *cmdptr = alias->cmdline.c_str(); *cmdptr != '\0'; cmdptr++) { - switch (*cmdptr) { + std::string buffer; + StringBuilder builder{buffer}; + + StringConsumer consumer{alias->cmdline}; + while (consumer.AnyBytesLeft()) { + auto c = consumer.TryReadUtf8(); + if (!c.has_value()) { + IConsolePrint(CC_ERROR, "Alias '{}' ('{}') contains malformed characters.", alias->name, alias->cmdline); + return; + } + + switch (*c) { case '\'': // ' will double for "" - alias_buffer += '\"'; + builder.PutChar('\"'); break; case ';': // Cmd separator; execute previous and start new command - IConsoleCmdExec(alias_buffer, recurse_count); + IConsoleCmdExec(builder.GetString(), recurse_count); - alias_buffer.clear(); - - cmdptr++; + buffer.clear(); break; case '%': // Some or all parameters - cmdptr++; - switch (*cmdptr) { + c = consumer.ReadUtf8(); + switch (*c) { case '+': { // All parameters separated: "[param 1]" "[param 2]" - for (uint i = 0; i != tokencount; i++) { - if (i != 0) alias_buffer += ' '; - alias_buffer += '\"'; - alias_buffer += tokens[i]; - alias_buffer += '\"'; + for (size_t i = 0; i < tokens.size(); ++i) { + if (i != 0) builder.PutChar(' '); + builder.PutChar('\"'); + builder += tokens[i]; + builder.PutChar('\"'); } break; } case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]" - alias_buffer += '\"'; - for (uint i = 0; i != tokencount; i++) { - if (i != 0) alias_buffer += " "; - alias_buffer += tokens[i]; + builder.PutChar('\"'); + for (size_t i = 0; i < tokens.size(); ++i) { + if (i != 0) builder.PutChar(' '); + builder += tokens[i]; } - alias_buffer += '\"'; + builder.PutChar('\"'); break; } default: { // One specific parameter: %A = [param 1] %B = [param 2] ... - int param = *cmdptr - 'A'; + size_t param = *c - 'A'; - if (param < 0 || param >= tokencount) { + if (param >= tokens.size()) { IConsolePrint(CC_ERROR, "Too many or wrong amount of parameters passed to alias."); IConsolePrint(CC_HELP, "Usage of alias '{}': '{}'.", alias->name, alias->cmdline); return; } - alias_buffer += '\"'; - alias_buffer += tokens[param]; - alias_buffer += '\"'; + builder.PutChar('\"'); + builder += tokens[param]; + builder.PutChar('\"'); break; } } break; default: - alias_buffer += *cmdptr; + builder.PutUtf8(*c); break; } - - if (alias_buffer.size() >= ICON_MAX_STREAMSIZE - 1) { - IConsolePrint(CC_ERROR, "Requested alias execution would overflow execution buffer."); - return; - } } - IConsoleCmdExec(alias_buffer, recurse_count); + IConsoleCmdExec(builder.GetString(), recurse_count); } /** @@ -288,88 +266,73 @@ static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, ch * individual tokens (separated by spaces), then execute it if possible * @param command_string string to be parsed and executed */ -void IConsoleCmdExec(const std::string &command_string, const uint recurse_count) +void IConsoleCmdExec(std::string_view command_string, const uint recurse_count) { - const char *cmdptr; - char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE]; - uint t_index, tstream_i; - - bool longtoken = false; - bool foundtoken = false; - if (command_string[0] == '#') return; // comments - for (cmdptr = command_string.c_str(); *cmdptr != '\0'; cmdptr++) { - if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) { - IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", command_string); - return; - } - } - Debug(console, 4, "Executing cmdline: '{}'", command_string); - memset(&tokens, 0, sizeof(tokens)); - memset(&tokenstream, 0, sizeof(tokenstream)); + std::string buffer; + StringBuilder builder{buffer}; + StringConsumer consumer{command_string}; + + std::vector tokens; + bool found_token = false; + bool in_quotes = false; /* 1. Split up commandline into tokens, separated by spaces, commands * enclosed in "" are taken as one token. We can only go as far as the amount * of characters in our stream or the max amount of tokens we can handle */ - for (cmdptr = command_string.c_str(), t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) { - if (tstream_i >= lengthof(tokenstream)) { - IConsolePrint(CC_ERROR, "Command line too long."); + while (consumer.AnyBytesLeft()) { + auto c = consumer.TryReadUtf8(); + if (!c.has_value()) { + IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", command_string); return; } - switch (*cmdptr) { - case ' ': // Token separator - if (!foundtoken) break; + switch (*c) { + case ' ': // Token separator + if (!found_token) break; - if (longtoken) { - tokenstream[tstream_i] = *cmdptr; - } else { - tokenstream[tstream_i] = '\0'; - foundtoken = false; - } - - tstream_i++; - break; - case '"': // Tokens enclosed in "" are one token - longtoken = !longtoken; - if (!foundtoken) { - if (t_index >= lengthof(tokens)) { - IConsolePrint(CC_ERROR, "Command line too long."); - return; + if (in_quotes) { + builder.PutUtf8(*c); + break; } - tokens[t_index++] = &tokenstream[tstream_i]; - foundtoken = true; - } - break; - case '\\': // Escape character for "" - if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) { - tokenstream[tstream_i++] = *++cmdptr; + + tokens.emplace_back(std::move(buffer)); + buffer.clear(); + found_token = false; break; - } - [[fallthrough]]; - default: // Normal character - tokenstream[tstream_i++] = *cmdptr; - if (!foundtoken) { - if (t_index >= lengthof(tokens)) { - IConsolePrint(CC_ERROR, "Command line too long."); - return; + case '"': // Tokens enclosed in "" are one token + in_quotes = !in_quotes; + found_token = true; + break; + + case '\\': // Escape character for "" + if (consumer.ReadUtf8If('"')) { + builder.PutUtf8('"'); + break; } - tokens[t_index++] = &tokenstream[tstream_i - 1]; - foundtoken = true; - } - break; + [[fallthrough]]; + + default: // Normal character + builder.PutUtf8(*c); + found_token = true; + break; } } - for (uint i = 0; i < lengthof(tokens) && tokens[i] != nullptr; i++) { + if (found_token) { + tokens.emplace_back(std::move(buffer)); + buffer.clear(); + } + + for (size_t i = 0; i < tokens.size(); i++) { Debug(console, 8, "Token {} is: '{}'", i, tokens[i]); } - if (StrEmpty(tokens[0])) return; // don't execute empty commands + if (tokens.empty() || tokens[0].empty()) return; // don't execute empty commands /* 2. Determine type of command (cmd or alias) and execute * First try commands, then aliases. Execute * the found action taking into account its hooking code @@ -378,21 +341,23 @@ void IConsoleCmdExec(const std::string &command_string, const uint recurse_count if (cmd != nullptr) { ConsoleHookResult chr = (cmd->hook == nullptr ? CHR_ALLOW : cmd->hook(true)); switch (chr) { - case CHR_ALLOW: - if (!cmd->proc(t_index, tokens)) { // index started with 0 - cmd->proc(0, nullptr); // if command failed, give help + case CHR_ALLOW: { + std::vector views; + for (auto &token : tokens) views.emplace_back(token); + if (!cmd->proc(views)) { // index started with 0 + cmd->proc({}); // if command failed, give help } return; + } case CHR_DISALLOW: return; case CHR_HIDE: break; } } - t_index--; IConsoleAlias *alias = IConsole::AliasGet(tokens[0]); if (alias != nullptr) { - IConsoleAliasExec(alias, t_index, &tokens[1], recurse_count + 1); + IConsoleAliasExec(alias, std::span(tokens).subspan(1), recurse_count + 1); return; } diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index b9dd9fe621..3f42c13c7e 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -8,6 +8,7 @@ /** @file console_cmds.cpp Implementation of the console hooks. */ #include "stdafx.h" +#include "core/string_consumer.hpp" #include "console_internal.h" #include "debug.h" #include "engine_func.h" @@ -58,7 +59,7 @@ static uint _script_current_depth; ///< Depth of scripts running (used to abort static std::string _scheduled_monthly_script; ///< Script scheduled to execute by the 'schedule' console command (empty if no script is scheduled). /** Timer that runs every month of game time for the 'schedule' console command. */ -static IntervalTimer _scheduled_monthly_timer = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) { +static const IntervalTimer _scheduled_monthly_timer = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) { if (_scheduled_monthly_script.empty()) { return; } @@ -69,9 +70,23 @@ static IntervalTimer _scheduled_monthly_timer = {{TimerGameCa _scheduled_monthly_script.clear(); IConsolePrint(CC_DEFAULT, "Executing scheduled script file '{}'...", filename); - IConsoleCmdExec(std::string("exec") + " " + filename); + IConsoleCmdExec(fmt::format("exec {}", filename)); }}; +/** + * Parse an integer using #ParseInteger and convert it to the requested type. + * @param arg The string to be converted. + * @tparam T The type to return. + * @return The number in the given type, or std::nullopt when it could not be parsed. + */ +template +static std::optional ParseType(std::string_view arg) +{ + auto i = ParseInteger(arg); + if (i.has_value()) return static_cast(*i); + return std::nullopt; +} + /** File list storage for the console, for caching the last 'ls' command. */ class ConsoleFileList : public FileList { public: @@ -107,11 +122,6 @@ static ConsoleFileList _console_file_list_savegame{FT_SAVEGAME, true}; ///< File static ConsoleFileList _console_file_list_scenario{FT_SCENARIO, false}; ///< File storage cache for scenarios. static ConsoleFileList _console_file_list_heightmap{FT_HEIGHTMAP, false}; ///< File storage cache for heightmaps. -/* console command defines */ -#define DEF_CONSOLE_CMD(function) static bool function([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) -#define DEF_CONSOLE_HOOK(function) static ConsoleHookResult function(bool echo) - - /**************** * command hooks ****************/ @@ -133,7 +143,7 @@ static inline bool NetworkAvailable(bool echo) * Check whether we are a server. * @return Are we a server? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookServerOnly) +static ConsoleHookResult ConHookServerOnly(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -148,7 +158,7 @@ DEF_CONSOLE_HOOK(ConHookServerOnly) * Check whether we are a client in a network game. * @return Are we a client in a network game? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookClientOnly) +static ConsoleHookResult ConHookClientOnly(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -163,7 +173,7 @@ DEF_CONSOLE_HOOK(ConHookClientOnly) * Check whether we are in a multiplayer game. * @return True when we are client or server in a network game. */ -DEF_CONSOLE_HOOK(ConHookNeedNetwork) +static ConsoleHookResult ConHookNeedNetwork(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -178,7 +188,7 @@ DEF_CONSOLE_HOOK(ConHookNeedNetwork) * Check whether we are in a multiplayer game and are playing, i.e. we are not the dedicated server. * @return Are we a client or non-dedicated server in a network game? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookNeedNonDedicatedNetwork) +static ConsoleHookResult ConHookNeedNonDedicatedNetwork(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -193,7 +203,7 @@ DEF_CONSOLE_HOOK(ConHookNeedNonDedicatedNetwork) * Check whether we are in singleplayer mode. * @return True when no network is active. */ -DEF_CONSOLE_HOOK(ConHookNoNetwork) +static ConsoleHookResult ConHookNoNetwork(bool echo) { if (_networking) { if (echo) IConsolePrint(CC_ERROR, "This command is forbidden in multiplayer."); @@ -206,7 +216,7 @@ DEF_CONSOLE_HOOK(ConHookNoNetwork) * Check if are either in singleplayer or a server. * @return True iff we are either in singleplayer or a server. */ -DEF_CONSOLE_HOOK(ConHookServerOrNoNetwork) +static ConsoleHookResult ConHookServerOrNoNetwork(bool echo) { if (_networking && !_network_server) { if (echo) IConsolePrint(CC_ERROR, "This command is only available to a network server."); @@ -215,7 +225,7 @@ DEF_CONSOLE_HOOK(ConHookServerOrNoNetwork) return CHR_ALLOW; } -DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool) +static ConsoleHookResult ConHookNewGRFDeveloperTool(bool echo) { if (_settings_client.gui.newgrf_developer_tools) { if (_game_mode == GM_MENU) { @@ -232,9 +242,9 @@ DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool) * Reset status of all engines. * @return Will always succeed. */ -DEF_CONSOLE_CMD(ConResetEngines) +static bool ConResetEngines(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Reset status data of all engines. This might solve some issues with 'lost' engines. Usage: 'resetengines'."); return true; } @@ -248,9 +258,9 @@ DEF_CONSOLE_CMD(ConResetEngines) * @return Will always return true. * @note Resetting the pool only succeeds when there are no vehicles ingame. */ -DEF_CONSOLE_CMD(ConResetEnginePool) +static bool ConResetEnginePool(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Reset NewGRF allocations of engine slots. This will remove invalid engine definitions, and might make default engines available again."); return true; } @@ -274,18 +284,18 @@ DEF_CONSOLE_CMD(ConResetEnginePool) * param tile number. * @return True when the tile is reset or the help on usage was printed (0 or two parameters). */ -DEF_CONSOLE_CMD(ConResetTile) +static bool ConResetTile(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Reset a tile to bare land. Usage: 'resettile '."); IConsolePrint(CC_HELP, "Tile can be either decimal (34161) or hexadecimal (0x4a5B)."); return true; } - if (argc == 2) { - uint32_t result; - if (GetArgumentInteger(&result, argv[1])) { - DoClearSquare((TileIndex)result); + if (argv.size() == 2) { + auto result = ParseInteger(argv[1], 0); + if (result.has_value() && IsValidTile(*result)) { + DoClearSquare(TileIndex{*result}); return true; } } @@ -299,46 +309,39 @@ DEF_CONSOLE_CMD(ConResetTile) * param level As defined by ZoomLevel and as limited by zoom_min/zoom_max from GUISettings. * @return True when either console help was shown or a proper amount of parameters given. */ -DEF_CONSOLE_CMD(ConZoomToLevel) +static bool ConZoomToLevel(std::span argv) { - switch (argc) { + switch (argv.size()) { case 0: IConsolePrint(CC_HELP, "Set the current zoom level of the main viewport."); IConsolePrint(CC_HELP, "Usage: 'zoomto '."); - if (ZOOM_LVL_MIN < _settings_client.gui.zoom_min) { - IConsolePrint(CC_HELP, "The lowest zoom-in level allowed by current client settings is {}.", std::max(ZOOM_LVL_MIN, _settings_client.gui.zoom_min)); + if (ZoomLevel::Min < _settings_client.gui.zoom_min) { + IConsolePrint(CC_HELP, "The lowest zoom-in level allowed by current client settings is {}.", std::max(ZoomLevel::Min, _settings_client.gui.zoom_min)); } else { - IConsolePrint(CC_HELP, "The lowest supported zoom-in level is {}.", std::max(ZOOM_LVL_MIN, _settings_client.gui.zoom_min)); + IConsolePrint(CC_HELP, "The lowest supported zoom-in level is {}.", std::max(ZoomLevel::Min, _settings_client.gui.zoom_min)); } - if (_settings_client.gui.zoom_max < ZOOM_LVL_MAX) { - IConsolePrint(CC_HELP, "The highest zoom-out level allowed by current client settings is {}.", std::min(_settings_client.gui.zoom_max, ZOOM_LVL_MAX)); + if (_settings_client.gui.zoom_max < ZoomLevel::Max) { + IConsolePrint(CC_HELP, "The highest zoom-out level allowed by current client settings is {}.", std::min(_settings_client.gui.zoom_max, ZoomLevel::Max)); } else { - IConsolePrint(CC_HELP, "The highest supported zoom-out level is {}.", std::min(_settings_client.gui.zoom_max, ZOOM_LVL_MAX)); + IConsolePrint(CC_HELP, "The highest supported zoom-out level is {}.", std::min(_settings_client.gui.zoom_max, ZoomLevel::Max)); } return true; case 2: { - uint32_t level; - if (GetArgumentInteger(&level, argv[1])) { - /* In case ZOOM_LVL_MIN is more than 0, the next if statement needs to be amended. - * A simple check for less than ZOOM_LVL_MIN does not work here because we are - * reading an unsigned integer from the console, so just check for a '-' char. */ - static_assert(ZOOM_LVL_MIN == 0); - if (argv[1][0] == '-') { - IConsolePrint(CC_ERROR, "Zoom-in levels below {} are not supported.", ZOOM_LVL_MIN); - } else if (level < _settings_client.gui.zoom_min) { - IConsolePrint(CC_ERROR, "Current client settings do not allow zooming in below level {}.", _settings_client.gui.zoom_min); - } else if (level > ZOOM_LVL_MAX) { - IConsolePrint(CC_ERROR, "Zoom-in levels above {} are not supported.", ZOOM_LVL_MAX); - } else if (level > _settings_client.gui.zoom_max) { - IConsolePrint(CC_ERROR, "Current client settings do not allow zooming out beyond level {}.", _settings_client.gui.zoom_max); + auto level = ParseInteger>(argv[1]); + if (level.has_value()) { + auto zoom_lvl = static_cast(*level); + if (!IsInsideMM(zoom_lvl, ZoomLevel::Begin, ZoomLevel::End)) { + IConsolePrint(CC_ERROR, "Invalid zoom level. Valid range is {} to {}.", ZoomLevel::Min, ZoomLevel::Max); + } else if (!IsInsideMM(zoom_lvl, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max + 1)) { + IConsolePrint(CC_ERROR, "Current client settings limit zoom levels to range {} to {}.", _settings_client.gui.zoom_min, _settings_client.gui.zoom_max); } else { Window *w = GetMainWindow(); Viewport &vp = *w->viewport; - while (vp.zoom > level) DoZoomInOutWindow(ZOOM_IN, w); - while (vp.zoom < level) DoZoomInOutWindow(ZOOM_OUT, w); + while (vp.zoom > zoom_lvl) DoZoomInOutWindow(ZOOM_IN, w); + while (vp.zoom < zoom_lvl) DoZoomInOutWindow(ZOOM_OUT, w); } return true; } @@ -358,46 +361,47 @@ DEF_CONSOLE_CMD(ConZoomToLevel) * and y coordinates. * @return True when either console help was shown or a proper amount of parameters given. */ -DEF_CONSOLE_CMD(ConScrollToTile) +static bool ConScrollToTile(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Center the screen on a given tile."); IConsolePrint(CC_HELP, "Usage: 'scrollto [instant] ' or 'scrollto [instant] '."); IConsolePrint(CC_HELP, "Numbers can be either decimal (34161) or hexadecimal (0x4a5B)."); IConsolePrint(CC_HELP, "'instant' will immediately move and redraw viewport without smooth scrolling."); return true; } - if (argc < 2) return false; + if (argv.size() < 2) return false; uint32_t arg_index = 1; bool instant = false; - if (strcmp(argv[arg_index], "instant") == 0) { + if (argv[arg_index] == "instant") { ++arg_index; instant = true; } - switch (argc - arg_index) { + switch (argv.size() - arg_index) { case 1: { - uint32_t result; - if (GetArgumentInteger(&result, argv[arg_index])) { - if (result >= Map::Size()) { + auto result = ParseInteger(argv[arg_index], 0); + if (result.has_value()) { + if (*result >= Map::Size()) { IConsolePrint(CC_ERROR, "Tile does not exist."); return true; } - ScrollMainWindowToTile((TileIndex)result, instant); + ScrollMainWindowToTile(TileIndex{*result}, instant); return true; } break; } case 2: { - uint32_t x, y; - if (GetArgumentInteger(&x, argv[arg_index]) && GetArgumentInteger(&y, argv[arg_index + 1])) { - if (x >= Map::SizeX() || y >= Map::SizeY()) { + auto x = ParseInteger(argv[arg_index], 0); + auto y = ParseInteger(argv[arg_index + 1], 0); + if (x.has_value() && y.has_value()) { + if (*x >= Map::SizeX() || *y >= Map::SizeY()) { IConsolePrint(CC_ERROR, "Tile does not exist."); return true; } - ScrollMainWindowToTile(TileXY(x, y), instant); + ScrollMainWindowToTile(TileXY(*x, *y), instant); return true; } break; @@ -412,16 +416,15 @@ DEF_CONSOLE_CMD(ConScrollToTile) * param filename the filename to save the map to. * @return True when help was displayed or the file attempted to be saved. */ -DEF_CONSOLE_CMD(ConSave) +static bool ConSave(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Save the current game. Usage: 'save '."); return true; } - if (argc == 2) { - std::string filename = argv[1]; - filename += ".sav"; + if (argv.size() == 2) { + std::string filename = fmt::format("{}.sav", argv[1]); IConsolePrint(CC_DEFAULT, "Saving map..."); if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, SAVE_DIR) != SL_OK) { @@ -439,9 +442,9 @@ DEF_CONSOLE_CMD(ConSave) * Explicitly save the configuration. * @return True. */ -DEF_CONSOLE_CMD(ConSaveConfig) +static bool ConSaveConfig(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Saves the configuration for new games to the configuration file, typically 'openttd.cfg'."); IConsolePrint(CC_HELP, "It does not save the configuration of the current game to the configuration file."); return true; @@ -452,20 +455,20 @@ DEF_CONSOLE_CMD(ConSaveConfig) return true; } -DEF_CONSOLE_CMD(ConLoad) +static bool ConLoad(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Load a game by name or index. Usage: 'load '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_savegame.ValidateFileList(); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_SAVEGAME) { + if (item->type.abstract == FT_SAVEGAME) { _switch_mode = SM_LOAD_GAME; _file_to_saveload.Set(*item); } else { @@ -478,20 +481,20 @@ DEF_CONSOLE_CMD(ConLoad) return true; } -DEF_CONSOLE_CMD(ConLoadScenario) +static bool ConLoadScenario(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Load a scenario by name or index. Usage: 'load_scenario '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_scenario.ValidateFileList(); const FiosItem *item = _console_file_list_scenario.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_SCENARIO) { + if (item->type.abstract == FT_SCENARIO) { _switch_mode = SM_LOAD_GAME; _file_to_saveload.Set(*item); } else { @@ -504,20 +507,20 @@ DEF_CONSOLE_CMD(ConLoadScenario) return true; } -DEF_CONSOLE_CMD(ConLoadHeightmap) +static bool ConLoadHeightmap(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Load a heightmap by name or index. Usage: 'load_heightmap '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_heightmap.ValidateFileList(); const FiosItem *item = _console_file_list_heightmap.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_HEIGHTMAP) { + if (item->type.abstract == FT_HEIGHTMAP) { _switch_mode = SM_START_HEIGHTMAP; _file_to_saveload.Set(*item); } else { @@ -530,21 +533,25 @@ DEF_CONSOLE_CMD(ConLoadHeightmap) return true; } -DEF_CONSOLE_CMD(ConRemove) +static bool ConRemove(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Remove a savegame by name or index. Usage: 'rm '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_savegame.ValidateFileList(); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - if (!FioRemove(item->name)) { - IConsolePrint(CC_ERROR, "Failed to delete '{}'.", item->name); + if (item->type.abstract == FT_SAVEGAME) { + if (!FioRemove(item->name)) { + IConsolePrint(CC_ERROR, "Failed to delete '{}'.", item->name); + } + } else { + IConsolePrint(CC_ERROR, "'{}' is not a savegame.", file); } } else { IConsolePrint(CC_ERROR, "'{}' could not be found.", file); @@ -556,69 +563,71 @@ DEF_CONSOLE_CMD(ConRemove) /* List all the files in the current dir via console */ -DEF_CONSOLE_CMD(ConListFiles) +static bool ConListFiles(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List all loadable savegames and directories in the current dir via console. Usage: 'ls | dir'."); return true; } _console_file_list_savegame.ValidateFileList(true); for (uint i = 0; i < _console_file_list_savegame.size(); i++) { - IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_savegame[i].title); + IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_savegame[i].title.GetDecodedString()); } return true; } /* List all the scenarios */ -DEF_CONSOLE_CMD(ConListScenarios) +static bool ConListScenarios(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List all loadable scenarios. Usage: 'list_scenarios'."); return true; } _console_file_list_scenario.ValidateFileList(true); for (uint i = 0; i < _console_file_list_scenario.size(); i++) { - IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_scenario[i].title); + IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_scenario[i].title.GetDecodedString()); } return true; } /* List all the heightmaps */ -DEF_CONSOLE_CMD(ConListHeightmaps) +static bool ConListHeightmaps(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List all loadable heightmaps. Usage: 'list_heightmaps'."); return true; } _console_file_list_heightmap.ValidateFileList(true); for (uint i = 0; i < _console_file_list_heightmap.size(); i++) { - IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_heightmap[i].title); + IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_heightmap[i].title.GetDecodedString()); } return true; } /* Change the dir via console */ -DEF_CONSOLE_CMD(ConChangeDirectory) +static bool ConChangeDirectory(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Change the dir via console. Usage: 'cd '."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - const char *file = argv[1]; + std::string_view file = argv[1]; _console_file_list_savegame.ValidateFileList(true); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - switch (item->type) { - case FIOS_TYPE_DIR: case FIOS_TYPE_DRIVE: case FIOS_TYPE_PARENT: + switch (item->type.detailed) { + case DFT_FIOS_DIR: + case DFT_FIOS_DRIVE: + case DFT_FIOS_PARENT: FiosBrowseTo(item); break; default: IConsolePrint(CC_ERROR, "{}: Not a directory.", file); @@ -631,9 +640,9 @@ DEF_CONSOLE_CMD(ConChangeDirectory) return true; } -DEF_CONSOLE_CMD(ConPrintWorkingDirectory) +static bool ConPrintWorkingDirectory(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Print out the current working directory. Usage: 'pwd'."); return true; } @@ -646,9 +655,9 @@ DEF_CONSOLE_CMD(ConPrintWorkingDirectory) return true; } -DEF_CONSOLE_CMD(ConClearBuffer) +static bool ConClearBuffer(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Clear the console buffer. Usage: 'clear'."); return true; } @@ -663,38 +672,42 @@ DEF_CONSOLE_CMD(ConClearBuffer) * Network Core Console Commands **********************************/ -static bool ConKickOrBan(const char *argv, bool ban, const std::string &reason) +static bool ConKickOrBan(std::string_view arg, bool ban, std::string_view reason) { uint n; - if (strchr(argv, '.') == nullptr && strchr(argv, ':') == nullptr) { // banning with ID - ClientID client_id = (ClientID)atoi(argv); + if (arg.find_first_of(".:") == std::string::npos) { // banning with ID + auto client_id = ParseType(arg); + if (!client_id.has_value()) { + IConsolePrint(CC_ERROR, "The given client-id is not a valid number."); + return true; + } /* Don't kill the server, or the client doing the rcon. The latter can't be kicked because * kicking frees closes and subsequently free the connection related instances, which we * would be reading from and writing to after returning. So we would read or write data * from freed memory up till the segfault triggers. */ - if (client_id == CLIENT_ID_SERVER || client_id == _redirect_console_to_client) { + if (*client_id == CLIENT_ID_SERVER || *client_id == _redirect_console_to_client) { IConsolePrint(CC_ERROR, "You can not {} yourself!", ban ? "ban" : "kick"); return true; } - NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id); + NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(*client_id); if (ci == nullptr) { - IConsolePrint(CC_ERROR, "Invalid client ID."); + IConsolePrint(CC_ERROR, "Invalid client-id."); return true; } if (!ban) { /* Kick only this client, not all clients with that IP */ - NetworkServerKickClient(client_id, reason); + NetworkServerKickClient(*client_id, reason); return true; } /* When banning, kick+ban all clients with that IP */ - n = NetworkServerKickOrBanIP(client_id, ban, reason); + n = NetworkServerKickOrBanIP(*client_id, ban, reason); } else { - n = NetworkServerKickOrBanIP(argv, ban, reason); + n = NetworkServerKickOrBanIP(arg, ban, reason); } if (n == 0) { @@ -706,21 +719,21 @@ static bool ConKickOrBan(const char *argv, bool ban, const std::string &reason) return true; } -DEF_CONSOLE_CMD(ConKick) +static bool ConKick(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Kick a client from a network game. Usage: 'kick []'."); IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'."); return true; } - if (argc != 2 && argc != 3) return false; + if (argv.size() != 2 && argv.size() != 3) return false; /* No reason supplied for kicking */ - if (argc == 2) return ConKickOrBan(argv[1], false, {}); + if (argv.size() == 2) return ConKickOrBan(argv[1], false, {}); /* Reason for kicking supplied */ - size_t kick_message_length = strlen(argv[2]); + size_t kick_message_length = argv[2].size(); if (kick_message_length >= 255) { IConsolePrint(CC_ERROR, "Maximum kick message length is 254 characters. You entered {} characters.", kick_message_length); return false; @@ -729,22 +742,22 @@ DEF_CONSOLE_CMD(ConKick) } } -DEF_CONSOLE_CMD(ConBan) +static bool ConBan(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Ban a client from a network game. Usage: 'ban []'."); IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'."); IConsolePrint(CC_HELP, "If the client is no longer online, you can still ban their IP."); return true; } - if (argc != 2 && argc != 3) return false; + if (argv.size() != 2 && argv.size() != 3) return false; /* No reason supplied for kicking */ - if (argc == 2) return ConKickOrBan(argv[1], true, {}); + if (argv.size() == 2) return ConKickOrBan(argv[1], true, {}); /* Reason for kicking supplied */ - size_t kick_message_length = strlen(argv[2]); + size_t kick_message_length = argv[2].size(); if (kick_message_length >= 255) { IConsolePrint(CC_ERROR, "Maximum kick message length is 254 characters. You entered {} characters.", kick_message_length); return false; @@ -753,15 +766,15 @@ DEF_CONSOLE_CMD(ConBan) } } -DEF_CONSOLE_CMD(ConUnBan) +static bool ConUnBan(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Unban a client from a network game. Usage: 'unban '."); IConsolePrint(CC_HELP, "For a list of banned IP's, see the command 'banlist'."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; /* Try by IP. */ uint index; @@ -771,7 +784,7 @@ DEF_CONSOLE_CMD(ConUnBan) /* Try by index. */ if (index >= _network_ban_list.size()) { - index = atoi(argv[1]) - 1U; // let it wrap + index = ParseInteger(argv[1]).value_or(0) - 1U; // let it wrap } if (index < _network_ban_list.size()) { @@ -785,9 +798,9 @@ DEF_CONSOLE_CMD(ConUnBan) return true; } -DEF_CONSOLE_CMD(ConBanList) +static bool ConBanList(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List the IP's of banned clients: Usage 'banlist'."); return true; } @@ -803,9 +816,9 @@ DEF_CONSOLE_CMD(ConBanList) return true; } -DEF_CONSOLE_CMD(ConPauseGame) +static bool ConPauseGame(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Pause a network game. Usage: 'pause'."); return true; } @@ -825,9 +838,9 @@ DEF_CONSOLE_CMD(ConPauseGame) return true; } -DEF_CONSOLE_CMD(ConUnpauseGame) +static bool ConUnpauseGame(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Unpause a network game. Usage: 'unpause'."); return true; } @@ -851,16 +864,16 @@ DEF_CONSOLE_CMD(ConUnpauseGame) return true; } -DEF_CONSOLE_CMD(ConRcon) +static bool ConRcon(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Remote control the server from another client. Usage: 'rcon '."); IConsolePrint(CC_HELP, "Remember to enclose the command in quotes, otherwise only the first parameter is sent."); IConsolePrint(CC_HELP, "When your client's public key is in the 'authorized keys' for 'rcon', the password is not checked and may be '*'."); return true; } - if (argc < 3) return false; + if (argv.size() < 3) return false; if (_network_server) { IConsoleCmdExec(argv[2]); @@ -870,9 +883,9 @@ DEF_CONSOLE_CMD(ConRcon) return true; } -DEF_CONSOLE_CMD(ConStatus) +static bool ConStatus(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List the status of all clients connected to the server. Usage 'status'."); return true; } @@ -881,9 +894,9 @@ DEF_CONSOLE_CMD(ConStatus) return true; } -DEF_CONSOLE_CMD(ConServerInfo) +static bool ConServerInfo(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "List current and maximum client/company limits. Usage 'server_info'."); IConsolePrint(CC_HELP, "You can change these values by modifying settings 'network.max_clients' and 'network.max_companies'."); return true; @@ -897,49 +910,63 @@ DEF_CONSOLE_CMD(ConServerInfo) return true; } -DEF_CONSOLE_CMD(ConClientNickChange) +static bool ConClientNickChange(std::span argv) { - if (argc != 3) { + if (argv.size() != 3) { IConsolePrint(CC_HELP, "Change the nickname of a connected client. Usage: 'client_name '."); IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'."); return true; } - ClientID client_id = (ClientID)atoi(argv[1]); + auto client_id = ParseType(argv[1]); + if (!client_id.has_value()) { + IConsolePrint(CC_ERROR, "The given client-id is not a valid number."); + return true; + } - if (client_id == CLIENT_ID_SERVER) { + if (*client_id == CLIENT_ID_SERVER) { IConsolePrint(CC_ERROR, "Please use the command 'name' to change your own name!"); return true; } - if (NetworkClientInfo::GetByClientID(client_id) == nullptr) { - IConsolePrint(CC_ERROR, "Invalid client ID."); + if (NetworkClientInfo::GetByClientID(*client_id) == nullptr) { + IConsolePrint(CC_ERROR, "Invalid client-id."); return true; } - std::string client_name(argv[2]); - StrTrimInPlace(client_name); + std::string client_name{StrTrimView(argv[2], StringConsumer::WHITESPACE_NO_NEWLINE)}; if (!NetworkIsValidClientName(client_name)) { IConsolePrint(CC_ERROR, "Cannot give a client an empty name."); return true; } - if (!NetworkServerChangeClientName(client_id, client_name)) { + if (!NetworkServerChangeClientName(*client_id, client_name)) { IConsolePrint(CC_ERROR, "Cannot give a client a duplicate name."); } return true; } -DEF_CONSOLE_CMD(ConJoinCompany) +static std::optional ParseCompanyID(std::string_view arg) { - if (argc < 2) { + auto company_id = ParseType(arg); + if (company_id.has_value() && *company_id <= MAX_COMPANIES) return static_cast(*company_id - 1); + return company_id; +} + +static bool ConJoinCompany(std::span argv) +{ + if (argv.size() < 2) { IConsolePrint(CC_HELP, "Request joining another company. Usage: 'join '."); IConsolePrint(CC_HELP, "For valid company-id see company list, use 255 for spectator."); return true; } - CompanyID company_id = (CompanyID)(atoi(argv[1]) <= MAX_COMPANIES ? atoi(argv[1]) - 1 : atoi(argv[1])); + auto company_id = ParseCompanyID(argv[1]); + if (!company_id.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); + return true; + } const NetworkClientInfo *info = NetworkClientInfo::GetByClientID(_network_own_client_id); if (info == nullptr) { @@ -948,46 +975,56 @@ DEF_CONSOLE_CMD(ConJoinCompany) } /* Check we have a valid company id! */ - if (!Company::IsValidID(company_id) && company_id != COMPANY_SPECTATOR) { + if (!Company::IsValidID(*company_id) && *company_id != COMPANY_SPECTATOR) { IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES); return true; } - if (info->client_playas == company_id) { + if (info->client_playas == *company_id) { IConsolePrint(CC_ERROR, "You are already there!"); return true; } - if (company_id != COMPANY_SPECTATOR && !Company::IsHumanID(company_id)) { + if (*company_id != COMPANY_SPECTATOR && !Company::IsHumanID(*company_id)) { IConsolePrint(CC_ERROR, "Cannot join AI company."); return true; } - if (!info->CanJoinCompany(company_id)) { + if (!info->CanJoinCompany(*company_id)) { IConsolePrint(CC_ERROR, "You are not allowed to join this company."); return true; } /* non-dedicated server may just do the move! */ if (_network_server) { - NetworkServerDoMove(CLIENT_ID_SERVER, company_id); + NetworkServerDoMove(CLIENT_ID_SERVER, *company_id); } else { - NetworkClientRequestMove(company_id); + NetworkClientRequestMove(*company_id); } return true; } -DEF_CONSOLE_CMD(ConMoveClient) +static bool ConMoveClient(std::span argv) { - if (argc < 3) { + if (argv.size() < 3) { IConsolePrint(CC_HELP, "Move a client to another company. Usage: 'move '."); IConsolePrint(CC_HELP, "For valid client-id see 'clients', for valid company-id see 'companies', use 255 for moving to spectators."); return true; } - const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID((ClientID)atoi(argv[1])); - CompanyID company_id = (CompanyID)(atoi(argv[2]) <= MAX_COMPANIES ? atoi(argv[2]) - 1 : atoi(argv[2])); + auto client_id = ParseType(argv[1]); + if (!client_id.has_value()) { + IConsolePrint(CC_ERROR, "The given client-id is not a valid number."); + return true; + } + const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(*client_id); + + auto company_id = ParseCompanyID(argv[2]); + if (!company_id.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); + return true; + } /* check the client exists */ if (ci == nullptr) { @@ -995,12 +1032,12 @@ DEF_CONSOLE_CMD(ConMoveClient) return true; } - if (!Company::IsValidID(company_id) && company_id != COMPANY_SPECTATOR) { + if (!Company::IsValidID(*company_id) && *company_id != COMPANY_SPECTATOR) { IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES); return true; } - if (company_id != COMPANY_SPECTATOR && !Company::IsHumanID(company_id)) { + if (*company_id != COMPANY_SPECTATOR && !Company::IsHumanID(*company_id)) { IConsolePrint(CC_ERROR, "You cannot move clients to AI companies."); return true; } @@ -1010,61 +1047,65 @@ DEF_CONSOLE_CMD(ConMoveClient) return true; } - if (ci->client_playas == company_id) { + if (ci->client_playas == *company_id) { IConsolePrint(CC_ERROR, "You cannot move someone to where they already are!"); return true; } /* we are the server, so force the update */ - NetworkServerDoMove(ci->client_id, company_id); + NetworkServerDoMove(ci->client_id, *company_id); return true; } -DEF_CONSOLE_CMD(ConResetCompany) +static bool ConResetCompany(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Remove an idle company from the game. Usage: 'reset_company '."); IConsolePrint(CC_HELP, "For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc."); return true; } - if (argc != 2) return false; + if (argv.size() != 2) return false; - CompanyID index = (CompanyID)(atoi(argv[1]) - 1); - - /* Check valid range */ - if (!Company::IsValidID(index)) { - IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES); + auto index = ParseCompanyID(argv[1]); + if (!index.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); return true; } - if (!Company::IsHumanID(index)) { + /* Check valid range */ + if (!Company::IsValidID(*index)) { + IConsolePrint(CC_ERROR, "Company does not exist. company-id must be between 1 and {}.", MAX_COMPANIES); + return true; + } + + if (!Company::IsHumanID(*index)) { IConsolePrint(CC_ERROR, "Company is owned by an AI."); return true; } - if (NetworkCompanyHasClients(index)) { + if (NetworkCompanyHasClients(*index)) { IConsolePrint(CC_ERROR, "Cannot remove company: a client is connected to that company."); return false; } const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(CLIENT_ID_SERVER); assert(ci != nullptr); - if (ci->client_playas == index) { + if (ci->client_playas == *index) { IConsolePrint(CC_ERROR, "Cannot remove company: the server is connected to that company."); return true; } /* It is safe to remove this company */ - Command::Post(CCA_DELETE, index, CRR_MANUAL, INVALID_CLIENT_ID); + Command::Post(CCA_DELETE, *index, CRR_MANUAL, INVALID_CLIENT_ID); IConsolePrint(CC_DEFAULT, "Company deleted."); return true; } -DEF_CONSOLE_CMD(ConNetworkClients) +static bool ConNetworkClients(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Get a list of connected clients including their ID, name, company-id, and IP. Usage: 'clients'."); return true; } @@ -1074,24 +1115,24 @@ DEF_CONSOLE_CMD(ConNetworkClients) return true; } -DEF_CONSOLE_CMD(ConNetworkReconnect) +static bool ConNetworkReconnect(std::span argv) { - if (argc == 0) { - IConsolePrint(CC_HELP, "Reconnect to server to which you were connected last time. Usage: 'reconnect []'."); - IConsolePrint(CC_HELP, "Company 255 is spectator (default, if not specified), 0 means creating new company."); + if (argv.empty()) { + IConsolePrint(CC_HELP, "Reconnect to server to which you were connected last time. Usage: 'reconnect []'."); + IConsolePrint(CC_HELP, "Company 255 is spectator (default, if not specified), 254 means creating new company."); IConsolePrint(CC_HELP, "All others are a certain company with Company 1 being #1."); return true; } - CompanyID playas = (argc >= 2) ? (CompanyID)atoi(argv[1]) : COMPANY_SPECTATOR; - switch (playas.base()) { - case 0: playas = COMPANY_NEW_COMPANY; break; - case COMPANY_SPECTATOR.base(): /* nothing to do */ break; - default: - /* From a user pov 0 is a new company, internally it's different and all - * companies are offset by one to ease up on users (eg companies 1-8 not 0-7) */ - if (playas < CompanyID::Begin() + 1 || playas > MAX_COMPANIES + 1) return false; - break; + CompanyID playas = COMPANY_SPECTATOR; + if (argv.size() >= 2) { + auto company_id = ParseCompanyID(argv[1]); + if (!company_id.has_value()) { + IConsolePrint(CC_ERROR, "The given company-id is not a valid number."); + return true; + } + if (*company_id >= MAX_COMPANIES && *company_id != COMPANY_NEW_COMPANY && *company_id != COMPANY_SPECTATOR) return false; + playas = *company_id; } if (_settings_client.network.last_joined.empty()) { @@ -1105,16 +1146,16 @@ DEF_CONSOLE_CMD(ConNetworkReconnect) return NetworkClientConnectGame(_settings_client.network.last_joined, playas); } -DEF_CONSOLE_CMD(ConNetworkConnect) +static bool ConNetworkConnect(std::span argv) { - if (argc == 0) { + if (argv.empty()) { IConsolePrint(CC_HELP, "Connect to a remote OTTD server and join the game. Usage: 'connect '."); IConsolePrint(CC_HELP, "IP can contain port and company: 'IP[:Port][#Company]', eg: 'server.ottd.org:443#2'."); IConsolePrint(CC_HELP, "Company #255 is spectator all others are a certain company with Company 1 being #1."); return true; } - if (argc < 2) return false; + if (argv.size() < 2) return false; return NetworkClientConnectGame(argv[1], COMPANY_NEW_COMPANY); } @@ -1123,19 +1164,20 @@ DEF_CONSOLE_CMD(ConNetworkConnect) * script file console commands *********************************/ -DEF_CONSOLE_CMD(ConExec) +static bool ConExec(std::span argv) { - if (argc == 0) { - IConsolePrint(CC_HELP, "Execute a local script file. Usage: 'exec