diff --git a/.changelog b/.changelog index 28e9b90a82..2e6467bc61 100644 --- a/.changelog +++ b/.changelog @@ -1,26 +1,85 @@ -12.2 (2022-04-02) +13.0-beta1 (2022-10-31) ------------------------------------------------------------------------ -Feature: Remember the last-used signal between games (#9792) -Change: [MacOS] Allow touchbar usage on all supported OS versions (#9776) -Change: Add a timestamp in name of crash files (#9761) -Fix #9736: Duplicate multiplayer window opens upon canceling password entry (#9842) -Fix: Removing long roads doesn't prioritise refusal of local authority over other errors (#9831) -Fix #9020: Glitchy station coverage highlight when changing selection (#9825) -Fix: Correct some Romanian town names (#9819) -Fix: Original music playback rate was slightly too fast (#9814) -Fix #9811: Use the NewGRF-defined vehicle center when dragging ships and aircraft (#9812) -Fix: Do not let shares in the company taking over another company disappear (#9808) -Fix #9802: Crash when using lots of NewGRF waypoint types (#9803) -Fix #9766: Don't write uninitialised data in config file (#9767) -Fix #9743: [MacOS] Don't try to render touchbar sprites with invalid zoom level (#9776) -Fix #9774: Building roadstop in estimation mode updates station acceptance (#9775) -Fix: If vehicles only refit to cargo-slots >= 32, the default cargo was wrong (#9744) -Fix #9735: Possible desync when replacing a depot on same tile (#9738) -Fix #9730: [Network] Connections can use an invalid socket due to a race condition (#9731) -Fix: Don't show sign edit window for GS-owned signs (#9716) -Fix #9702: Display order window for vehicle group on ctrl-click only when using shared orders (#9704) -Fix #9680: Crash when loading really old savegames with aircraft in certain places (#9699) -Fix: Update last servicing dates when using the date cheat (#9694) -Fix: Error message shows about missing glyphs while suitable fallback font is found (#9692) +Feature: Airport construction GUI displays infrastructure cost (#10094) +Feature: Purchase land multiple tiles at a time (#10027) +Feature: Add sticky pin & shade widgets to Object Selection UI panel (#10019, #10020) +Feature: Improved handling of HiDPI and mixed-DPI screens (#9994, #9996, #9997, #10064) +Feature: Alternative linkgraph colour schemes (#9866) +Feature: Allow Shift+Insert as paste in edit box (#9836) +Feature: Setting to make the local town authority rubber-stamp all actions (#9833) +Feature: Add/extend console commands to enable screenshot automation (#9771) +Feature: [Linkgraph] Show a tooltip with statistics when hovering a link (#9760) +Feature: Build objects by area (#9709) +Feature: Add setting to hide news about competitors vehicle crash (#9653) +Feature: Ctrl-click to remove fully autoreplaced vehicles from list (#9639) +Feature: Wide rivers on map generation (#9628) +Add: [Script] ScriptCargo::GetWeight to get cargo weights (#9930) +Add: Command line option to skip NewGRF scanning (#9879) +Add: Show video driver name in Game Options window (#9872) +Add: [NewGRF] Map seed as global variable (#9834) +Add: [Script] IndustryType::ResolveNewGRFID to resolve industry id from grf_local_id and grfid (#9798) +Add: [Script] ObjectType::ResolveNewGRFID to resolve object id from grfid and grf_local_id (#9795) +Update: To all the friends we have lost and those we have gained (#10000) +Change: Use the Simulation subcategory to openttd.desktop (#10015) +Change: Constantly update destination of 'any depot' orders (#9959) +Change: Use an indent, not a dash, to list train capacity (#9887) +Change: [NewGRF] Increase vehicle sprite stack from 4 layers to 8 (#9863) +Change: Don't pay Property Maintenance on stations when Infrastructure Maintenance is disabled (#9828) +Change: Improved layout of the finance window (#9827) +Change: [Admin] Bump admin port protocol due to command changes (#9754) +Change: Suppress vehicle age warnings for stopped vehicles (#9718) +Change: Make pf.yapf.rail_firstred_twoway_eol on by default (#9544) +Change: Deliver cargo to the closest industry first (#9536) +Fix: Lots of fixes to how windows handle resizing (#10040, #10042, #10046, #10051, #10056, #10068, #10070, #10098) +Fix: Console commands list_ai output was truncated with a suitably large number of AIs (#10075) +Fix #9876: MacBook Touch Bar crash / render issues w/ 32bpp graphics (#10060) +Fix: Reduce framerate overhead in Train::Tick (#10055) +Fix: Only open scenario editor date query once (#10050) +Fix #10048: Don't relocate company HQ on the same exact location (#10049) +Fix #10038: Missing upper bounds check when loading custom playlists (#10039) +Fix: Wrong string used to determine size of zoomed out station sign (#10036) +Fix: Disable "turn around" button for other companies' road vehicles (#10033) +Fix: Online Players list mouse hover behaviour (#10031) +Fix: [NewGRF] Weirdness of new stations (#10017) +Fix #9854: DrawStringMultiLine() could draw beyond its bounding box (#10014) +Fix: Incorrect player name in online players window (#10013) +Fix #8099: News window zoom level fixes (#10005) +Fix: [NewGRF] Upper 16 random bits should be the same for all station tiles in callback 140 (#9992) +Fix #9989: £0 Net Profit is neither negative nor positive (#9991) +Fix #9804: Only apply sprite_zoom_min setting when sprites available (#9988) +Fix #9972: Add missing fill/resize flags on Framerate window widgets (#9982) +Fix #9935: Use more selectivity when building SSE specific code (#9980) +Fix #9940: Print debuglevel parse errors to console when changed from console (#9979) +Fix #9977: Clearing the console with a large number of lines could cause a crash (#9978) +Fix #9974: Console command getsysdate did not work due to off-by-one error (#9975) +Fix: [NewGRF] Default value of RailVehicleInfo::railveh_type was inconsistent with other default properties (#9967) +Fix #8584: Vehicles with shared orders getting invalid or unexpected start dates (#9955) +Fix #9951: [NewGRF] Scenario editor random industries button broke NewGRF persistent storage (#9952) +Fix: Validation of various internal command parameters that could have allowed a rogue client to crash servers (#9942, #9943, #9944, #9945, #9946, #9947, #9948, #9950) +Fix #9937: Station industries_near incorrect after removing part moved sign (#9938) +Fix: [Script] ScriptRoad::HasRoadType really check for RoadType (#9934) +Fix #9363: Rebuild client list on reinit event (#9929) +Fix #9925: Industry tile layout validation for layouts of only one tile (#9926) +Fix #9918: Reset industy last production year on scenario start (#9920) +Fix #9914: Prevent more useless pathfinder run for blocked vehicles (#9917) +Fix: List a max of four share owners instead of three (#9905) +Fix: [NewGRF] Industry layouts with zero regular tiles should be invalid (#9902) +Fix #9869: Remove docking tile when doing a clear square (#9898) +Fix: New player companies use favorite manager face, if saved (#9895) +Fix: Towns don't build parallel, redundant bridges (#9891) +Fix #9712: Cap town bridge length at original 11-tile limit (#9890) +Fix #9883: Show cost/income float over end tile of rail or road construction (#9889) +Fix #9870: Don't update infrastructure totals when overbuilding object on canal (#9888) +Fix #9877: GS could trigger 'Cost: £0' cost message (#9878) +Fix 44f2ef1: [strgen] Allow gender for {CARGO_SHORT} (#9873) +Fix #9867: Industry::stations_near not filled at industry creation (#9868) +Fix #9853: Incorrect merge of guiflags and flags for osk_activation (#9855) +Fix #6544: Don't join AI company when loading network game in singleplayer (#9794) +Fix: Company values do not properly account for shares (#9770) +Fix #9546: Crash when no industries are present in game (#9726) +Fix #9708: [Linkgraph] Don't assume vehicles have a non-zero max speed (#9693) +Fix #9665: [Linkgraph] Fix travel times of non-direct journeys (#9693) +Fix #8797: Use logical rail length when placing signals (#9652) +Cleanup: [NewGRF] Remove unused flag sprites (#10052) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b7016b1650..b95f8baaf0 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -20,10 +20,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: /emsdk/upstream/emscripten/cache key: 2.0.31-${{ runner.os }} @@ -91,7 +91,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install dependencies run: | @@ -166,15 +166,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare cache key id: key run: | - echo "::set-output name=image::$ImageOS-$ImageVersion" + echo "image=$ImageOS-$ImageVersion" >> $GITHUB_OUTPUT - name: Enable vcpkg cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: /usr/local/share/vcpkg/installed key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-0 # Increase the number whenever dependencies are modified @@ -244,7 +244,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare cache key id: key @@ -253,10 +253,10 @@ jobs: # Work around caching failure with GNU tar New-Item -Type Junction -Path vcpkg -Target c:\vcpkg - Write-Output "::set-output name=image::$env:ImageOS-$env:ImageVersion" + Write-Output "image=$env:ImageOS-$env:ImageVersion" >> $env:GITHUB_OUTPUT - name: Enable vcpkg cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: vcpkg/installed key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-0 # Increase the number whenever dependencies are modified @@ -338,7 +338,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup MSYS2 uses: msys2/setup-msys2@v2 @@ -408,4 +408,4 @@ jobs: steps: - name: Check annotations - uses: OpenTTD/actions/annotation-check@v2 + uses: OpenTTD/actions/annotation-check@v3 diff --git a/.github/workflows/commit-checker.yml b/.github/workflows/commit-checker.yml index acf622a87e..91fc54b104 100644 --- a/.github/workflows/commit-checker.yml +++ b/.github/workflows/commit-checker.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 4 diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 2a280a6fcd..d2935cefbc 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -31,7 +31,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.PREVIEW_GITHUB_TOKEN }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.client_payload.sha }} @@ -41,7 +41,7 @@ jobs: git checkout -b ${name} - name: Setup cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: /emsdk/upstream/emscripten/cache key: 2.0.31-${{ runner.os }} diff --git a/.github/workflows/preview_label.yml b/.github/workflows/preview_label.yml index 6b3e17ea1a..503d4e53eb 100644 --- a/.github/workflows/preview_label.yml +++ b/.github/workflows/preview_label.yml @@ -59,7 +59,7 @@ jobs: - if: steps.core_developer.outcome == 'success' name: Trigger 'preview build' - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v2 with: token: ${{ secrets.PREVIEW_GITHUB_TOKEN }} event-type: "Preview build #${{ github.event.number }}" diff --git a/.github/workflows/preview_push.yml b/.github/workflows/preview_push.yml index cb68c8f6ce..3868c16f90 100644 --- a/.github/workflows/preview_push.yml +++ b/.github/workflows/preview_push.yml @@ -59,7 +59,7 @@ jobs: - if: toJson(fromJson(steps.earlier_preview.outputs.data)) != '[]' && contains(fromJson(steps.preview_label.outputs.data).*.name, 'preview') name: Trigger 'preview build' - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v2 with: token: ${{ secrets.PREVIEW_GITHUB_TOKEN }} event-type: "Preview build #${{ github.event.number }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3b4dc329b..7a06e23baf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,14 +29,14 @@ jobs: steps: - name: Checkout (Release) if: github.event_name == 'release' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # We generate a changelog; for this we need the full git log. fetch-depth: 0 - name: Checkout (Manual) if: github.event_name == 'workflow_dispatch' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.ref }} # We generate a changelog; for this we need the full git log. @@ -44,7 +44,7 @@ jobs: - name: Checkout (Trigger) if: github.event_name == 'repository_dispatch' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.client_payload.ref }} # We generate a changelog; for this we need the full git log. @@ -138,10 +138,10 @@ jobs: echo "Folder on CDN: ${FOLDER}" echo "Workflow trigger: ${TRIGGER_TYPE}" - echo "::set-output name=version::$(cat .version)" - echo "::set-output name=is_tag::${IS_TAG}" - echo "::set-output name=folder::${FOLDER}" - echo "::set-output name=trigger_type::${TRIGGER_TYPE}" + echo "version=$(cat .version)" >> $GITHUB_OUTPUT + echo "is_tag=${IS_TAG}" >> $GITHUB_OUTPUT + echo "folder=${FOLDER}" >> $GITHUB_OUTPUT + echo "trigger_type=${TRIGGER_TYPE}" >> $GITHUB_OUTPUT env: NIGHTLIES_BRANCH: master FOLDER_RELEASES: openttd-releases @@ -174,14 +174,14 @@ jobs: echo "::endgroup::" - name: Store bundles - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: openttd-source path: build/bundles/* retention-days: 5 - name: Store source (for other jobs) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: internal-source path: source.tar.gz @@ -195,7 +195,7 @@ jobs: steps: - name: Download source - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: internal-source @@ -258,7 +258,7 @@ jobs: echo "::endgroup::" - name: Store bundles - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: openttd-docs path: build/bundles/*.tar.xz @@ -276,7 +276,7 @@ jobs: steps: - name: Download source - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: internal-source @@ -355,7 +355,7 @@ jobs: echo "::endgroup::" - name: Store bundles - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: openttd-linux-generic path: build/bundles @@ -371,14 +371,11 @@ jobs: fail-fast: false matrix: include: - - container_image: "ubuntu:18.04" - bundle_name: "bionic" - compiler: "g++-8" - container_image: "ubuntu:20.04" bundle_name: "focal" compiler: "g++" - - container_image: "debian:buster" - bundle_name: "buster" + - container_image: "ubuntu:22.04" + bundle_name: "jammy" compiler: "g++" - container_image: "debian:bullseye" bundle_name: "bullseye" @@ -390,7 +387,7 @@ jobs: steps: - name: Download source - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: internal-source @@ -460,7 +457,7 @@ jobs: echo "::endgroup::" - name: Store bundles - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: openttd-linux-${{ matrix.bundle_name }} path: build/bundles @@ -470,13 +467,13 @@ jobs: name: MacOS needs: source - runs-on: macos-10.15 + runs-on: macos-11 env: MACOSX_DEPLOYMENT_TARGET: 10.14 steps: - name: Download source - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: internal-source @@ -494,10 +491,10 @@ jobs: - name: Prepare cache key id: key run: | - echo "::set-output name=image::$ImageOS-$ImageVersion" + echo "image=$ImageOS-$ImageVersion" >> $GITHUB_OUTPUT - name: Enable vcpkg cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: /usr/local/share/vcpkg/installed key: ${{ steps.key.outputs.image }}-vcpkg-release-0 # Increase the number whenever dependencies are modified @@ -651,7 +648,7 @@ jobs: mv _CPack_Packages/*/Bundle/openttd-*.zip bundles/ - name: Store bundles - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: openttd-macos-universal path: build-x64/bundles @@ -676,7 +673,7 @@ jobs: steps: - name: Download source - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: internal-source @@ -697,10 +694,10 @@ jobs: # Work around caching failure with GNU tar New-Item -Type Junction -Path vcpkg -Target c:\vcpkg - Write-Output "::set-output name=image::$env:ImageOS-$env:ImageVersion" + Write-Output "image=$env:ImageOS-$env:ImageVersion" >> $env:GITHUB_OUTPUT - name: Enable vcpkg cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: vcpkg/installed key: ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}-0 # Increase the number whenever dependencies are modified @@ -844,12 +841,195 @@ jobs: WINDOWS_CERTIFICATE_COMMON_NAME: ${{ secrets.WINDOWS_CERTIFICATE_COMMON_NAME }} - name: Store bundles - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: openttd-windows-${{ matrix.arch }} path: build/bundles retention-days: 5 + windows-appx: + name: Windows Store + needs: + - source + - windows + + if: needs.source.outputs.is_tag == 'true' + runs-on: windows-latest + + steps: + - name: Download source + uses: actions/download-artifact@v3 + with: + name: internal-source + + - name: Unpack source + shell: bash + run: | + tar -xf source.tar.gz --strip-components=1 + + - name: Download x86 build + uses: actions/download-artifact@v3 + with: + name: openttd-windows-x86 + + - name: Download x64 build + uses: actions/download-artifact@v3 + with: + name: openttd-windows-x64 + + - name: Download arm64 build + uses: actions/download-artifact@v3 + with: + name: openttd-windows-arm64 + + - name: Unpack builds + shell: bash + run: | + mkdir builds + cd builds + + function extract { + mkdir $1 + + # Extract the zip version of the release + unzip ../openttd-*-windows-$2.zip -d $1 + + # Remove the extraneous directory + mv $1/openttd-*-windows-$2/* $1/ + rmdir $1/openttd-*-windows-$2 + + # Move the openttd.exe to the '{arch}-binaries' folder + mkdir $1-binaries + mv $1/openttd.exe $1-binaries/ + } + + extract x86 win32 + extract x64 win64 + extract arm64 arm64 + + # Use the "x86" folder as the source of the common binaries (lang files, etc) and remove the others + mv x86 common-binaries + rm -rf x64 arm64 + + - name: Install OpenGFX + shell: bash + run: | + mkdir -p builds/common-binaries/baseset + cd builds/common-binaries/baseset + + echo "::group::Download OpenGFX" + curl -L https://cdn.openttd.org/opengfx-releases/7.1/opengfx-7.1-all.zip -o opengfx-all.zip + echo "::endgroup::" + + echo "::group::Unpack OpenGFX" + unzip opengfx-all.zip + tar xf opengfx-*.tar + echo "::endgroup::" + + rm -f opengfx-all.zip opengfx-*.tar + + - name: Install OpenMSX + shell: bash + run: | + mkdir -p builds/common-binaries/baseset + cd builds/common-binaries/baseset + + echo "::group::Download OpenMSX" + 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" + unzip openmsx-all.zip + tar xf openmsx-*.tar + echo "::endgroup::" + + rm -f openmsx-all.zip openmsx-*.tar + + - name: Install OpenSFX + shell: bash + run: | + mkdir -p builds/common-binaries/baseset/opensfx + cd builds/common-binaries/baseset/opensfx + + echo "::group::Download OpenSFX" + curl -L https://cdn.openttd.org/opensfx-releases/1.0.3/opensfx-1.0.3-all.zip -o opensfx-all.zip + echo "::endgroup::" + + echo "::group::Unpack OpenSFX" + unzip opensfx-all.zip + tar xf opensfx-*.tar + echo "::endgroup::" + + rm -f opensfx-all.zip opensfx-*.tar + + - name: Generate signing certificate + shell: cmd + run: | + cd builds + + REM We need to provide a signed .appx to the Windows Store, so generate a certificate with password 'password' + call ..\os\windows\winstore\generate-key.bat "CN=78024DA8-4BE4-4C77-B12E-547BBF7359D2" password cert.pfx + + - name: Generate assets + shell: cmd + run: | + cd os\windows\winstore + call generate-assets.bat + + - name: Prepare manifests + shell: cmd + run: | + cd builds + mkdir manifests + + REM Set the version environment variable + call ..\os\windows\winstore\set-version.bat x86-binaries\openttd.exe + + call ..\os\windows\winstore\prepare-manifests.bat manifests "CN=78024DA8-4BE4-4C77-B12E-547BBF7359D2" "57420OpenTTDDevelopers.OpenTTDofficial" + + - name: Prepare binaries + shell: bash + run: | + cd builds + + # Copy the Windows Store assets + mkdir common-binaries/Assets + cp -R ../os/windows/winstore/assets-common/* common-binaries/Assets/ + + mkdir Assets + cp -R ../os/windows/winstore/assets/* Assets/ + + cp manifests/*.xml . + + - name: Build + shell: cmd + run: | + REM Add the Windows SDK tools to the PATH + + echo|set /p="SET VS_INSTALLDIR=" > _vspath.bat + vswhere -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath >> _vspath.bat + call _vspath.bat + call "%VS_INSTALLDIR%\Common7\Tools\VsDevCmd.bat" + + REM Set the version environment variable + call os\windows\winstore\set-version.bat builds\x86-binaries\openttd.exe + + cd builds + mkdir output + + REM Build and sign the package + makeappx build /v /f PackagingLayout.xml /op output\ /bv %OTTD_VERSION% /pv %OTTD_VERSION% /ca + SignTool sign /fd sha256 /a /f cert.pfx /p password "output\OpenTTD.appxbundle" + + - name: Store appx + uses: actions/upload-artifact@v3 + with: + name: openttd-windows-store + path: | + builds/output/OpenTTD.appxbundle + builds/cert.pfx + retention-days: 5 + upload: name: Upload (AWS) needs: @@ -871,7 +1051,7 @@ jobs: steps: - name: Download all bundles - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 - name: Calculate checksums run: | @@ -903,7 +1083,7 @@ jobs: AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} - name: Trigger 'New OpenTTD release' - uses: peter-evans/repository-dispatch@v1 + uses: peter-evans/repository-dispatch@v2 with: token: ${{ secrets.DEPLOYMENT_TOKEN }} repository: OpenTTD/workflows @@ -924,7 +1104,7 @@ jobs: steps: - name: Download all bundles - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 - name: Setup steamcmd uses: CyberAndrii/setup-steamcmd@v1 diff --git a/.github/workflows/unused-strings.yml b/.github/workflows/unused-strings.yml index d63da91c30..6026671307 100644 --- a/.github/workflows/unused-strings.yml +++ b/.github/workflows/unused-strings.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Check for unused strings run: | diff --git a/.ottdrev b/.ottdrev index a4e83a48cf..203d48e351 100644 --- a/.ottdrev +++ b/.ottdrev @@ -1 +1 @@ -12.2 20220402 0 1893dba8a172636791dc79137a3331bd41cc50bc 1 1 2022 +13.0-beta1 20221031 0 c4e655b1d4fa83b8f192d2df0062f6dd7f346aad 1 0 2022 diff --git a/.release_date b/.release_date index 3f8c9f2c93..cf53716dd2 100644 --- a/.release_date +++ b/.release_date @@ -1 +1 @@ -2022-04-02 10:38 UTC +2022-11-01 19:19 UTC diff --git a/.version b/.version index 5807e59269..61c359a5f0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -12.2 +13.0-beta1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e50567b1f..cb599426de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ if(NOT BINARY_NAME) endif() project(${BINARY_NAME} - VERSION 12.2 + VERSION 13.0 ) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9ffdf98ce..b0ca0ab7a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,8 +94,8 @@ Although we really appreciate feedback and ideas, we will close feature requests Many of those ideas etc do have a place on the [forums](https://www.tt-forums.net); and if enough people like it, someone will stand up and make it. -It's usually best discuss in [irc](https://wiki.openttd.org/en/Development/IRC%20channel) before opening a feature request or working on a large feature in a fork. -Discussion in irc can take time, but it can be productive and avoid disappointment :) +It's usually best to discuss on [Discord](https://discord.gg/openttd) before opening a feature request or working on a large feature in a fork. +Discussion can take time, but it can be productive and avoid disappointment. :) ## Pull requests @@ -212,8 +212,8 @@ When it comes to gameplay features there are at least these groups of interests: - *Control freak:* micromanagement like conditional orders, refitting and loading etc. - *Casual:* automatisation like cargodist, path based signalling etc. -To please everyone, the official branch tries to stay close to the original gameplay; after all, that is what everyone brought here. -The preferred method to alter and extent the gameplay is via add-ons like NewGRF and GameScripts. +To please everyone, the official branch tries to stay close to the original gameplay; after all, that is what brought everyone here. +The preferred method to alter and extend the gameplay is via add-ons like NewGRF and GameScripts. For a long time, the official branch was also open to features which could be enabled/disabled, but the corner-cases that came with some configurations have rendered some parts of the code very complicated. Today, new features have to work with all the already existing features, which is not only challenging in corner cases, but also requires spending considerable more work than just "making it work in the game mode that I play". diff --git a/CREDITS.md b/CREDITS.md index 33f8836265..698ea54ebb 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,31 +1,31 @@ ### The OpenTTD team (in alphabetical order): -- Grzegorz Duczyński (adf88) - General coding (since 1.7.2) -- Albert Hofkamp (Alberth) - GUI expert (since 0.7) - Matthijs Kooijman (blathijs) - Pathfinder-guru, Debian port (since 0.3) -- Ulf Hermann (fonsinchen) - Cargo Distribution (since 1.3) - Christoph Elsenhans (frosch) - General coding (since 0.6) -- Loïc Guilloux (glx) - Windows Expert (since 0.4.5) +- Loïc Guilloux (glx) - General / Windows Expert (since 0.4.5) - Charles Pigott (LordAro) - General / Correctness police (since 1.9) -- Michael Lutz (michi_cc) - Path based signals (since 0.7) +- Michael Lutz (michi_cc) - General / Path based signals (since 0.7) - Niels Martin Hansen (nielsm) - Music system, general coding (since 1.9) - Owen Rudge (orudge) - Forum host, OS/2 port (since 0.1) - Peter Nelson (peter1138) - Spiritual descendant from newGRF gods (since 0.4.5) -- Ingo von Borstel (planetmaker) - General coding, Support (since 1.1) -- Remko Bijker (Rubidium) - Lead coder and way more (since 0.4.5) -- José Soler (Terkhen) - General coding (since 1.0) +- Remko Bijker (Rubidium) - Coder and way more (since 0.4.5) +- Patric Stout (TrueBrain) - NoProgrammer (since 0.3), sys op ### Inactive Developers: +- Grzegorz Duczyński (adf88) - General coding (1.7 - 1.8) +- Albert Hofkamp (Alberth) - GUI expert (0.7 - 1.9) - Jean-François Claeys (Belugas) - GUI, newindustries and more (0.4.5 - 1.0) - Bjarni Corfitzen (Bjarni) - macOS port, coder and vehicles (0.3 - 0.7) - Victor Fischer (Celestar) - Programming everywhere you need him to (0.3 - 0.6) +- Ulf Hermann (fonsinchen) - Cargo Distribution (1.3 - 1.6) - Jaroslav Mazanec (KUDr) - YAPG (Yet Another Pathfinder God) ;) (0.4.5 - 0.6) - Jonathan Coome (Maedhros) - High priest of the NewGRF Temple (0.5 - 0.6) -- Attila Bán (MiHaMiX) - WebTranslator 1 and 2 (0.3 - 0.5) +- Attila Bán (MiHaMiX) - Developer WebTranslator 1 and 2 (0.3 - 0.5) +- Ingo von Borstel (planetmaker) - General coding, Support (1.1 - 1.9) - Zdeněk Sojka (SmatZ) - Bug finder and fixer (0.6 - 1.3) +- José Soler (Terkhen) - General coding (1.0 - 1.4) - Christoph Mallon (Tron) - Programmer, code correctness police (0.3 - 0.5) -- Patric Stout (TrueBrain) - NoProgrammer (0.3 - 1.2), sys op (active) - Thijs Marinussen (Yexo) - AI Framework, General (0.6 - 1.3) - Leif Linse (Zuu) - AI/Game Script (1.2 - 1.6) @@ -33,11 +33,11 @@ - Tamás Faragó (Darkvater) - Ex-Lead coder (0.3 - 0.5) - Dominik Scherer (dominik81) - Lead programmer, GUI expert (0.3 - 0.3) -- Emil Djupfeld (egladil) - macOS port (0.4 - 0.6) +- Emil Djupfeld (egladil) - macOS port (0.4.5 - 0.6) - Simon Sasburg (HackyKid) - Bug fixer (0.4 - 0.4.5) - Ludvig Strigeus (ludde) - Original author of OpenTTD, main coder (0.1 - 0.3) - Cian Duffy (MYOB) - BeOS port / manual writing (0.1 - 0.3) -- Petr Baudiš (pasky) - Many patches, newgrf support, etc. (0.3 - 0.3) +- Petr Baudiš (pasky) - Many patches, newgrf support (0.3 - 0.3) - Benedikt Brüggemeier (skidd13) - Bug fixer and code reworker (0.6 - 0.7) - Serge Paquet (vurlix) - 2nd contributor after ludde (0.1 - 0.3) @@ -52,7 +52,7 @@ - Alberto Demichelis - Squirrel scripting language - L. Peter Deutsch - MD5 implementation - Michael Blunck - For revolutionizing TTD with awesome graphics -- George - Canal graphics +- George - Canal/Lock graphics - Andrew Parkhouse (andythenorth) - River graphics - David Dallaston (Pikka) - Tram tracks - All Translators - For their support to make OpenTTD a truly international game diff --git a/README.md b/README.md index 311f9e0ed0..96f137307c 100644 --- a/README.md +++ b/README.md @@ -116,35 +116,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) AI opponents - -OpenTTD comes without AI opponents, so if you want to play with AIs you have to download them. - -The easiest way is via the 'Check Online Content' button in the main menu. - -You can select some AIs that you think are compatible with your playing style. - -AI help and discussions may also be found in the [AI section of the forum](https://www.tt-forums.net/viewforum.php?f=65). - -### 1.5.2) Scenarios and height maps - -Scenarios and heightmaps can be added via the 'Check Online Content' button in the main menu. - -### 1.5.3) NewGRFs - -A wide range of add-content is available as NewGRFs, including vehicles, industries, stations, landscape objects, town names and more. - -NewGRFs can be added via the 'Check Online Content' button in the main menu. - -See also the wiki [guide to NewGRFs](https://wiki.openttd.org/en/Manual/NewGRF) and [the forum graphics development section](https://www.tt-forums.net/viewforum.php?f=66). - -### 1.5.4) Game scripts - -Game scripts can provide additional challenges or changes to the standard OpenTTD gameplay, for example setting transport goals, or changing town growth behaviour. - -Game scripts can be added via the 'Check Online Content' button in the main menu. - -See also the wiki [guide to game scripts](https://wiki.openttd.org/en/Manual/Game%20script) and [the forum graphics game script section](https://www.tt-forums.net/viewforum.php?f=65). ### 1.6) OpenTTD directories @@ -162,6 +133,7 @@ If you want to compile OpenTTD from source, instructions can be found in [COMPIL 'Official' channels - [OpenTTD website](https://www.openttd.org) +- [OpenTTD official Discord](https://discord.gg/openttd) - IRC chat using #openttd on irc.oftc.net [more info about our irc channel](https://wiki.openttd.org/en/Development/IRC%20channel) - [OpenTTD on Github](https://github.com/OpenTTD/) for code repositories and for reporting issues - [forum.openttd.org](https://forum.openttd.org/) - the primary community forum site for discussing OpenTTD and related games diff --git a/bin/ai/compat_0.7.nut b/bin/ai/compat_0.7.nut index c40308592c..341b543bd3 100644 --- a/bin/ai/compat_0.7.nut +++ b/bin/ai/compat_0.7.nut @@ -379,3 +379,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.0.nut b/bin/ai/compat_1.0.nut index b8da71a194..6b76f11534 100644 --- a/bin/ai/compat_1.0.nut +++ b/bin/ai/compat_1.0.nut @@ -131,3 +131,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.1.nut b/bin/ai/compat_1.1.nut index f1bda9c7fe..9c568a7006 100644 --- a/bin/ai/compat_1.1.nut +++ b/bin/ai/compat_1.1.nut @@ -68,3 +68,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.10.nut b/bin/ai/compat_1.10.nut index 2baaddb836..44bc2542ce 100644 --- a/bin/ai/compat_1.10.nut +++ b/bin/ai/compat_1.10.nut @@ -6,3 +6,16 @@ */ AILog.Info("1.10 API compatibility in effect."); + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.11.nut b/bin/ai/compat_1.11.nut index 887f3f7fd9..3d8370ffc6 100644 --- a/bin/ai/compat_1.11.nut +++ b/bin/ai/compat_1.11.nut @@ -6,3 +6,16 @@ */ AILog.Info("1.11 API compatibility in effect."); + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.2.nut b/bin/ai/compat_1.2.nut index 550f79969c..fc52b04935 100644 --- a/bin/ai/compat_1.2.nut +++ b/bin/ai/compat_1.2.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.3.nut b/bin/ai/compat_1.3.nut index 6b2c7e8a71..a06e98d2dd 100644 --- a/bin/ai/compat_1.3.nut +++ b/bin/ai/compat_1.3.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.4.nut b/bin/ai/compat_1.4.nut index a9ab5a4757..6de24bf80c 100644 --- a/bin/ai/compat_1.4.nut +++ b/bin/ai/compat_1.4.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.5.nut b/bin/ai/compat_1.5.nut index 23944149f6..8ff5a39d15 100644 --- a/bin/ai/compat_1.5.nut +++ b/bin/ai/compat_1.5.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.6.nut b/bin/ai/compat_1.6.nut index bcbe91455c..91744512a7 100644 --- a/bin/ai/compat_1.6.nut +++ b/bin/ai/compat_1.6.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.7.nut b/bin/ai/compat_1.7.nut index 7c2fd9b825..584a970f60 100644 --- a/bin/ai/compat_1.7.nut +++ b/bin/ai/compat_1.7.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.8.nut b/bin/ai/compat_1.8.nut index a118a63b57..f57a0eab27 100644 --- a/bin/ai/compat_1.8.nut +++ b/bin/ai/compat_1.8.nut @@ -20,3 +20,16 @@ AIGroup.CreateGroup <- function(vehicle_type) { return AIGroup._CreateGroup(vehicle_type, AIGroup.GROUP_INVALID); } + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_1.9.nut b/bin/ai/compat_1.9.nut index a3d0941327..0dde6dc6da 100644 --- a/bin/ai/compat_1.9.nut +++ b/bin/ai/compat_1.9.nut @@ -6,3 +6,16 @@ */ AILog.Info("1.9 API compatibility in effect."); + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_12.nut b/bin/ai/compat_12.nut index 3081fb58e8..d54895632f 100644 --- a/bin/ai/compat_12.nut +++ b/bin/ai/compat_12.nut @@ -4,3 +4,18 @@ * 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 . */ + +AILog.Info("12 API compatibility in effect."); + +/* 13 really checks RoadType against RoadType */ +AIRoad._HasRoadType <- AIRoad.HasRoadType; +AIRoad.HasRoadType <- function(tile, road_type) +{ + local list = AIRoadTypeList(AIRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (AIRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/ai/compat_13.nut b/bin/ai/compat_13.nut new file mode 100644 index 0000000000..3081fb58e8 --- /dev/null +++ b/bin/ai/compat_13.nut @@ -0,0 +1,6 @@ +/* + * 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 . + */ diff --git a/bin/game/compat_1.10.nut b/bin/game/compat_1.10.nut index 92cef84c53..2559ff0a9c 100644 --- a/bin/game/compat_1.10.nut +++ b/bin/game/compat_1.10.nut @@ -13,3 +13,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.11.nut b/bin/game/compat_1.11.nut index fa240b5d2d..eac9a05d36 100644 --- a/bin/game/compat_1.11.nut +++ b/bin/game/compat_1.11.nut @@ -6,3 +6,16 @@ */ GSLog.Info("1.11 API compatibility in effect."); + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.2.nut b/bin/game/compat_1.2.nut index 5fb29efedf..66e2ca5d62 100644 --- a/bin/game/compat_1.2.nut +++ b/bin/game/compat_1.2.nut @@ -35,3 +35,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.3.nut b/bin/game/compat_1.3.nut index 7546e54c69..9986ea03e5 100644 --- a/bin/game/compat_1.3.nut +++ b/bin/game/compat_1.3.nut @@ -35,3 +35,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.4.nut b/bin/game/compat_1.4.nut index c90b3e550b..b3ec5c45cb 100644 --- a/bin/game/compat_1.4.nut +++ b/bin/game/compat_1.4.nut @@ -28,3 +28,15 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.5.nut b/bin/game/compat_1.5.nut index 0c62e56462..2d25d7acf0 100644 --- a/bin/game/compat_1.5.nut +++ b/bin/game/compat_1.5.nut @@ -20,3 +20,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.6.nut b/bin/game/compat_1.6.nut index 198b863a77..d205832209 100644 --- a/bin/game/compat_1.6.nut +++ b/bin/game/compat_1.6.nut @@ -20,3 +20,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.7.nut b/bin/game/compat_1.7.nut index 76dc424353..1108f41a7c 100644 --- a/bin/game/compat_1.7.nut +++ b/bin/game/compat_1.7.nut @@ -20,3 +20,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.8.nut b/bin/game/compat_1.8.nut index b9d27458a9..139f91214b 100644 --- a/bin/game/compat_1.8.nut +++ b/bin/game/compat_1.8.nut @@ -20,3 +20,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_1.9.nut b/bin/game/compat_1.9.nut index 32eec114af..053e377d4c 100644 --- a/bin/game/compat_1.9.nut +++ b/bin/game/compat_1.9.nut @@ -13,3 +13,16 @@ GSCompany.ChangeBankBalance <- function(company, delta, expenses_type) { return GSCompany._ChangeBankBalance(company, delta, expenses_type, GSMap.TILE_INVALID); } + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_12.nut b/bin/game/compat_12.nut index 3081fb58e8..a11cd04a23 100644 --- a/bin/game/compat_12.nut +++ b/bin/game/compat_12.nut @@ -4,3 +4,18 @@ * 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 . */ + +GSLog.Info("12 API compatibility in effect."); + +/* 13 really checks RoadType against RoadType */ +GSRoad._HasRoadType <- GSRoad.HasRoadType; +GSRoad.HasRoadType <- function(tile, road_type) +{ + local list = GSRoadTypeList(GSRoad.GetRoadTramType(road_type)); + foreach (rt, _ in list) { + if (GSRoad._HasRoadType(tile, rt)) { + return true; + } + } + return false; +} diff --git a/bin/game/compat_13.nut b/bin/game/compat_13.nut new file mode 100644 index 0000000000..3081fb58e8 --- /dev/null +++ b/bin/game/compat_13.nut @@ -0,0 +1,6 @@ +/* + * 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 . + */ diff --git a/changelog.txt b/changelog.txt index 5e0f4204f9..12d3ffe756 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,88 @@ +13.0-beta1 (2022-10-31) +------------------------------------------------------------------------ +Feature: Airport construction GUI displays infrastructure cost (#10094) +Feature: Purchase land multiple tiles at a time (#10027) +Feature: Add sticky pin & shade widgets to Object Selection UI panel (#10019, #10020) +Feature: Improved handling of HiDPI and mixed-DPI screens (#9994, #9996, #9997, #10064) +Feature: Alternative linkgraph colour schemes (#9866) +Feature: Allow Shift+Insert as paste in edit box (#9836) +Feature: Setting to make the local town authority rubber-stamp all actions (#9833) +Feature: Add/extend console commands to enable screenshot automation (#9771) +Feature: [Linkgraph] Show a tooltip with statistics when hovering a link (#9760) +Feature: Build objects by area (#9709) +Feature: Add setting to hide news about competitors vehicle crash (#9653) +Feature: Ctrl-click to remove fully autoreplaced vehicles from list (#9639) +Feature: Wide rivers on map generation (#9628) +Add: [Script] ScriptCargo::GetWeight to get cargo weights (#9930) +Add: Command line option to skip NewGRF scanning (#9879) +Add: Show video driver name in Game Options window (#9872) +Add: [NewGRF] Map seed as global variable (#9834) +Add: [Script] IndustryType::ResolveNewGRFID to resolve industry id from grf_local_id and grfid (#9798) +Add: [Script] ObjectType::ResolveNewGRFID to resolve object id from grfid and grf_local_id (#9795) +Update: To all the friends we have lost and those we have gained (#10000) +Change: Use the Simulation subcategory to openttd.desktop (#10015) +Change: Constantly update destination of 'any depot' orders (#9959) +Change: Use an indent, not a dash, to list train capacity (#9887) +Change: [NewGRF] Increase vehicle sprite stack from 4 layers to 8 (#9863) +Change: Don't pay Property Maintenance on stations when Infrastructure Maintenance is disabled (#9828) +Change: Improved layout of the finance window (#9827) +Change: [Admin] Bump admin port protocol due to command changes (#9754) +Change: Suppress vehicle age warnings for stopped vehicles (#9718) +Change: Make pf.yapf.rail_firstred_twoway_eol on by default (#9544) +Change: Deliver cargo to the closest industry first (#9536) +Fix: Lots of fixes to how windows handle resizing (#10040, #10042, #10046, #10051, #10056, #10068, #10070, #10098) +Fix: Console commands list_ai output was truncated with a suitably large number of AIs (#10075) +Fix #9876: MacBook Touch Bar crash / render issues w/ 32bpp graphics (#10060) +Fix: Reduce framerate overhead in Train::Tick (#10055) +Fix: Only open scenario editor date query once (#10050) +Fix #10048: Don't relocate company HQ on the same exact location (#10049) +Fix #10038: Missing upper bounds check when loading custom playlists (#10039) +Fix: Wrong string used to determine size of zoomed out station sign (#10036) +Fix: Disable "turn around" button for other companies' road vehicles (#10033) +Fix: Online Players list mouse hover behaviour (#10031) +Fix: [NewGRF] Weirdness of new stations (#10017) +Fix #9854: DrawStringMultiLine() could draw beyond its bounding box (#10014) +Fix: Incorrect player name in online players window (#10013) +Fix #8099: News window zoom level fixes (#10005) +Fix: [NewGRF] Upper 16 random bits should be the same for all station tiles in callback 140 (#9992) +Fix #9989: £0 Net Profit is neither negative nor positive (#9991) +Fix #9804: Only apply sprite_zoom_min setting when sprites available (#9988) +Fix #9972: Add missing fill/resize flags on Framerate window widgets (#9982) +Fix #9935: Use more selectivity when building SSE specific code (#9980) +Fix #9940: Print debuglevel parse errors to console when changed from console (#9979) +Fix #9977: Clearing the console with a large number of lines could cause a crash (#9978) +Fix #9974: Console command getsysdate did not work due to off-by-one error (#9975) +Fix: [NewGRF] Default value of RailVehicleInfo::railveh_type was inconsistent with other default properties (#9967) +Fix #8584: Vehicles with shared orders getting invalid or unexpected start dates (#9955) +Fix #9951: [NewGRF] Scenario editor random industries button broke NewGRF persistent storage (#9952) +Fix: Validation of various internal command parameters that could have allowed a rogue client to crash servers (#9942, #9943, #9944, #9945, #9946, #9947, #9948, #9950) +Fix #9937: Station industries_near incorrect after removing part moved sign (#9938) +Fix: [Script] ScriptRoad::HasRoadType really check for RoadType (#9934) +Fix #9363: Rebuild client list on reinit event (#9929) +Fix #9925: Industry tile layout validation for layouts of only one tile (#9926) +Fix #9918: Reset industy last production year on scenario start (#9920) +Fix #9914: Prevent more useless pathfinder run for blocked vehicles (#9917) +Fix: List a max of four share owners instead of three (#9905) +Fix: [NewGRF] Industry layouts with zero regular tiles should be invalid (#9902) +Fix #9869: Remove docking tile when doing a clear square (#9898) +Fix: New player companies use favorite manager face, if saved (#9895) +Fix: Towns don't build parallel, redundant bridges (#9891) +Fix #9712: Cap town bridge length at original 11-tile limit (#9890) +Fix #9883: Show cost/income float over end tile of rail or road construction (#9889) +Fix #9870: Don't update infrastructure totals when overbuilding object on canal (#9888) +Fix #9877: GS could trigger 'Cost: £0' cost message (#9878) +Fix 44f2ef1: [strgen] Allow gender for {CARGO_SHORT} (#9873) +Fix #9867: Industry::stations_near not filled at industry creation (#9868) +Fix #9853: Incorrect merge of guiflags and flags for osk_activation (#9855) +Fix #6544: Don't join AI company when loading network game in singleplayer (#9794) +Fix: Company values do not properly account for shares (#9770) +Fix #9546: Crash when no industries are present in game (#9726) +Fix #9708: [Linkgraph] Don't assume vehicles have a non-zero max speed (#9693) +Fix #9665: [Linkgraph] Fix travel times of non-direct journeys (#9693) +Fix #8797: Use logical rail length when placing signals (#9652) +Cleanup: [NewGRF] Remove unused flag sprites (#10052) + + 12.2 (2022-04-02) ------------------------------------------------------------------------ Feature: Remember the last-used signal between games (#9792) diff --git a/cmake/CompileFlags.cmake b/cmake/CompileFlags.cmake index ad4a46aca7..b1cec89daf 100644 --- a/cmake/CompileFlags.cmake +++ b/cmake/CompileFlags.cmake @@ -56,6 +56,11 @@ macro(compile_flags) if(MSVC) add_compile_options(/W3) + if(MSVC_VERSION GREATER 1929) + # Starting with version 19.30, there is an optimisation bug, see #9966 for details + # This flag disables the broken optimisation to work around the bug + add_compile_options(/d2ssa-rse-) + endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") add_compile_options( -W diff --git a/cmake/InstallAndPackage.cmake b/cmake/InstallAndPackage.cmake index 057f0934f8..be37b9b745 100644 --- a/cmake/InstallAndPackage.cmake +++ b/cmake/InstallAndPackage.cmake @@ -122,6 +122,8 @@ set(CPACK_STRIP_FILES YES) set(CPACK_OUTPUT_FILE_PREFIX "bundles") if(APPLE) + # Stripping would produce unreadable stacktraces. + set(CPACK_STRIP_FILES NO) set(CPACK_GENERATOR "Bundle") include(PackageBundle) @@ -129,7 +131,7 @@ if(APPLE) set(CPACK_PACKAGE_FILE_NAME "openttd-#CPACK_PACKAGE_VERSION#-macos-universal") else() set(CPACK_PACKAGE_FILE_NAME "openttd-#CPACK_PACKAGE_VERSION#-macos-${CPACK_SYSTEM_NAME}") - endif() + endif() elseif(WIN32) set(CPACK_GENERATOR "ZIP") if(OPTION_USE_NSIS) @@ -181,6 +183,10 @@ elseif(UNIX) if(DISTRO_ID STREQUAL "arch") set(PLATFORM "arch") set(CPACK_GENERATOR "TXZ") + elseif(DISTRO_ID STREQUAL "fedora" OR DISTRO_ID STREQUAL "rhel") + set(PLATFORM "fedora") + set(CPACK_GENERATOR "RPM") + include(PackageRPM) else() set(UNSUPPORTED_PLATFORM_NAME "Linux distribution '${DISTRO_ID}' from /etc/os-release") endif() diff --git a/cmake/LinkPackage.cmake b/cmake/LinkPackage.cmake index 0f62d3296c..bea9deb0cf 100644 --- a/cmake/LinkPackage.cmake +++ b/cmake/LinkPackage.cmake @@ -18,6 +18,6 @@ function(link_package NAME) message(STATUS "${NAME} found -- -DWITH_${UCNAME} -- ${${NAME}_INCLUDE_DIRS} ${${NAME}_INCLUDE_DIR} -- ${${NAME}_LIBRARIES} ${${NAME}_LIBRARY}") endif() elseif(LP_ENCOURAGED) - message(WARNING "${NAME} not found; compiling OpenTTD without ${NAME} is strongly disencouraged") + message(WARNING "${NAME} not found; compiling OpenTTD without ${NAME} is strongly discouraged") endif() endfunction() diff --git a/cmake/MSVCFilters.cmake b/cmake/MSVCFilters.cmake index db53fc2258..b334f6a91a 100644 --- a/cmake/MSVCFilters.cmake +++ b/cmake/MSVCFilters.cmake @@ -3,6 +3,7 @@ source_group("AI Core" REGULAR_EXPRESSION "src/ai/") source_group("Blitters" REGULAR_EXPRESSION "src/blitter/") source_group("Core Source Code" REGULAR_EXPRESSION "src/core/") +source_group("Font Cache" REGULAR_EXPRESSION "src/fontcache/") source_group("Game Core" REGULAR_EXPRESSION "src/game/") source_group("MD5" REGULAR_EXPRESSION "src/3rdparty/md5/") source_group("Misc" REGULAR_EXPRESSION "src/misc/") diff --git a/cmake/Options.cmake b/cmake/Options.cmake index bfa14b6c3c..42d1127906 100644 --- a/cmake/Options.cmake +++ b/cmake/Options.cmake @@ -56,7 +56,7 @@ function(set_options) option(OPTION_DEDICATED "Build dedicated server only (no GUI)" OFF) option(OPTION_INSTALL_FHS "Install with Filesystem Hierarchy Standard folders" ${DEFAULT_OPTION_INSTALL_FHS}) - option(OPTION_USE_ASSERTS "Use assertions; leave enabled for nightlies, betas, and RCs" OFF) + option(OPTION_USE_ASSERTS "Use assertions; leave enabled for nightlies, betas, and RCs" ON) if(EMSCRIPTEN) # Although pthreads is supported, it is not in a way yet that is # useful for us. diff --git a/cmake/PackageRPM.cmake b/cmake/PackageRPM.cmake new file mode 100644 index 0000000000..d91bb28326 --- /dev/null +++ b/cmake/PackageRPM.cmake @@ -0,0 +1 @@ +set(CPACK_RPM_PACKAGE_ARCHITECTURE "${ARCHITECTURE}") diff --git a/cmake/scripts/GenerateWidget.cmake b/cmake/scripts/GenerateWidget.cmake index f46a67e690..b6748422f5 100644 --- a/cmake/scripts/GenerateWidget.cmake +++ b/cmake/scripts/GenerateWidget.cmake @@ -13,6 +13,7 @@ cmake_minimum_required(VERSION 3.5) # The parameter "enumname" specifies the enumeration to extract. This can also be a regular expression. # The parameter "filename" specifies the relative path to the file, where the enumeration is extracted from. This can also be a glob expression. # +# All files where enumerations are extracted from are automatically added via #include # if(NOT GENERATE_SOURCE_FILE) @@ -41,6 +42,7 @@ foreach(ENUM IN LISTS ENUM_LINES) string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" FILE ${FILE}) string(APPEND ${PLACE_HOLDER} "\n${ADD_INDENT}/* automatically generated from ${FILE} */") + list(APPEND INCLUDES "#include \"${FILE}\"") foreach(LINE IN LISTS SOURCE_LINES) string(REPLACE "${RM_INDENT}" "" LINE "${LINE}") @@ -116,4 +118,7 @@ foreach(ENUM IN LISTS ENUM_LINES) endforeach() endforeach() + list(REMOVE_DUPLICATES INCLUDES) + string(REPLACE ";" "\n" INCLUDES "${INCLUDES}") + configure_file(${GENERATE_SOURCE_FILE} ${GENERATE_BINARY_FILE}) diff --git a/cmake/scripts/Regression.cmake b/cmake/scripts/Regression.cmake index e21a86d292..19fece83f5 100644 --- a/cmake/scripts/Regression.cmake +++ b/cmake/scripts/Regression.cmake @@ -35,6 +35,7 @@ execute_process(COMMAND ${OPENTTD_EXECUTABLE} -vnull:ticks=30000 -d script=2 -d misc=9 + -Q OUTPUT_VARIABLE REGRESSION_OUTPUT ERROR_VARIABLE REGRESSION_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE diff --git a/cmake/scripts/SquirrelIncludes.cmake b/cmake/scripts/SquirrelIncludes.cmake index 672ad09531..ef81ddf041 100644 --- a/cmake/scripts/SquirrelIncludes.cmake +++ b/cmake/scripts/SquirrelIncludes.cmake @@ -12,32 +12,11 @@ endif() if(NOT APIUC) message(FATAL_ERROR "Script needs APIUC defined") endif() +if(NOT API_FILES) + message(FATAL_ERROR "Script needs API_FILES defined") +endif() -set(ARGC 1) -set(ARG_READ NO) - -# For MSVC CMake runs this script from a batch file using || to detect errors, -# depending on source path it may quote args, and cause cmd to not understand || -# and pass it as argument to ourself. -# Read all the arguments given to CMake; we are looking for -- and everything -# that follows, until ||. Those are our api files. -while(ARGC LESS CMAKE_ARGC) - set(ARG ${CMAKE_ARGV${ARGC}}) - - if(ARG STREQUAL "||") - set(ARG_READ NO) - endif() - - if(ARG_READ) - list(APPEND SCRIPT_API_BINARY_FILES "${ARG}") - endif() - - if(ARG STREQUAL "--") - set(ARG_READ YES) - endif() - - math(EXPR ARGC "${ARGC} + 1") -endwhile() +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\\)$") diff --git a/media/baseset/openttd.grf b/media/baseset/openttd.grf index 837e4b12d2..e3ebf2dd67 100644 Binary files a/media/baseset/openttd.grf and b/media/baseset/openttd.grf differ diff --git a/media/baseset/openttd/flags.nfo b/media/baseset/openttd/flags.nfo deleted file mode 100644 index 3c125ec79a..0000000000 --- a/media/baseset/openttd/flags.nfo +++ /dev/null @@ -1,43 +0,0 @@ -// This file is part of OpenTTD. -// OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. -// OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . -// - -1 * 0 0C "Flag graphics" - -1 * 3 05 14 24 - -1 sprites/flags.png 8bpp 34 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 50 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 66 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 82 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 98 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 114 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 130 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 146 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 162 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 178 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 194 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 210 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 226 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 242 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 258 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 274 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 290 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 306 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 322 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 338 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 354 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 370 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 386 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 402 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 418 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 434 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 450 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 466 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 482 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 498 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 514 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 530 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 546 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 562 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 578 8 11 8 0 0 normal - -1 sprites/flags.png 8bpp 594 8 11 8 0 0 normal diff --git a/media/baseset/openttd/flags.png b/media/baseset/openttd/flags.png deleted file mode 100644 index 2e0401ffce..0000000000 Binary files a/media/baseset/openttd/flags.png and /dev/null differ diff --git a/media/baseset/openttd/openttd.nfo b/media/baseset/openttd/openttd.nfo index b0d80824e8..c1ed751c55 100644 --- a/media/baseset/openttd/openttd.nfo +++ b/media/baseset/openttd/openttd.nfo @@ -92,7 +92,6 @@ #include "roadstops.nfo" #include "aqueduct.nfo" #include "autorail.nfo" -#include "flags.nfo" #include "openttdgui.nfo" #include "airport_preview.nfo" #include "chars.nfo" diff --git a/media/baseset/opntitle.dat b/media/baseset/opntitle.dat index da09c5e3a5..264aaff60b 100644 Binary files a/media/baseset/opntitle.dat and b/media/baseset/opntitle.dat differ diff --git a/media/openttd.desktop b/media/openttd.desktop index e0aef004cd..6882a0d5c7 100644 --- a/media/openttd.desktop +++ b/media/openttd.desktop @@ -6,6 +6,6 @@ Name=OpenTTD Icon=${BINARY_NAME} Exec=${BINARY_NAME} Terminal=false -Categories=Game; +Categories=Game;Simulation; Keywords=game;simulation;transport;tycoon;deluxe;economics;multiplayer;money;train;ship;bus;truck;aircraft;cargo; @Comment_STR_DESKTOP_SHORTCUT_COMMENT@ diff --git a/os/windows/openttd.manifest b/os/windows/openttd.manifest index ee1c7ea224..cb536a819d 100644 --- a/os/windows/openttd.manifest +++ b/os/windows/openttd.manifest @@ -10,6 +10,7 @@ True/PM + PerMonitorV2,PerMonitor diff --git a/os/windows/winstore/generate-assets.bat b/os/windows/winstore/generate-assets.bat new file mode 100644 index 0000000000..387fd57b85 --- /dev/null +++ b/os/windows/winstore/generate-assets.bat @@ -0,0 +1,2 @@ +@echo off +powershell -File "%~dp0generate-assets.ps1" diff --git a/os/windows/winstore/generate-assets.ps1 b/os/windows/winstore/generate-assets.ps1 new file mode 100644 index 0000000000..89c48e6ecf --- /dev/null +++ b/os/windows/winstore/generate-assets.ps1 @@ -0,0 +1,48 @@ +function ResizeImage() { + param([String]$sourcePath, [Int]$targetWidth, [Int]$targetHeight, [String]$targetPath) + + Add-Type -AssemblyName "System.Drawing" + + $img = [System.Drawing.Image]::FromFile($sourcePath) + + $ratioX = $targetWidth / $img.Width; + $ratioY = $targetHeight / $img.Height; + + $ratio = $ratioY + + if ($ratioX -le $ratioY) { + $ratio = $ratioX + } + + $newWidth = [int] ($img.Width * $ratio) + $newHeight = [int] ($img.Height * $ratio) + + $resizedImage = New-Object System.Drawing.Bitmap($targetWidth, $targetHeight) + $graph = [System.Drawing.Graphics]::FromImage($resizedImage) + $graph.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + + $graph.Clear([System.Drawing.Color]::Transparent) + $graph.DrawImage($img, $targetWidth / 2 - $newWidth / 2, $targetHeight / 2 - $newHeight / 2, $newWidth, $newHeight) + + $resizedImage.Save($targetPath) + $resizedImage.Dispose() + $img.Dispose() +} + +$logoPath = "..\..\..\media\openttd.2048.png" + +# Create the main image assets required for the Windows Store +New-Item -Path "." -Name "assets" -ItemType "directory" -Force +ResizeImage $logoPath 1240 1240 "assets\LargeTile.png" +ResizeImage $logoPath 284 284 "assets\SmallTile.png" +ResizeImage $logoPath 2480 1200 "assets\SplashScreen.png" +ResizeImage $logoPath 176 176 "assets\Square44x44Logo.png" +Copy-Item "assets\Square44x44Logo.png" -Destination "assets\Square44x44Logo.targetsize-44_altform-unplated.png" +ResizeImage $logoPath 600 600 "assets\Square150x150Logo.png" +Copy-Item "assets\Square150x150Logo.png" -Destination "assets\Square150x150Logo.targetsize-150_altform-unplated.png" +ResizeImage $logoPath 200 200 "assets\StoreLogo.png" +ResizeImage $logoPath 1240 600 "assets\Wide310x150Logo.png" + +# Copy the logo for the store for the common package +New-Item -Path "." -Name "assets-common" -ItemType "directory" -Force +Copy-Item "assets\StoreLogo.png" -Destination "assets-common\StoreLogo.png" diff --git a/os/windows/winstore/generate-key.bat b/os/windows/winstore/generate-key.bat new file mode 100644 index 0000000000..859d595d9c --- /dev/null +++ b/os/windows/winstore/generate-key.bat @@ -0,0 +1,2 @@ +@echo off +powershell -File "%~dp0generate-key.ps1" %1 %2 %3 diff --git a/os/windows/winstore/generate-key.ps1 b/os/windows/winstore/generate-key.ps1 new file mode 100644 index 0000000000..f59eb038e4 --- /dev/null +++ b/os/windows/winstore/generate-key.ps1 @@ -0,0 +1,21 @@ +[CmdletBinding()] +[Alias()] +Param +( + # Publisher ("CN=xyz") + [Parameter(Mandatory=$true, Position=0)] + $Publisher, + + # Password + [Parameter(Mandatory=$true, Position=1)] + $PasswordParam, + + # Filename + [Parameter(Mandatory=$true, Position=2)] + $OutputFilename +) + +$cert = New-SelfSignedCertificate -Type Custom -Subject $Publisher -KeyUsage DigitalSignature -FriendlyName "OpenTTD signing certificate" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") + +$password = ConvertTo-SecureString -String $PasswordParam -Force -AsPlainText +Export-PfxCertificate -cert "Cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath $OutputFilename -Password $password diff --git a/os/windows/winstore/manifests/AssetsPackage.appxmanifest b/os/windows/winstore/manifests/AssetsPackage.appxmanifest new file mode 100644 index 0000000000..a0e8f29eda --- /dev/null +++ b/os/windows/winstore/manifests/AssetsPackage.appxmanifest @@ -0,0 +1,68 @@ + + + + + false + OpenTTD (official) + OpenTTD Developers + Assets\StoreLogoCommon.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/os/windows/winstore/manifests/Package.appxmanifest b/os/windows/winstore/manifests/Package.appxmanifest new file mode 100644 index 0000000000..7eb4a5c2b7 --- /dev/null +++ b/os/windows/winstore/manifests/Package.appxmanifest @@ -0,0 +1,83 @@ + + + + + OpenTTD (official) + OpenTTD Developers + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/os/windows/winstore/manifests/PackagingLayout.xml b/os/windows/winstore/manifests/PackagingLayout.xml new file mode 100644 index 0000000000..dc16e45fa8 --- /dev/null +++ b/os/windows/winstore/manifests/PackagingLayout.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/os/windows/winstore/prepare-manifests.bat b/os/windows/winstore/prepare-manifests.bat new file mode 100644 index 0000000000..634b51e5ff --- /dev/null +++ b/os/windows/winstore/prepare-manifests.bat @@ -0,0 +1,2 @@ +@echo off +powershell -File "%~dp0prepare-manifests.ps1" %1 %2 %3 %OTTD_VERSION% diff --git a/os/windows/winstore/prepare-manifests.ps1 b/os/windows/winstore/prepare-manifests.ps1 new file mode 100644 index 0000000000..4bd1f4cb2f --- /dev/null +++ b/os/windows/winstore/prepare-manifests.ps1 @@ -0,0 +1,42 @@ +[CmdletBinding()] +[Alias()] +Param +( + # Output folder + [Parameter(Mandatory=$true, Position=0)] + $OutputFolder, + + # Publisher ("CN=xyz") + [Parameter(Mandatory=$true, Position=1)] + $Publisher, + + # IdentityName + [Parameter(Mandatory=$true, Position=2)] + $IdentityName, + + # Version + [Parameter(Mandatory=$true, Position=3)] + $AppVersion +) + +function Prepare-Manifest { + param ( + $Architecture + ) + + (Get-Content "$($PSScriptRoot)\manifests\Package.appxmanifest").replace('$PUBLISHER$', $Publisher).replace('$IDENTITY_NAME$', $IdentityName).replace('$VERSION$', $AppVersion).replace('$ARCHITECTURE$', $Architecture) | Set-Content "$($OutputFolder)\Package-$($Architecture).appxmanifest" +} + +# Prepare the application binary manifests +Prepare-Manifest x86 +Prepare-Manifest x64 +Prepare-Manifest arm64 + +# Prepare the assets package manifest +(Get-Content "$($PSScriptRoot)\manifests\AssetsPackage.appxmanifest").replace('$PUBLISHER$', $Publisher).replace('$IDENTITY_NAME$', $IdentityName).replace('$VERSION$', $AppVersion) | Set-Content "$($OutputFolder)\AssetsPackage.appxmanifest" + +# Prepare the overall package manifest +(Get-Content "$($PSScriptRoot)\manifests\Package.appxmanifest").replace('$PUBLISHER$', $Publisher).replace('$IDENTITY_NAME$', $IdentityName).replace('$VERSION$', $AppVersion).replace(' ProcessorArchitecture="$ARCHITECTURE$"', '') | Set-Content "$($OutputFolder)\Package.appxmanifest" + +# Copy the PackagingLayout XML file +(Get-Content "$($PSScriptRoot)\manifests\PackagingLayout.xml") | Set-Content "$($OutputFolder)\PackagingLayout.xml" diff --git a/os/windows/winstore/set-version.bat b/os/windows/winstore/set-version.bat new file mode 100644 index 0000000000..f198661787 --- /dev/null +++ b/os/windows/winstore/set-version.bat @@ -0,0 +1,14 @@ +@echo off +if [%1]==[] goto err + +powershell -File "%~dp0set-version.ps1" %1 > "%temp%\ottd-set-version.bat" +if not errorlevel 0 goto err +call "%temp%\ottd-set-version.bat" +del /q "%temp%\ottd-set-version.bat" + +@rem Version number will now be in %OTTD_VERSION% + +goto :eof + +:err +echo Please supply the path of openttd.exe as the argument to this batch file. diff --git a/os/windows/winstore/set-version.ps1 b/os/windows/winstore/set-version.ps1 new file mode 100644 index 0000000000..ba048581e2 --- /dev/null +++ b/os/windows/winstore/set-version.ps1 @@ -0,0 +1,23 @@ +[CmdletBinding()] +[Alias()] +Param +( + # EXE path + [Parameter(Mandatory=$true, Position=0)] + $ExePath +) + +try +{ + $versionInfo = (Get-Item "$ExePath").VersionInfo + + # Generate the app version - the build number (MS calls it revision) is always 0 because the Windows Store requires that + $AppVersion = "$($versionInfo.FileMajorPart).$($versionInfo.FileMinorPart).$($versionInfo.FileBuildPart).0" + + Write-Output "SET OTTD_VERSION=$($AppVersion)" +} +catch +{ + Write-Output "@ECHO Error retrieving EXE version - did you provide a path?" + exit 1 +} diff --git a/regression/regression/info.nut b/regression/regression/info.nut index 1a52cfebbb..8799d98628 100644 --- a/regression/regression/info.nut +++ b/regression/regression/info.nut @@ -4,7 +4,7 @@ class Regression extends AIInfo { function GetShortName() { return "REGR"; } function GetDescription() { return "This runs regression-tests on some commands. On the same map the result should always be the same."; } function GetVersion() { return 1; } - function GetAPIVersion() { return "12"; } + function GetAPIVersion() { return "13"; } function GetDate() { return "2007-03-18"; } function CreateInstance() { return "Regression"; } function UseAsRandomAI() { return false; } diff --git a/regression/regression/main.nut b/regression/regression/main.nut index f32c708614..05d6f275bd 100644 --- a/regression/regression/main.nut +++ b/regression/regression/main.nut @@ -341,6 +341,7 @@ function Regression::Cargo() print(" GetCargoIncome(10, 10): " + AICargo.GetCargoIncome(i, 10, 10)); print(" GetCargoIncome(100, 10): " + AICargo.GetCargoIncome(i, 100, 10)); print(" GetCargoIncome(10, 100): " + AICargo.GetCargoIncome(i, 10, 100)); + print(" GetWeight(100): " + AICargo.GetWeight(i, 100)); print(" GetRoadVehicleTypeForCargo(): " + AIRoad.GetRoadVehicleTypeForCargo(i)); } } @@ -1023,6 +1024,30 @@ function Regression::Rail() print(" IsRailTile(): " + AIRail.IsRailTile(10002)); print(" BuildRailTrack(): " + AIRail.BuildRailTrack(10002, AIRail.RAILTRACK_NW_SE)); print(" BuildSignal(): " + AIRail.BuildSignal(10002, 10258, AIRail.SIGNALTYPE_PBS)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10258)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 9746)); + print(" RemoveSignal(): " + AIRail.RemoveSignal(10002, 10258)); + print(" BuildSignal(): " + AIRail.BuildSignal(10002, 9746, AIRail.SIGNALTYPE_ENTRY)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10258)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 9746)); + print(" RemoveSignal(): " + AIRail.RemoveSignal(10002, 9746)); + print(" BuildSignal(): " + AIRail.BuildSignal(10002, 9746, AIRail.SIGNALTYPE_EXIT_TWOWAY)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10258)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 9746)); + print(" RemoveRailTrack(): " + AIRail.RemoveRailTrack(10002, AIRail.RAILTRACK_NW_NE)); + print(" RemoveRailTrack(): " + AIRail.RemoveRailTrack(10002, AIRail.RAILTRACK_NW_SE)); + print(" BuildRailTrack(): " + AIRail.BuildRailTrack(10002, AIRail.RAILTRACK_NW_NE)); + print(" BuildSignal(): " + AIRail.BuildSignal(10002, 10003, AIRail.SIGNALTYPE_PBS)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10003)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10001)); + print(" RemoveSignal(): " + AIRail.RemoveSignal(10002, 10003)); + print(" BuildSignal(): " + AIRail.BuildSignal(10002, 10001, AIRail.SIGNALTYPE_ENTRY)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10003)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10001)); + print(" RemoveSignal(): " + AIRail.RemoveSignal(10002, 10001)); + print(" BuildSignal(): " + AIRail.BuildSignal(10002, 10001, AIRail.SIGNALTYPE_EXIT_TWOWAY)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10003)); + print(" GetSignalType(): " + AIRail.GetSignalType(10002, 10001)); print(" RemoveRailTrack(): " + AIRail.RemoveRailTrack(10002, AIRail.RAILTRACK_NW_NE)); print(" RemoveRailTrack(): " + AIRail.RemoveRailTrack(10002, AIRail.RAILTRACK_NW_SE)); print(" BuildRail(): " + AIRail.BuildRail(10002, 10003, 10006)); diff --git a/regression/regression/result.txt b/regression/regression/result.txt index 984c1de82b..27f90220dc 100644 --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -1118,6 +1118,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): -1 GetCargoIncome(100, 10): -1 GetCargoIncome(10, 100): -1 + GetWeight(100): -1 GetRoadVehicleTypeForCargo(): 1 Cargo 0 IsValidCargo(): true @@ -1130,6 +1131,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 3 GetCargoIncome(100, 10): 38 GetCargoIncome(10, 100): 3 + GetWeight(100): 6 GetRoadVehicleTypeForCargo(): 0 Cargo 1 IsValidCargo(): true @@ -1142,6 +1144,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 7 GetCargoIncome(100, 10): 71 GetCargoIncome(10, 100): 6 + GetWeight(100): 100 GetRoadVehicleTypeForCargo(): 1 Cargo 2 IsValidCargo(): true @@ -1154,6 +1157,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 5 GetCargoIncome(100, 10): 55 GetCargoIncome(10, 100): 5 + GetWeight(100): 25 GetRoadVehicleTypeForCargo(): 1 Cargo 3 IsValidCargo(): true @@ -1166,6 +1170,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 5 GetCargoIncome(100, 10): 53 GetCargoIncome(10, 100): 5 + GetWeight(100): 100 GetRoadVehicleTypeForCargo(): 1 Cargo 4 IsValidCargo(): true @@ -1178,6 +1183,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 5 GetCargoIncome(100, 10): 52 GetCargoIncome(10, 100): 4 + GetWeight(100): 18 GetRoadVehicleTypeForCargo(): 1 Cargo 5 IsValidCargo(): true @@ -1190,6 +1196,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 7 GetCargoIncome(100, 10): 74 GetCargoIncome(10, 100): 6 + GetWeight(100): 50 GetRoadVehicleTypeForCargo(): 1 Cargo 6 IsValidCargo(): true @@ -1202,6 +1209,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 5 GetCargoIncome(100, 10): 58 GetCargoIncome(10, 100): 4 + GetWeight(100): 100 GetRoadVehicleTypeForCargo(): 1 Cargo 7 IsValidCargo(): true @@ -1214,6 +1222,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 6 GetCargoIncome(100, 10): 60 GetCargoIncome(10, 100): 5 + GetWeight(100): 100 GetRoadVehicleTypeForCargo(): 1 Cargo 8 IsValidCargo(): true @@ -1226,6 +1235,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 6 GetCargoIncome(100, 10): 62 GetCargoIncome(10, 100): 5 + GetWeight(100): 100 GetRoadVehicleTypeForCargo(): 1 Cargo 9 IsValidCargo(): true @@ -1238,6 +1248,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 6 GetCargoIncome(100, 10): 69 GetCargoIncome(10, 100): 6 + GetWeight(100): 100 GetRoadVehicleTypeForCargo(): 1 Cargo 10 IsValidCargo(): true @@ -1250,6 +1261,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): 9 GetCargoIncome(100, 10): 90 GetCargoIncome(10, 100): 7 + GetWeight(100): 12 GetRoadVehicleTypeForCargo(): 1 Cargo 11 IsValidCargo(): false @@ -1262,6 +1274,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): -1 GetCargoIncome(100, 10): -1 GetCargoIncome(10, 100): -1 + GetWeight(100): -1 GetRoadVehicleTypeForCargo(): 1 Cargo 12 IsValidCargo(): false @@ -1274,6 +1287,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): -1 GetCargoIncome(100, 10): -1 GetCargoIncome(10, 100): -1 + GetWeight(100): -1 GetRoadVehicleTypeForCargo(): 1 Cargo 13 IsValidCargo(): false @@ -1286,6 +1300,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): -1 GetCargoIncome(100, 10): -1 GetCargoIncome(10, 100): -1 + GetWeight(100): -1 GetRoadVehicleTypeForCargo(): 1 Cargo 14 IsValidCargo(): false @@ -1298,6 +1313,7 @@ ERROR: IsEnd() is invalid as Begin() is never called GetCargoIncome(10, 10): -1 GetCargoIncome(100, 10): -1 GetCargoIncome(10, 100): -1 + GetWeight(100): -1 GetRoadVehicleTypeForCargo(): 1 --CargoList-- @@ -7411,8 +7427,32 @@ ERROR: IsEnd() is invalid as Begin() is never called IsRailTile(): false BuildRailTrack(): true BuildSignal(): true + GetSignalType(): 4 + GetSignalType(): 255 + RemoveSignal(): true + BuildSignal(): true + GetSignalType(): 255 + GetSignalType(): 1 + RemoveSignal(): true + BuildSignal(): true + GetSignalType(): 10 + GetSignalType(): 10 RemoveRailTrack(): false RemoveRailTrack(): true + BuildRailTrack(): true + BuildSignal(): false + GetSignalType(): 255 + GetSignalType(): 255 + RemoveSignal(): false + BuildSignal(): true + GetSignalType(): 255 + GetSignalType(): 1 + RemoveSignal(): true + BuildSignal(): true + GetSignalType(): 255 + GetSignalType(): 10 + RemoveRailTrack(): true + RemoveRailTrack(): false BuildRail(): true HasTransportType(): true HasTransportType(): false @@ -9272,12 +9312,12 @@ ERROR: IsEnd() is invalid as Begin() is never called GetLocation(): 33417 GetEngineType(): 153 GetUnitNumber(): 1 - GetAge(): 1 + GetAge(): 0 GetMaxAge(): 5490 - GetAgeLeft(): 5489 + GetAgeLeft(): 5490 GetCurrentSpeed(): 7 GetRunningCost(): 421 - GetProfitThisYear(): -1 + GetProfitThisYear(): 0 GetProfitLastYear(): 0 GetCurrentValue(): 5947 GetVehicleType(): 1 @@ -9287,7 +9327,7 @@ ERROR: IsEnd() is invalid as Begin() is never called IsInDepot(): false GetNumWagons(): 1 GetWagonEngineType(): 153 - GetWagonAge(): 1 + GetWagonAge(): 0 GetLength(): 8 GetOwner(): 1 BuildVehicle(): 14 @@ -9360,11 +9400,11 @@ ERROR: IsEnd() is invalid as Begin() is never called 14 => 1 12 => 1 Age ListDump: + 17 => 1 + 16 => 1 + 14 => 1 13 => 1 12 => 1 - 17 => 0 - 16 => 0 - 14 => 0 MaxAge ListDump: 16 => 10980 14 => 10980 @@ -9372,9 +9412,9 @@ ERROR: IsEnd() is invalid as Begin() is never called 13 => 5490 12 => 5490 AgeLeft ListDump: - 16 => 10980 - 14 => 10980 - 17 => 7320 + 16 => 10979 + 14 => 10979 + 17 => 7319 13 => 5489 12 => 5489 CurrentSpeed ListDump: diff --git a/regression/stationlist/info.nut b/regression/stationlist/info.nut index ad91c7645c..099a7d12ec 100644 --- a/regression/stationlist/info.nut +++ b/regression/stationlist/info.nut @@ -4,7 +4,7 @@ class StationList extends AIInfo { function GetShortName() { return "REGS"; } function GetDescription() { return "This runs stationlist-tests on some commands. On the same map the result should always be the same."; } function GetVersion() { return 1; } - function GetAPIVersion() { return "12"; } + function GetAPIVersion() { return "13"; } function GetDate() { return "2007-03-18"; } function CreateInstance() { return "StationList"; } function UseAsRandomAI() { return false; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6d053a1eca..d5e5796b9d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(ai) add_subdirectory(blitter) add_subdirectory(citymania) add_subdirectory(core) +add_subdirectory(fontcache) add_subdirectory(game) add_subdirectory(lang) add_subdirectory(linkgraph) @@ -30,18 +31,15 @@ add_files( viewport_sprite_sorter_sse4.cpp CONDITION SSE_FOUND ) -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - set_compile_flags( - viewport_sprite_sorter_sse4.cpp - COMPILE_FLAGS -msse4.1) -endif() add_files( aircraft.h aircraft_cmd.cpp + aircraft_cmd.h aircraft_gui.cpp airport.cpp airport.h + airport_cmd.h airport_gui.cpp animated_tile.cpp animated_tile_func.h @@ -50,6 +48,7 @@ add_files( autoreplace.cpp autoreplace_base.h autoreplace_cmd.cpp + autoreplace_cmd.h autoreplace_func.h autoreplace_gui.cpp autoreplace_gui.h @@ -85,12 +84,12 @@ add_files( clear_cmd.cpp clear_func.h clear_map.h - cmd_helper.h command.cpp command_func.h command_type.h company_base.h company_cmd.cpp + company_cmd.h company_func.h company_gui.cpp company_gui.h @@ -120,6 +119,7 @@ add_files( depot.cpp depot_base.h depot_cmd.cpp + depot_cmd.h depot_func.h depot_gui.cpp depot_map.h @@ -128,11 +128,13 @@ add_files( direction_type.h disaster_vehicle.cpp disaster_vehicle.h + dock_cmd.h dock_gui.cpp driver.cpp driver.h economy.cpp economy_base.h + economy_cmd.h economy_func.h economy_type.h effectvehicle.cpp @@ -142,6 +144,7 @@ add_files( elrail_func.h engine.cpp engine_base.h + engine_cmd.h engine_func.h engine_gui.cpp engine_gui.h @@ -156,7 +159,6 @@ add_files( fios_gui.cpp fontcache.cpp fontcache.h - fontcache_internal.h fontdetection.h framerate_gui.cpp framerate_type.h @@ -175,6 +177,7 @@ add_files( gfxinit.h goal.cpp goal_base.h + goal_cmd.h goal_gui.cpp goal_type.h graph_gui.cpp @@ -183,6 +186,7 @@ add_files( ground_vehicle.hpp group.h group_cmd.cpp + group_cmd.h group_gui.cpp group_gui.h group_type.h @@ -199,6 +203,7 @@ add_files( house_type.h industry.h industry_cmd.cpp + industry_cmd.h industry_gui.cpp industry_map.h industry_type.h @@ -209,6 +214,7 @@ add_files( intro_gui.cpp landscape.cpp landscape.h + landscape_cmd.h landscape_type.h language.h livery.h @@ -218,6 +224,7 @@ add_files( map_type.h misc.cpp misc_cmd.cpp + misc_cmd.h misc_gui.cpp mixer.cpp mixer.h @@ -278,6 +285,7 @@ add_files( newgrf_town.h newgrf_townname.cpp newgrf_townname.h + news_cmd.h news_func.h news_gui.cpp news_gui.h @@ -285,6 +293,7 @@ add_files( object.h object_base.h object_cmd.cpp + object_cmd.h object_gui.cpp object_map.h object_type.h @@ -294,6 +303,7 @@ add_files( order_backup.h order_base.h order_cmd.cpp + order_cmd.h order_func.h order_gui.cpp order_type.h @@ -306,6 +316,7 @@ add_files( rail.cpp rail.h rail_cmd.cpp + rail_cmd.h rail_gui.cpp rail_gui.h rail_map.h @@ -328,6 +339,7 @@ add_files( roadstop_base.h roadveh.h roadveh_cmd.cpp + roadveh_cmd.h roadveh_gui.cpp safeguards.h screenshot_gui.cpp @@ -335,6 +347,7 @@ add_files( screenshot.cpp screenshot.h settings.cpp + settings_cmd.h settings_func.h settings_gui.cpp settings_gui.h @@ -344,6 +357,7 @@ add_files( settings_type.h ship.h ship_cmd.cpp + ship_cmd.h ship_gui.cpp signal.cpp signal_func.h @@ -351,6 +365,7 @@ add_files( signs.cpp signs_base.h signs_cmd.cpp + signs_cmd.h signs_func.h signs_gui.cpp signs_type.h @@ -369,6 +384,7 @@ add_files( station.cpp station_base.h station_cmd.cpp + station_cmd.h station_func.h station_gui.cpp station_gui.h @@ -380,6 +396,7 @@ add_files( stdafx.h story.cpp story_base.h + story_cmd.h story_gui.cpp story_type.h strgen/strgen.h @@ -394,11 +411,13 @@ add_files( strings_type.h subsidy.cpp subsidy_base.h + subsidy_cmd.h subsidy_func.h subsidy_gui.cpp subsidy_type.h tar_type.h terraform_cmd.cpp + terraform_cmd.h terraform_gui.cpp terraform_gui.h textbuf.cpp @@ -423,11 +442,13 @@ add_files( tilematrix_type.hpp timetable.h timetable_cmd.cpp + timetable_cmd.h timetable_gui.cpp toolbar_gui.cpp toolbar_gui.h town.h town_cmd.cpp + town_cmd.h town_gui.cpp town_kdtree.h town_map.h @@ -439,22 +460,26 @@ add_files( track_type.h train.h train_cmd.cpp + train_cmd.h train_gui.cpp transparency.h transparency_gui.cpp transparency_gui.h transport_type.h tree_cmd.cpp + tree_cmd.h tree_gui.cpp tree_map.h tunnel_map.cpp tunnel_map.h tunnelbridge.h tunnelbridge_cmd.cpp + tunnelbridge_cmd.h tunnelbridge_map.h vehicle.cpp vehicle_base.h vehicle_cmd.cpp + vehicle_cmd.h vehicle_func.h vehicle_gui.cpp vehicle_gui.h @@ -463,6 +488,7 @@ add_files( vehiclelist.cpp vehiclelist.h viewport.cpp + viewport_cmd.h viewport_func.h viewport_gui.cpp viewport_kdtree.h @@ -473,10 +499,12 @@ add_files( walltime_func.h water.h water_cmd.cpp + water_cmd.h water_map.h waypoint.cpp waypoint_base.h waypoint_cmd.cpp + waypoint_cmd.h waypoint_func.h waypoint_gui.cpp widget.cpp diff --git a/src/ai/ai.hpp b/src/ai/ai.hpp index 59886486fe..f9112fc177 100644 --- a/src/ai/ai.hpp +++ b/src/ai/ai.hpp @@ -139,9 +139,9 @@ public: static int GetStartNextTime(); /** Wrapper function for AIScanner::GetAIConsoleList */ - static char *GetConsoleList(char *p, const char *last, bool newest_only = false); + static std::string GetConsoleList(bool newest_only = false); /** Wrapper function for AIScanner::GetAIConsoleLibraryList */ - static char *GetConsoleLibraryList(char *p, const char *last); + static std::string GetConsoleLibraryList(); /** Wrapper function for AIScanner::GetAIInfoList */ static const ScriptInfoList *GetInfoList(); /** Wrapper function for AIScanner::GetUniqueAIInfoList */ diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp index e45738776d..add76e0685 100644 --- a/src/ai/ai_core.cpp +++ b/src/ai/ai_core.cpp @@ -315,14 +315,14 @@ return DAYS_IN_YEAR; } -/* static */ char *AI::GetConsoleList(char *p, const char *last, bool newest_only) +/* static */ std::string AI::GetConsoleList(bool newest_only) { - return AI::scanner_info->GetConsoleList(p, last, newest_only); + return AI::scanner_info->GetConsoleList(newest_only); } -/* static */ char *AI::GetConsoleLibraryList(char *p, const char *last) +/* static */ std::string AI::GetConsoleLibraryList() { - return AI::scanner_library->GetConsoleList(p, last, true); + return AI::scanner_library->GetConsoleList(true); } /* static */ const ScriptInfoList *AI::GetInfoList() diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp index c7ae4740e2..b97e9f72ea 100644 --- a/src/ai/ai_gui.cpp +++ b/src/ai/ai_gui.cpp @@ -28,6 +28,8 @@ #include "../hotkeys.h" #include "../core/geometry_func.hpp" #include "../guitimer_func.h" +#include "../company_cmd.h" +#include "../misc_cmd.h" #include "ai.hpp" #include "ai_gui.hpp" @@ -683,10 +685,10 @@ static const NWidgetPart _nested_ai_config_widgets[] = { NWidget(WWT_PANEL, COLOUR_MAUVE, WID_AIC_BACKGROUND), NWidget(NWID_VERTICAL), SetPIP(4, 4, 4), NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7), - NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE), SetFill(0, 1), SetDataTip(AWV_DECREASE, STR_NULL), - NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE), SetFill(0, 1), SetDataTip(AWV_INCREASE, STR_NULL), + NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE), SetDataTip(AWV_DECREASE, STR_NULL), + NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE), SetDataTip(AWV_INCREASE, STR_NULL), NWidget(NWID_SPACER), SetMinimalSize(6, 0), - NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_NUMBER), SetDataTip(STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS, STR_NULL), SetFill(1, 0), SetPadding(1, 0, 0, 0), + NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_NUMBER), SetDataTip(STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS, STR_NULL), SetFill(1, 0), EndContainer(), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7), NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_MOVE_UP), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_CONFIG_MOVE_UP, STR_AI_CONFIG_MOVE_UP_TOOLTIP), @@ -778,6 +780,11 @@ struct AIConfigWindow : public Window { void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override { switch (widget) { + case WID_AIC_DECREASE: + case WID_AIC_INCREASE: + *size = maxdim(*size, NWidgetScrollbar::GetHorizontalDimension()); + break; + case WID_AIC_GAMELIST: this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM; size->height = 1 * this->line_height; @@ -1290,8 +1297,8 @@ struct AIDebugWindow : public Window { case WID_AID_RELOAD_TOGGLE: if (ai_debug_company == OWNER_DEITY) break; /* First kill the company of the AI, then start a new one. This should start the current AI again */ - DoCommandP(0, CCA_DELETE | ai_debug_company << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL); - DoCommandP(0, CCA_NEW_AI | ai_debug_company << 16, 0, CMD_COMPANY_CTRL); + Command::Post(CCA_DELETE, ai_debug_company, CRR_MANUAL, INVALID_CLIENT_ID); + Command::Post(CCA_NEW_AI, ai_debug_company, CRR_NONE, INVALID_CLIENT_ID); break; case WID_AID_SETTINGS: @@ -1330,7 +1337,7 @@ struct AIDebugWindow : public Window { } if (all_unpaused) { /* All scripts have been unpaused => unpause the game. */ - DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE); + Command::Post(PM_PAUSED_NORMAL, false); } } } @@ -1379,7 +1386,7 @@ struct AIDebugWindow : public Window { /* Pause the game. */ if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) { - DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE); + Command::Post(PM_PAUSED_NORMAL, true); } /* Highlight row that matched */ diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp index 17ad012579..fcd1b5a1a9 100644 --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -25,7 +25,7 @@ */ static bool CheckAPIVersion(const char *api_version) { - static const std::set versions = { "0.7", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12" }; + static const std::set versions = { "0.7", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10", "1.11", "12", "13" }; return versions.find(api_version) != versions.end(); } diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 9e1f001631..0e8355d733 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -18,6 +18,7 @@ #include "ai.hpp" #include "../script/script_storage.hpp" +#include "../script/script_cmd.h" #include "ai_info.hpp" #include "ai_instance.hpp" @@ -92,13 +93,13 @@ ScriptInfo *AIInstance::FindLibrary(const char *library, int version) /** * DoCommand callback function for all commands executed by AIs. + * @param cmd cmd as given to DoCommandPInternal. * @param result The result of the command. * @param tile The tile on which the command was executed. - * @param p1 p1 as given to DoCommandPInternal. - * @param p2 p2 as given to DoCommandPInternal. - * @param cmd cmd as given to DoCommandPInternal. + * @param data Command data as given to Command<>::Post. + * @param result_data Additional returned data from the command. */ -void CcAI(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) +void CcAI(Commands cmd, const CommandCost &result, TileIndex tile, const CommandDataBuffer &data, CommandDataBuffer result_data) { /* * The company might not exist anymore. Check for this. @@ -109,12 +110,12 @@ void CcAI(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint3 const Company *c = Company::GetIfValid(_current_company); if (c == nullptr || c->ai_instance == nullptr) return; - if (c->ai_instance->DoCommandCallback(result, tile, p1, p2, cmd)) { + if (c->ai_instance->DoCommandCallback(result, tile, data, std::move(result_data), cmd)) { c->ai_instance->Continue(); } } -CommandCallback *AIInstance::GetDoCommandCallback() +CommandCallbackData *AIInstance::GetDoCommandCallback() { return &CcAI; } diff --git a/src/ai/ai_instance.hpp b/src/ai/ai_instance.hpp index f8d2100b1c..2cdabd9913 100644 --- a/src/ai/ai_instance.hpp +++ b/src/ai/ai_instance.hpp @@ -29,7 +29,7 @@ public: private: void RegisterAPI() override; void Died() override; - CommandCallback *GetDoCommandCallback() override; + CommandCallbackData *GetDoCommandCallback() override; void LoadDummyScript() override; }; diff --git a/src/aircraft.h b/src/aircraft.h index d201743a6f..0a5c0e5530 100644 --- a/src/aircraft.h +++ b/src/aircraft.h @@ -91,7 +91,7 @@ struct Aircraft FINAL : public SpecializedVehicle { void MarkDirty(); void UpdateDeltaXY(); - ExpensesType GetExpenseType(bool income) const { return income ? EXPENSES_AIRCRAFT_INC : EXPENSES_AIRCRAFT_RUN; } + ExpensesType GetExpenseType(bool income) const { return income ? EXPENSES_AIRCRAFT_REVENUE : EXPENSES_AIRCRAFT_RUN; } bool IsPrimaryVehicle() const { return this->IsNormalAircraft(); } void GetImage(Direction direction, EngineImageType image_type, VehicleSpriteSeq *result) const; int GetDisplaySpeed() const { return this->cur_speed; } diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index f4f933982a..d67969c006 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -37,6 +37,8 @@ #include "disaster_vehicle.h" #include "newgrf_airporttiles.h" #include "framerate_type.h" +#include "aircraft_cmd.h" +#include "vehicle_cmd.h" #include "table/strings.h" @@ -188,7 +190,7 @@ void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteS const Aircraft *w = v->Next()->Next(); if (is_custom_sprite(v->spritenum)) { - GetCustomRotorSprite(v, false, image_type, result); + GetCustomRotorSprite(v, image_type, result); if (result->IsValid()) return; } @@ -258,14 +260,13 @@ void GetAircraftSpriteSize(EngineID engine, uint &width, uint &height, int &xoff /** * Build an aircraft. - * @param tile tile of the depot where aircraft is built. * @param flags type of operation. + * @param tile tile of the depot where aircraft is built. * @param e the engine to build. - * @param data unused. * @param[out] ret the vehicle that has been built. * @return the cost of this operation or an error. */ -CommandCost CmdBuildAircraft(TileIndex tile, DoCommandFlag flags, const Engine *e, uint16 data, Vehicle **ret) +CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **ret) { const AircraftVehicleInfo *avi = &e->u.air; const Station *st = Station::GetByTile(tile); @@ -327,8 +328,6 @@ CommandCost CmdBuildAircraft(TileIndex tile, DoCommandFlag flags, const Engine * v->reliability_spd_dec = e->reliability_spd_dec; v->max_age = e->GetLifeLengthInDays(); - _new_vehicle_id = v->index; - v->pos = GetVehiclePosOnBuild(tile); v->state = HANGAR; @@ -1274,7 +1273,7 @@ void HandleMissingAircraftOrders(Aircraft *v) const Station *st = GetTargetAirportIfValid(v); if (st == nullptr) { Backup cur_company(_current_company, v->owner, FILE_LINE); - CommandCost ret = DoCommand(v->tile, v->index, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT); + CommandCost ret = Command::Do(DC_EXEC, v->index, DepotCommand::None, {}); cur_company.Restore(); if (ret.Failed()) CrashAirplane(v); @@ -1341,7 +1340,12 @@ static void CrashAirplane(Aircraft *v) AI::NewEvent(v->owner, new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING)); Game::NewEvent(new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING)); - AddTileNewsItem(newsitem, NT_ACCIDENT, vt, nullptr, st != nullptr ? st->index : INVALID_STATION); + NewsType newstype = NT_ACCIDENT; + if (v->owner != _local_company) { + newstype = NT_ACCIDENT_OTHER; + } + + AddTileNewsItem(newsitem, newstype, vt, nullptr, st != nullptr ? st->index : INVALID_STATION); ModifyStationRatingAround(vt, v->owner, -160, 30); if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v); @@ -1632,7 +1636,7 @@ static void AircraftEventHandler_HeliTakeOff(Aircraft *v, const AirportFTAClass /* Send the helicopter to a hangar if needed for replacement */ if (v->NeedsAutomaticServicing()) { Backup cur_company(_current_company, v->owner, FILE_LINE); - DoCommand(v->tile, v->index | DEPOT_SERVICE | DEPOT_LOCATE_HANGAR, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT); + Command::Do(DC_EXEC, v->index, DepotCommand::Service | DepotCommand::LocateHangar, {}); cur_company.Restore(); } } @@ -1683,7 +1687,7 @@ static void AircraftEventHandler_Landing(Aircraft *v, const AirportFTAClass *apc /* check if the aircraft needs to be replaced or renewed and send it to a hangar if needed */ if (v->NeedsAutomaticServicing()) { Backup cur_company(_current_company, v->owner, FILE_LINE); - DoCommand(v->tile, v->index | DEPOT_SERVICE, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT); + Command::Do(DC_EXEC, v->index, DepotCommand::Service, {}); cur_company.Restore(); } } diff --git a/src/aircraft_cmd.h b/src/aircraft_cmd.h new file mode 100644 index 0000000000..df58739a54 --- /dev/null +++ b/src/aircraft_cmd.h @@ -0,0 +1,19 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file aircraft_cmd.h Command definitions related to aircraft. */ + +#ifndef AIRCRAFT_CMD_H +#define AIRCRAFT_CMD_H + +#include "command_type.h" +#include "engine_type.h" +#include "vehicle_type.h" + +CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *e, Vehicle **v); + +#endif /* AIRCRAFT_CMD_H */ diff --git a/src/aircraft_gui.cpp b/src/aircraft_gui.cpp index 311bc497bf..422f422002 100644 --- a/src/aircraft_gui.cpp +++ b/src/aircraft_gui.cpp @@ -100,7 +100,7 @@ void DrawAircraftImage(const Vehicle *v, int left, int right, int y, VehicleID s if (helicopter) { const Aircraft *a = Aircraft::From(v); VehicleSpriteSeq rotor_seq; - GetCustomRotorSprite(a, true, image_type, &rotor_seq); + GetCustomRotorSprite(a, image_type, &rotor_seq); if (!rotor_seq.IsValid()) rotor_seq.Set(SPR_ROTOR_STOPPED); heli_offs = ScaleGUITrad(5); rotor_seq.Draw(x, y + y_offs - heli_offs, PAL_NONE, false); diff --git a/src/airport_cmd.h b/src/airport_cmd.h new file mode 100644 index 0000000000..2e27057d8c --- /dev/null +++ b/src/airport_cmd.h @@ -0,0 +1,17 @@ +/* + * 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 airport_cmd.h Command definitions related to airports. */ + +#ifndef AIRPORT_CMD_H +#define AIRPORT_CMD_H + +#include "command_type.h" + +CommandCallback CcBuildAirport; + +#endif /* AIRPORT_CMD_H */ diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 4d09fbdfdf..f8ef410d85 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -8,6 +8,7 @@ /** @file airport_gui.cpp The GUI for airports. */ #include "stdafx.h" +#include "economy_func.h" #include "window_gui.h" #include "station_gui.h" #include "terraform_gui.h" @@ -26,6 +27,10 @@ #include "hotkeys.h" #include "vehicle_func.h" #include "gui.h" +#include "command_func.h" +#include "airport_cmd.h" +#include "station_cmd.h" +#include "zoom_func.h" #include "widgets/airport_widget.h" @@ -40,7 +45,7 @@ static void ShowBuildAirportPicker(Window *parent); SpriteID GetCustomAirportSprite(const AirportSpec *as, byte layout); -void CcBuildAirport(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) +void CcBuildAirport(Commands cmd, const CommandCost &result, TileIndex tile) { if (result.Failed()) return; @@ -55,13 +60,20 @@ void CcBuildAirport(const CommandCost &result, TileIndex tile, uint32 p1, uint32 static void PlaceAirport(TileIndex tile) { if (_selected_airport_index == -1) return; - uint32 p2 = _ctrl_pressed; - SB(p2, 16, 16, INVALID_STATION); // no station to join - uint32 p1 = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex(); - p1 |= _selected_airport_layout << 8; - CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_AIRPORT | CMD_MSG(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE), CcBuildAirport, "" }; - ShowSelectStationIfNeeded(cmdcont, TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE)); + byte airport_type = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index)->GetIndex(); + byte layout = _selected_airport_layout; + bool adjacent = _ctrl_pressed; + + auto proc = [=](bool test, StationID to_join) -> bool { + if (test) { + return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), tile, airport_type, layout, INVALID_STATION, adjacent).Succeeded(); + } else { + return Command::Post(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, CcBuildAirport, tile, airport_type, layout, to_join, adjacent); + } + }; + + ShowSelectStationIfNeeded(TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE), proc); } /** Airport build toolbar window handler. */ @@ -413,11 +425,8 @@ public: { this->DrawWidgets(); - uint16 top = this->GetWidget(WID_AP_BTN_DOHILIGHT)->pos_y + this->GetWidget(WID_AP_BTN_DOHILIGHT)->current_y + WD_PAR_VSEP_NORMAL; - NWidgetBase *panel_nwi = this->GetWidget(WID_AP_BOTTOMPANEL); - - int right = panel_nwi->pos_x + panel_nwi->current_x; - int bottom = panel_nwi->pos_y + panel_nwi->current_y; + Rect r = this->GetWidget(WID_AP_ACCEPTANCE)->GetCurrentRect(); + int top = r.top + ScaleGUITrad(WD_PAR_VSEP_NORMAL); if (_selected_airport_index != -1) { const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index); @@ -427,20 +436,27 @@ public: if (_settings_game.economy.station_noise_level) { /* show the noise of the selected airport */ SetDParam(0, as->noise_level); - DrawString(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, STR_STATION_BUILD_NOISE); - top += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL; + DrawString(r.left, r.right, top, STR_STATION_BUILD_NOISE); + top += FONT_HEIGHT_NORMAL + ScaleGUITrad(WD_PAR_VSEP_NORMAL); + } + + if (_settings_game.economy.infrastructure_maintenance) { + Money monthly = _price[PR_INFRASTRUCTURE_AIRPORT] * as->maintenance_cost >> 3; + SetDParam(0, monthly * 12); + DrawString(r.left, r.right, top, STR_STATION_BUILD_INFRASTRUCTURE_COST); + top += FONT_HEIGHT_NORMAL + ScaleGUITrad(WD_PAR_VSEP_NORMAL); } /* strings such as 'Size' and 'Coverage Area' */ - top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL; - top = DrawStationCoverageAreaText(panel_nwi->pos_x + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL; + top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, false) + ScaleGUITrad(WD_PAR_VSEP_NORMAL); + top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, true) + ScaleGUITrad(WD_PAR_VSEP_NORMAL); } /* Resize background if the window is too small. * Never make the window smaller to avoid oscillating if the size change affects the acceptance. * (This is the case, if making the window bigger moves the mouse into the window.) */ - if (top > bottom) { - ResizeWindow(this, 0, top - bottom, false); + if (top > r.bottom) { + ResizeWindow(this, 0, top - r.bottom, false); } } @@ -586,8 +602,8 @@ static const NWidgetPart _nested_build_airport_widgets[] = { NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_AP_EXTRA_TEXT), SetFill(1, 0), SetMinimalSize(150, 0), EndContainer(), /* Bottom panel. */ - NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_AP_BOTTOMPANEL), SetPIP(2, 2, 2), - NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_AP_BOTTOMPANEL), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetPadding(WD_FRAMERECT_TOP, WD_FRAMERECT_RIGHT, WD_FRAMERECT_BOTTOM, WD_FRAMERECT_LEFT), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0), NWidget(NWID_HORIZONTAL), NWidget(NWID_SPACER), SetMinimalSize(14, 0), SetFill(1, 0), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), @@ -598,7 +614,7 @@ static const NWidgetPart _nested_build_airport_widgets[] = { EndContainer(), NWidget(NWID_SPACER), SetMinimalSize(14, 0), SetFill(1, 0), EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 10), SetResize(0, 1), SetFill(1, 0), + NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_AP_ACCEPTANCE), SetPadding(0, WD_FRAMERECT_RIGHT, 0, WD_FRAMERECT_LEFT), SetResize(0, 1), SetFill(1, 0), EndContainer(), }; diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 4f9cf92bb4..654003e39e 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -22,6 +22,11 @@ #include "ai/ai.hpp" #include "news_func.h" #include "strings_func.h" +#include "autoreplace_cmd.h" +#include "group_cmd.h" +#include "order_cmd.h" +#include "train_cmd.h" +#include "vehicle_cmd.h" #include "table/strings.h" @@ -206,7 +211,7 @@ static int GetIncompatibleRefitOrderIdForAutoreplace(const Vehicle *v, EngineID const Order *o; const Vehicle *u = (v->type == VEH_TRAIN) ? v->First() : v; - const OrderList *orders = u->orders.list; + const OrderList *orders = u->orders; if (orders == nullptr) return -1; for (VehicleOrderID i = 0; i < orders->GetNumOrders(); i++) { o = orders->GetOrderAt(i); @@ -340,23 +345,24 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic } /* Build the new vehicle */ - cost = DoCommand(old_veh->tile, e | (CT_INVALID << 24), 0, DC_EXEC | DC_AUTOREPLACE, GetCmdBuildVeh(old_veh)); + VehicleID new_veh_id; + std::tie(cost, new_veh_id, std::ignore, std::ignore) = Command::Do(DC_EXEC | DC_AUTOREPLACE, old_veh->tile, e, true, CT_INVALID, INVALID_CLIENT_ID); if (cost.Failed()) return cost; - Vehicle *new_veh = Vehicle::Get(_new_vehicle_id); + Vehicle *new_veh = Vehicle::Get(new_veh_id); *new_vehicle = new_veh; /* Refit the vehicle if needed */ if (refit_cargo != CT_NO_REFIT) { byte subtype = GetBestFittingSubType(old_veh, new_veh, refit_cargo); - cost.AddCost(DoCommand(0, new_veh->index, refit_cargo | (subtype << 8), DC_EXEC, GetCmdRefitVeh(new_veh))); + cost.AddCost(std::get<0>(Command::Do(DC_EXEC, new_veh->index, refit_cargo, subtype, false, false, 0))); assert(cost.Succeeded()); // This should be ensured by GetNewCargoTypeForReplace() } /* 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)) { - DoCommand(0, new_veh->index, true, DC_EXEC, CMD_REVERSE_TRAIN_DIRECTION); + Command::Do(DC_EXEC, new_veh->index, true); } return cost; @@ -368,9 +374,9 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic * @param evaluate_callback shall the start/stop callback be evaluated? * @return success or error */ -static inline CommandCost CmdStartStopVehicle(const Vehicle *v, bool evaluate_callback) +static inline CommandCost DoCmdStartStopVehicle(const Vehicle *v, bool evaluate_callback) { - return DoCommand(0, v->index, evaluate_callback ? 1 : 0, DC_EXEC | DC_AUTOREPLACE, CMD_START_STOP_VEHICLE); + return Command::Do(DC_EXEC | DC_AUTOREPLACE, v->index, evaluate_callback); } /** @@ -383,7 +389,7 @@ static inline CommandCost CmdStartStopVehicle(const Vehicle *v, bool evaluate_ca */ static inline CommandCost CmdMoveVehicle(const Vehicle *v, const Vehicle *after, DoCommandFlag flags, bool whole_chain) { - return DoCommand(0, v->index | (whole_chain ? 1 : 0) << 20, after != nullptr ? after->index : INVALID_VEHICLE, flags | DC_NO_CARGO_CAP_CHECK, CMD_MOVE_RAIL_VEHICLE); + return Command::Do(flags | DC_NO_CARGO_CAP_CHECK, v->index, after != nullptr ? after->index : INVALID_VEHICLE, whole_chain); } /** @@ -397,19 +403,19 @@ static CommandCost CopyHeadSpecificThings(Vehicle *old_head, Vehicle *new_head, CommandCost cost = CommandCost(); /* Share orders */ - if (cost.Succeeded() && old_head != new_head) cost.AddCost(DoCommand(0, new_head->index | CO_SHARE << 30, old_head->index, DC_EXEC, CMD_CLONE_ORDER)); + if (cost.Succeeded() && old_head != new_head) cost.AddCost(Command::Do(DC_EXEC, CO_SHARE, new_head->index, old_head->index)); /* Copy group membership */ - if (cost.Succeeded() && old_head != new_head) cost.AddCost(DoCommand(0, old_head->group_id, new_head->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP)); + if (cost.Succeeded() && old_head != new_head) cost.AddCost(std::get<0>(Command::Do(DC_EXEC, old_head->group_id, new_head->index, false))); /* Perform start/stop check whether the new vehicle suits newgrf restrictions etc. */ if (cost.Succeeded()) { /* Start the vehicle, might be denied by certain things */ assert((new_head->vehstatus & VS_STOPPED) != 0); - cost.AddCost(CmdStartStopVehicle(new_head, true)); + cost.AddCost(DoCmdStartStopVehicle(new_head, true)); /* Stop the vehicle again, but do not care about evil newgrfs allowing starting but not stopping :p */ - if (cost.Succeeded()) cost.AddCost(CmdStartStopVehicle(new_head, false)); + if (cost.Succeeded()) cost.AddCost(DoCmdStartStopVehicle(new_head, false)); } /* Last do those things which do never fail (resp. we do not care about), but which are not undo-able */ @@ -466,11 +472,11 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b } /* Sell the old vehicle */ - cost.AddCost(DoCommand(0, old_v->index, 0, flags, GetCmdSellVeh(old_v))); + cost.AddCost(Command::Do(flags, old_v->index, false, false, INVALID_CLIENT_ID)); /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { - DoCommand(0, new_v->index, 0, DC_EXEC, GetCmdSellVeh(new_v)); + Command::Do(DC_EXEC, new_v->index, false, false, INVALID_CLIENT_ID); } } @@ -597,7 +603,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon assert(RailVehInfo(wagon->engine_type)->railveh_type == RAILVEH_WAGON); /* Sell wagon */ - [[maybe_unused]] CommandCost ret = DoCommand(0, wagon->index, 0, DC_EXEC, GetCmdSellVeh(wagon)); + [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC, wagon->index, false, false, INVALID_CLIENT_ID); assert(ret.Succeeded()); new_vehs[i] = nullptr; @@ -629,7 +635,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon /* Sell the vehicle. * Note: This might temporarily construct new trains, so use DC_AUTOREPLACE to prevent * it from failing due to engine limits. */ - cost.AddCost(DoCommand(0, w->index, 0, flags | DC_AUTOREPLACE, GetCmdSellVeh(w))); + cost.AddCost(Command::Do(flags | DC_AUTOREPLACE, w->index, false, false, INVALID_CLIENT_ID)); if ((flags & DC_EXEC) != 0) { old_vehs[i] = nullptr; if (i == 0) old_head = nullptr; @@ -660,7 +666,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon if ((flags & DC_EXEC) == 0) { for (int i = num_units - 1; i >= 0; i--) { if (new_vehs[i] != nullptr) { - DoCommand(0, new_vehs[i]->index, 0, DC_EXEC, GetCmdSellVeh(new_vehs[i])); + Command::Do(DC_EXEC, new_vehs[i]->index, false, false, INVALID_CLIENT_ID); new_vehs[i] = nullptr; } } @@ -691,12 +697,12 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon } /* Sell the old vehicle */ - cost.AddCost(DoCommand(0, old_head->index, 0, flags, GetCmdSellVeh(old_head))); + cost.AddCost(Command::Do(flags, old_head->index, false, false, INVALID_CLIENT_ID)); } /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { - DoCommand(0, new_head->index, 0, DC_EXEC, GetCmdSellVeh(new_head)); + Command::Do(DC_EXEC, new_head->index, false, false, INVALID_CLIENT_ID); } } } @@ -707,22 +713,18 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon /** * Autoreplaces a vehicle * Trains are replaced as a whole chain, free wagons in depot are replaced on their own - * @param tile not used * @param flags type of operation - * @param p1 Index of vehicle - * @param p2 not used - * @param text unused + * @param veh_id Index of vehicle * @return the cost of this operation or an error */ -CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text) +CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id) { - Vehicle *v = Vehicle::GetIfValid(p1); + Vehicle *v = Vehicle::GetIfValid(veh_id); if (v == nullptr) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; - if (!v->IsChainInDepot()) return CMD_ERROR; if (v->vehstatus & VS_CRASHED) return CMD_ERROR; bool free_wagon = false; @@ -734,6 +736,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 } else { if (!v->IsPrimaryVehicle()) return CMD_ERROR; } + if (!v->IsChainInDepot()) return CMD_ERROR; const Company *c = Company::Get(_current_company); bool wagon_removal = c->settings.renew_keep_length; @@ -759,7 +762,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); /* Stop the vehicle */ - if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, true)); + if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true)); if (cost.Failed()) return cost; assert(free_wagon || v->IsStoppedInDepot()); @@ -787,7 +790,7 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 } /* Restart the vehicle */ - if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, false)); + if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false)); } if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); @@ -796,35 +799,29 @@ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1 /** * Change engine renewal parameters - * @param tile unused * @param flags operation to perform - * @param p1 packed data - * - bit 0 = replace when engine gets old? - * - bits 16-31 = engine group - * @param p2 packed data - * - bits 0-15 = old engine type - * - bits 16-31 = new engine type - * @param text unused + * @param id_g engine group + * @param old_engine_type old engine type + * @param new_engine_type new engine type + * @param when_old replace when engine gets old? * @return the cost of this operation or an error */ -CommandCost CmdSetAutoReplace(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const std::string &text) +CommandCost CmdSetAutoReplace(DoCommandFlag flags, GroupID id_g, EngineID old_engine_type, EngineID new_engine_type, bool when_old) { Company *c = Company::GetIfValid(_current_company); if (c == nullptr) return CMD_ERROR; - EngineID old_engine_type = GB(p2, 0, 16); - EngineID new_engine_type = GB(p2, 16, 16); - GroupID id_g = GB(p1, 16, 16); CommandCost cost; if (Group::IsValidID(id_g) ? Group::Get(id_g)->owner != _current_company : !IsAllGroupID(id_g) && !IsDefaultGroupID(id_g)) return CMD_ERROR; if (!Engine::IsValidID(old_engine_type)) return CMD_ERROR; + if (Group::IsValidID(id_g) && Group::Get(id_g)->vehicle_type != Engine::Get(old_engine_type)->type) return CMD_ERROR; if (new_engine_type != INVALID_ENGINE) { if (!Engine::IsValidID(new_engine_type)) return CMD_ERROR; if (!CheckAutoreplaceValidity(old_engine_type, new_engine_type, _current_company)) return CMD_ERROR; - cost = AddEngineReplacementForCompany(c, old_engine_type, new_engine_type, id_g, HasBit(p1, 0), flags); + cost = AddEngineReplacementForCompany(c, old_engine_type, new_engine_type, id_g, when_old, flags); } else { cost = RemoveEngineReplacementForCompany(c, old_engine_type, id_g, flags); } diff --git a/src/autoreplace_cmd.h b/src/autoreplace_cmd.h new file mode 100644 index 0000000000..c42e740c93 --- /dev/null +++ b/src/autoreplace_cmd.h @@ -0,0 +1,24 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file autoreplace_cmd.h Command definitions related to autoreplace. */ + +#ifndef AUTOREPLACE_CMD_H +#define AUTOREPLACE_CMD_H + +#include "command_type.h" +#include "vehicle_type.h" +#include "engine_type.h" +#include "group_type.h" + +CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id); +CommandCost CmdSetAutoReplace(DoCommandFlag flags, GroupID id_g, EngineID old_engine_type, EngineID new_engine_type, bool when_old); + +DEF_CMD_TRAIT(CMD_AUTOREPLACE_VEHICLE, CmdAutoreplaceVehicle, 0, CMDT_VEHICLE_MANAGEMENT) +DEF_CMD_TRAIT(CMD_SET_AUTOREPLACE, CmdSetAutoReplace, 0, CMDT_VEHICLE_MANAGEMENT) + +#endif /* AUTOREPLACE_CMD_H */ diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 9f2deaf10a..4dae0eedc7 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -25,6 +25,9 @@ #include "rail_gui.h" #include "road_gui.h" #include "widgets/dropdown_func.h" +#include "autoreplace_cmd.h" +#include "group_cmd.h" +#include "settings_cmd.h" #include "widgets/autoreplace_widget.h" @@ -217,7 +220,7 @@ class ReplaceVehicleWindow : public Window { { EngineID veh_from = this->sel_engine[0]; EngineID veh_to = this->sel_engine[1]; - DoCommandP(0, (replace_when_old ? 1 : 0) | (this->sel_group << 16), veh_from + (veh_to << 16), CMD_SET_AUTOREPLACE); + Command::Post(this->sel_group, veh_from, veh_to, replace_when_old); } public: @@ -541,10 +544,10 @@ public: case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: { const Group *g = Group::GetIfValid(this->sel_group); if (g != nullptr) { - DoCommandP(0, this->sel_group | (GroupFlags::GF_REPLACE_WAGON_REMOVAL << 16), (HasBit(g->flags, GroupFlags::GF_REPLACE_WAGON_REMOVAL) ? 0 : 1) | (_ctrl_pressed << 1), CMD_SET_GROUP_FLAG); + Command::Post(this->sel_group, GroupFlags::GF_REPLACE_WAGON_REMOVAL, !HasBit(g->flags, GroupFlags::GF_REPLACE_WAGON_REMOVAL), _ctrl_pressed); } else { // toggle renew_keep_length - DoCommandP(0, 0, Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1, CMD_CHANGE_COMPANY_SETTING, nullptr, "company.renew_keep_length"); + Command::Post("company.renew_keep_length", Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1); } break; } @@ -562,7 +565,7 @@ public: case WID_RV_STOP_REPLACE: { // Stop replacing EngineID veh_from = this->sel_engine[0]; - DoCommandP(0, this->sel_group << 16, veh_from + (INVALID_ENGINE << 16), CMD_SET_AUTOREPLACE); + Command::Post(this->sel_group, veh_from, INVALID_ENGINE, false); break; } @@ -578,6 +581,16 @@ public: size_t engine_count = this->engines[click_side].size(); EngineID e = engine_count > i ? this->engines[click_side][i] : INVALID_ENGINE; + + /* If Ctrl is pressed on the left side and we don't have any engines of the selected type, stop autoreplacing. + * This is most common when we have finished autoreplacing the engine and want to remove it from the list. */ + if (click_side == 0 && _ctrl_pressed && e != INVALID_ENGINE && + (GetGroupNumEngines(_local_company, sel_group, e) == 0 || GetGroupNumEngines(_local_company, ALL_GROUP, e) == 0)) { + EngineID veh_from = e; + Command::Post(this->sel_group, veh_from, INVALID_ENGINE, false); + break; + } + if (e == this->sel_engine[click_side]) break; // we clicked the one we already selected this->sel_engine[click_side] = e; if (click_side == 0) { diff --git a/src/blitter/32bpp_anim_sse2.cpp b/src/blitter/32bpp_anim_sse2.cpp index ee243cc21e..117e6ec5c2 100644 --- a/src/blitter/32bpp_anim_sse2.cpp +++ b/src/blitter/32bpp_anim_sse2.cpp @@ -19,6 +19,7 @@ /** Instantiation of the partially SSSE2 32bpp with animation blitter factory. */ static FBlitter_32bppSSE2_Anim iFBlitter_32bppSSE2_Anim; +GNU_TARGET("sse2") void Blitter_32bppSSE2_Anim::PaletteAnimate(const Palette &palette) { assert(!_screen_disable_anim); diff --git a/src/blitter/32bpp_anim_sse2.hpp b/src/blitter/32bpp_anim_sse2.hpp index 8b84f703e7..669cef80a7 100644 --- a/src/blitter/32bpp_anim_sse2.hpp +++ b/src/blitter/32bpp_anim_sse2.hpp @@ -16,6 +16,10 @@ #define SSE_VERSION 2 #endif +#ifndef SSE_TARGET +#define SSE_TARGET "sse2" +#endif + #ifndef FULL_ANIMATION #define FULL_ANIMATION 1 #endif diff --git a/src/blitter/32bpp_anim_sse4.cpp b/src/blitter/32bpp_anim_sse4.cpp index adfe625287..93c9cb20c6 100644 --- a/src/blitter/32bpp_anim_sse4.cpp +++ b/src/blitter/32bpp_anim_sse4.cpp @@ -29,6 +29,7 @@ static FBlitter_32bppSSE4_Anim iFBlitter_32bppSSE4_Anim; */ IGNORE_UNINITIALIZED_WARNING_START template +GNU_TARGET("sse4.1") inline void Blitter_32bppSSE4_Anim::Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom) { const byte * const remap = bp->remap; @@ -52,6 +53,7 @@ inline void Blitter_32bppSSE4_Anim::Draw(const Blitter::BlitterParams *bp, ZoomL const __m128i a_cm = ALPHA_CONTROL_MASK; const __m128i pack_low_cm = PACK_LOW_CONTROL_MASK; const __m128i tr_nom_base = TRANSPARENT_NOM_BASE; + const __m128i a_am = ALPHA_AND_MASK; for (int y = bp->height; y != 0; y--) { Colour *dst = dst_line; @@ -143,7 +145,7 @@ inline void Blitter_32bppSSE4_Anim::Draw(const Blitter::BlitterParams *bp, ZoomL /* Blend colours. */ bmno_alpha_blend: - srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm); + srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am); bmno_full_opacity: _mm_storel_epi64((__m128i *) dst, srcABCD); bmno_full_transparency: @@ -170,7 +172,7 @@ bmno_full_transparency: } else { srcABCD = _mm_cvtsi32_si128(src->data); } - dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm)); + dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am)); } } break; @@ -254,7 +256,7 @@ bmno_full_transparency: /* Blend colours. */ bmcr_alpha_blend: - srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm); + srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am); bmcr_full_opacity: _mm_storel_epi64((__m128i *) dst, srcABCD); bmcr_full_transparency: @@ -287,7 +289,7 @@ bmcr_full_transparency: if (src->a < 255) { bmcr_alpha_blend_single: __m128i dstABCD = _mm_cvtsi32_si128(dst->data); - srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm); + srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, a_cm, pack_low_cm, a_am); } dst->data = _mm_cvtsi128_si32(srcABCD); } @@ -366,6 +368,12 @@ IGNORE_UNINITIALIZED_WARNING_STOP */ void Blitter_32bppSSE4_Anim::Draw(Blitter::BlitterParams *bp, BlitterMode mode, ZoomLevel zoom) { + 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 Draw() */ + Blitter_32bppSSE4::Draw(bp, mode, zoom); + return; + } + const Blitter_32bppSSE_Base::SpriteFlags sprite_flags = ((const Blitter_32bppSSE_Base::SpriteData *) bp->sprite)->flags; switch (mode) { default: { diff --git a/src/blitter/32bpp_anim_sse4.hpp b/src/blitter/32bpp_anim_sse4.hpp index 7674182a8f..1b32e085c5 100644 --- a/src/blitter/32bpp_anim_sse4.hpp +++ b/src/blitter/32bpp_anim_sse4.hpp @@ -16,6 +16,10 @@ #define SSE_VERSION 4 #endif +#ifndef SSE_TARGET +#define SSE_TARGET "sse4.1" +#endif + #ifndef FULL_ANIMATION #define FULL_ANIMATION 1 #endif @@ -28,7 +32,7 @@ #define MARGIN_NORMAL_THRESHOLD 4 /** The SSE4 32 bpp blitter with palette animation. */ -class Blitter_32bppSSE4_Anim FINAL : public Blitter_32bppSSE2_Anim, public Blitter_32bppSSE_Base { +class Blitter_32bppSSE4_Anim FINAL : public Blitter_32bppSSE2_Anim, public Blitter_32bppSSE4 { private: public: @@ -39,13 +43,14 @@ public: return Blitter_32bppSSE_Base::Encode(sprite, allocator); } const char *GetName() override { return "32bpp-sse4-anim"; } + using Blitter_32bppSSE2_Anim::LookupColourInPalette; }; /** Factory for the SSE4 32 bpp blitter (with palette animation). */ class FBlitter_32bppSSE4_Anim: public BlitterFactory { public: FBlitter_32bppSSE4_Anim() : BlitterFactory("32bpp-sse4-anim", "32bpp SSE4 Blitter (palette animation)", HasCPUIDFlag(1, 2, 19)) {} - Blitter *CreateInstance() override { return new Blitter_32bppSSE4_Anim(); } + Blitter *CreateInstance() override { return static_cast(new Blitter_32bppSSE4_Anim()); } }; #endif /* WITH_SSE */ diff --git a/src/blitter/32bpp_sse2.hpp b/src/blitter/32bpp_sse2.hpp index 12105516f8..445fa97eb5 100644 --- a/src/blitter/32bpp_sse2.hpp +++ b/src/blitter/32bpp_sse2.hpp @@ -16,6 +16,10 @@ #define SSE_VERSION 2 #endif +#ifndef SSE_TARGET +#define SSE_TARGET "sse2" +#endif + #ifndef FULL_ANIMATION #define FULL_ANIMATION 0 #endif diff --git a/src/blitter/32bpp_sse4.hpp b/src/blitter/32bpp_sse4.hpp index 7d44926b88..deb4fbed92 100644 --- a/src/blitter/32bpp_sse4.hpp +++ b/src/blitter/32bpp_sse4.hpp @@ -16,6 +16,10 @@ #define SSE_VERSION 4 #endif +#ifndef SSE_TARGET +#define SSE_TARGET "sse4.1" +#endif + #ifndef FULL_ANIMATION #define FULL_ANIMATION 0 #endif diff --git a/src/blitter/32bpp_sse_func.hpp b/src/blitter/32bpp_sse_func.hpp index d6e12af619..e8d9c05061 100644 --- a/src/blitter/32bpp_sse_func.hpp +++ b/src/blitter/32bpp_sse_func.hpp @@ -12,6 +12,7 @@ #ifdef WITH_SSE +GNU_TARGET(SSE_TARGET) static inline void InsertFirstUint32(const uint32 value, __m128i &into) { #if (SSE_VERSION >= 4) @@ -22,6 +23,7 @@ static inline void InsertFirstUint32(const uint32 value, __m128i &into) #endif } +GNU_TARGET(SSE_TARGET) static inline void InsertSecondUint32(const uint32 value, __m128i &into) { #if (SSE_VERSION >= 4) @@ -32,6 +34,7 @@ static inline void InsertSecondUint32(const uint32 value, __m128i &into) #endif } +GNU_TARGET(SSE_TARGET) static inline void LoadUint64(const uint64 value, __m128i &into) { #ifdef POINTER_IS_64BIT @@ -46,6 +49,7 @@ static inline void LoadUint64(const uint64 value, __m128i &into) #endif } +GNU_TARGET(SSE_TARGET) static inline __m128i PackUnsaturated(__m128i from, const __m128i &mask) { #if (SSE_VERSION == 2) @@ -56,36 +60,43 @@ static inline __m128i PackUnsaturated(__m128i from, const __m128i &mask) #endif } +GNU_TARGET(SSE_TARGET) static inline __m128i DistributeAlpha(const __m128i from, const __m128i &mask) { #if (SSE_VERSION == 2) __m128i alphaAB = _mm_shufflelo_epi16(from, 0x3F); // PSHUFLW, put alpha1 in front of each rgb1 - return _mm_shufflehi_epi16(alphaAB, 0x3F); // PSHUFHW, put alpha2 in front of each rgb2 + alphaAB = _mm_shufflehi_epi16(alphaAB, 0x3F); // PSHUFHW, put alpha2 in front of each rgb2 + return _mm_andnot_si128(mask, alphaAB); // PANDN, set alpha fields to 0 #else return _mm_shuffle_epi8(from, mask); #endif } -static inline __m128i AlphaBlendTwoPixels(__m128i src, __m128i dst, const __m128i &distribution_mask, const __m128i &pack_mask) +GNU_TARGET(SSE_TARGET) +static inline __m128i AlphaBlendTwoPixels(__m128i src, __m128i dst, const __m128i &distribution_mask, const __m128i &pack_mask, const __m128i &alpha_mask) { __m128i srcAB = _mm_unpacklo_epi8(src, _mm_setzero_si128()); // PUNPCKLBW, expand each uint8 into uint16 __m128i dstAB = _mm_unpacklo_epi8(dst, _mm_setzero_si128()); - __m128i alphaAB = _mm_cmpgt_epi16(srcAB, _mm_setzero_si128()); // PCMPGTW, if (alpha > 0) a++; - alphaAB = _mm_srli_epi16(alphaAB, 15); - alphaAB = _mm_add_epi16(alphaAB, srcAB); + __m128i alphaMaskAB = _mm_cmpgt_epi16(srcAB, _mm_setzero_si128()); // PCMPGTW (alpha > 0) ? 0xFFFF : 0 + __m128i alphaAB = _mm_sub_epi16(srcAB, alphaMaskAB); // if (alpha > 0) a++; alphaAB = DistributeAlpha(alphaAB, distribution_mask); srcAB = _mm_sub_epi16(srcAB, dstAB); // PSUBW, (r - Cr) srcAB = _mm_mullo_epi16(srcAB, alphaAB); // PMULLW, a*(r - Cr) srcAB = _mm_srli_epi16(srcAB, 8); // PSRLW, a*(r - Cr)/256 srcAB = _mm_add_epi16(srcAB, dstAB); // PADDW, a*(r - Cr)/256 + Cr + + alphaMaskAB = _mm_and_si128(alphaMaskAB, alpha_mask); // PAND, set non alpha fields to 0 + srcAB = _mm_or_si128(srcAB, alphaMaskAB); // POR, set alpha fields to 0xFFFF is src alpha was > 0 + return PackUnsaturated(srcAB, pack_mask); } /* Darken 2 pixels. * rgb = rgb * ((256/4) * 4 - (alpha/4)) / ((256/4) * 4) */ +GNU_TARGET(SSE_TARGET) static inline __m128i DarkenTwoPixels(__m128i src, __m128i dst, const __m128i &distribution_mask, const __m128i &tr_nom_base) { __m128i srcAB = _mm_unpacklo_epi8(src, _mm_setzero_si128()); @@ -99,6 +110,7 @@ static inline __m128i DarkenTwoPixels(__m128i src, __m128i dst, const __m128i &d } IGNORE_UNINITIALIZED_WARNING_START +GNU_TARGET(SSE_TARGET) static Colour ReallyAdjustBrightness(Colour colour, uint8 brightness) { uint64 c16 = colour.b | (uint64) colour.g << 16 | (uint64) colour.r << 32; @@ -141,6 +153,7 @@ static inline Colour AdjustBrightneSSE(Colour colour, uint8 brightness) return ReallyAdjustBrightness(colour, brightness); } +GNU_TARGET(SSE_TARGET) static inline __m128i AdjustBrightnessOfTwoPixels(__m128i from, uint32 brightness) { #if (SSE_VERSION < 3) @@ -192,6 +205,7 @@ static inline __m128i AdjustBrightnessOfTwoPixels(__m128i from, uint32 brightnes */ IGNORE_UNINITIALIZED_WARNING_START template +GNU_TARGET(SSE_TARGET) #if (SSE_VERSION == 2) inline void Blitter_32bppSSE2::Draw(const Blitter::BlitterParams *bp, ZoomLevel zoom) #elif (SSE_VERSION == 3) @@ -217,9 +231,11 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel const MapValue *src_mv = src_mv_line; /* Load these variables into register before loop. */ + const __m128i alpha_and = ALPHA_AND_MASK; + #define ALPHA_BLEND_PARAM_3 alpha_and #if (SSE_VERSION == 2) const __m128i clear_hi = CLEAR_HIGH_BYTE_MASK; - #define ALPHA_BLEND_PARAM_1 clear_hi + #define ALPHA_BLEND_PARAM_1 alpha_and #define ALPHA_BLEND_PARAM_2 clear_hi #define DARKEN_PARAM_1 tr_nom_base #define DARKEN_PARAM_2 tr_nom_base @@ -265,7 +281,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel for (uint x = (uint) effective_width / 2; x > 0; x--) { __m128i srcABCD = _mm_loadl_epi64((const __m128i*) src); __m128i dstABCD = _mm_loadl_epi64((__m128i*) dst); - _mm_storel_epi64((__m128i*) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2)); + _mm_storel_epi64((__m128i*) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3)); src += 2; dst += 2; } @@ -273,7 +289,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel if ((bt_last == BT_NONE && effective_width & 1) || bt_last == BT_ODD) { __m128i srcABCD = _mm_cvtsi32_si128(src->data); __m128i dstABCD = _mm_cvtsi32_si128(dst->data); - dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2)); + dst->data = _mm_cvtsi128_si32(AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3)); } break; @@ -318,7 +334,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel } /* Blend colours. */ - _mm_storel_epi64((__m128i *) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2)); + _mm_storel_epi64((__m128i *) dst, AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3)); dst += 2; src += 2; src_mv += 2; @@ -347,7 +363,7 @@ inline void Blitter_32bppSSE4::Draw(const Blitter::BlitterParams *bp, ZoomLevel if (src->a < 255) { bmcr_alpha_blend_single: __m128i dstABCD = _mm_cvtsi32_si128(dst->data); - srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2); + srcABCD = AlphaBlendTwoPixels(srcABCD, dstABCD, ALPHA_BLEND_PARAM_1, ALPHA_BLEND_PARAM_2, ALPHA_BLEND_PARAM_3); } dst->data = _mm_cvtsi128_si32(srcABCD); } diff --git a/src/blitter/32bpp_sse_type.h b/src/blitter/32bpp_sse_type.h index 33c76d9b4d..fe91b294be 100644 --- a/src/blitter/32bpp_sse_type.h +++ b/src/blitter/32bpp_sse_type.h @@ -51,6 +51,7 @@ typedef union ALIGN(16) um128i { #define OVERBRIGHT_VALUE_MASK _mm_setr_epi8(-1, 0, -1, 0, -1, 0, 0, 0, -1, 0, -1, 0, -1, 0, 0, 0) #define OVERBRIGHT_CONTROL_MASK _mm_setr_epi8( 0, 1, 0, 1, 0, 1, 7, 7, 2, 3, 2, 3, 2, 3, 7, 7) #define TRANSPARENT_NOM_BASE _mm_setr_epi16(256, 256, 256, 256, 256, 256, 256, 256) +#define ALPHA_AND_MASK _mm_setr_epi16( 0, 0, 0, -1, 0, 0, 0, -1) #endif /* WITH_SSE */ #endif /* BLITTER_32BPP_SSE_TYPE_H */ diff --git a/src/blitter/32bpp_ssse3.hpp b/src/blitter/32bpp_ssse3.hpp index cc710ed05b..c95095d4df 100644 --- a/src/blitter/32bpp_ssse3.hpp +++ b/src/blitter/32bpp_ssse3.hpp @@ -16,6 +16,10 @@ #define SSE_VERSION 3 #endif +#ifndef SSE_TARGET +#define SSE_TARGET "ssse3" +#endif + #ifndef FULL_ANIMATION #define FULL_ANIMATION 0 #endif diff --git a/src/blitter/CMakeLists.txt b/src/blitter/CMakeLists.txt index ddcc9a0082..50faefbc1c 100644 --- a/src/blitter/CMakeLists.txt +++ b/src/blitter/CMakeLists.txt @@ -38,21 +38,6 @@ add_files( CONDITION NOT OPTION_DEDICATED AND OPENGL_FOUND ) - -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - set_compile_flags( - 32bpp_anim_sse2.cpp - 32bpp_sse2.cpp - COMPILE_FLAGS -msse2) - set_compile_flags( - 32bpp_ssse3.cpp - COMPILE_FLAGS -mssse3) - set_compile_flags( - 32bpp_anim_sse4.cpp - 32bpp_sse4.cpp - COMPILE_FLAGS -msse4.1) -endif() - add_files( base.hpp common.hpp diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index d8e4aedec9..ddfda8f50b 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -286,16 +286,16 @@ bool HandleBootstrap() /* No user interface, bail out with an error. */ if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure; - /* If there is no network or no freetype, then there is nothing we can do. Go straight to failure. */ + /* If there is no network or no non-sprite font, then there is nothing we can do. Go straight to failure. */ #if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA) if (!_network_available) goto failure; /* First tell the game we're bootstrapping. */ _game_mode = GM_BOOTSTRAP; - /* Initialise the freetype font code. */ + /* Initialise the font cache. */ InitializeUnicodeGlyphMap(); - /* Next "force" finding a suitable freetype font as the local font is missing. */ + /* Next "force" finding a suitable non-sprite font as the local font is missing. */ CheckForMissingGlyphs(false); /* Initialise the palette. The biggest step is 'faking' some recolour sprites. diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index 74c7234d57..515a9318c3 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -20,9 +20,9 @@ #include "sortlist_type.h" #include "widgets/dropdown_func.h" #include "core/geometry_func.hpp" -#include "cmd_helper.h" #include "tunnelbridge_map.h" #include "road_gui.h" +#include "tunnelbridge_cmd.h" #include "widgets/bridge_widget.h" @@ -50,27 +50,22 @@ typedef GUIList GUIBridgeList; ///< List of bridges, used in #B * Callback executed after a build Bridge CMD has been called * * @param result Whether the build succeeded - * @param end_tile End tile of the bridge. - * @param p1 packed start tile coords (~ dx) - * @param p2 various bitstuffed elements - * - p2 = (bit 0- 7) - bridge type (hi bh) - * - p2 = (bit 8-13) - rail type or road types. - * - p2 = (bit 15-16) - transport type. * @param cmd unused + * @param end_tile End tile of the bridge. + * @param tile_start start tile + * @param transport_type transport type. */ -void CcBuildBridge(const CommandCost &result, TileIndex end_tile, uint32 p1, uint32 p2, uint32 cmd) +void CcBuildBridge(Commands cmd, const CommandCost &result, TileIndex end_tile, TileIndex tile_start, TransportType transport_type, BridgeType, byte) { if (result.Failed()) return; if (_settings_client.sound.confirm) SndPlayTileFx(SND_27_CONSTRUCTION_BRIDGE, end_tile); - TransportType transport_type = Extract(p2); - if (transport_type == TRANSPORT_ROAD) { DiagDirection end_direction = ReverseDiagDir(GetTunnelBridgeDirection(end_tile)); ConnectRoadToStructure(end_tile, end_direction); - DiagDirection start_direction = ReverseDiagDir(GetTunnelBridgeDirection(p1)); - ConnectRoadToStructure(p1, start_direction); + DiagDirection start_direction = ReverseDiagDir(GetTunnelBridgeDirection(tile_start)); + ConnectRoadToStructure(tile_start, start_direction); } } @@ -87,7 +82,8 @@ private: /* Internal variables */ TileIndex start_tile; TileIndex end_tile; - uint32 type; + TransportType transport_type; + byte road_rail_type; GUIBridgeList *bridges; int bridgetext_offset; ///< Horizontal offset of the text describing the bridge properties in #WID_BBS_BRIDGE_LIST relative to the left edge. Scrollbar *vscroll; @@ -112,13 +108,13 @@ private: void BuildBridge(uint8 i) { - switch ((TransportType)(this->type >> 15)) { + switch (this->transport_type) { case TRANSPORT_RAIL: _last_railbridge_type = this->bridges->at(i).index; break; case TRANSPORT_ROAD: _last_roadbridge_type = this->bridges->at(i).index; break; default: break; } - DoCommandP(this->end_tile, this->start_tile, this->type | this->bridges->at(i).index, - CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge); + Command::Post(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE, CcBuildBridge, + this->end_tile, this->start_tile, this->transport_type, this->bridges->at(i).index, this->road_rail_type); } /** Sort the builable bridges */ @@ -135,19 +131,20 @@ private: } public: - BuildBridgeWindow(WindowDesc *desc, TileIndex start, TileIndex end, uint32 br_type, GUIBridgeList *bl) : Window(desc), + BuildBridgeWindow(WindowDesc *desc, TileIndex start, TileIndex end, TransportType transport_type, byte road_rail_type, GUIBridgeList *bl) : Window(desc), start_tile(start), end_tile(end), - type(br_type), + transport_type(transport_type), + road_rail_type(road_rail_type), bridges(bl) { this->CreateNestedTree(); this->vscroll = this->GetScrollbar(WID_BBS_SCROLLBAR); /* Change the data, or the caption of the gui. Set it to road or rail, accordingly. */ - this->GetWidget(WID_BBS_CAPTION)->widget_data = (GB(this->type, 15, 2) == TRANSPORT_ROAD) ? STR_SELECT_ROAD_BRIDGE_CAPTION : STR_SELECT_RAIL_BRIDGE_CAPTION; - this->FinishInitNested(GB(br_type, 15, 2)); // Initializes 'this->bridgetext_offset'. + this->GetWidget(WID_BBS_CAPTION)->widget_data = (transport_type == TRANSPORT_ROAD) ? STR_SELECT_ROAD_BRIDGE_CAPTION : STR_SELECT_RAIL_BRIDGE_CAPTION; + this->FinishInitNested(transport_type); // Initializes 'this->bridgetext_offset'. - this->parent = FindWindowById(WC_BUILD_TOOLBAR, GB(this->type, 15, 2)); + this->parent = FindWindowById(WC_BUILD_TOOLBAR, transport_type); this->bridges->SetListing(this->last_sorting); this->bridges->SetSortFuncs(this->sorter_funcs); this->bridges->NeedResort(); @@ -362,12 +359,6 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo { CloseWindowByClass(WC_BUILD_BRIDGE); - /* Data type for the bridge. - * Bit 16,15 = transport type, - * 14..8 = road/rail types, - * 7..0 = type of bridge */ - uint32 type = (transport_type << 15) | (road_rail_type << 8); - /* The bridge length without ramps. */ const uint bridge_len = GetTunnelBridgeLength(start, end); @@ -383,14 +374,14 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo default: break; // water ways and air routes don't have bridge types } if (_ctrl_pressed && CheckBridgeAvailability(last_bridge_type, bridge_len).Succeeded()) { - DoCommandP(end, start, type | last_bridge_type, CMD_BUILD_BRIDGE | CMD_MSG(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE), CcBuildBridge); + Command::Post(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE, CcBuildBridge, end, start, transport_type, last_bridge_type, road_rail_type); return; } /* 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 = DoCommand(end, start, type, CommandFlagsToDCFlags(GetCommandFlags(CMD_BUILD_BRIDGE)) | DC_QUERY_COST, CMD_BUILD_BRIDGE); + CommandCost ret = Command::Do(CommandFlagsToDCFlags(GetCommandFlags()) | DC_QUERY_COST, end, start, transport_type, 0, road_rail_type); GUIBridgeList *bl = nullptr; if (ret.Failed()) { @@ -450,7 +441,7 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo } if (bl != nullptr && bl->size() != 0) { - new BuildBridgeWindow(&_build_bridge_desc, start, end, type, bl); + new BuildBridgeWindow(&_build_bridge_desc, start, end, transport_type, road_rail_type, bl); } else { delete bl; ShowErrorMessage(STR_ERROR_CAN_T_BUILD_BRIDGE_HERE, errmsg, WL_INFO, TileX(end) * TILE_SIZE, TileY(end) * TILE_SIZE); diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 547ac3e003..04392c9b03 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -30,6 +30,9 @@ #include "cargotype.h" #include "core/geometry_func.hpp" #include "autoreplace_func.h" +#include "engine_cmd.h" +#include "train_cmd.h" +#include "vehicle_cmd.h" #include "widgets/build_vehicle_widget.h" @@ -1079,7 +1082,7 @@ struct BuildVehicleWindow : Window { { this->vehicle_type = type; this->listview_mode = tile == INVALID_TILE; - this->window_number = this->listview_mode ? (int)type : tile; + this->window_number = this->listview_mode ? (int)type : (int)tile; this->sel_engine = INVALID_ENGINE; @@ -1114,7 +1117,7 @@ struct BuildVehicleWindow : Window { this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9); - this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile); + this->FinishInitNested(tile == INVALID_TILE ? (int)type : (int)tile); this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company; @@ -1226,11 +1229,11 @@ struct BuildVehicleWindow : Window { if (!this->listview_mode) { /* Query for cost and refitted capacity */ - CommandCost ret = DoCommand(this->window_number, this->sel_engine | (cargo << 24), 0, DC_QUERY_COST, GetCmdBuildVeh(this->vehicle_type)); + auto [ret, veh_id, refit_capacity, refit_mail] = Command::Do(DC_QUERY_COST, this->window_number, this->sel_engine, true, cargo, INVALID_CLIENT_ID); if (ret.Succeeded()) { this->te.cost = ret.GetCost() - e->GetCost(); - this->te.capacity = _returned_refit_capacity; - this->te.mail_capacity = _returned_mail_refit_capacity; + this->te.capacity = refit_capacity; + this->te.mail_capacity = refit_mail; this->te.cargo = (cargo == CT_INVALID) ? e->GetDefaultCargoType() : cargo; return; } @@ -1458,7 +1461,7 @@ struct BuildVehicleWindow : Window { case WID_BV_SHOW_HIDE: { const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine); if (e != nullptr) { - DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY); + Command::Post(this->sel_engine, !e->IsHidden(_current_company)); } break; } @@ -1466,10 +1469,13 @@ struct BuildVehicleWindow : Window { case WID_BV_BUILD: { EngineID sel_eng = this->sel_engine; if (sel_eng != INVALID_ENGINE) { - CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle; CargoID cargo = this->cargo_filter[this->cargo_filter_criteria]; if (cargo == CF_ANY || cargo == CF_ENGINES) cargo = CF_NONE; - DoCommandP(this->window_number, sel_eng | (cargo << 24), 0, GetCmdBuildVeh(this->vehicle_type), callback); + if (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) { + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + } else { + Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID); + } } break; } @@ -1634,7 +1640,7 @@ struct BuildVehicleWindow : Window { { if (str == nullptr) return; - DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str); + Command::Post(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type, this->rename_engine, str); } void OnDropdownSelect(int widget, int index) override @@ -1681,7 +1687,7 @@ void ShowBuildVehicleWindow(TileIndex tile, VehicleType type) * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number. * As it always is a low value, it won't collide with any real tile * number. */ - uint num = (tile == INVALID_TILE) ? (int)type : tile; + uint num = (tile == INVALID_TILE) ? (int)type : (int)tile; assert(IsCompanyBuildableVehicleType(type)); diff --git a/src/cargomonitor.cpp b/src/cargomonitor.cpp index 2941a29556..7614cba6ec 100644 --- a/src/cargomonitor.cpp +++ b/src/cargomonitor.cpp @@ -149,9 +149,9 @@ void AddCargoDelivery(CargoID cargo_type, CompanyID company, uint32 amount, Sour if (iter != _cargo_deliveries.end()) iter->second += amount; /* Industry delivery. */ - for (Industry *ind : st->industries_near) { - if (ind->index != dest) continue; - CargoMonitorID num = EncodeCargoIndustryMonitor(company, cargo_type, ind->index); + for (const auto &i : st->industries_near) { + if (i.industry->index != dest) continue; + CargoMonitorID num = EncodeCargoIndustryMonitor(company, cargo_type, i.industry->index); CargoMonitorMap::iterator iter = _cargo_deliveries.find(num); if (iter != _cargo_deliveries.end()) iter->second += amount; } diff --git a/src/cargopacket.cpp b/src/cargopacket.cpp index 86bba0261b..e9c6e1aa77 100644 --- a/src/cargopacket.cpp +++ b/src/cargopacket.cpp @@ -75,7 +75,7 @@ CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, T source_id(source_id), source(source), source_xy(source_xy), - loaded_at_xy(loaded_at_xy) + loaded_at_xy(loaded_at_xy.value) { assert(count != 0); this->source_type = source_type; diff --git a/src/cargotype.h b/src/cargotype.h index 5ed9ac90e2..54d24f1d7a 100644 --- a/src/cargotype.h +++ b/src/cargotype.h @@ -23,7 +23,7 @@ typedef uint32 CargoLabel; /** Town growth effect when delivering cargo. */ -enum TownEffect { +enum TownEffect : byte { TE_BEGIN = 0, TE_NONE = TE_BEGIN, ///< Cargo has no effect. TE_PASSENGERS, ///< Cargo behaves passenger-like. @@ -66,7 +66,6 @@ struct CargoSpec { bool is_freight; ///< Cargo type is considered to be freight (affects train freight multiplier). TownEffect town_effect; ///< The effect that delivering this cargo type has on towns. Also affects destination of subsidies. - uint16 multipliertowngrowth; ///< Size of the effect. uint8 callback_mask; ///< Bitmask of cargo callbacks that have to be called StringID name; ///< Name of this type of cargo. diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index a0ac5fab8f..eb62dc0317 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -28,6 +28,7 @@ #include "tile_map.h" #include "newgrf.h" #include "error.h" +#include "misc_cmd.h" #include "widgets/cheat_widget.h" @@ -54,7 +55,7 @@ static int32 _money_cheat_amount = 10000000; */ static int32 ClickMoneyCheat(int32 p1, int32 p2) { - DoCommandP(0, (uint32)(p2 * _money_cheat_amount), 0, CMD_MONEY_CHEAT); + Command::Post(p2 * _money_cheat_amount); return _money_cheat_amount; } diff --git a/src/clear_cmd.cpp b/src/clear_cmd.cpp index 4036313123..17711a6267 100644 --- a/src/clear_cmd.cpp +++ b/src/clear_cmd.cpp @@ -16,6 +16,7 @@ #include "water.h" #include "core/random_func.hpp" #include "newgrf_generic.h" +#include "landscape_cmd.h" #include "table/strings.h" #include "table/sprites.h" @@ -381,7 +382,7 @@ static void ChangeTileOwner_Clear(TileIndex tile, Owner old_owner, Owner new_own static CommandCost TerraformTile_Clear(TileIndex tile, DoCommandFlag flags, int z_new, Slope tileh_new) { - return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + return Command::Do(flags, tile); } extern const TileTypeProcs _tile_type_clear_procs = { diff --git a/src/cmd_helper.h b/src/cmd_helper.h deleted file mode 100644 index ceb4d4bd9b..0000000000 --- a/src/cmd_helper.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . - */ - -/** @file cmd_helper.h Helper functions to extract data from command parameters. */ - -#ifndef CMD_HELPER_H -#define CMD_HELPER_H - -#include "core/enum_type.hpp" -#include "core/bitmath_func.hpp" - -/** - * Extracts a given type from a value. - * @tparam T The type of data we're looking for. - * @tparam S The offset in the data. - * @tparam N The amount of bits to read. - * @tparam U The type of data passed to us. - * @param v The data to extract the value from. - */ -template static inline T Extract(U v) -{ - /* Check if there are enough bits in v */ - static_assert(N == EnumPropsT::num_bits); - static_assert(S + N <= sizeof(U) * 8); - static_assert(EnumPropsT::end <= (1 << N)); - U masked = GB(v, S, N); - return IsInsideMM(masked, EnumPropsT::begin, EnumPropsT::end) ? (T)masked : EnumPropsT::invalid; -} - -#endif /* CMD_HELPER_H */ diff --git a/src/command.cpp b/src/command.cpp index 7d1a72dec9..65e5acf10c 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -24,187 +24,69 @@ #include "signal_func.h" #include "core/backup_type.hpp" #include "object_base.h" +#include "autoreplace_cmd.h" +#include "company_cmd.h" +#include "depot_cmd.h" +#include "economy_cmd.h" +#include "engine_cmd.h" +#include "goal_cmd.h" +#include "group_cmd.h" +#include "industry_cmd.h" +#include "landscape_cmd.h" +#include "misc_cmd.h" +#include "news_cmd.h" +#include "object_cmd.h" +#include "order_cmd.h" +#include "rail_cmd.h" +#include "road_cmd.h" +#include "roadveh_cmd.h" +#include "settings_cmd.h" +#include "signs_cmd.h" +#include "station_cmd.h" +#include "story_cmd.h" +#include "subsidy_cmd.h" +#include "terraform_cmd.h" +#include "timetable_cmd.h" +#include "town_cmd.h" +#include "train_cmd.h" +#include "tree_cmd.h" +#include "tunnelbridge_cmd.h" +#include "vehicle_cmd.h" +#include "viewport_cmd.h" +#include "water_cmd.h" +#include "waypoint_cmd.h" +#include "misc/endian_buffer.hpp" +#include "string_func.h" + +#include #include "table/strings.h" #include "safeguards.h" -CommandProc CmdBuildRailroadTrack; -CommandProc CmdRemoveRailroadTrack; -CommandProc CmdBuildSingleRail; -CommandProc CmdRemoveSingleRail; -CommandProc CmdLandscapeClear; +int RecursiveCommandCounter::_counter = 0; -CommandProc CmdBuildBridge; -CommandProc CmdBuildRailStation; -CommandProc CmdRemoveFromRailStation; -CommandProc CmdConvertRail; +/** + * Define a command with the flags which belongs to it. + * + * This struct connects a command handler function with the flags created with + * the #CMD_AUTO, #CMD_OFFLINE and #CMD_SERVER values. + */ +struct CommandInfo { + const char *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. +}; +/* Helpers to generate the master command table from the command traits. */ +template +inline constexpr CommandInfo CommandFromTrait() noexcept { return { T::name, T::flags, T::type }; }; -CommandProc CmdBuildSingleSignal; -CommandProc CmdRemoveSingleSignal; - -CommandProc CmdTerraformLand; - -CommandProc CmdBuildObject; -CommandProc CmdSellLandArea; - -CommandProc CmdBuildTunnel; - -CommandProc CmdBuildTrainDepot; -CommandProc CmdBuildRailWaypoint; -CommandProc CmdRenameWaypoint; -CommandProc CmdRemoveFromRailWaypoint; - -CommandProc CmdBuildRoadStop; -CommandProc CmdRemoveRoadStop; - -CommandProc CmdBuildLongRoad; -CommandProc CmdRemoveLongRoad; -CommandProc CmdBuildRoad; - -CommandProc CmdBuildRoadDepot; - -CommandProc CmdConvertRoad; - -CommandProc CmdBuildAirport; - -CommandProc CmdBuildDock; - -CommandProc CmdBuildShipDepot; - -CommandProc CmdBuildBuoy; - -CommandProc CmdPlantTree; - -CommandProc CmdMoveRailVehicle; - -CommandProc CmdBuildVehicle; -CommandProc CmdSellVehicle; -CommandProc CmdRefitVehicle; -CommandProc CmdSendVehicleToDepot; -CommandProc CmdSetVehicleVisibility; - -CommandProc CmdForceTrainProceed; -CommandProc CmdReverseTrainDirection; - -CommandProc CmdClearOrderBackup; -CommandProc CmdModifyOrder; -CommandProc CmdSkipToOrder; -CommandProc CmdDeleteOrder; -CommandProc CmdInsertOrder; -CommandProc CmdChangeServiceInt; - -CommandProc CmdBuildIndustry; -CommandProc CmdIndustryCtrl; - -CommandProc CmdSetCompanyManagerFace; -CommandProc CmdSetCompanyColour; - -CommandProc CmdIncreaseLoan; -CommandProc CmdDecreaseLoan; - -CommandProc CmdWantEnginePreview; -CommandProc CmdEngineCtrl; - -CommandProc CmdRenameVehicle; -CommandProc CmdRenameEngine; - -CommandProc CmdRenameCompany; -CommandProc CmdRenamePresident; - -CommandProc CmdRenameStation; -CommandProc CmdRenameDepot; - -CommandProc CmdPlaceSign; -CommandProc CmdRenameSign; - -CommandProc CmdTurnRoadVeh; - -CommandProc CmdPause; - -CommandProc CmdBuyShareInCompany; -CommandProc CmdSellShareInCompany; -CommandProc CmdBuyCompany; - -CommandProc CmdFoundTown; -CommandProc CmdRenameTown; -CommandProc CmdDoTownAction; -CommandProc CmdTownGrowthRate; -CommandProc CmdTownRating; -CommandProc CmdTownCargoGoal; -CommandProc CmdTownSetText; -CommandProc CmdExpandTown; -CommandProc CmdDeleteTown; - -CommandProc CmdChangeSetting; -CommandProc CmdChangeCompanySetting; - -CommandProc CmdOrderRefit; -CommandProc CmdCloneOrder; - -CommandProc CmdClearArea; - -CommandProc CmdGiveMoney; -CommandProc CmdMoneyCheat; -CommandProc CmdChangeBankBalance; -CommandProc CmdBuildCanal; -CommandProc CmdBuildLock; - -CommandProc CmdCreateSubsidy; -CommandProc CmdCompanyCtrl; -CommandProc CmdCustomNewsItem; -CommandProc CmdCreateGoal; -CommandProc CmdRemoveGoal; -CommandProc CmdSetGoalText; -CommandProc CmdSetGoalProgress; -CommandProc CmdSetGoalCompleted; -CommandProc CmdGoalQuestion; -CommandProc CmdGoalQuestionAnswer; -CommandProc CmdCreateStoryPage; -CommandProc CmdCreateStoryPageElement; -CommandProc CmdUpdateStoryPageElement; -CommandProc CmdSetStoryPageTitle; -CommandProc CmdSetStoryPageDate; -CommandProc CmdShowStoryPage; -CommandProc CmdRemoveStoryPage; -CommandProc CmdRemoveStoryPageElement; -CommandProc CmdScrollViewport; -CommandProc CmdStoryPageButton; - -CommandProc CmdLevelLand; - -CommandProc CmdBuildSignalTrack; -CommandProc CmdRemoveSignalTrack; - -CommandProc CmdSetAutoReplace; - -CommandProc CmdCloneVehicle; -CommandProc CmdStartStopVehicle; -CommandProc CmdMassStartStopVehicle; -CommandProc CmdAutoreplaceVehicle; -CommandProc CmdDepotSellAllVehicles; -CommandProc CmdDepotMassAutoReplace; - -CommandProc CmdCreateGroup; -CommandProc CmdAlterGroup; -CommandProc CmdDeleteGroup; -CommandProc CmdAddVehicleGroup; -CommandProc CmdAddSharedVehicleGroup; -CommandProc CmdRemoveAllVehiclesGroup; -CommandProc CmdSetGroupFlag; -CommandProc CmdSetGroupLivery; - -CommandProc CmdMoveOrder; -CommandProc CmdChangeTimetable; -CommandProc CmdSetVehicleOnTime; -CommandProc CmdAutofillTimetable; -CommandProc CmdSetTimetableStart; - -CommandProc CmdOpenCloseAirport; - -#define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type} +template +inline constexpr auto MakeCommandsFromTraits(std::integer_sequence) noexcept { + return std::array{{ CommandFromTrait(i)>>()... }}; +} /** * The master command table @@ -213,174 +95,18 @@ CommandProc CmdOpenCloseAirport; * the flags which belongs to it. The indices are the same * as the value from the CMD_* enums. */ -static const Command _command_proc_table[] = { - DEF_CMD(CmdBuildRailroadTrack, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAILROAD_TRACK - DEF_CMD(CmdRemoveRailroadTrack, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_RAILROAD_TRACK - DEF_CMD(CmdBuildSingleRail, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SINGLE_RAIL - DEF_CMD(CmdRemoveSingleRail, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SINGLE_RAIL - DEF_CMD(CmdLandscapeClear, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_LANDSCAPE_CLEAR - DEF_CMD(CmdBuildBridge, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_BRIDGE - DEF_CMD(CmdBuildRailStation, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAIL_STATION - DEF_CMD(CmdBuildTrainDepot, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TRAIN_DEPOT - DEF_CMD(CmdBuildSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SIGNALS - DEF_CMD(CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNALS - DEF_CMD(CmdTerraformLand, CMD_ALL_TILES | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_TERRAFORM_LAND - DEF_CMD(CmdBuildObject, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_OBJECT - DEF_CMD(CmdBuildTunnel, CMD_DEITY | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TUNNEL - DEF_CMD(CmdRemoveFromRailStation, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_STATION - DEF_CMD(CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_RAILD - DEF_CMD(CmdBuildRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_RAIL_WAYPOINT - DEF_CMD(CmdRenameWaypoint, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_WAYPOINT - DEF_CMD(CmdRemoveFromRailWaypoint, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_WAYPOINT +static constexpr auto _command_proc_table = MakeCommandsFromTraits(std::make_integer_sequence, CMD_END>{}); - DEF_CMD(CmdBuildRoadStop, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD_STOP - DEF_CMD(CmdRemoveRoadStop, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_ROAD_STOP - DEF_CMD(CmdBuildLongRoad,CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_LONG_ROAD - DEF_CMD(CmdRemoveLongRoad, CMD_NO_TEST | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_LONG_ROAD; towns may disallow removing road bits (as they are connected) in test, but in exec they're removed and thus removing is allowed. - DEF_CMD(CmdBuildRoad, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD - DEF_CMD(CmdBuildRoadDepot, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_ROAD_DEPOT - DEF_CMD(CmdConvertRoad, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_ROAD - - DEF_CMD(CmdBuildAirport, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_AIRPORT - DEF_CMD(CmdBuildDock, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_DOCK - DEF_CMD(CmdBuildShipDepot, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SHIP_DEPOT - DEF_CMD(CmdBuildBuoy, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_BUOY - DEF_CMD(CmdPlantTree, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_PLANT_TREE - - DEF_CMD(CmdBuildVehicle, CMD_CLIENT_ID, CMDT_VEHICLE_CONSTRUCTION ), // CMD_BUILD_VEHICLE - DEF_CMD(CmdSellVehicle, CMD_CLIENT_ID, CMDT_VEHICLE_CONSTRUCTION ), // CMD_SELL_VEHICLE - DEF_CMD(CmdRefitVehicle, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_REFIT_VEHICLE - DEF_CMD(CmdSendVehicleToDepot, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_SEND_VEHICLE_TO_DEPOT - DEF_CMD(CmdSetVehicleVisibility, 0, CMDT_COMPANY_SETTING ), // CMD_SET_VEHICLE_VISIBILITY - - DEF_CMD(CmdMoveRailVehicle, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_MOVE_RAIL_VEHICLE - DEF_CMD(CmdForceTrainProceed, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_FORCE_TRAIN_PROCEED - DEF_CMD(CmdReverseTrainDirection, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_REVERSE_TRAIN_DIRECTION - - DEF_CMD(CmdClearOrderBackup, CMD_CLIENT_ID, CMDT_SERVER_SETTING ), // CMD_CLEAR_ORDER_BACKUP - DEF_CMD(CmdModifyOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_MODIFY_ORDER - DEF_CMD(CmdSkipToOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SKIP_TO_ORDER - DEF_CMD(CmdDeleteOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_DELETE_ORDER - DEF_CMD(CmdInsertOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_INSERT_ORDER - - DEF_CMD(CmdChangeServiceInt, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_CHANGE_SERVICE_INT - - DEF_CMD(CmdBuildIndustry, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_INDUSTRY - DEF_CMD(CmdIndustryCtrl, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_CTRL - - DEF_CMD(CmdSetCompanyManagerFace, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_MANAGER_FACE - DEF_CMD(CmdSetCompanyColour, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_COLOUR - - DEF_CMD(CmdIncreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_INCREASE_LOAN - DEF_CMD(CmdDecreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_DECREASE_LOAN - - DEF_CMD(CmdWantEnginePreview, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_WANT_ENGINE_PREVIEW - DEF_CMD(CmdEngineCtrl, CMD_DEITY, CMDT_VEHICLE_MANAGEMENT ), // CMD_ENGINE_CTRL - - DEF_CMD(CmdRenameVehicle, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_VEHICLE - DEF_CMD(CmdRenameEngine, CMD_SERVER, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_ENGINE - - DEF_CMD(CmdRenameCompany, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_COMPANY - DEF_CMD(CmdRenamePresident, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_PRESIDENT - - DEF_CMD(CmdRenameStation, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_STATION - DEF_CMD(CmdRenameDepot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_DEPOT - - DEF_CMD(CmdPlaceSign, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_PLACE_SIGN - DEF_CMD(CmdRenameSign, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_SIGN - - DEF_CMD(CmdTurnRoadVeh, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_TURN_ROADVEH - - DEF_CMD(CmdPause, CMD_SERVER | CMD_NO_EST, CMDT_SERVER_SETTING ), // CMD_PAUSE - - DEF_CMD(CmdBuyShareInCompany, 0, CMDT_MONEY_MANAGEMENT ), // CMD_BUY_SHARE_IN_COMPANY - DEF_CMD(CmdSellShareInCompany, 0, CMDT_MONEY_MANAGEMENT ), // CMD_SELL_SHARE_IN_COMPANY - DEF_CMD(CmdBuyCompany, 0, CMDT_MONEY_MANAGEMENT ), // CMD_BUY_COMANY - - DEF_CMD(CmdFoundTown, CMD_DEITY | CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_FOUND_TOWN; founding random town can fail only in exec run - DEF_CMD(CmdRenameTown, CMD_DEITY | CMD_SERVER, CMDT_OTHER_MANAGEMENT ), // CMD_RENAME_TOWN - DEF_CMD(CmdDoTownAction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_DO_TOWN_ACTION - DEF_CMD(CmdTownCargoGoal, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_CARGO_GOAL - DEF_CMD(CmdTownGrowthRate, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_GROWTH_RATE - DEF_CMD(CmdTownRating, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_RATING - DEF_CMD(CmdTownSetText, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_TOWN_SET_TEXT - DEF_CMD(CmdExpandTown, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_EXPAND_TOWN - DEF_CMD(CmdDeleteTown, CMD_OFFLINE, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_DELETE_TOWN - - DEF_CMD(CmdOrderRefit, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ORDER_REFIT - DEF_CMD(CmdCloneOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CLONE_ORDER - - DEF_CMD(CmdClearArea, CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CLEAR_AREA; destroying multi-tile houses makes town rating differ between test and execution - - DEF_CMD(CmdMoneyCheat, CMD_OFFLINE, CMDT_CHEAT ), // CMD_MONEY_CHEAT - DEF_CMD(CmdChangeBankBalance, CMD_DEITY, CMDT_MONEY_MANAGEMENT ), // CMD_CHANGE_BANK_BALANCE - DEF_CMD(CmdBuildCanal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_CANAL - DEF_CMD(CmdCreateSubsidy, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_SUBSIDY - DEF_CMD(CmdCompanyCtrl, CMD_SPECTATOR | CMD_CLIENT_ID | CMD_NO_EST, CMDT_SERVER_SETTING ), // CMD_COMPANY_CTRL - DEF_CMD(CmdCustomNewsItem, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CUSTOM_NEWS_ITEM - DEF_CMD(CmdCreateGoal, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_GOAL - DEF_CMD(CmdRemoveGoal, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_GOAL - DEF_CMD(CmdSetGoalText, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_GOAL_TEXT - DEF_CMD(CmdSetGoalProgress, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_GOAL_PROGRESS - DEF_CMD(CmdSetGoalCompleted, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_GOAL_COMPLETED - DEF_CMD(CmdGoalQuestion, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_GOAL_QUESTION - DEF_CMD(CmdGoalQuestionAnswer, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_GOAL_QUESTION_ANSWER - DEF_CMD(CmdCreateStoryPage, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_STORY_PAGE - DEF_CMD(CmdCreateStoryPageElement, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_STORY_PAGE_ELEMENT - DEF_CMD(CmdUpdateStoryPageElement, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_UPDATE_STORY_PAGE_ELEMENT - DEF_CMD(CmdSetStoryPageTitle, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_STORY_PAGE_TITLE - DEF_CMD(CmdSetStoryPageDate, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SET_STORY_PAGE_DATE - DEF_CMD(CmdShowStoryPage, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SHOW_STORY_PAGE - DEF_CMD(CmdRemoveStoryPage, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_STORY_PAGE - DEF_CMD(CmdRemoveStoryPageElement, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_STORY_ELEMENT_PAGE - DEF_CMD(CmdScrollViewport, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_SCROLL_VIEWPORT - DEF_CMD(CmdStoryPageButton, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_STORY_PAGE_BUTTON - - DEF_CMD(CmdLevelLand, CMD_ALL_TILES | CMD_NO_TEST | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_LEVEL_LAND; test run might clear tiles multiple times, in execution that only happens once - - DEF_CMD(CmdBuildLock, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_LOCK - - DEF_CMD(CmdBuildSignalTrack, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_SIGNAL_TRACK - DEF_CMD(CmdRemoveSignalTrack, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNAL_TRACK - - DEF_CMD(CmdGiveMoney, 0, CMDT_MONEY_MANAGEMENT ), // CMD_GIVE_MONEY - DEF_CMD(CmdChangeSetting, CMD_SERVER, CMDT_SERVER_SETTING ), // CMD_CHANGE_SETTING - DEF_CMD(CmdChangeCompanySetting, 0, CMDT_COMPANY_SETTING ), // CMD_CHANGE_COMPANY_SETTING - DEF_CMD(CmdSetAutoReplace, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_SET_AUTOREPLACE - DEF_CMD(CmdCloneVehicle, CMD_NO_TEST, CMDT_VEHICLE_CONSTRUCTION ), // CMD_CLONE_VEHICLE; NewGRF callbacks influence building and refitting making it impossible to correctly estimate the cost - DEF_CMD(CmdStartStopVehicle, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_START_STOP_VEHICLE - DEF_CMD(CmdMassStartStopVehicle, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_MASS_START_STOP - DEF_CMD(CmdAutoreplaceVehicle, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_AUTOREPLACE_VEHICLE - DEF_CMD(CmdDepotSellAllVehicles, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_DEPOT_SELL_ALL_VEHICLES - DEF_CMD(CmdDepotMassAutoReplace, 0, CMDT_VEHICLE_CONSTRUCTION ), // CMD_DEPOT_MASS_AUTOREPLACE - DEF_CMD(CmdCreateGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CREATE_GROUP - DEF_CMD(CmdDeleteGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_DELETE_GROUP - DEF_CMD(CmdAlterGroup, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ALTER_GROUP - DEF_CMD(CmdAddVehicleGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ADD_VEHICLE_GROUP - DEF_CMD(CmdAddSharedVehicleGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ADD_SHARE_VEHICLE_GROUP - DEF_CMD(CmdRemoveAllVehiclesGroup, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_REMOVE_ALL_VEHICLES_GROUP - DEF_CMD(CmdSetGroupFlag, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_GROUP_FLAG - DEF_CMD(CmdSetGroupLivery, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_GROUP_LIVERY - DEF_CMD(CmdMoveOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_MOVE_ORDER - DEF_CMD(CmdChangeTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_CHANGE_TIMETABLE - DEF_CMD(CmdSetVehicleOnTime, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_VEHICLE_ON_TIME - DEF_CMD(CmdAutofillTimetable, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_AUTOFILL_TIMETABLE - DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START - - DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT -}; /*! - * This function range-checks a cmd, and checks if the cmd is not nullptr + * This function range-checks a cmd. * * @param cmd The integer value of a command * @return true if the command is valid (and got a CommandProc function) */ -bool IsValidCommand(uint32 cmd) +bool IsValidCommand(Commands cmd) { - cmd &= CMD_ID_MASK; - - return cmd < lengthof(_command_proc_table) && _command_proc_table[cmd].proc != nullptr; + return cmd < _command_proc_table.size(); } /*! @@ -390,11 +116,11 @@ bool IsValidCommand(uint32 cmd) * @param cmd The integer value of the command * @return The flags for this command */ -CommandFlags GetCommandFlags(uint32 cmd) +CommandFlags GetCommandFlags(Commands cmd) { assert(IsValidCommand(cmd)); - return _command_proc_table[cmd & CMD_ID_MASK].flags; + return _command_proc_table[cmd].flags; } /*! @@ -404,11 +130,11 @@ CommandFlags GetCommandFlags(uint32 cmd) * @param cmd The integer value of the command * @return The name for this command */ -const char *GetCommandName(uint32 cmd) +const char *GetCommandName(Commands cmd) { assert(IsValidCommand(cmd)); - return _command_proc_table[cmd & CMD_ID_MASK].name; + return _command_proc_table[cmd].name; } /** @@ -416,7 +142,7 @@ const char *GetCommandName(uint32 cmd) * @param cmd The command to check. * @return True if the command is allowed while paused, false otherwise. */ -bool IsCommandAllowedWhilePaused(uint32 cmd) +bool IsCommandAllowedWhilePaused(Commands cmd) { /* Lookup table for the command types that are allowed for a given pause level setting. */ static const int command_type_lookup[] = { @@ -433,89 +159,7 @@ bool IsCommandAllowedWhilePaused(uint32 cmd) static_assert(lengthof(command_type_lookup) == CMDT_END); assert(IsValidCommand(cmd)); - return _game_mode == GM_EDITOR || command_type_lookup[_command_proc_table[cmd & CMD_ID_MASK].type] <= _settings_game.construction.command_pause_level; -} - - -static int _docommand_recursive = 0; - -/** - * Shorthand for calling the long DoCommand with a container. - * - * @param container Container with (almost) all information - * @param flags Flags for the command and how to execute the command - * @see CommandProc - * @return the cost - */ -CommandCost DoCommand(const CommandContainer *container, DoCommandFlag flags) -{ - return DoCommand(container->tile, container->p1, container->p2, flags, container->cmd & CMD_ID_MASK, container->text); -} - -/*! - * This function executes a given command with the parameters from the #CommandProc parameter list. - * Depending on the flags parameter it execute or test a command. - * - * @param tile The tile to apply the command on (for the #CommandProc) - * @param p1 Additional data for the command (for the #CommandProc) - * @param p2 Additional data for the command (for the #CommandProc) - * @param flags Flags for the command and how to execute the command - * @param cmd The command-id to execute (a value of the CMD_* enums) - * @param text The text to pass - * @see CommandProc - * @return the cost - */ -CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const std::string &text) -{ - CommandCost res; - - /* Do not even think about executing out-of-bounds tile-commands */ - if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return CMD_ERROR; - - /* Chop of any CMD_MSG or other flags; we don't need those here */ - CommandProc *proc = _command_proc_table[cmd & CMD_ID_MASK].proc; - - _docommand_recursive++; - - /* only execute the test call if it's toplevel, or we're not execing. */ - if (_docommand_recursive == 1 || !(flags & DC_EXEC) ) { - if (_docommand_recursive == 1) _cleared_object_areas.clear(); - SetTownRatingTestMode(true); - res = proc(tile, flags & ~DC_EXEC, p1, p2, text); - SetTownRatingTestMode(false); - if (res.Failed()) { - goto error; - } - - if (_docommand_recursive == 1 && - !(flags & DC_QUERY_COST) && - !(flags & DC_BANKRUPT) && - !CheckCompanyHasMoney(res)) { // CheckCompanyHasMoney() modifies 'res' to an error if it fails. - goto error; - } - - if (!(flags & DC_EXEC)) { - _docommand_recursive--; - return res; - } - } - - /* Execute the command here. All cost-relevant functions set the expenses type - * themselves to the cost object at some point */ - if (_docommand_recursive == 1) _cleared_object_areas.clear(); - res = proc(tile, flags, p1, p2, text); - if (res.Failed()) { -error: - _docommand_recursive--; - return res; - } - - /* if toplevel, subtract the money. */ - if (--_docommand_recursive == 0 && !(flags & DC_BANKRUPT)) { - SubtractMoneyFromCompany(res); - } - - return res; + return _game_mode == GM_EDITOR || command_type_lookup[_command_proc_table[cmd].type] <= _settings_game.construction.command_pause_level; } /*! @@ -532,70 +176,93 @@ Money GetAvailableMoneyForCommand() return Company::Get(company)->money; } + /** - * Shortcut for the long DoCommandP when having a container with the data. - * @param container the container with information. - * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) - * @return true if the command succeeded, else false + * Prepare for calling a command proc. + * @param top_level Top level of command execution, i.e. command from a command. + * @param test Test run of command? */ -bool DoCommandP(const CommandContainer *container, bool my_cmd) +void CommandHelperBase::InternalDoBefore(bool top_level, bool test) { - return DoCommandP(container->tile, container->p1, container->p2, container->cmd, container->callback, container->text, my_cmd); + if (top_level) _cleared_object_areas.clear(); + if (test) SetTownRatingTestMode(true); } -/*! - * Toplevel network safe docommand function for the current company. Must not be called recursively. - * The callback is called when the command succeeded or failed. The parameters - * \a tile, \a p1, and \a p2 are from the #CommandProc function. The parameter \a cmd is the command to execute. - * The parameter \a my_cmd is used to indicate if the command is from a company or the server. - * - * @param tile The tile to perform a command on (see #CommandProc) - * @param p1 Additional data for the command (see #CommandProc) - * @param p2 Additional data for the command (see #CommandProc) - * @param cmd The command to execute (a CMD_* value) - * @param callback A callback function to call after the command is finished - * @param text The text to pass - * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) - * @return \c true if the command succeeded, else \c false. +/** + * Process result after calling a command proc. + * @param[in,out] res Command result, may be modified. + * @param flags Command flags. + * @param top_level Top level of command execution, i.e. command from a command. + * @param test Test run of command? */ -bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, bool my_cmd) +void CommandHelperBase::InternalDoAfter(CommandCost &res, DoCommandFlag flags, bool top_level, bool test) +{ + if (test) { + SetTownRatingTestMode(false); + + if (res.Succeeded() && top_level && !(flags & DC_QUERY_COST) && !(flags & DC_BANKRUPT)) { + CheckCompanyHasMoney(res); // CheckCompanyHasMoney() modifies 'res' to an error if it fails. + } + } else { + /* If top-level, subtract the money. */ + if (res.Succeeded() && top_level && !(flags & DC_BANKRUPT)) { + SubtractMoneyFromCompany(res); + } + } +} + +/** + * Decide what to do with the command depending on current game state. + * @param cmd Command to execute. + * @param flags Command flags. + * @param tile Tile of command execution. + * @param err_message Message prefix to show on error. + * @param network_command Does this command come from the network? + * @return error state + do only cost estimation? + send to network only? + */ +std::tuple CommandHelperBase::InternalPostBefore(Commands cmd, CommandFlags flags, TileIndex tile, StringID err_message, bool network_command) { /* Cost estimation is generally only done when the * local user presses shift while doing something. * However, in case of incoming network commands, * map generation or the pause button we do want * to execute. */ - bool estimate_only = _shift_pressed && IsLocalCompany() && - !_generating_world && - !(cmd & CMD_NETWORK_COMMAND) && - !(GetCommandFlags(cmd) & CMD_NO_EST); + bool estimate_only = _shift_pressed && IsLocalCompany() && !_generating_world && !network_command && !(flags & CMD_NO_EST); /* We're only sending the command, so don't do * fancy things for 'success'. */ - bool only_sending = _networking && !(cmd & CMD_NETWORK_COMMAND); + bool only_sending = _networking && !network_command; - /* Where to show the message? */ + if (_pause_mode != PM_UNPAUSED && !IsCommandAllowedWhilePaused(cmd) && !estimate_only) { + ShowErrorMessage(err_message, STR_ERROR_NOT_ALLOWED_WHILE_PAUSED, WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE); + return { true, estimate_only, only_sending }; + } else { + return { false, estimate_only, only_sending }; + } +} + +/** + * Process result of executing a command, possibly displaying any error to the player. + * @param res Command result. + * @param tile Tile of command execution. + * @param estimate_only Is this just cost estimation? + * @param only_sending Was the command only sent to network? + * @param err_message Message prefix to show on error. + * @param my_cmd Is the command from this client? + */ +void CommandHelperBase::InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd) +{ int x = TileX(tile) * TILE_SIZE; int y = TileY(tile) * TILE_SIZE; - if (_pause_mode != PM_UNPAUSED && !IsCommandAllowedWhilePaused(cmd) && !estimate_only) { - ShowErrorMessage(GB(cmd, 16, 16), STR_ERROR_NOT_ALLOWED_WHILE_PAUSED, WL_INFO, x, y); - return false; - } - - /* Only set p2 when the command does not come from the network. */ - if (!(cmd & CMD_NETWORK_COMMAND) && GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = CLIENT_ID_SERVER; - - CommandCost res = DoCommandPInternal(tile, p1, p2, cmd, callback, text, my_cmd, estimate_only); if (res.Failed()) { /* Only show the error when it's for us. */ - StringID error_part1 = GB(cmd, 16, 16); - if (estimate_only || (IsLocalCompany() && error_part1 != 0 && my_cmd)) { - ShowErrorMessage(error_part1, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackGRF(), res.GetTextRefStackSize(), res.GetTextRefStack()); + if (estimate_only || (IsLocalCompany() && err_message != 0 && my_cmd)) { + ShowErrorMessage(err_message, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackGRF(), res.GetTextRefStackSize(), res.GetTextRefStack()); } } else if (estimate_only) { ShowEstimatedCostOrIncome(res.GetCost(), x, y); - } else if (!only_sending && res.GetCost() != 0 && tile != 0 && IsLocalCompany() && _game_mode != GM_EDITOR) { + } else if (!only_sending && tile != 0 && IsLocalCompany() && _game_mode != GM_EDITOR) { /* Only show the cost animation when we did actually * execute the command, i.e. we're not sending it to * the server, when it has cost the local company @@ -603,63 +270,23 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallbac * concept of cost, so don't show it there either. */ ShowCostOrIncomeAnimation(x, y, GetSlopePixelZ(x, y), res.GetCost()); } - - if (!estimate_only && !only_sending && callback != nullptr) { - callback(res, tile, p1, p2, cmd); - } - - return res.Succeeded(); } +/** Helper to make a desync log for a command. */ +void CommandHelperBase::LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed) +{ + Debug(desync, 1, "{}: {:08x}; {:02x}; {:02x}; {:08x}; {:08x}; {:06x}; {} ({})", failed ? "cmdf" : "cmd", _date, _date_fract, (int)_current_company, cmd, err_message, tile, FormatArrayAsHex(args), GetCommandName(cmd)); +} /** - * Helper to deduplicate the code for returning. - * @param cmd the command cost to return. + * Prepare for the test run of a command proc call. + * @param cmd_flags Command flags. + * @param tile Tile of command execution. + * @param[in,out] cur_company Backup of current company at start of command execution. + * @return True if test run can go ahead, false on error. */ -#define return_dcpi(cmd) { _docommand_recursive = 0; return cmd; } - -/*! - * Helper function for the toplevel network safe docommand function for the current company. - * - * @param tile The tile to perform a command on (see #CommandProc) - * @param p1 Additional data for the command (see #CommandProc) - * @param p2 Additional data for the command (see #CommandProc) - * @param cmd The command to execute (a CMD_* value) - * @param callback A callback function to call after the command is finished - * @param text The text to pass - * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) - * @param estimate_only whether to give only the estimate or also execute the command - * @return the command cost of this function. - */ -CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, bool my_cmd, bool estimate_only) +bool CommandHelperBase::InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup &cur_company) { - /* Prevent recursion; it gives a mess over the network */ - assert(_docommand_recursive == 0); - _docommand_recursive = 1; - - /* Reset the state. */ - _additional_cash_required = 0; - - /* Get pointer to command handler */ - byte cmd_id = cmd & CMD_ID_MASK; - assert(cmd_id < lengthof(_command_proc_table)); - - CommandProc *proc = _command_proc_table[cmd_id].proc; - /* Shouldn't happen, but you never know when someone adds - * NULLs to the _command_proc_table. */ - assert(proc != nullptr); - - /* Command flags are used internally */ - CommandFlags cmd_flags = GetCommandFlags(cmd); - /* Flags get send to the DoCommand */ - DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags); - - /* Make sure p2 is properly set to a ClientID. */ - assert(!(cmd_flags & CMD_CLIENT_ID) || p2 != 0); - - /* Do not even think about executing out-of-bounds tile-commands */ - if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return_dcpi(CMD_ERROR); - /* Always execute server and spectator commands as spectator */ bool exec_as_spectator = (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0; @@ -667,65 +294,72 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, * The server will ditch any server commands a client sends to it, so effectively * this guards the server from executing functions for an invalid company. */ if (_game_mode == GM_NORMAL && !exec_as_spectator && !Company::IsValidID(_current_company) && !(_current_company == OWNER_DEITY && (cmd_flags & CMD_DEITY) != 0)) { - return_dcpi(CMD_ERROR); + return false; } - Backup cur_company(_current_company, FILE_LINE); if (exec_as_spectator) cur_company.Change(COMPANY_SPECTATOR); - bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0; - - /* Test the command. */ + /* Enter test mode. */ _cleared_object_areas.clear(); SetTownRatingTestMode(true); BasePersistentStorageArray::SwitchMode(PSM_ENTER_TESTMODE); - CommandCost res = proc(tile, flags, p1, p2, text); + return true; +} + +/** + * Validate result of test run and prepare for real execution. + * @param cmd_flags Command flags. + * @param[in,out] res Command result of test run, may be modified. + * @param estimate_only Is this just cost estimation? + * @param network_command Does this command come from the network? + * @param[in,out] cur_company Backup of current company at start of command execution. + * @return True if test run can go ahead, false on error. + */ +std::tuple CommandHelperBase::InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup &cur_company) +{ BasePersistentStorageArray::SwitchMode(PSM_LEAVE_TESTMODE); SetTownRatingTestMode(false); /* Make sure we're not messing things up here. */ - assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); + assert((cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0 ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); /* If the command fails, we're doing an estimate * or the player does not have enough money * (unless it's a command where the test and * execution phase might return different costs) * we bail out here. */ - if (res.Failed() || estimate_only || - (!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) { - if (!_networking || _generating_world || (cmd & CMD_NETWORK_COMMAND) != 0) { - /* Log the failed command as well. Just to be able to be find - * causes of desyncs due to bad command test implementations. */ - Debug(desync, 1, "cmdf: {:08x}; {:02x}; {:02x}; {:06x}; {:08x}; {:08x}; {:08x}; \"{}\" ({})", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); - } - cur_company.Restore(); - return_dcpi(res); + bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0; + if (res.Failed() || estimate_only || (!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) { + return { true, !_networking || _generating_world || network_command, false }; } - /* - * If we are in network, and the command is not from the network - * send it to the command-queue and abort execution - */ - if (_networking && !_generating_world && !(cmd & CMD_NETWORK_COMMAND)) { - NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company); - cur_company.Restore(); + bool send_net = _networking && !_generating_world && !network_command; - /* Don't return anything special here; no error, no costs. - * This way it's not handled by DoCommand and only the - * actual execution of the command causes messages. Also - * reset the storages as we've not executed the command. */ - return_dcpi(CommandCost()); + if (!send_net) { + /* Prepare for command execution. */ + _cleared_object_areas.clear(); + BasePersistentStorageArray::SwitchMode(PSM_ENTER_COMMAND); } - Debug(desync, 1, "cmd: {:08x}; {:02x}; {:02x}; {:06x}; {:08x}; {:08x}; {:08x}; \"{}\" ({})", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); - /* Actually try and execute the command. If no cost-type is given - * use the construction one */ - _cleared_object_areas.clear(); - BasePersistentStorageArray::SwitchMode(PSM_ENTER_COMMAND); - CommandCost res2 = proc(tile, flags | DC_EXEC, p1, p2, text); + return { false, _debug_desync_level >= 1, send_net }; +} + +/** + * Process the result of a command test run and execution run. + * @param cmd Command that was executed. + * @param cmd_flags Command flags. + * @param res_test Command result of test run. + * @param tes_exec Command result of real run. + * @param extra_cash Additional cash required for successful command execution. + * @param tile Tile of command execution. + * @param[in,out] cur_company Backup of current company at start of command execution. + * @return Final command result. + */ +CommandCost CommandHelperBase::InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup &cur_company) +{ BasePersistentStorageArray::SwitchMode(PSM_LEAVE_COMMAND); - if (cmd_id == CMD_COMPANY_CTRL) { + if (cmd == CMD_COMPANY_CTRL) { cur_company.Trash(); /* We are a new company -> Switch to new local company. * We were closed down -> Switch to spectator @@ -733,7 +367,7 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, _current_company = _local_company; } else { /* Make sure nothing bad happened, like changing the current company. */ - assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); + assert((cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0 ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); cur_company.Restore(); } @@ -741,20 +375,21 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, * return of the command. Otherwise we can check whether the * test and execution have yielded the same result, * i.e. cost and error state are the same. */ + bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0; if (!test_and_exec_can_differ) { - assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check - } else if (res2.Failed()) { - return_dcpi(res2); + assert(res_test.GetCost() == res_exec.GetCost() && res_test.Failed() == res_exec.Failed()); // sanity check + } else if (res_exec.Failed()) { + return res_exec; } /* If we're needing more money and we haven't done * anything yet, ask for the money! */ - if (_additional_cash_required != 0 && res2.GetCost() == 0) { + if (extra_cash != 0 && res_exec.GetCost() == 0) { /* It could happen we removed rail, thus gained money, and deleted something else. * So make sure the signal buffer is empty even in this case */ UpdateSignalsInBuffer(); - SetDParam(0, _additional_cash_required); - return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY)); + SetDParam(0, extra_cash); + return CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); } /* update last build coordinate of company. */ @@ -763,14 +398,13 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, if (c != nullptr) c->last_build_coordinate = tile; } - SubtractMoneyFromCompany(res2); + SubtractMoneyFromCompany(res_exec); /* update signals if needed */ UpdateSignalsInBuffer(); - return_dcpi(res2); + return res_exec; } -#undef return_dcpi /** diff --git a/src/command_func.h b/src/command_func.h index 426283bd3a..d78c8229a4 100644 --- a/src/command_func.h +++ b/src/command_func.h @@ -11,7 +11,12 @@ #define COMMAND_FUNC_H #include "command_type.h" +#include "network/network_type.h" #include "company_type.h" +#include "company_func.h" +#include "core/backup_type.hpp" +#include "misc/endian_buffer.hpp" +#include "tile_map.h" /** * Define a default return value for a failed command. @@ -32,30 +37,26 @@ static const CommandCost CMD_ERROR = CommandCost(INVALID_STRING_ID); */ #define return_cmd_error(errcode) return CommandCost(errcode); -CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const std::string &text = {}); -CommandCost DoCommand(const CommandContainer *container, DoCommandFlag flags); +void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, TileIndex location, const CommandDataBuffer &cmd_data); -bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback = nullptr, const std::string &text = {}, bool my_cmd = true); -bool DoCommandP(const CommandContainer *container, bool my_cmd = true); - -CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, bool my_cmd, bool estimate_only); - -void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, CompanyID company); - -extern Money _additional_cash_required; - -bool IsValidCommand(uint32 cmd); -CommandFlags GetCommandFlags(uint32 cmd); -const char *GetCommandName(uint32 cmd); +bool IsValidCommand(Commands cmd); +CommandFlags GetCommandFlags(Commands cmd); +const char *GetCommandName(Commands cmd); Money GetAvailableMoneyForCommand(); -bool IsCommandAllowedWhilePaused(uint32 cmd); +bool IsCommandAllowedWhilePaused(Commands cmd); + +template +constexpr CommandFlags GetCommandFlags() +{ + return CommandTraits::flags; +} /** * Extracts the DC flags needed for DoCommand from the flags returned by GetCommandFlags * @param cmd_flags Flags from GetCommandFlags * @return flags for DoCommand */ -static inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags) +static constexpr inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags) { DoCommandFlag flags = DC_NONE; if (cmd_flags & CMD_NO_WATER) flags |= DC_NO_WATER; @@ -64,60 +65,433 @@ static inline DoCommandFlag CommandFlagsToDCFlags(CommandFlags cmd_flags) return flags; } -/*** All command callbacks that exist ***/ +/** Helper class to keep track of command nesting level. */ +struct RecursiveCommandCounter { + RecursiveCommandCounter() noexcept { _counter++; } + ~RecursiveCommandCounter() noexcept { _counter--; } -/* ai/ai_instance.cpp */ -CommandCallback CcAI; + /** Are we in the top-level command execution? */ + bool IsTopLevel() const { return _counter == 1; } +private: + static int _counter; +}; -/* airport_gui.cpp */ -CommandCallback CcBuildAirport; +#if defined(__GNUC__) && !defined(__clang__) +/* + * We cast specialized function pointers to a generic one, but don't use the + * converted value to call the function, which is safe, except that GCC + * helpfully thinks it is not. + * + * "Any pointer to function can be converted to a pointer to a different function type. + * Calling the function through a pointer to a different function type is undefined, + * but converting such pointer back to pointer to the original function type yields + * the pointer to the original function." */ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +# define SILENCE_GCC_FUNCTION_POINTER_CAST +#endif -/* bridge_gui.cpp */ -CommandCallback CcBuildBridge; +template struct CommandHelper; -/* dock_gui.cpp */ -CommandCallback CcBuildDocks; -CommandCallback CcPlaySound_CONSTRUCTION_WATER; +class CommandHelperBase { +protected: + static void InternalDoBefore(bool top_level, bool test); + static void InternalDoAfter(CommandCost &res, DoCommandFlag flags, bool top_level, bool test); + static std::tuple InternalPostBefore(Commands cmd, CommandFlags flags, TileIndex tile, StringID err_message, bool network_command); + static void InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd); + static bool InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup &cur_company); + static std::tuple InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup &cur_company); + static CommandCost InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup &cur_company); + static void LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed); +}; -/* depot_gui.cpp */ -CommandCallback CcCloneVehicle; +/** + * Templated wrapper that exposes the command parameter arguments + * for the various Command::Do/Post calls. + * @tparam Tcmd The command-id to execute. + * @tparam Tret Return type of the command. + * @tparam Targs The command parameter types. + */ +template +struct CommandHelper : protected CommandHelperBase { +private: + /** Extract the \c CommandCost from a command proc result. */ + static inline CommandCost &ExtractCommandCost(Tret &ret) + { + if constexpr (std::is_same_v) { + return ret; + } else { + return std::get<0>(ret); + } + } -/* game/game_instance.cpp */ -CommandCallback CcGame; + /** Make a command proc result from a \c CommandCost. */ + static inline Tret MakeResult(const CommandCost &cost) + { + Tret ret{}; + ExtractCommandCost(ret) = cost; + return ret; + } -/* group_gui.cpp */ -CommandCallback CcCreateGroup; -CommandCallback CcAddVehicleNewGroup; +public: + /** + * This function executes a given command with the parameters from the #CommandProc parameter list. + * Depending on the flags parameter it executes or tests a command. + * + * @note This function is to be called from the StateGameLoop or from within the execution of a Command. + * This function must not be called from the context of a "player" (real person, AI, game script). + * Use ::Post for commands directly triggered by "players". + * + * @param flags Flags for the command and how to execute the command + * @param args Parameters for the command + * @see CommandProc + * @return the cost + */ + static Tret Do(DoCommandFlag flags, Targs... args) + { + if constexpr (std::is_same_v>>) { + /* Do not even think about executing out-of-bounds tile-commands. */ + TileIndex tile = std::get<0>(std::make_tuple(args...)); + if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return MakeResult(CMD_ERROR); + } -/* industry_gui.cpp */ -CommandCallback CcBuildIndustry; + RecursiveCommandCounter counter{}; -/* main_gui.cpp */ -CommandCallback CcPlaySound_EXPLOSION; -CommandCallback CcPlaceSign; -CommandCallback CcTerraform; + /* Only execute the test call if it's toplevel, or we're not execing. */ + if (counter.IsTopLevel() || !(flags & DC_EXEC)) { + InternalDoBefore(counter.IsTopLevel(), true); + Tret res = CommandTraits::proc(flags & ~DC_EXEC, args...); + InternalDoAfter(ExtractCommandCost(res), flags, counter.IsTopLevel(), true); // Can modify res. -/* rail_gui.cpp */ -CommandCallback CcPlaySound_CONSTRUCTION_RAIL; -CommandCallback CcRailDepot; -CommandCallback CcStation; -CommandCallback CcBuildRailTunnel; + if (ExtractCommandCost(res).Failed() || !(flags & DC_EXEC)) return res; + } -/* road_gui.cpp */ -CommandCallback CcPlaySound_CONSTRUCTION_OTHER; -CommandCallback CcBuildRoadTunnel; -CommandCallback CcRoadDepot; -CommandCallback CcRoadStop; + /* Execute the command here. All cost-relevant functions set the expenses type + * themselves to the cost object at some point. */ + InternalDoBefore(counter.IsTopLevel(), false); + Tret res = CommandTraits::proc(flags, args...); + InternalDoAfter(ExtractCommandCost(res), flags, counter.IsTopLevel(), false); -/* train_gui.cpp */ -CommandCallback CcBuildWagon; + return res; + } -/* town_gui.cpp */ -CommandCallback CcFoundTown; -CommandCallback CcFoundRandomTown; + /** + * Shortcut for the long Post when not using a callback. + * @param err_message Message prefix to show on error + * @param args Parameters for the command + */ + static inline bool Post(StringID err_message, Targs... args) { return Post(err_message, nullptr, std::forward(args)...); } + /** + * Shortcut for the long Post when not using an error message. + * @param callback A callback function to call after the command is finished + * @param args Parameters for the command + */ + template + static inline bool Post(Tcallback *callback, Targs... args) { return Post((StringID)0, callback, std::forward(args)...); } + /** + * Shortcut for the long Post when not using a callback or an error message. + * @param args Parameters for the command + */ + static inline bool Post(Targs... args) { return Post((StringID)0, nullptr, std::forward(args)...); } -/* vehicle_gui.cpp */ -CommandCallback CcBuildPrimaryVehicle; -CommandCallback CcStartStopVehicle; + /** + * Top-level network safe command execution for the current company. + * Must not be called recursively. The callback is called when the + * command succeeded or failed. + * + * @param err_message Message prefix to show on error + * @param callback A callback function to call after the command is finished + * @param args Parameters for the command + * @return \c true if the command succeeded, else \c false. + */ + template + static bool Post(StringID err_message, Tcallback *callback, Targs... args) + { + return InternalPost(err_message, callback, true, false, std::forward_as_tuple(args...)); + } + + /** + * Execute a command coming from the network. + * @param err_message Message prefix to show on error + * @param callback A callback function to call after the command is finished + * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) + * @param location Tile location for user feedback. + * @param args Parameters for the command + * @return \c true if the command succeeded, else \c false. + */ + template + static bool PostFromNet(StringID err_message, Tcallback *callback, bool my_cmd, TileIndex location, std::tuple args) + { + if constexpr (std::is_same_v>) { + /* Do not even think about executing out-of-bounds tile-commands. */ + TileIndex tile = std::get<0>(args); + if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (GetCommandFlags() & CMD_ALL_TILES) == 0))) return false; + } + + return InternalPost(err_message, callback, my_cmd, true, location, std::move(args)); + } + + /** + * Prepare a command to be send over the network + * @param cmd The command to execute (a CMD_* value) + * @param err_message Message prefix to show on error + * @param company The company that wants to send the command + * @param args Parameters for the command + */ + static void SendNet(StringID err_message, CompanyID company, Targs... args) + { + auto args_tuple = std::forward_as_tuple(args...); + + TileIndex tile{}; + if constexpr (std::is_same_v>) { + tile = std::get<0>(args_tuple); + } + + ::NetworkSendCommand(Tcmd, err_message, nullptr, _current_company, tile, EndianBufferWriter::FromValue(args_tuple)); + } + + /** + * Top-level network safe command execution without safety checks. + * @param err_message Message prefix to show on error + * @param callback A callback function to call after the command is finished + * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) + * @param estimate_only whether to give only the estimate or also execute the command + * @param location Tile location for user feedback. + * @param args Parameters for the command + * @return the command cost of this function. + */ + template + static Tret Unsafe(StringID err_message, Tcallback *callback, bool my_cmd, bool estimate_only, TileIndex location, std::tuple args) + { + return Execute(err_message, reinterpret_cast(callback), my_cmd, estimate_only, false, location, std::move(args)); + } + +protected: + /** Helper to process a single ClientID argument. */ + template + static inline void SetClientIdHelper(T &data) + { + if constexpr (std::is_same_v) { + if (data == INVALID_CLIENT_ID) data = CLIENT_ID_SERVER; + } + } + + /** Set all invalid ClientID's to the proper value. */ + template + static inline void SetClientIds(Ttuple &values, std::index_sequence) + { + ((SetClientIdHelper(std::get(values))), ...); + } + + /** Remove the first element of a tuple. */ + template