diff --git a/.clang-tidy b/.clang-tidy index a7ed7d8ee..220aac065 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,4 @@ -ExcludeHeaderFilterRegex: ^.*(lib\/).* +#ExcludeHeaderFilterRegex: ^.*(lib\/).* Checks: > #-*, @@ -16,26 +16,36 @@ Checks: > -clang-analyzer-security.insecureAPI.strcpy, -clang-diagnostic-format-security, -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-no-malloc, -cppcoreguidelines-init-variables, # false positives -cppcoreguidelines-owning-memory, -cppcoreguidelines-prefer-member-initializer, - -cppcoreguidelines-pro-bounds-array-pointer-decay, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + #-cppcoreguidelines-pro-bounds-array-pointer-decay, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-type-const-cast, # we have some instances, and we should remove them... - -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, # explicit move, copy contructors... ##-hicpp-use-auto, #duplicate ##-hicpp-braces-around-statements, ##-hicpp-vararg, #duplicate + -misc-const-correctness, # temporary -misc-include-cleaner, + -misc-non-private-member-variables-in-classes, -modernize-avoid-c-arrays, -modernize-use-auto, -modernize-use-trailing-return-type, -modernize-loop-convert, + -modernize-macro-to-enum, + -modernize-use-designated-initializers, + -modernize-use-nodiscard, + -modernize-use-using, -readability-braces-around-statements, -readability-delete-null-pointer, -readability-else-after-return, @@ -43,6 +53,7 @@ Checks: > -readability-identifier-length, -readability-identifier-naming, -readability-implicit-bool-conversion, + -readability-inconsistent-declaration-parameter-name, -readability-magic-numbers, -readability-misleading-indentation, # to be enabled after the source is properly formatted -readability-simplify-boolean-expr, @@ -52,12 +63,14 @@ Checks: > ##-readability-braces-around-statements, ##-readability-magic-numbers + #-format security + # Turn all the warnings from the checks above into errors. #WarningsAsErrors: "*" CheckOptions: - - key: 'clang-analyzer-core.NonNullParamChecker:assert_like_macro' - value: 'DEBUG_ASSERT,ASSERT,ASSERT_ALWAYS' +# - key: 'clang-analyzer-core.NonNullParamChecker:assert_like_macro' +# value: 'DEBUG_ASSERT,ASSERT,ASSERT_ALWAYS' - key: 'clang-analyzer-config:noreturn_function' value: 'RaiseImmediateAbort,RaiseRecoverableAbort' - { key: readability-identifier-naming.NamespaceCase, value: lower_case } diff --git a/.clangd b/.clangd index 243a3c7f2..bdc6dfd1c 100644 --- a/.clangd +++ b/.clangd @@ -17,4 +17,5 @@ CompileFlags: # Diagnostics: # Suppress: ['-Wunused-include-directive'] -# TODO: disable clazy-non-pod-global-static +Diagnostics: + Suppress: non-pod-global-static diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 000000000..3855583ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,50 @@ +name: 🐛 Bug Report +description: Report unexpected behavior +labels: ["bug"] +#assignees: +# - cbnolok +body: + - type: markdown + attributes: + value: | + Before opening a bug report, please search for the behaviour in the existing issues. + --- + Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information. + - type: textarea + id: bug-description + attributes: + label: Bug description + description: A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Step-by-step guide for reproducing the bug in a vanilla SphereServer instance. If addressing a regression, please provide the sphere revision number or commit since when the bug started to occur. + validations: + required: false + - type: textarea + id: logs + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: false + - type: input + id: sphere-rev + attributes: + label: Revision Number and Git Version Hash + placeholder: "e.g. 4031 bbfc587bb2c270c53ecb54cb5e1ea2f54cd9031f" + validations: + required: true + - type: dropdown + id: sphere-branch + attributes: + label: Branch + description: "Select the branch you are working with" + options: + - "master" + - "dev" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..7a3b1dbdf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: Support & Discussions & More + url: https://discord.gg/ZrMTXrs + about: '' + - name: Documentation (Wiki) + url: https://wiki.spherecommunity.net/ + about: '' + - name: Changelogs (Master branch) + url: https://github.com/Sphereserver/Source-X/blob/master/Changelog.txt + about: '' + - name: Changelogs (Dev branch) + url: https://github.com/Sphereserver/Source-X/blob/dev/Changelog.txt + about: '' diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..2c7f08c55 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,14 @@ +--- +name: "\U0001F4A1 Feature Request" +about: Suggest an idea. +title: '' +labels: feature +#assignees: cbnolok + +--- + +#### Description + + +#### Why this should be added + diff --git a/.github/workflows/build_aux_files.yml b/.github/workflows/build_aux_files.yml index 8fc747e9d..52fbc1e22 100644 --- a/.github/workflows/build_aux_files.yml +++ b/.github/workflows/build_aux_files.yml @@ -15,10 +15,14 @@ jobs: # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches #if: contains(fromJson('["master", "main"]'), github.ref_name) + permissions: + contents: write + actions: write + runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/build_linux_x86.yml b/.github/workflows/build_linux_x86.yml index 4ea4c16f6..062e5b2cf 100644 --- a/.github/workflows/build_linux_x86.yml +++ b/.github/workflows/build_linux_x86.yml @@ -23,11 +23,14 @@ on: jobs: linux-x86: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + permissions: + contents: read + actions: read steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install prerequisites @@ -130,11 +133,14 @@ jobs: needs: linux-x86 # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches if: contains(fromJson('["master", "main"]'), github.ref_name) + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-linux-x86 merge-multiple: true @@ -153,11 +159,14 @@ jobs: upload_selfhost_release: needs: linux-x86 if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-linux-x86 merge-multiple: true diff --git a/.github/workflows/build_linux_x86_64.yml b/.github/workflows/build_linux_x86_64.yml index 006cdfca1..ef5221b83 100644 --- a/.github/workflows/build_linux_x86_64.yml +++ b/.github/workflows/build_linux_x86_64.yml @@ -20,11 +20,14 @@ on: jobs: linux-x86_64: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + permissions: + contents: read + actions: read steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install prerequisites @@ -85,11 +88,14 @@ jobs: needs: linux-x86_64 # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches if: contains(fromJson('["master", "main"]'), github.ref_name) + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-linux-x86_64 merge-multiple: true @@ -107,11 +113,14 @@ jobs: upload_selfhost_release: needs: linux-x86_64 if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-linux-x86_64 merge-multiple: true diff --git a/.github/workflows/build_osx_arm.yml b/.github/workflows/build_osx_arm.yml index 1ae8559f4..23b108ba5 100644 --- a/.github/workflows/build_osx_arm.yml +++ b/.github/workflows/build_osx_arm.yml @@ -20,7 +20,11 @@ on: jobs: macos-arm64: - runs-on: macos-14 # apple silicon + runs-on: macos-15 # apple silicon + permissions: + contents: read + actions: read + env: CMAKE_GEN: Ninja #CMAKE_TCH: cmake/toolchains/OSX-AppleClang-native.cmake @@ -28,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install prerequisites @@ -72,14 +76,16 @@ jobs: upload_github_release: - needs: macos-arm64 - # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches + needs: macos-arm64 # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches if: contains(fromJson('["master", "main"]'), github.ref_name) + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-osx-arm64 merge-multiple: true @@ -98,11 +104,14 @@ jobs: upload_selfhost_release: needs: macos-arm64 if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-osx-arm64 merge-multiple: true diff --git a/.github/workflows/build_osx_x86_64.yml b/.github/workflows/build_osx_x86_64.yml index f2e65ec58..050d703f1 100644 --- a/.github/workflows/build_osx_x86_64.yml +++ b/.github/workflows/build_osx_x86_64.yml @@ -20,14 +20,18 @@ on: jobs: macos-x86_64: - runs-on: macos-13 # intel + runs-on: macos-15-intel + permissions: + contents: read + actions: read + env: CMAKE_GEN: Ninja CMAKE_TCH: cmake/toolchains/OSX-AppleClang-x86_64.cmake steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install prerequisites @@ -70,13 +74,17 @@ jobs: upload_github_release: needs: macos-x86_64 + permissions: + contents: read + actions: write + # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches if: contains(fromJson('["master", "main"]'), github.ref_name) runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-osx-x86_64 merge-multiple: true @@ -99,7 +107,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-osx-x86_64 merge-multiple: true diff --git a/.github/workflows/build_win_x86.yml b/.github/workflows/build_win_x86.yml index 6664b77ed..730233c5a 100644 --- a/.github/workflows/build_win_x86.yml +++ b/.github/workflows/build_win_x86.yml @@ -22,9 +22,13 @@ jobs: windows-x86: runs-on: windows-latest + permissions: + contents: read + actions: read + steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Add msbuild to PATH @@ -54,11 +58,14 @@ jobs: needs: windows-x86 # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches if: contains(fromJson('["master", "main"]'), github.ref_name) + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-win-x86 merge-multiple: true @@ -77,11 +84,14 @@ jobs: upload_selfhost_release: needs: windows-x86 if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-win-x86 merge-multiple: true diff --git a/.github/workflows/build_win_x86_64.yml b/.github/workflows/build_win_x86_64.yml index ee74649de..2beaf49ac 100644 --- a/.github/workflows/build_win_x86_64.yml +++ b/.github/workflows/build_win_x86_64.yml @@ -21,10 +21,13 @@ on: jobs: windows-x86_64: runs-on: windows-latest + permissions: + contents: read + actions: read steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Add msbuild to PATH @@ -55,11 +58,14 @@ jobs: needs: windows-x86_64 # Upload artifact linked to GitHub RELEASE we are creating - only if the run is for a pull request, or for selected branches if: contains(fromJson('["master", "main"]'), github.ref_name) + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-win-x86_64 merge-multiple: true @@ -77,11 +83,14 @@ jobs: upload_selfhost_release: needs: windows-x86_64 if: contains(fromJson('["master", "main"]'), github.ref_name) && (github.repository == 'SphereServer/Source-X') + permissions: + contents: read + actions: write runs-on: ubuntu-latest steps: - name: Download builds - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Build-win-x86_64 merge-multiple: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6ba6ae5be..a6db06206 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,7 +3,7 @@ name: CodeQL on: push: - branches: ["static-analysis"] + branches: ["static-analysis", "master", "main"] paths-ignore: - '.gitignore' - '.gitattributes' @@ -22,7 +22,7 @@ on: jobs: analyze: name: Analyze - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 permissions: actions: read contents: read @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Install prerequisites run: | @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -67,7 +67,7 @@ jobs: # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # c++ queries: https://codeql.github.com/codeql-query-help/cpp-cwe/ @@ -88,7 +88,7 @@ jobs: # https://josh-ops.com/posts/github-codeql-ignore-files/ # https://github.com/advanced-security/filter-sarif - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" upload: false # disable the upload here - we will upload in a different action @@ -103,6 +103,6 @@ jobs: output: sarif-results/${{ matrix.language }}.sarif - name: Upload SARIF - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: sarif-results/${{ matrix.language }}.sarif diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index df0f9c86c..3ba0f5bc5 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -2,7 +2,7 @@ name: coverity-scan on: push: - branches: ["static-analysis"] + branches: ["static-analysis", "master", "main"] paths-ignore: - '.gitignore' - '.gitattributes' @@ -13,10 +13,14 @@ on: jobs: latest: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 + permissions: + contents: read + actions: read + steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 @@ -43,7 +47,7 @@ jobs: - name: Download Coverity Build Tool env: TOKEN: ${{ secrets.COVERITY_TOKEN }} -#echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + #echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- run: | wget -nv https://scan.coverity.com/download/cxx/linux64 --post-data "token=${TOKEN}&project=Sphereserver/Source-X" -O cov-analysis-linux64.tar.gz mkdir cov-analysis-linux64 @@ -75,13 +79,12 @@ jobs: cd build tar czvf build-data.tgz cov-int curl \ - --form project=SphereServer/Source-X \ --form token=$TOKEN \ --form email=$EMAIL \ --form file=@build-data.tgz \ - --form version=trunk \ + --form version="trunk" \ --form description="${{github.sha}}" \ - https://scan.coverity.com/builds?project=SphereServer-X + https://scan.coverity.com/builds?project=Sphereserver%2FSource-X env: TOKEN: ${{ secrets.COVERITY_TOKEN }} EMAIL: ${{ secrets.COVERITY_EMAIL }} diff --git a/.gitignore b/.gitignore index 96543c3cc..4f89e8de3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ /.ctagsd /.codelite +/_deps /CMakeFiles +/external /spheresvr.dir /bin @@ -63,3 +65,8 @@ CMakeCache.txt /lib/mariadb_connector_c/mysql/ma_config.h /lib/mariadb_connector_c/mysql/mariadb_version.h /lib/zlib/zlib/zconf.h + +# Ignore debian package files. +/sphereserver*.buildinfo +/sphereserver*.changes +/sphereserver*.deb diff --git a/CMakeLists.txt b/CMakeLists.txt index 89abf7887..5f8aa791d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,6 @@ set(CMAKE_CXX_FLAGS_MINSIZEREL "") message(STATUS "Scanning system build tools...") project(SphereServer CXX C) # does a scan for C++ and C compilers - # If we have not specified a toolchain, let's detect which one we should use, using the detected arch. # We need to do this after "project", otherwise CMake doesn't know where it's running. if(NOT TOOLCHAIN_LOADED) @@ -61,6 +60,8 @@ endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_SCAN_FOR_MODULES OFF) # disable C++ module scanning +set(CMAKE_CXX_VISIBILITY_PRESET "hidden") +set(CMAKE_VISIBILITY_INLINES_HIDDEN "YES") # Need to clear shared library flags. If not, cmake sets -rdynamic and this # add to the executable the full symbol table (included unused symbols). @@ -70,18 +71,16 @@ set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") # In some cases, CMake also sets other predefined flags, which we don't want. #[[ -set(CMAKE_C_FLAGS "") set(CMAKE_C_FLAGS_DEBUG "") set(CMAKE_C_FLAGS_NIGHTLY "") set(CMAKE_C_FLAGS_RELEASE "") -set(CMAKE_CXX_FLAGS "") set(CMAKE_CXX_FLAGS_DEBUG "") set(CMAKE_CXX_FLAGS_NIGHTLY "") set(CMAKE_CXX_FLAGS_RELEASE "") -set(CMAKE_EXE_LINKER_FLAGS "") set(CMAKE_EXE_LINKER_FLAGS_DEBUG "") +set(CMAKE_EXE_LINKER_FLAGS_NIGHTLY "") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "") ]] # Example: using MSVC, /RTC1 is globally added (all projects) to DEBUG builds. It's mostly fine, except in some cases where we don't want it... @@ -106,6 +105,7 @@ if(WIN32) option(WIN_GENERATE_CRASHDUMP "For non-Debug builds, add Windows Structured Exception Handling and generate a Crash Dump if a fatal SE is thrown." FALSE) endif() endif() +option(FORCE_DEBUG_INFO "Force generation of debug info, useful for profiling Nightly/Release builds." FALSE) option(CROSSCOMPILE "Are we compiling for a different target architecture?" FALSE) option( RUNTIME_STATIC_LINK @@ -131,11 +131,18 @@ option(USE_UBSAN "Enable Undefined Behavior Sanitizer." FALSE) option(USE_LSAN "Enable LeakSanitizer." FALSE) option(USE_MSAN "Enable MemorySanitizer." FALSE) +option(UNIT_TESTING "Create an executable for unit tests." FALSE) + +option(USE_CPPCHECK "Perform static analysis with CppCheck." FALSE) + set(ENABLED_SANITIZER FALSE CACHE INTERNAL BOOL "Am i using a sanitizer?") if(USE_ASAN OR USE_MSAN OR USE_LSAN OR USE_MSAN) set(ENABLED_SANITIZER TRUE) endif() +if(USE_CPPCHECK) + include("cmake/CppCheck.cmake") +endif() # -------------------- Stuff to set right away, after having parsed the checkboxes/CLI arguments. @@ -162,7 +169,6 @@ endif() # TODO: check if Ninja can handle different targets. if(SINGLE_TARGET) # If you want to manually specify the build type, call cmake with parameter: -DCMAKE_BUILD_TYPE=something - # TODO: add NightlyWithDebInfo ? message(STATUS "Single-target build system (${CMAKE_GENERATOR}) detected: generating multiple projects!") set (CMAKE_BUILD_TYPE "Nightly" CACHE STRING "Set the build type. Expected values: Debug, Nightly or Release.") # Set only if unset. set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Nightly" "Release") @@ -203,6 +209,10 @@ endif(SINGLE_TARGET) # Include the list of all Sphere source files include("src/CMakeSources.cmake") +if(UNIT_TESTING) + # Add the unit tests source files, to compile them and add to the IDe project files list + include("tests/CMakeSources.cmake") +endif() # Configure output binaries set(TARGETS "" CACHE INTERNAL "List of Sphere targets to build.") @@ -212,59 +222,55 @@ if(SINGLE_TARGET) add_executable( spheresvr_release ${is_win32_app_linker} - ${SPHERE_SOURCES} + ${SPHERE_SOURCES} ${SPHERE_HEADERS} #${docs_TEXT} ) set_target_properties(spheresvr_release PROPERTIES OUTPUT_NAME SphereSvrX${ARCH_BITS}_release) - #set_target_properties(spheresvr_release PROPERTIES CXX_EXTENSIONS OFF) - #target_compile_features(spheresvr_release PUBLIC cxx_std_20) - target_precompile_headers(spheresvr_release ${pch_options}) - add_custom_command(TARGET spheresvr_release POST_BUILD - COMMAND ${CMAKE_STRIP} $ - COMMENT "Stripping executable" - ) + if(NOT ${FORCE_DEBUG_INFO}) + add_custom_command(TARGET spheresvr_release POST_BUILD + COMMAND ${CMAKE_STRIP} $ + COMMENT "Stripping executable" + ) + endif() endif() if(("${CMAKE_BUILD_TYPE}" STREQUAL "") OR (${CMAKE_BUILD_TYPE} MATCHES "(N|n?)ightly")) set(TARGETS ${TARGETS} spheresvr_nightly) add_executable( spheresvr_nightly ${is_win32_app_linker} - ${SPHERE_SOURCES} + ${SPHERE_SOURCES} ${SPHERE_HEADERS} #${docs_TEXT} ) set_target_properties(spheresvr_nightly PROPERTIES OUTPUT_NAME SphereSvrX${ARCH_BITS}_nightly) - #set_target_properties(spheresvr_nightly PROPERTIES CXX_EXTENSIONS OFF) - #target_compile_features(spheresvr_nightly PUBLIC cxx_std_20) - target_precompile_headers(spheresvr_nightly ${pch_options}) - add_custom_command(TARGET spheresvr_nightly POST_BUILD - COMMAND ${CMAKE_STRIP} $ - COMMENT "Stripping executable" - ) + if(NOT ${FORCE_DEBUG_INFO}) + add_custom_command(TARGET spheresvr_nightly POST_BUILD + COMMAND ${CMAKE_STRIP} $ + COMMENT "Stripping executable" + ) + endif() endif() if(("${CMAKE_BUILD_TYPE}" STREQUAL "") OR (${CMAKE_BUILD_TYPE} MATCHES "(D|d?)ebug")) set(TARGETS ${TARGETS} spheresvr_debug) add_executable( spheresvr_debug ${is_win32_app_linker} - ${SPHERE_SOURCES} + ${SPHERE_SOURCES} ${SPHERE_HEADERS} #${docs_TEXT} ) set_target_properties(spheresvr_debug PROPERTIES OUTPUT_NAME SphereSvrX${ARCH_BITS}_debug) - #set_target_properties(spheresvr_debug PROPERTIES CXX_EXTENSIONS OFF) - #target_compile_features(spheresvr_debug PUBLIC cxx_std_20) - target_precompile_headers(spheresvr_debug ${pch_options}) endif() else(SINGLE_TARGET) set(TARGETS ${TARGETS} spheresvr) - add_executable(spheresvr ${is_win32_app_linker} ${SPHERE_SOURCES} ${docs_TEXT}) + add_executable(spheresvr + ${is_win32_app_linker} + ${SPHERE_SOURCES} ${SPHERE_HEADERS} + ${docs_TEXT} + ) set_target_properties(spheresvr PROPERTIES OUTPUT_NAME_RELEASE SphereSvrX${ARCH_BITS}_release) set_target_properties(spheresvr PROPERTIES OUTPUT_NAME_NIGHTLY SphereSvrX${ARCH_BITS}_nightly) set_target_properties(spheresvr PROPERTIES OUTPUT_NAME_DEBUG SphereSvrX${ARCH_BITS}_debug) - #set_target_properties(spheresvr PROPERTIES CXX_EXTENSIONS OFF) - #target_compile_features(spheresvr PUBLIC cxx_std_20) - target_precompile_headers(spheresvr ${pch_options}) # To be tested #add_custom_command(TARGET spheresvr POST_BUILD @@ -274,6 +280,13 @@ else(SINGLE_TARGET) #) endif(SINGLE_TARGET) +foreach(tgt ${TARGETS}) + #set_target_properties(${tgt} PROPERTIES CXX_EXTENSIONS OFF) + #target_compile_features(${tgt} PUBLIC cxx_std_20) + target_precompile_headers(${tgt} ${pch_list}) +endforeach() + + # -------------------- message(STATUS) @@ -289,8 +302,14 @@ add_subdirectory(lib) toolchain_exe_stuff() # stuff to be executed after ADD_EXECUTABLE +if(UNIT_TESTING) + include("tests/CMakeLists.txt") # quick and dirty + setup_tests() +endif() + # Get the Git revision number message(STATUS) message(STATUS "Retrieving git data...") include("cmake/GitStatus.cmake") message(STATUS) + diff --git a/Changelog.txt b/Changelog.txt index 2e34870e8..01b71f701 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -3967,16 +3967,16 @@ Added: 'H' shortcut for variables to get the value as hexadecimal. 6-12-2024, canerksk - Added: LOCAL.WOPTalkMode in @SpellCast trigger and ini setting WOPTalkMode. In some clients, different operations can be performed based on this. For example, if this talk mode was sent from Sphere as TALKMODE_SPELL, the operation is performed according to the client version. The possibility of changing the talk mode according to the client version used is now provided. By default, TALKMODE_SPELL. - + Default talk mode: TALKMODE_SPELL = 10 - + Sphere.ini: WOPTalkMode = 0/14 - + Trigger: @SpellCast Local.WOPTalkMode = 0/14 - + TALKMODE_SAY = 0 // A character speaking. TALKMODE_SYSTEM = 1 // Display as system prompt. TALKMODE_EMOTE = 2 // *smiles* at object (client shortcut: :+space). @@ -4019,7 +4019,7 @@ Added: 'H' shortcut for variables to get the value as hexadecimal. VerboseItemBounce = 0 // (default) 22-04-2025, canerksk -- Added: New item function, CARVECORPSE: carves a corpse with SRC = char carving the corpse. +- Added: New item function, CARVECORPSE: carves a corpse with SRC = char carving the corpse. - Added: New ini RevealFlag setting REVEALF_ONHORSE, to allow or disallow stealth walking if you are on a mount. - Added: new ini CombatFlags setting COMBAT_ATTACK_NOAGGREIVED. In the old behavior (55i/55r/56b/56c) we were not guilty when we hit a murderer. Now when we attack a criminal or murderer we are guilty towards that person. An ini setting option has been added for this behavior. Although this is a normal behavior in OSI, it can be perceived as a problem since not every server designs games like OSI. @@ -4047,3 +4047,26 @@ When setting a property like MORE to the a spell or skill defname, trying to rea 08-05-2025, Nolok - Added: NPCWanderingLookAroundChance ini setting and TAG.OVERRIDE.LOOKAROUNDCHANCE. It allows to change the chance that a wandering NPC will analyze its surroundings for the current step. This also means that the trigger @NPCLookAtChar will fire more frequently. + +16-05-2025, Mulambo +- Changed: When equipping item by dragging or using Dress macro, the item layer is now showing actual layer of the object in EquipTest and ItemEquipTest trigger (instead of 31 = LAYER_DRAGGING). This is the same behaviour, as equipping by double clicking. + +22-09-2025, Mulambo +- Changed: NPC Breath Damage (brain_dragon) is no longer capped at 200, but can go up to approx 65535. + +24-09-2025, Mulambo +- Added: ARGN3 for trigger @PersonalSpace [R/W], allowing to bypass maximum stamina requirement (default 1 = require maximum stamina) when stepping over players. + +26-09-2025, Mulambo +- Changed: When the party leader gets disconnected (or deleted), new party leader is appointed, instead of disbanding the party. + +30-09-2025, Mulambo +- Changed: Login rejection (packet `0x82`) is now sent to all clients trying to log in (used to be only to clients, that Sphere could identify). + +03-11-2025, Nolok +- Fixed: Corner cases issues with number parsing from scripts. +- Fixed: Function call stack messed up ordering. +- Fixed: Wrong script line pointed to in console log messages. +- Fixed: Changing Char flags didn't always trigger a full update packet. +- Fixed: Sector indexing issue. +- Changed: Made both Windows and Linux load scripts in a folder in alphabetical order. diff --git a/README.md b/README.md index 61473798f..3f4c2521d 100644 --- a/README.md +++ b/README.md @@ -14,23 +14,23 @@ Ultima Online game server, developed in C++. | [![Discord Shield](https://discordapp.com/api/guilds/354358315373035542/widget.png?style=shield)](https://discord.gg/ZrMTXrs) | ## Getting Started tutorial -If you're new to the Sphere server and want to setup your first shard, [this is your go-to guide](docs/Getting-started.md)! +If you're new to the Sphere server and want to set up your first shard, [this is your go-to guide](docs/Getting-started.md)! ## Releases ### **Core** -| Branch: Master
(most stable pre-releases) | Branch: Dev
(most recent, potentially unstable) | -| :--- | :--- | -| [![GitHub last commit on Master branch](https://img.shields.io/github/last-commit/Sphereserver/Source-X/master.svg)](https://github.com/Sphereserver/Source-X/)   Changelog | [![GitHub last commit on Dev branch](https://img.shields.io/github/last-commit/Sphereserver/Source-X/dev.svg)](https://github.com/Sphereserver/Source-X/tree/dev)   Changelog | -| **Nightly builds:** | **Nightly builds:** | -| [![Build status: Windows x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml?query=branch%3Amaster) [![Build status: Windows x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml?query=branch%3Amaster) | [![Build status: Windows x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml?query=branch%3Adev) [![Build status: Windows x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml?query=branch%3Adev)| -| [![Build status: Linux x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml) [![Build status: Linux x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml?query=branch%3Amaster) | [![Build status: Linux x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml?query=branch%3Adev) [![Build status: Linux x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml?query=branch%3Adev) | -| [![Build status: MacOS x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml?query=branch%3Amaster) [![Build status: MacOS ARM](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml?query=branch%3Amaster) | [![Build status: MacOS x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml?query=branch%3Adev) [![Build status: MacOS ARM](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml?query=branch%3Adev) | +| Branch: Master
(most stable pre-releases) | Branch: Dev
(most recent, potentially unstable) | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [![GitHub last commit on Master branch](https://img.shields.io/github/last-commit/Sphereserver/Source-X/master.svg)](https://github.com/Sphereserver/Source-X/)   Changelog | [![GitHub last commit on Dev branch](https://img.shields.io/github/last-commit/Sphereserver/Source-X/dev.svg)](https://github.com/Sphereserver/Source-X/tree/dev)   Changelog | +| **Nightly builds:** | **Nightly builds:** | +| [![Build status: Windows x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml?query=branch%3Amaster) [![Build status: Windows x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml?query=branch%3Amaster) | [![Build status: Windows x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86_64.yml?query=branch%3Adev) [![Build status: Windows x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_win_x86.yml?query=branch%3Adev) | +| [![Build status: Linux x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml) [![Build status: Linux x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml?query=branch%3Amaster) | [![Build status: Linux x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86_64.yml?query=branch%3Adev) [![Build status: Linux x86](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_linux_x86.yml?query=branch%3Adev) | +| [![Build status: macOS x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml?query=branch%3Amaster) [![Build status: macOS ARM](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml/badge.svg?branch=master)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml?query=branch%3Amaster) | [![Build status: macOS x86_64](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_x86_64.yml?query=branch%3Adev) [![Build status: macOS ARM](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml/badge.svg?branch=dev)](https://github.com/Sphereserver/Source-X/actions/workflows/build_osx_arm.yml?query=branch%3Adev) | **Click the badges or follow the links:** -+ Github Nightly builds ++ GitHub Nightly builds + SphereServer Website ### **ScriptPack** @@ -95,7 +95,7 @@ Most notable changes (right now) are: Only recent compilers are supported, since Sphere uses C++20 features: the newer the compiler, the better. Oldest compiler versions supporting C++20: Visual Studio 2019 version 16.11, GCC 8, MinGW distributions using GCC 8, Clang version 10. No pre-built project files are included. You need to build Visual Studio solution (.sln), Makefiles or Ninja Build files (or project files for other IDE or build systems) with CMake. -Below there's is a small guide. +Below there is a small guide. You can use CMake GUI (graphical interface) or its CLI (command line interface). To pass a flag to using the CLI, just prepend `-D` to the name of the variable, then assign the value with the equal sign, eg: `-DFOO=TRUE`. @@ -117,15 +117,15 @@ Before starting: does CMake give you an error? Ensure that you have Git installe + Via CMake CLI (command line interface): pass the parameter `-DCMAKE_TOOLCHAIN_FILE="..."` + Build **Configuration** (type) - When using Makefiles or Ninja, you can specify a build type by setting (also this via GUI or CLI) `CMAKE_BUILD_TYPE="build"`, where build is **Nightly**, **Debug** or **Release**. If the build type was not set, by default the makefiles for all of the three build types are generated. - **Debug** build is expected to be slow and it's to be used, you guessed it, for debugging purposes (best coupled with a debugger or with sanitizers enabled, more on them right below), so don't use it for a live shard! + When using Makefiles or Ninja, you can specify a build type by setting (also this via GUI or CLI) `CMAKE_BUILD_TYPE="build"`, where build is **Nightly**, **Debug** or **Release**. If the build type was not set, by default the makefiles for all three build types are generated. + **Debug** build is expected to be slow, and it's to be used, you guessed it, for debugging purposes (best coupled with a debugger or with sanitizers enabled, more on them right below), so don't use it for a live shard! Other useful CMake flags: + You can add other compiler flags with the custom variables `C_FLAGS_EXTRA`, `CXX_FLAGS_EXTRA`, `CMAKE_EXE_LINKER_FLAGS_EXTRA`. + Enable Sanitizers: `USE_ASAN[=ON]`, `USE_UBSAN`, `USE_LSAN`, etc. If you are using Address Sanitizer, Undefined Behaviour Sanitizer, Leak Sanitizer on Windows, it might be useful to redirect stderr to stdout to correctly show the output: `SphereSvrX64_nightly > Sphere_ASan_log.txt 2>&1`. - Remember to setup Sanitizers settings by setting the respective shell variables. To ease this, there are some batch/shell scripts doing that for you inside `utilities/`. + Remember to set up Sanitizers settings by setting the respective shell variables. To ease this, there are some batch/shell scripts doing that for you inside `utilities/`. + `CROSSCOMPILING_ARCH`: set this to TRUE to tell the compiler you are building binary files for a different architecture (not from x86_64 to x86, but for example from x86 to ARM). Example to build makefiles on Linux for a 64 bits Nightly version, inside the "build" directory (run the command inside the project's root folder): @@ -145,26 +145,78 @@ Building will require more packages than the ones needed to run Sphere. Install these additional packages: + Build tools (other than the compiler): `sudo apt-get install git cmake`. + MariaDB client: `sudo apt-get install libmariadb-dev` and `libmariadb3` or `mariadb-client` (depends on the OS version) - If you are on a 64 bits architecture but you want to compile (or execute) a 32 bits binary, you will need to + If you are on a 64 bits architecture, but you want to compile (or execute) a 32 bits binary, you will need to install MariaDB packages adding the postfix `:i386` to each package name. + CentOS - Red Hat Enterprise Linux - Fedora Install these additional packages via yum (CentOS or RH) or dnf (Fedora). + Build tools (other than the compiler): `git cmake glibc-devel` + MariaDB client: `mariadb-connector-c mariadb-connector-c-devel` - If you are on a 64 bits architecture but you want to compile (or execute) a 32 bits binary, you will need to install MariaDB packages adding the postfix `.i686` to each package name. + If you are on a 64 bits architecture, but you want to compile (or execute) a 32 bits binary, you will need to install MariaDB packages adding the postfix `.i686` to each package name. #### Compiling on Linux Just run `cmake --build .` in the build directory (the one where you have asked CMake to create its files). +## Debian package + +Debian package is an installation package for Linux Debian and its derivates (Ubuntu, Linux Mint, etc.) and intended to +be run as a service. Folder structure is as follows: +- Binary is located in `/usr/bin/` directory (use `which sphereserver` to locate it). +- Configuration files (ini) are in `/etc/sphereserver/` directory. +- Log files are stored in `/var/log/sphereserver/` directory. +- Everything else is in `/opt/sphereserver/`. + +These paths are automatically replaced in sphere.ini during installation. + +### Build + +Building deb package is pretty straightforward. The requirements are the same, as you would compile code by hand. One +extra requirement is _debhelper_ package (`sudo apt install debhelper`). + +**Step by step building:** +1) Clone repository. +2) Change directory to wherever you cloned repository and then go to `packaging` directory. +3) Since we are in nightly, there is no actual SemVer changelog that we can include, and we have to use workaround to + generate one. By running `cat debian/data/changelog | sed -e "s/@version@/$(git rev-list --count HEAD)/" -e "s/@date@/$(date -R)/" > debian/changelog` + you will generate one. This command takes deb changelog template (in `debian/data/changelog`) and replaces __version__ + and __date__ variables with git revision and current date. +4) Build package by running `dpkg-buildpackage -us -uc -b` (arguments are `-b` = build, `-us` and `-uc` = don't sign + built files with OpenPGP key). This will compile sources and create debian package, which will be stored in the root + of the project directory (i.e. `/home/dev/source-x/`). The cmake is predefined to build a Linux x86_64 Nightly version + of SphereServer. All build commands and switches are defined in `debian/rules` file. + +### Installation + +When you download or build the package, you have to make it executable `sudo chmod +x FILENAME.deb`. Then you can +install the package by running `sudo apt install FILENAME.deb`. The installation process will download required +dependencies, show you some info about SphereServer being in nightly version, create an owner game account (admin/admin), +create dedicated linux user `sphereserver`, directory structure and service files for you. + +After installation, you will have to provide mul files and scripts to `/opt/sphereserver/mul/` and`/opt/sphereserver/scripts/` +folder and `AGREE=1` in sphere.ini. + +When you are finished with configuration, you can enable/start services by running `sudo systemctl enable sphereserver` +and `sudo systemctl start sphereserver`. To check, if service is running, type `systemctl status sphereserver`, check +the journal (`journalctl -u sphereserver`) or sphere logs. + +### Updating + +Updating is the same as installation, download / build package, run `sudo chmod +x FILENAME.deb` and `sudo apt install FILENAME.deb`. +If any changes happened in ini files and are in conflict with your settings, you will be asked to review them. + +### Removal + +To remove sphereserver, run `sudo apt remove sphereserver` and `sudo apt remove sphereserver --purge`. All files related +to sphereserver will be deleted. + ## Coding Notes (add as you wish to standardize the coding for new contributors) + Make sure you can compile and run the program before pushing a commit. + Rebasing instead of pulling the project is a better practice to avoid unnecessary "merge branch master" commits. + Removing/Changing/Adding anything that was working in one way for years should be followed by an ini setting if the changes cannot be replicated from script to keep some backwards compatibility. -+ Comment your code, add informations about its logic. It's very important since it helps others to understand your work. ++ Comment your code, add information about its logic. It's very important since it helps others to understand your work. + Be sure to use Sphere's custom datatypes and the string formatting macros described in src/common/datatypes.h. + When casting numeric data types, always prefer C-style casts, like (int), to C++ static_cast<int>(). It's way more concise. + Be wary that in SphereScript unsigned values does not exist, all numbers are considered signed, and that 64 bits integers meant @@ -173,7 +225,7 @@ Just run `cmake --build .` in the build directory (the one where you have asked Use fixed width variables only for values that need to fit a limited range. + For strings, use pointers: to "char" for strings that should always have ASCII encoding; - to "tchar" for strings that may be ASCII or Unicode, depending from compilation settings (more info in "datatypes.h"); + to "tchar" for strings that may be ASCII or Unicode, depending on compilation settings (more info in "datatypes.h"); to "wchar" for string that should always have Unicode encoding. ### Naming Conventions @@ -199,7 +251,7 @@ These are meant to be applied to new code and, if there's some old code not foll + For char, wchar, tchar use respectively the prefixes "c", "wc", "tc". + When handling strings, "lpstr", "lpcstr", "lpwstr", "lpcwstr", "lptstr", "lpctstr" data types are preferred aliases. You'll find a lot of "psz" prefixes for strings: the reason is that in the past Sphere coders wanted to be consistent with Microsoft's Hungarian Notation. - The correct and up to date notation is "pc" for lpstr/lpcstr (which are respectively char*and const char*), "pwc" (wchar*and const wchar*), + The correct and up-to-date notation is "pc" for lpstr/lpcstr (which are respectively char*and const char*), "pwc" (wchar*and const wchar*), "ptc" for lptstr/lpctstr (tchar*and const tchar*). Use the "s" or "ps" (if pointer) when using CString or std::string. Always prefer CString over std::string, unless in your case there are obvious advantages for using the latter. diff --git a/cmake/CompilerFlagsBase.cmake b/cmake/CompilerFlagsBase.cmake index ac6ef5972..65f345ac9 100644 --- a/cmake/CompilerFlagsBase.cmake +++ b/cmake/CompilerFlagsBase.cmake @@ -19,6 +19,14 @@ if(NOT MSVC) -fdata-sections ) + if(FORCE_DEBUG_INFO) + list( + APPEND + compiler_options_base + -ggdb3 + ) + endif() + list( APPEND compiler_options_warnings_base @@ -95,7 +103,7 @@ if(NOT MSVC) if("${ENABLED_SANITIZER}" STREQUAL "TRUE") # -fno-omit-frame-pointer disables a good optimization which might corrupt the debugger stack trace. - set(local_compile_options_nondebug -ggdb3 -Og -fno-omit-frame-pointer -fno-inline) + set(local_compile_options_nondebug -ggdb3 -O1 -fno-omit-frame-pointer -fno-inline-small-functions) set(local_link_options_nondebug) #-gsplit-dwarf or -gmacro if i want to add lto and there are some missing symbols else() # If using ThinLTO: -flto=thin -fsplit-lto-unit @@ -104,17 +112,17 @@ if(NOT MSVC) set(local_link_options_nondebug)#-flto) endif() - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - list(APPEND local_link_options_nondebug -Wl,-dead_strip) - else() - list(APPEND local_link_options_nondebug -Wl,--gc-sections) - endif() - + #if(NOT FORCE_DEBUG_INFO) + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + list(APPEND local_link_options_nondebug -Wl,-dead_strip) + else() + list(APPEND local_link_options_nondebug -Wl,--gc-sections) + endif() + #endif() set(custom_compile_options_release ${local_compile_options_nondebug} -flto=full - -fvirtual-function-elimination -ffunction-sections -fdata-sections #-fno-unique-section-names @@ -125,7 +133,7 @@ if(NOT MSVC) CACHE INTERNAL STRING ) set(custom_compile_options_debug - -ggdb3 -O0 -fno-omit-frame-pointer -fno-inline + -ggdb3 -O0 -fno-omit-frame-pointer -fno-inline-functions #-fno-inline CACHE INTERNAL STRING ) @@ -156,4 +164,4 @@ if(NOT MSVC) message(STATUS "Adding the following linker options specific to 'Debug' build: ${custom_link_options_debug}.") endif() -endif() +endif(NOT MSVC) diff --git a/cmake/CompilerFlagsChecker.cmake b/cmake/CompilerFlagsChecker.cmake index 83350c590..f69e64737 100644 --- a/cmake/CompilerFlagsChecker.cmake +++ b/cmake/CompilerFlagsChecker.cmake @@ -1,10 +1,11 @@ include(CheckCXXCompilerFlag) include(CheckLinkerFlag) -message(STATUS "Checking available compiler flags...") +message(STATUS "Checking available compiler and linker flags...") if(NOT MSVC) - message(STATUS "-- Compilation options:") + message(STATUS "-- Linker options:") + # Linker flags. if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") @@ -14,9 +15,9 @@ if(NOT MSVC) check_linker_flag(CXX -Wl,--fatal-warnings LINKER_HAS_FATAL_WARN) endif() check_linker_flag(CXX -Wl,--as-needed LINKER_HAS_AS_NEEDED) - check_linker_flag(CXX -Wl,--icf=safe LINKER_HAS_ICF) # icf = identical code folding (not supported by mold) + message(STATUS "-- Compiler options:") # Compiler option flags. Common to both compilers, but older versions might not support the following. #check_cxx_compiler_flag("" COMP_HAS_) @@ -24,15 +25,16 @@ if(NOT MSVC) check_cxx_compiler_flag("-fno-expensive-optimizations" COMP_HAS_FNO_EXPENSIVE_OPTIMIZATIONS) # Compiler option flags. Expected to work on Clang but not GCC, at the moment. + # check_cxx_compiler_flag(CXX "-fvirtual-function-elimination" COMP_HAS_VFUNC_ELIMINATION) # only for release build? is it stable? # check_cxx_compiler_flag("-fforce-emit-vtables" COMP_HAS_F_FORCE_EMIT_VTABLES) # -fwhole-program-vtables # -fstrict-vtable-pointers if(${USE_ASAN}) # Compiler option flags. Sanitizers related (ASAN). - message(STATUS "-- Compilation options (Address Sanitizer):") + message(STATUS "-- Compiler options (Address Sanitizer):") - set(CMAKE_REQUIRED_LIBRARIES -fsanitize=address) # link against sanitizer lib, being required by the following compilation test + set(CMAKE_REQUIRED_LIBRARIES -fsanitize=address) # link against sanitizer lib, being required by the following compiler flag check_cxx_compiler_flag("-fsanitize=address" COMP_HAS_ASAN) unset(CMAKE_REQUIRED_LIBRARIES) if(NOT COMP_HAS_ASAN) @@ -48,9 +50,9 @@ if(NOT MSVC) if(${USE_UBSAN}) # Compiler option flags. Sanitizers related (UBSAN). - message(STATUS "-- Compilation options (Undefined Behavior Sanitizer):") + message(STATUS "-- Compiler options (Undefined Behavior Sanitizer):") - set(CMAKE_REQUIRED_LIBRARIES -fsanitize=undefined) # link against sanitizer lib, being required by the following compilation test + set(CMAKE_REQUIRED_LIBRARIES -fsanitize=undefined) # link against sanitizer lib, being required by the following compiler flag check_cxx_compiler_flag("-fsanitize=undefined" COMP_HAS_UBSAN) unset(CMAKE_REQUIRED_LIBRARIES) if(NOT COMP_HAS_UBSAN) @@ -58,7 +60,7 @@ if(NOT MSVC) endif() check_cxx_compiler_flag("-fsanitize=float-divide-by-zero" COMP_HAS_FSAN_FLOAT_DIVIDE_BY_ZERO) - check_cxx_compiler_flag("-fsanitize=unsigned-integer-overflow" COMP_HAS_FSAN_UNSIGNED_INTEGER_OVERFLOW) #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. + #check_cxx_compiler_flag("-fsanitize=unsigned-integer-overflow" COMP_HAS_FSAN_UNSIGNED_INTEGER_OVERFLOW) #Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. #check_cxx_compiler_flag("-fsanitize=implicit-conversion" COMP_HAS_FSAN_IMPLICIT_CONVERSION) #check_cxx_compiler_flag("-fsanitize=local-bounds" COMP_HAS_FSAN_LOCAL_BOUNDS) check_cxx_compiler_flag("-fno-sanitize=enum" COMP_HAS_FSAN_NO_ENUM) @@ -66,9 +68,9 @@ if(NOT MSVC) if(${USE_LSAN}) # Compiler option flags. Sanitizers related (LSAN). - message(STATUS "-- Compilation options (Leak Sanitizer):") + message(STATUS "-- Compiler options (Leak Sanitizer):") - set(CMAKE_REQUIRED_LIBRARIES -fsanitize=leak) # link against sanitizer lib, being required by the following compilation test + set(CMAKE_REQUIRED_LIBRARIES -fsanitize=leak) # link against sanitizer lib, being required by the following compiler flag check_cxx_compiler_flag("-fsanitize=leak" COMP_HAS_LSAN) unset(CMAKE_REQUIRED_LIBRARIES) if(NOT COMP_HAS_LSAN) @@ -78,9 +80,9 @@ if(NOT MSVC) if(${USE_MSAN}) # Compiler option flags. Sanitizers related (UBSAN). - message(STATUS "-- Compilation options (Memory Behavior Sanitizer):") + message(STATUS "-- Compiler options (Memory Behavior Sanitizer):") - set(CMAKE_REQUIRED_LIBRARIES -fsanitize=memory) # link against sanitizer lib, being required by the following compilation test + set(CMAKE_REQUIRED_LIBRARIES -fsanitize=memory) # link against sanitizer lib, being required by the following compiler flag check_cxx_compiler_flag("-fsanitize=memory" COMP_HAS_MSAN) unset(CMAKE_REQUIRED_LIBRARIES) if(NOT COMP_HAS_MSAN) @@ -113,10 +115,15 @@ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/Mem message(FATAL_ERROR "This compiler doesn't support statically linking libasan. Turn off RUNTIME_STATIC_LINK?") endif() endif() + else() + # icf = identical code folding: + # - not supported by mold + # - error when linking with lld and asan or other sanitizers + check_linker_flag(CXX -Wl,--icf=safe LINKER_HAS_ICF) endif() if(USE_COMPILER_HARDENING_OPTIONS) - message(STATUS "-- Compilation options (code hardening):") + message(STATUS "-- Compiler options (code hardening):") # Compiler option flags. Other sanitizers or code hardening. if(NOT USE_ASAN) check_cxx_compiler_flag("-fsanitize=safe-stack" COMP_HAS_F_SANITIZE_SAFE_STACK) # Can't be used with asan! @@ -135,7 +142,7 @@ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/Mem endif() # Compiler warning flags. - message(STATUS "-- Compilation options (warnings):") + message(STATUS "-- Compiler options (warnings):") #check_cxx_compiler_flag("-Wuseless-cast" COMP_HAS_W_USELESS_CAST) check_cxx_compiler_flag("-Wnull-dereference" COMP_HAS_W_NULL_DEREFERENCE) #check_cxx_compiler_flag("-Wconversion" COMP_HAS_W_CONVERSION) # Temporarily disabled. Implicit type conversions that might change a value, such as narrowing conversions. @@ -167,7 +174,7 @@ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/Mem # -Wdate-time # Compiler warning flags. To be disabled. - message(STATUS "-- Compilation options (disable specific warnings):") + message(STATUS "-- Compiler options (disable specific warnings):") check_cxx_compiler_flag("-Wnonnull-compare" COMP_HAS_WNO_NONNULL_COMPARE) check_cxx_compiler_flag("-Wmaybe-uninitialized" COMP_HAS_WNO_MAYBE_UNINIT) check_cxx_compiler_flag("-Wlanguage-extension-token" COMP_HAS_WNO_LANGUAGE_EXTENSION_TOKEN) @@ -177,7 +184,7 @@ See comments in the toolchain and: https://github.com/google/sanitizers/wiki/Mem elseif(MSVC) # Compiler warning flags (MSVC). - message(STATUS "-- Compilation options (MSVC):") + message(STATUS "-- Compiler options (MSVC):") check_cxx_compiler_flag("/W5105" COMP_HAS_W_DEFINE_MACRO_EXPANSION) # False positive warning, maybe even a MSVC bug. #check_cxx_compiler_flag("" COMP_HAS_) diff --git a/cmake/CppCheck.cmake b/cmake/CppCheck.cmake new file mode 100644 index 000000000..31caa9bf1 --- /dev/null +++ b/cmake/CppCheck.cmake @@ -0,0 +1,28 @@ +find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck) +if(CMAKE_CXX_CPPCHECK) + include(CheckTypeSize) + check_type_size("void*" SIZEOF_VOID_P) + #message(STATUS "Pointer size = ${SIZEOF_VOID_P}") + + list( + APPEND CMAKE_CXX_CPPCHECK + "--verbose" + #"--debug" + "--report-progress" + "--enable=all" + "--safety" + "--inconclusive" + #"--force" + #"--inline-suppr" + "--check-level=exhaustive" + "--template='{file}({line}): {severity} ({id}): {message}'" + #"--checkers-report=${CMAKE_SOURCE_DIR}/cppcheck_out.txt" + "--output-file=${CMAKE_BINARY_DIR}/cppcheck.log" + "--suppressions-list=${CMAKE_SOURCE_DIR}/static_analysis/cppcheck-suppressions.txt" + "--config-exclude=lib/" + "-i lib/" + "-D__SIZEOF_POINTER__=${SIZEOF_VOID_P}" + "-U_TESTEXCEPTION" + "-U_EXCEPTIONS_DEBUG" + ) +endif() diff --git a/cmake/FetchGithubDep.cmake b/cmake/FetchGithubDep.cmake new file mode 100644 index 000000000..39dbbb336 --- /dev/null +++ b/cmake/FetchGithubDep.cmake @@ -0,0 +1,195 @@ +include(FetchContent) + +# ------------------------------------------------------------------ +# fetch_github_library(): +# - Derives the GitHub API URL for “latest release” +# - Caches the JSON metadata locally +# - Extracts `tag_name` from JSON (cached or freshly downloaded) +# - Invokes FetchContent to clone/update into a local cache dir +# - On offline/download failure, automatically reuses the existing cache +# ------------------------------------------------------------------ + +function(fetch_github_library project_name repo_url local_cache_dir) + # project_name e.g. "doctest" + # repo_url e.g. "https://github.com/doctest/doctest.git" + # local_cache_dir e.g. "${CMAKE_SOURCE_DIR}/external" + + # -------------------------------------------------------------- + # 1) Parse and from the provided Git URL + # -------------------------------------------------------------- + string(REGEX MATCH + "github\\.com[/:]([^/]+)/([^/]+)\\.git$" + _ "${repo_url}" + ) + #[[ + Parsing the GitHub owner/repo from a `.git` URL + - `github\.com` + - `\.` → a literal dot. (In regex, `.` normally means “any character,” so we escape it to match an actual `.`.) + - `[/:]` + - A character class matching exactly one of the characters inside: either a slash `/` or a colon `:`. + - This covers both URL styles: + - HTTPS: `https://github.com/owner/repo.git` + - SSH: `git@github.com:owner/repo.git` + - `([^/]+)` + - `(` `)` → defines a capturing group, so whatever matches inside will be saved as CMAKE_MATCH_1. + - `[^/]` → another character class, but with a `^` at the start: “anything but a slash.” + - `+` → “one or more” of the preceding item. + - Together, `([^/]+)` means “capture one or more characters that are not `/`.” That gives us the owner. + - `/` + - Literal, not a special character. Separates owner from repo name. + - `([^/]+)` + - Same as the previous group, capturing the repo name. + - `\.git` + - Literal. Matches the suffix `.git`. + - `$` + - The “end-of-string” anchor. Ensures there’s nothing after `.git`. + ]] + + if(NOT CMAKE_MATCH_COUNT EQUAL 2) + message(FATAL_ERROR + "Could not parse owner/repo from '${repo_url}'") + endif() + set(owner "${CMAKE_MATCH_1}") + set(repo "${CMAKE_MATCH_2}") + + # -------------------------------------------------------------- + # 2) Build the GitHub API URL for “latest release” + # -------------------------------------------------------------- + set(api_url + "https://api.github.com/repos/${owner}/${repo}/releases/latest" + ) + + # -------------------------------------------------------------- + # 3) Prepare cache paths (JSON + source tree) + # -------------------------------------------------------------- + file(MAKE_DIRECTORY "${local_cache_dir}") + if(NOT EXISTS "${local_cache_dir}") + message(FATAL_ERROR + "Failed to create cache directory: ${local_cache_dir}") + endif() + + set(json_file "${local_cache_dir}/${project_name}_latest.json") + set(source_cache_dir "${local_cache_dir}/${project_name}") + + # -------------------------------------------------------------- + # 4) Try to read a cached JSON → tag_name + # -------------------------------------------------------------- + set(lib_tag "") + set(offline FALSE) + + if(EXISTS "${json_file}") + message(STATUS "Found cached JSON for ${project_name}, validating…") + file(READ "${json_file}" _data) + string(REGEX MATCH "\"tag_name\"[ \t]*:[ \t]*\"([^\"]+)\"" _ "${_data}") + #[[ + Extracting the `"tag_name"` value from the JSON + - `"tag_name"` + - A literal double-quoted string, matching exactly `"tag_name"`. + - `[ \t]*` + - A character class containing a space and `\t` (tab). + - `*` means “zero or more” of that class. + - So this matches any amount of whitespace (spaces or tabs) after `"tag_name"`. + - `:` + - A literal colon, since in JSON the key/value separator is `:`. + - `[ \t]*` + - Again, “any amount of spaces/tabs” after the colon. + - `"` + - A literal opening quote for the tag value. + - `([^"]+)` + - Capturing group #1. + - `[^"]` is “any character except a quote.” + - `+` is “one or more.” + - So this grabs all characters inside the quotes (e.g. `v2.4.1`) until it hits the next `"`. + - `"` + - The closing literal quote. + ]] + if(CMAKE_MATCH_COUNT EQUAL 1) + set(lib_tag "${CMAKE_MATCH_1}") + message(STATUS "✔ Using cached tag: ${lib_tag}") + else() + message(WARNING "Cached JSON is invalid. Will attempt fresh download.") + endif() + endif() + + # -------------------------------------------------------------- + # 5) If no valid cached tag, download the latest JSON + # -------------------------------------------------------------- + if(NOT lib_tag) + message(STATUS "Downloading latest release info: ${api_url}") + file(DOWNLOAD + "${api_url}" # ← remote + "${json_file}" # ← local destination + STATUS dl_status + TIMEOUT 5 + SHOW_PROGRESS + INACTIVITY_TIMEOUT 5 + ) + list(GET dl_status 0 code) + if(code EQUAL 0 AND EXISTS "${json_file}") + file(READ "${json_file}" _data) + string(REGEX MATCH "\"tag_name\"[ \t]*:[ \t]*\"([^\"]+)\"" _ "${_data}") + if(CMAKE_MATCH_COUNT EQUAL 1) + set(lib_tag "${CMAKE_MATCH_1}") + message(STATUS "✔ Fetched tag: ${lib_tag}") + else() + message(WARNING "Failed to parse tag_name from downloaded JSON.") + set(offline TRUE) + endif() + else() + message(WARNING "Download failed (code=${code}). Will fall back to cache if any.") + set(offline TRUE) + endif() + endif() + + # -------------------------------------------------------------- + # 6) Final sanity check: ensure we have a lib_tag + # -------------------------------------------------------------- + if(NOT lib_tag) + message(FATAL_ERROR + "Could not determine a valid tag for ${project_name}. Aborting.") + endif() + + # -------------------------------------------------------------- + # 7) Declare FetchContent variables *before* we call it + # + # NEW: In offline mode we must prevent both “git pull” and the + # very first “git clone”. The trick is to: + # + # a) Point CMake at the already-downloaded directory via + # FETCHCONTENT_SOURCE_DIR_ + # b) Disable updates via FETCHCONTENT_UPDATES_DISCONNECTED_ + # + # These must be set *before* FetchContent_MakeAvailable(). + # -------------------------------------------------------------- + string(TOUPPER "${project_name}" UC_NAME) # doctest → DOCTEST + + if(offline AND EXISTS "${source_cache_dir}/CMakeLists.txt") + # ---------------------------------------------------------- + # We *have* source on disk and cannot reach the network → + # pretend the project is “already populated”. + # ---------------------------------------------------------- + set(FETCHCONTENT_SOURCE_DIR_${UC_NAME} "${source_cache_dir}") + set(FETCHCONTENT_UPDATES_DISCONNECTED_${UC_NAME} ON) + message(STATUS "Offline mode → reusing existing source at ${source_cache_dir}") + else() + message(STATUS "Online mode → CMake may clone or update ${project_name}") + endif() + + # -------------------------------------------------------------- + # 8) Formal declaration (still points at the cache dir so both + # online and offline modes end up in the same place). + # -------------------------------------------------------------- + FetchContent_Declare( + ${project_name} + GIT_REPOSITORY ${repo_url} + GIT_TAG ${lib_tag} + SOURCE_DIR "${source_cache_dir}" + ) + + # -------------------------------------------------------------- + # 9) Bring the dependency into the build + # -------------------------------------------------------------- + FetchContent_MakeAvailable(${project_name}) + +endfunction() + diff --git a/cmake/toolchains/Linux-Clang-x86_64.cmake b/cmake/toolchains/Linux-Clang-x86_64.cmake index 1cb11614e..2baa30e91 100644 --- a/cmake/toolchains/Linux-Clang-x86_64.cmake +++ b/cmake/toolchains/Linux-Clang-x86_64.cmake @@ -1,8 +1,18 @@ include("${CMAKE_CURRENT_LIST_DIR}/include/Linux-Clang_common.inc.cmake") function(toolchain_force_compiler) - set(CMAKE_C_COMPILER "clang" CACHE STRING "C compiler" FORCE) - set(CMAKE_CXX_COMPILER "clang++" CACHE STRING "C++ compiler" FORCE) + #if(DEFINED ENV{CC}) + # set(CMAKE_C_COMPILER $ENV{CC}) + #else() + # set(CMAKE_C_COMPILER "clang" CACHE STRING "C compiler") + set(CMAKE_C_COMPILER "clang" CACHE STRING "C compiler" FORCE) + #endif() + #if(DEFINED ENV{CXX}) + # set(CMAKE_CXX_COMPILER $ENV{CXX}) + #elseif("${CMAKE_CXX_COMPILER}" STREQUAL "") + #set(CMAKE_CXX_COMPILER "clang++" CACHE STRING "C++ compiler") + set(CMAKE_CXX_COMPILER "clang++" CACHE STRING "C++ compiler" FORCE) + #endif() if(CROSSCOMPILING_ARCH) message(FATAL_ERROR "Incomplete/to be tested.") # are the names/paths correct? diff --git a/cmake/toolchains/Windows-MSVC.cmake b/cmake/toolchains/Windows-MSVC.cmake index 7944549dd..b5e6f4561 100644 --- a/cmake/toolchains/Windows-MSVC.cmake +++ b/cmake/toolchains/Windows-MSVC.cmake @@ -119,14 +119,37 @@ function(toolchain_exe_stuff) else() set(local_msvc_exception_handler /EHsc) endif() + # gersemi: off target_compile_options(spheresvr PRIVATE ${cxx_compiler_flags_common} - $<$: ${local_msvc_cmdline_runtime_lib_nondebug} ${local_msvc_exception_handler} /Oy /GL /GA /Gw /Gy /GF $,/O1 /Zi,/O2>> - $<$: ${local_msvc_cmdline_runtime_lib_nondebug} ${local_msvc_exception_handler} /Oy /GL /GA /Gw /Gy /GF $,/O1 /Zi,/O2>> - $<$: ${local_msvc_cmdline_runtime_lib_debug} /EHsc /Oy- /ob1 /Od /Gs $,/Zi,/ZI>> + + $<$: + ${local_msvc_cmdline_runtime_lib_nondebug} + ${local_msvc_exception_handler} + /Oy /GL /GA /Gw /Gy /GF + $,/O1,/O2> + $<$,$>:/Zi> + > + + $<$: + ${local_msvc_cmdline_runtime_lib_nondebug} + ${local_msvc_exception_handler} + /Oy /GL /GA /Gw /Gy /GF + $,/O1,/O2> + $<$,$>:/Zi> + > + + $<$: + ${local_msvc_cmdline_runtime_lib_debug} + /EHsc + /Oy- /ob1 /Od + /Gs + $,/Zi,/ZI> + > # ASan (and compilation for ARM arch) doesn't support edit and continue option (ZI) ) + # gersemi: on if("${ARCH}" STREQUAL "x86_64") diff --git a/cmake/toolchains/include/Linux-Clang_common.inc.cmake b/cmake/toolchains/include/Linux-Clang_common.inc.cmake index b12267713..88542f3ab 100644 --- a/cmake/toolchains/include/Linux-Clang_common.inc.cmake +++ b/cmake/toolchains/include/Linux-Clang_common.inc.cmake @@ -29,13 +29,13 @@ function(toolchain_exe_stuff_common) #-- Apply compiler flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_options(spheresvr_release PUBLIC ${custom_compile_options_release}) + target_compile_options(spheresvr_release PRIVATE ${custom_compile_options_release}) endif() if(TARGET spheresvr_nightly) - target_compile_options(spheresvr_nightly PUBLIC ${custom_compile_options_nightly}) + target_compile_options(spheresvr_nightly PRIVATE ${custom_compile_options_nightly}) endif() if(TARGET spheresvr_debug) - target_compile_options(spheresvr_debug PUBLIC ${custom_compile_options_debug}) + target_compile_options(spheresvr_debug PRIVATE ${custom_compile_options_debug}) endif() #-- Store common linker flags. @@ -50,13 +50,13 @@ function(toolchain_exe_stuff_common) #-- Apply linker flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_link_options(spheresvr_release PUBLIC ${custom_link_options_release}) + target_link_options(spheresvr_release PRIVATE ${custom_link_options_release}) endif() if(TARGET spheresvr_nightly) - target_link_options(spheresvr_nightly PUBLIC ${custom_link_options_nightly}) + target_link_options(spheresvr_nightly PRIVATE ${custom_link_options_nightly}) endif() if(TARGET spheresvr_debug) - target_link_options(spheresvr_debug PUBLIC ${custom_link_options_debug}) + target_link_options(spheresvr_debug PRIVATE ${custom_link_options_debug}) endif() @@ -73,13 +73,13 @@ function(toolchain_exe_stuff_common) #-- Apply define macros, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_definitions(spheresvr_release PUBLIC NDEBUG) + target_compile_definitions(spheresvr_release PRIVATE NDEBUG) endif(TARGET spheresvr_release) if(TARGET spheresvr_nightly) - target_compile_definitions(spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) + target_compile_definitions(spheresvr_nightly PRIVATE NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) endif(TARGET spheresvr_nightly) if(TARGET spheresvr_debug) - target_compile_definitions(spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) + target_compile_definitions(spheresvr_debug PRIVATE _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) endif(TARGET spheresvr_debug) #-- Now add back the common compiler options, preprocessor macros, linker targets and options. diff --git a/cmake/toolchains/include/Linux-GNU_common.inc.cmake b/cmake/toolchains/include/Linux-GNU_common.inc.cmake index 5890a36a9..0a0489b4d 100644 --- a/cmake/toolchains/include/Linux-GNU_common.inc.cmake +++ b/cmake/toolchains/include/Linux-GNU_common.inc.cmake @@ -23,13 +23,13 @@ function(toolchain_exe_stuff_common) #-- Apply compiler flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_options(spheresvr_release PUBLIC ${custom_compile_options_release}) + target_compile_options(spheresvr_release PRIVATE ${custom_compile_options_release}) endif() if(TARGET spheresvr_nightly) - target_compile_options(spheresvr_nightly PUBLIC ${custom_compile_options_nightly}) + target_compile_options(spheresvr_nightly PRIVATE ${custom_compile_options_nightly}) endif() if(TARGET spheresvr_debug) - target_compile_options(spheresvr_debug PUBLIC ${custom_compile_options_debug}) + target_compile_options(spheresvr_debug PRIVATE ${custom_compile_options_debug}) endif() #-- Store common linker flags. @@ -46,13 +46,13 @@ function(toolchain_exe_stuff_common) #-- Apply linker flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_link_options(spheresvr_release PUBLIC ${custom_link_options_release}) + target_link_options(spheresvr_release PRIVATE ${custom_link_options_release}) endif() if(TARGET spheresvr_nightly) - target_link_options(spheresvr_nightly PUBLIC ${custom_link_options_nightly}) + target_link_options(spheresvr_nightly PRIVATE ${custom_link_options_nightly}) endif() if(TARGET spheresvr_debug) - target_link_options(spheresvr_debug PUBLIC ${custom_link_options_debug}) + target_link_options(spheresvr_debug PRIVATE ${custom_link_options_debug}) endif() #-- Store common define macros. @@ -68,13 +68,13 @@ function(toolchain_exe_stuff_common) #-- Apply define macros, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_definitions(spheresvr_release PUBLIC NDEBUG) + target_compile_definitions(spheresvr_release PRIVATE NDEBUG) endif(TARGET spheresvr_release) if(TARGET spheresvr_nightly) - target_compile_definitions(spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) + target_compile_definitions(spheresvr_nightly PRIVATE NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) endif(TARGET spheresvr_nightly) if(TARGET spheresvr_debug) - target_compile_definitions(spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) + target_compile_definitions(spheresvr_debug PRIVATE _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) endif(TARGET spheresvr_debug) #-- Now apply the common compiler options, preprocessor macros, linker options. diff --git a/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake b/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake index 14d760a63..0a159830c 100644 --- a/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake +++ b/cmake/toolchains/include/OSX-AppleClang_common.inc.cmake @@ -32,13 +32,13 @@ function(toolchain_exe_stuff_common) #-- Apply compiler flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_options(spheresvr_release PUBLIC ${custom_compile_options_release}) + target_compile_options(spheresvr_release PRIVATE ${custom_compile_options_release}) endif() if(TARGET spheresvr_nightly) - target_compile_options(spheresvr_nightly PUBLIC ${custom_compile_options_nightly}) + target_compile_options(spheresvr_nightly PRIVATE ${custom_compile_options_nightly}) endif() if(TARGET spheresvr_debug) - target_compile_options(spheresvr_debug PUBLIC ${custom_compile_options_debug}) + target_compile_options(spheresvr_debug PRIVATE ${custom_compile_options_debug}) endif() #-- Store common linker flags. @@ -53,13 +53,13 @@ function(toolchain_exe_stuff_common) #-- Apply linker flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_link_options(spheresvr_release PUBLIC ${custom_link_options_release}) + target_link_options(spheresvr_release PRIVATE ${custom_link_options_release}) endif() if(TARGET spheresvr_nightly) - target_link_options(spheresvr_nightly PUBLIC ${custom_link_options_nightly}) + target_link_options(spheresvr_nightly PRIVATE ${custom_link_options_nightly}) endif() if(TARGET spheresvr_debug) - target_link_options(spheresvr_debug PUBLIC ${custom_link_options_debug}) + target_link_options(spheresvr_debug PRIVATE ${custom_link_options_debug}) endif() #-- Store common define macros. @@ -75,13 +75,13 @@ function(toolchain_exe_stuff_common) #-- Apply define macros, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_definitions(spheresvr_release PUBLIC NDEBUG) + target_compile_definitions(spheresvr_release PRIVATE NDEBUG) endif(TARGET spheresvr_release) if(TARGET spheresvr_nightly) - target_compile_definitions(spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) + target_compile_definitions(spheresvr_nightly PRIVATE NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) endif(TARGET spheresvr_nightly) if(TARGET spheresvr_debug) - target_compile_definitions(spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) + target_compile_definitions(spheresvr_debug PRIVATE _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) endif(TARGET spheresvr_debug) #-- Now add back the common compiler options, preprocessor macros, linker options. diff --git a/cmake/toolchains/include/Windows-Clang_common.inc.cmake b/cmake/toolchains/include/Windows-Clang_common.inc.cmake index 47912afa2..5b2b1882d 100644 --- a/cmake/toolchains/include/Windows-Clang_common.inc.cmake +++ b/cmake/toolchains/include/Windows-Clang_common.inc.cmake @@ -133,15 +133,15 @@ function(toolchain_exe_stuff_common) #-- Apply define macros, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_definitions(spheresvr_release PUBLIC NDEBUG THREAD_TRACK_CALLSTACK) + target_compile_definitions(spheresvr_release PRIVATE NDEBUG THREAD_TRACK_CALLSTACK) endif() if(TARGET spheresvr_nightly) - target_compile_definitions(spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) + target_compile_definitions(spheresvr_nightly PRIVATE NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) endif() if(TARGET spheresvr_debug) - target_compile_definitions(spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) + target_compile_definitions(spheresvr_debug PRIVATE _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) if(USE_ASAN AND NOT CLANG_USE_GCC_LINKER) - target_compile_definitions(spheresvr_debug PUBLIC _HAS_ITERATOR_DEBUGGING=0 _ITERATOR_DEBUG_LEVEL=0) + target_compile_definitions(spheresvr_debug PRIVATE _HAS_ITERATOR_DEBUGGING=0 _ITERATOR_DEBUG_LEVEL=0) endif() endif() diff --git a/cmake/toolchains/include/Windows-GNU_common.inc.cmake b/cmake/toolchains/include/Windows-GNU_common.inc.cmake index a940c6a26..f3007782a 100644 --- a/cmake/toolchains/include/Windows-GNU_common.inc.cmake +++ b/cmake/toolchains/include/Windows-GNU_common.inc.cmake @@ -25,13 +25,13 @@ function(toolchain_exe_stuff_common) #-- Apply compiler flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_options(spheresvr_release PUBLIC ${custom_compile_options_release}) + target_compile_options(spheresvr_release PRIVATE ${custom_compile_options_release}) endif() if(TARGET spheresvr_nightly) - target_compile_options(spheresvr_nightly PUBLIC ${custom_compile_options_nightly}) + target_compile_options(spheresvr_nightly PRIVATE ${custom_compile_options_nightly}) endif() if(TARGET spheresvr_debug) - target_compile_options(spheresvr_debug PUBLIC ${custom_compile_options_debug}) + target_compile_options(spheresvr_debug PRIVATE ${custom_compile_options_debug}) endif() #-- Store common linker flags. @@ -46,13 +46,13 @@ function(toolchain_exe_stuff_common) #-- Apply linker flags, only the ones specific per build type. if(TARGET spheresvr_release) - target_link_options(spheresvr_release PUBLIC ${custom_link_options_release}) + target_link_options(spheresvr_release PRIVATE ${custom_link_options_release}) endif() if(TARGET spheresvr_nightly) - target_link_options(spheresvr_nightly PUBLIC ${custom_link_options_nightly}) + target_link_options(spheresvr_nightly PRIVATE ${custom_link_options_nightly}) endif() if(TARGET spheresvr_debug) - target_link_options(spheresvr_debug PUBLIC ${custom_link_options_debug}) + target_link_options(spheresvr_debug PRIVATE ${custom_link_options_debug}) endif() #-- Store common define macros. @@ -72,13 +72,13 @@ function(toolchain_exe_stuff_common) #-- Apply define macros, only the ones specific per build type. if(TARGET spheresvr_release) - target_compile_definitions(spheresvr_release PUBLIC NDEBUG THREAD_TRACK_CALLSTACK) + target_compile_definitions(spheresvr_release PRIVATE NDEBUG THREAD_TRACK_CALLSTACK) endif() if(TARGET spheresvr_nightly) - target_compile_definitions(spheresvr_nightly PUBLIC NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) + target_compile_definitions(spheresvr_nightly PRIVATE NDEBUG THREAD_TRACK_CALLSTACK _NIGHTLYBUILD) endif() if(TARGET spheresvr_debug) - target_compile_definitions(spheresvr_debug PUBLIC _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) + target_compile_definitions(spheresvr_debug PRIVATE _DEBUG THREAD_TRACK_CALLSTACK _PACKETDUMP) endif() #-- Now apply the common compiler options, preprocessor macros, linker options. diff --git a/docs/Getting-started.md b/docs/Getting-started.md index 8a63917cf..ee8b6a56e 100644 --- a/docs/Getting-started.md +++ b/docs/Getting-started.md @@ -51,7 +51,7 @@ Once you've extracted your archive, you'll need to move the server's executable into the root directory of the server. The file is contained into the `build-xx` folder. -For instance, for a 64 bits linux installation, your root folder's structure +For instance, for 64 bits linux installation, your root folder's structure should go from this: ``` - accounts/ @@ -113,7 +113,7 @@ shard. Before you can start fiddling with different things, you'll need to agree of using a nightly build. A nightly build gets created automatically every night based on the latest changes made by the developers. This means it might contain -potential bugs. With this settings you agree to take full responsibility of all +potential bugs. With this setting you agree to take full responsibility of all the implications of this choice. So, let's open the `sphere.ini` file and add a new line to the `SPHERE` @@ -167,7 +167,7 @@ contain all the assets of the game. This includes (but not limited to): These files are contained into the Ultima Online client's installation. -**WARNING:** If there a discrepancy between server's muls and client's muls +**WARNING:** If there is a discrepancy between server's muls and client's muls players will experience serious bugs or crashes. For this reason we need to make sure that the server is picking up the mul @@ -175,7 +175,7 @@ files from the Ultima Online client's installation. There are different ways to achieve this. #### A. Do not change the Sphere.ini file -If you're using Windows and you've got just one Ultima Online Client installed +If you're using Windows, and you've got just one Ultima Online Client installed through the official EA installer, you can just skip this step and move on with the next step of this guide. @@ -200,7 +200,7 @@ MulFiles=C:/Program Files/EA Games/Ultima Online/ ``` #### C. Copy client's files into the server's folder -Create a new folder into the server's root folder. For instance you can call it +Create a new folder into the server's root folder. For instance, you can call it `mul`. For simplicity, copy all the contents of your Ultima Online client's @@ -228,7 +228,7 @@ MulFiles=mul/ **NOTE**: You don't need all the files contained into the Ultima Online client's installation. You just need all the `.mul` and `.idx` files. -**NOTE 2**: On linux the name of the files is case sensitive so, make sure all +**NOTE 2**: On linux the name of the files is case-sensitive so, make sure all the file names are lowercase! ### Configure maps @@ -265,14 +265,14 @@ Map1=7168,4096,-1,1,1 //ML size ## Setup Scripts We've got a nice server. But it is completely brainless: it doesn't know what -is a vendor, an npc and what are their behaviours. In order to give it a brain +is a vendor, a npc and what are their behaviours. In order to give it a brain we should add some scripts. If we were to write all of them, it would take ages. For this reason Sphere comes with a pre-made set of scripts to get you started in no time. ### Download scripts Head out to the -[SphereX Github page](https://github.com/Sphereserver/Scripts-X) click on `Code` +[SphereX GitHub page](https://github.com/Sphereserver/Scripts-X) click on `Code` and hit `Download ZIP`. Unzip the content of the package into the server's `scripts` folder. @@ -410,7 +410,7 @@ Use '?' to view available console commands ## Connect to your server Now that you've configured the server, you're finally ready to start playing! -By default the Ultima Online Client will connect to the official game servers. +By default, the Ultima Online Client will connect to the official game servers. However, what we want to do is to connect to our server. There are multiple tools you can use like Razor - just Google a bit ;). @@ -434,7 +434,7 @@ configuration you can now try to login with any username and password: this operation will automatically create a new user for you. So, pick a username and password and try to login. Create a new character et -voila': you're the first player of your own personal Ultima Online Shard! +voilà': you're the first player of your own personal Ultima Online Shard! ## Making yourself an admin Well, since this is your server, I guess it's only fair to set yourself as an @@ -551,4 +551,4 @@ about Sphere server, access the If you need further help, get into the [Sphere discord server](https://discord.gg/ZrMTXrs). -Have fun! \ No newline at end of file +Have fun! diff --git a/docs/Porting from 0.56 to X.txt b/docs/Porting from 0.56 to X.txt index 7749cdcc4..8fd54ffd0 100644 --- a/docs/Porting from 0.56 to X.txt +++ b/docs/Porting from 0.56 to X.txt @@ -4,7 +4,7 @@ Changed starting from 0.56b ------------------------ - [sphere_spells.scp]: mostly new spell flags. - [sphere_skills.scp]: mostly new skill flags. -- [sphere_defs.scp]: lots of new defs, most importantly new mounts defs. +- [sphere_defs.scp]: lots of new defs, most importantly new mounts' defs. (Mounts are now handled there, if they are not specified in the file they cannot be ridden). - Update multis definitions from [itemdef 04xxx] to [multidef 0x]. - Changed MAPPLANE->MAP, LOCALLIGHT->LIGHT. @@ -32,7 +32,7 @@ Compatibility changes from 0.56d to X1 Changes to script parsing: - Changed: Now conditional statements (IF/ELIF/ELSEIF) will perform a lazy evaluation of the conditional expression, whereas before they fully evaluated the whole expression. - Lazy evaluation means that the expression will be evaluated one piece (or, one subexpression) at a time. At each stage, if it's sure that the whole expression value (true or false) won't change + Lazy evaluation means that the expression will be evaluated one piece (or, one subexpression) at a time. At each stage, if it's certain that the whole expression value (true or false) won't change even if new subexpressions will be evaluated, the evaluation will stop there. Example: IF ( && ( == 1)) Won't return an error if item's LINK is invalid or not set, because the evaluation will only halt to . @@ -52,9 +52,9 @@ Changes to script parsing: Using ISVALID to check an object validity/existence is now the preferred way (instead of checking its value with magic numbers like 0 or -1/0FFFFFFFF). - Now TRY can be used to explicitly "TRY" to execute a command or set a property on an object which may or may not exist. If the object doesn't exist, no operation is done and no error is returned. It can't be used to TRY to retrieve a value (like ). - Its use is DISCOURAGED but sometimes it might be an easy way to execute trivial operations without a lot of if/endif clauses. + Its use is DISCOURAGED, but sometimes it might be an easy way to execute trivial operations without a lot of if/endif clauses. This is a way to explicitly allow what Sphere already did by default in the past (not checking if the object is valid and not returning an error), - which is something that only sometimes we expect and we want, most of the times it caused obscure bugs. + which is something that only sometimes we expect and we want, most of the time it caused obscure bugs. Changes to script keywords: - CAN property is no more RW, but now it's read-only. Use CANMASK to change it on a per-char basis. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index f153491b3..fdf20b991 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -44,7 +44,7 @@ foreach(tgt ${TARGETS}) target_include_directories( ${tgt} PRIVATE $ - PRIVATE $ + PRIVATE $ PRIVATE $ PRIVATE $ PRIVATE $ @@ -58,11 +58,14 @@ foreach(tgt ${TARGETS}) target_link_libraries(${tgt} PRIVATE bcrypt twofish) # cdrc (not needed for now) if(${LIB_LIBEV_BUILD}) target_link_libraries(${tgt} PRIVATE libev) + add_dependencies(${tgt} libev) endif() - if(${LIB_SQLITE_BUILD}) + #if(${LIB_SQLITE_BUILD}) # search it in the system, if not compiling from sources? target_link_libraries(${tgt} PRIVATE sqlite) - endif() - if(${LIB_ZLIB_BUILD}) + #endif() + #if(${LIB_ZLIB_BUILD}) # search it in the system, if not compiling from sources? target_link_libraries(${tgt} PRIVATE zlib) - endif() + #endif() + add_dependencies(${tgt} bcrypt twofish sqlite zlib) + endforeach() diff --git a/lib/bcrypt/CMakeLists.txt b/lib/bcrypt/CMakeLists.txt index 7d330d600..bf865f160 100644 --- a/lib/bcrypt/CMakeLists.txt +++ b/lib/bcrypt/CMakeLists.txt @@ -10,7 +10,7 @@ set(lib_SOURCES bcrypt/wrapper.c ) -add_library(bcrypt STATIC ${lib_SOURCES}) +add_library(bcrypt OBJECT ${lib_SOURCES}) # make the headers available in the include path (accessible with < >). target_include_directories(bcrypt PUBLIC .) diff --git a/lib/libev/CMakeLists.txt b/lib/libev/CMakeLists.txt index c74db4f04..3d01799ab 100644 --- a/lib/libev/CMakeLists.txt +++ b/lib/libev/CMakeLists.txt @@ -25,7 +25,7 @@ set(lib_SOURCES source_group(lib\\libev FILES ${lib_SOURCES}) # create the target. equivalent to add_executable, but for libraries -add_library(libev STATIC ${lib_SOURCES}) +add_library(libev OBJECT ${lib_SOURCES}) # make the headers available in the include path (accessible with < >). target_include_directories(libev PUBLIC .) diff --git a/lib/mariadb_connector_c/CMakeLists.txt b/lib/mariadb_connector_c/CMakeLists.txt index 460c5090b..af685ff94 100644 --- a/lib/mariadb_connector_c/CMakeLists.txt +++ b/lib/mariadb_connector_c/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.19) -project(mariadb_connector C) +project(mariadb_connector_c C) # we do not build the library here, only provide the headers for a precompiled one. #include("lib_compiler_flags_common_c.cmake") @@ -9,9 +9,9 @@ include("mysql/configure.cmake") set(lib_SOURCES mysql/config.h mysql/mysql.h) # create the target. equivalent to add_executable, but for libraries -add_library(mariadb_connector INTERFACE ${lib_SOURCES}) +add_library(mariadb_connector_c INTERFACE ${lib_SOURCES}) # make the headers available in the include path (accessible with < >). -target_include_directories(mariadb_connector INTERFACE .) +target_include_directories(mariadb_connector_c INTERFACE .) # add to the proper directories in the solution explorer (eg. Visual Studio) source_group(lib\\mariadb_connector_c FILES ${lib_SOURCES}) diff --git a/lib/sqlite/CMakeLists.txt b/lib/sqlite/CMakeLists.txt index ccca6be37..92d0f445e 100644 --- a/lib/sqlite/CMakeLists.txt +++ b/lib/sqlite/CMakeLists.txt @@ -7,7 +7,7 @@ set(lib_SOURCES sqlite/sqlite3.c sqlite/sqlite3.h) source_group(lib\\sqlite FILES ${lib_SOURCES}) # create the target. equivalent to add_executable, but for libraries -add_library(sqlite STATIC ${lib_SOURCES}) +add_library(sqlite OBJECT ${lib_SOURCES}) # make the headers available in the include path (accessible with < >). target_include_directories(sqlite PUBLIC .) diff --git a/lib/twofish/CMakeLists.txt b/lib/twofish/CMakeLists.txt index 59e5d001e..26c750bd3 100644 --- a/lib/twofish/CMakeLists.txt +++ b/lib/twofish/CMakeLists.txt @@ -7,7 +7,7 @@ set(lib_SOURCES twofish/twofish.h twofish/twofish_aux.h twofish/twofish.c) source_group(lib\\twofish FILES ${lib_SOURCES}) # create the target. equivalent to add_executable, but for libraries -add_library(twofish STATIC ${lib_SOURCES}) +add_library(twofish OBJECT ${lib_SOURCES}) # make the headers available in the include path (accessible with < >). target_include_directories(twofish PUBLIC .) diff --git a/lib/zlib/CMakeLists.txt b/lib/zlib/CMakeLists.txt index 5fbbab415..5c3e4411b 100644 --- a/lib/zlib/CMakeLists.txt +++ b/lib/zlib/CMakeLists.txt @@ -94,7 +94,7 @@ set(ZLIB_SRCS source_group(lib\\zlib FILES ${ZLIB_PUBLIC_HDRS}) # make the headers available in the include path (accessible with < >). -add_library(zlib STATIC ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS} ${ZLIB_SRCS}) +add_library(zlib OBJECT ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS} ${ZLIB_SRCS}) # add to the proper directories in the solution explorer (eg. Visual Studio) target_include_directories(zlib PUBLIC .) diff --git a/packaging/debian/.gitignore b/packaging/debian/.gitignore new file mode 100644 index 000000000..c06b01b37 --- /dev/null +++ b/packaging/debian/.gitignore @@ -0,0 +1,9 @@ +*.debhelper.log +*.debhelper +*.substvars +debhelper-build-stamp +files +sphereserver/ + +# Temporary disable changelog from versioning, since we will be using bash script during development. +/changelog diff --git a/packaging/debian/control b/packaging/debian/control new file mode 100644 index 000000000..a60b93aa2 --- /dev/null +++ b/packaging/debian/control @@ -0,0 +1,19 @@ +Source: sphereserver +Section: net +Priority: optional +Maintainer: SphereServer development team +Build-Depends: debhelper-compat (= 13), cmake (>= 3.29), libmariadb-dev, git +Standards-Version: 4.7.2 +Vcs-Browser: https://github.com/Sphereserver/Source-X +Vcs-Git: https://github.com/Sphereserver/Source-X.git +Homepage: https://www.sphereserver.net/ + +Package: sphereserver +Architecture: any +Pre-Depends: ${misc:Pre-Depends} +Depends: adduser, mariadb-client, debconf, ${shlibs:Depends}, ${misc:Depends} +Recommends: telnet +Suggests: mariadb-server, nginx +Description: Ultima Online server emulator + SphereServer is an Ultima Online server emulator allowing players to run their + own shards. diff --git a/packaging/debian/copyright b/packaging/debian/copyright new file mode 100644 index 000000000..52277360d --- /dev/null +++ b/packaging/debian/copyright @@ -0,0 +1,8 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Sphereserver +Source: https://github.com/Sphereserver/Source-X + +Files: * +Copyright: + 2025 SphereServer development team +License: Apache 2.0 diff --git a/packaging/debian/data/changelog b/packaging/debian/data/changelog new file mode 100644 index 000000000..90cc6355b --- /dev/null +++ b/packaging/debian/data/changelog @@ -0,0 +1,5 @@ +sphereserver (0.X.nightly-@version@) unstable; urgency=medium + + * See https://github.com/Sphereserver/Source-X/blob/master/Changelog.txt. + + -- SphereServer development team @date@ diff --git a/packaging/debian/data/sphereacct.scp b/packaging/debian/data/sphereacct.scp new file mode 100644 index 000000000..0be37c2a8 --- /dev/null +++ b/packaging/debian/data/sphereacct.scp @@ -0,0 +1,7 @@ +// Accounts are periodically moved to the sphereaccu.scp file. +// All account changes should be made here. +// Use the /ACCOUNT UPDATE command to force accounts to update. + +[admin] +PLEVEL=7 +PASSWORD=admin diff --git a/packaging/debian/rules b/packaging/debian/rules new file mode 100644 index 000000000..aeccdf2e9 --- /dev/null +++ b/packaging/debian/rules @@ -0,0 +1,28 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_configure: + cmake -S ../ -B ../build -DCMAKE_BUILD_TYPE="Nightly" -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/Linux-GNU-x86_64.cmake -DCMAKE_C_FLAGS="-fpch-preprocess" -DCMAKE_CXX_FLAGS="-fpch-preprocess" + +override_dh_auto_build: + cmake --build ../build + +override_dh_auto_install: + # Prepare binary. + install -d debian/sphereserver/usr/bin + install -m 0755 ../build/bin-x86_64/SphereSvrX64_nightly debian/sphereserver/usr/bin/sphereserver + # Prepare accounts file. + install -d debian/sphereserver/tmp + install -m 0644 debian/data/sphereacct.scp debian/sphereserver/tmp/sphereacct.scp + # Copy ini files. + install -m 0644 ../src/sphereCrypt.ini debian/sphereserver/etc/sphereserver/sphereCrypt.ini + # Replace relative paths with absolute one in sphere.ini. + cat ../src/sphere.ini | sed -e "s/ScpFiles=/ScpFiles=\/opt\/sphereserver\//" -e "s/WorldSave=/WorldSave=\/opt\/sphereserver\//" -e "s/AcctFiles=/AcctFiles=\/opt\/sphereserver\//" -e "s/WebPageSrc=/WebPageSrc=\/opt\/sphereserver\//" -e "s/WebPageFile=/WebPageFile=\/opt\/sphereserver\//" -e "s/\/\/MulFiles=/MulFiles=\/opt\/sphereserver\//" -e "s/Log=logs/Log=\/var\/log\/sphereserver/" > debian/sphereserver/etc/sphereserver/sphere.ini + +override_dh_installinit: + dh_installinit --no-enable --no-start --no-restart-after-upgrade + +override_dh_installsystemd: + dh_installsystemd --no-enable --no-start --no-restart-after-upgrade diff --git a/packaging/debian/source/format b/packaging/debian/source/format new file mode 100644 index 000000000..89ae9db8f --- /dev/null +++ b/packaging/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/packaging/debian/sphereserver.6 b/packaging/debian/sphereserver.6 new file mode 100644 index 000000000..76dcea8b0 --- /dev/null +++ b/packaging/debian/sphereserver.6 @@ -0,0 +1,34 @@ +.TH SPHERESERVER "6" "April 2025" "sphereserver" +.SH NAME +SphereServer \- Ultima Online server emulator +.SH DESCRIPTION +SphereServer is an Ultima Online server emulator allowing players to run their +own shards. +.SH OPTIONS +.TP +\fB\-H, -?\fR +display command line switches +.TP +\fB\-I\fR= +set path to ini files. +.TP +\fB\-C\fR +do not use colored console output (default: on). +.TP +\fB\-D\fR +dump global variable DEFNAMEs to defs.txt. +.TP +\fB\-G\fR +defrags sphere saves. +.TP +\fB\-N\fR +set the sphere name. +.TP +\fB\-P\fR +set the port number. +.TP +\fB\-O\fR +output console to this file name. +.TP +\fB\-Q\fR +quit when finished. diff --git a/packaging/debian/sphereserver.config b/packaging/debian/sphereserver.config new file mode 100644 index 000000000..6e7bf02fa --- /dev/null +++ b/packaging/debian/sphereserver.config @@ -0,0 +1,10 @@ +#!/bin/sh + +PRIORITY="medium" + +set -e +. /usr/share/debconf/confmodule + +# Information about manual config. +db_input $PRIORITY sphereserver/info || true +db_go diff --git a/packaging/debian/sphereserver.dirs b/packaging/debian/sphereserver.dirs new file mode 100644 index 000000000..07f589af9 --- /dev/null +++ b/packaging/debian/sphereserver.dirs @@ -0,0 +1 @@ +etc/sphereserver diff --git a/packaging/debian/sphereserver.init b/packaging/debian/sphereserver.init new file mode 100644 index 000000000..2f3c12583 --- /dev/null +++ b/packaging/debian/sphereserver.init @@ -0,0 +1,57 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: sphereserver +# Required-Start: +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start/stop the sphereserver daemon +# Description: SphereServer - Ultima Online server emulator. +### END INIT INFO + +set -e + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/bin/sphereserver + +# Gracefully exit if the package has been removed. +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +ret=0 + +case "$1" in + start) + log_daemon_msg "Starting SphereServer" + if start-stop-daemon --start --oknodo --quiet \ + --name sphereserver --startas $DAEMON -- -I=/etc/sphereserver/ + then + log_end_msg 0 + else + ret=$? + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping SphereServer" + if start-stop-daemon --stop --oknodo --quiet --exec $DAEMON \ + --name sphereserver + then + log_end_msg 0 + else + ret=$? + log_end_msg 1 + fi + ;; + reload|force-reload|restart) + $0 stop + $0 start + ret=$? + ;; + *) + exit 1 + ;; +esac + +exit $ret diff --git a/packaging/debian/sphereserver.manpages b/packaging/debian/sphereserver.manpages new file mode 100644 index 000000000..d53842270 --- /dev/null +++ b/packaging/debian/sphereserver.manpages @@ -0,0 +1 @@ +debian/sphereserver.6 diff --git a/packaging/debian/sphereserver.postinst b/packaging/debian/sphereserver.postinst new file mode 100644 index 000000000..f857d5e1d --- /dev/null +++ b/packaging/debian/sphereserver.postinst @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Post-installation script for Sphereserver. +# + +# Parameters +NAME="sphereserver" + +# Ensure that the scripts abort in case of any errors. +set -e + +. /usr/share/debconf/confmodule + +# Check, if user exists. +if ! getent passwd $NAME >/dev/null; then + # Create user. + adduser --home /opt/$NAME --system --group $NAME +fi + +# Create directories. +install -g $NAME -o $NAME -d /var/log/$NAME +install -g $NAME -o $NAME -d /opt/$NAME/accounts +install -g $NAME -o $NAME -d /opt/$NAME/mul +install -g $NAME -o $NAME -d /opt/$NAME/save +install -g $NAME -o $NAME -d /opt/$NAME/scripts + +# Create sphereacct file, don't overwrite it. +if [ ! -f /opt/$NAME/accounts/sphereacct.scp ]; then + install -g $NAME -o $NAME -m 0644 /tmp/sphereacct.scp /opt/$NAME/accounts/sphereacct.scp +fi + +#DEBHELPER# diff --git a/packaging/debian/sphereserver.postrm b/packaging/debian/sphereserver.postrm new file mode 100644 index 000000000..7befe141e --- /dev/null +++ b/packaging/debian/sphereserver.postrm @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Post-removal script for Sphereserver. +# + +# Parameters +NAME="sphereserver" + +# Ensure that the scripts abort in case of any errors +set -e + +if [ "$1" = "remove" ]; then + # Remove user. + userdel $NAME + + # Remove files. + rm -rf /opt/$NAME + rm -rf /etc/$NAME + rm -rf /var/log/$NAME +fi + +#DEBHELPER# diff --git a/packaging/debian/sphereserver.service b/packaging/debian/sphereserver.service new file mode 100644 index 000000000..31f372be3 --- /dev/null +++ b/packaging/debian/sphereserver.service @@ -0,0 +1,18 @@ +# SphereServer systemd service file +# Copyright 2025 SphereServer development team +# SPDX-License-Identifier: Apache-2.0 + +[Unit] +Description=Sphereserver +After=network.target + +[Service] +Type=simple +WorkingDirectory=/opt/sphereserver +ExecStart=/usr/bin/sphereserver -I=/etc/sphereserver/ +Restart=on-failure +User=sphereserver +Group=sphereserver + +[Install] +WantedBy=multi-user.target diff --git a/packaging/debian/sphereserver.templates b/packaging/debian/sphereserver.templates new file mode 100644 index 000000000..d113053f2 --- /dev/null +++ b/packaging/debian/sphereserver.templates @@ -0,0 +1,14 @@ +Template: sphereserver/info +Type: note +Description: This package is intended to be run as a system service + Installer will install default configuration and administrator account + (admin/admin). + . + You need to supply scripts to /opt/sphereserver/scripts, mul files to + /opt/sphereserver/mul (or any path defined in sphere.ini) and explicitly + enable AGREE option in sphere.ini. + . + Ini files are placed in /etc/sphereserver/ folder. + . + After configuration, you can enable and start sphereserver service and connect + to game via client or telnet. diff --git a/src/CMakeSources.cmake b/src/CMakeSources.cmake index fbecf9e6b..88ac007d8 100644 --- a/src/CMakeSources.cmake +++ b/src/CMakeSources.cmake @@ -1,6 +1,11 @@ +############################## +# Precompiled Header Files +############################## + # Precompiled header: it's parsed only once and stored in memory as an intermediate form. This speeds up compilation drastically. -# Be wise in chosing those, since every time we change one of them, we'll have to recompile every one on the list. -set(pch_options +# Be wise in choosing those, since every time we change one of them, we'll have to recompile every one on the list. + +set(pch_list PRIVATE src/common/common.h #PRIVATE src/common/sphere_library/CSRand.h PRIVATE src/common/sphere_library/CSString.h @@ -17,180 +22,186 @@ set(pch_options #PRIVATE src/network/send.h #PRIVATE src/network/CSocket.h #PRIVATE src/network/receive.h + PRIVATE src/game/CEntity.h + PRIVATE src/game/CEntityProps.h + PRIVATE src/game/CServerConfig.h #PRIVATE src/game/clients/CClient.h #PRIVATE src/game/chars/CChar.h #PRIVATE src/game/items/CItem.h - ) -# Main program files: threads, console... -set(sphere_SRCS +############################## +# Sphere Main +############################## +set(sphere_S src/sphere/asyncdb.cpp + src/sphere/ConsoleInterface.cpp + src/sphere/GlobalInitializer.cpp + src/sphere/MainThread.cpp + src/sphere/ProfileData.cpp + src/sphere/ProfileTask.cpp + src/sphere/StartupMonitorThread.cpp + src/sphere/threads.cpp + src/sphere/ntservice.cpp + src/sphere/ntwindow.cpp + src/sphere/UnixTerminal.cpp +) +set(sphere_H src/sphere/asyncdb.h src/sphere/containers.h - src/sphere/ConsoleInterface.cpp src/sphere/ConsoleInterface.h - src/sphere/ProfileData.cpp + src/sphere/GlobalInitializer.h + src/sphere/MainThread.h src/sphere/ProfileData.h - src/sphere/ProfileTask.cpp src/sphere/ProfileTask.h - src/sphere/threads.cpp + src/sphere/StartupMonitorAPI.h + src/sphere/StartupMonitorThread.h src/sphere/threads.h - src/sphere/ntservice.cpp src/sphere/ntservice.h - src/sphere/ntwindow.cpp src/sphere/ntwindow.h - src/sphere/UnixTerminal.cpp src/sphere/UnixTerminal.h ) -source_group(sphere FILES ${sphere_SRCS}) +source_group(sphere FILES ${sphere_H} ${sphere_S}) -# Network management files -set(network_SRCS +############################## +# Network Management +############################## +set(network_S src/network/CClientIterator.cpp - src/network/CClientIterator.h src/network/CIPHistoryManager.cpp - src/network/CIPHistoryManager.h src/network/CNetState.cpp - src/network/CNetState.h src/network/CNetworkInput.cpp - src/network/CNetworkInput.h src/network/CNetworkManager.cpp - src/network/CNetworkManager.h src/network/CNetworkOutput.cpp - src/network/CNetworkOutput.h src/network/CNetworkThread.cpp - src/network/CNetworkThread.h src/network/CPacketManager.cpp - src/network/CPacketManager.h src/network/CSocket.cpp - src/network/CSocket.h src/network/linuxev.cpp - src/network/linuxev.h - src/network/net_datatypes.h src/network/net_datatypes.cpp src/network/packet.cpp - src/network/packet.h src/network/receive.cpp - src/network/receive.h src/network/send.cpp - src/network/send.h src/network/PingServer.cpp +) +set(network_H + src/network/CClientIterator.h + src/network/CIPHistoryManager.h + src/network/CNetState.h + src/network/CNetworkInput.h + src/network/CNetworkManager.h + src/network/CNetworkOutput.h + src/network/CNetworkThread.h + src/network/CPacketManager.h + src/network/CSocket.h + src/network/linuxev.h + src/network/net_datatypes.h + src/network/packet.h + src/network/receive.h + src/network/send.h src/network/PingServer.h ) -source_group(network FILES ${network_SRCS}) +source_group(network FILES ${network_H} ${network_S}) -# Login encryption handling -set(crypto_SRCS - src/common/crypto/CBCrypt.cpp - src/common/crypto/CBCrypt.h - src/common/crypto/CCrypto.cpp - src/common/crypto/CCrypto.h - src/common/crypto/CCryptoBlowFish.cpp - src/common/crypto/CCryptoLogin.cpp - src/common/crypto/CCryptoKeyCalc.cpp - src/common/crypto/CCryptoKeyCalc.h - src/common/crypto/CCryptoMD5Interface.cpp - src/common/crypto/CCryptoTwoFishInterface.cpp - src/common/crypto/CHuffman.cpp - src/common/crypto/CHuffman.h - src/common/crypto/CMD5.cpp - src/common/crypto/CMD5.h - src/common/crypto/crypto_common.h +############################## +# UO Files +############################## +set(uofiles_S + src/game/uo_files/CUOHuesRec.cpp + src/game/uo_files/CUOIndexRec.cpp + src/game/uo_files/CUOItemInfo.cpp + src/game/uo_files/CUOMapList.cpp + src/game/uo_files/CUOMapMeter.cpp + src/game/uo_files/CUOMobtypes.cpp + src/game/uo_files/CUOMultiItemRec.cpp + src/game/uo_files/CUOStaticItemRec.cpp + src/game/uo_files/CUOTerrainInfo.cpp + src/game/uo_files/CUOTiledata.cpp + src/game/uo_files/CUOVersionBlock.cpp ) -source_group(common\\crypto FILES ${crypto_SRCS}) - -# Handle UO Client files -set(uofiles_SRCS +set(uofiles_H src/game/uo_files/CUOHuesRec.h - src/game/uo_files/CUOHuesRec.cpp src/game/uo_files/CUOIndexRec.h - src/game/uo_files/CUOIndexRec.cpp src/game/uo_files/CUOItemInfo.h - src/game/uo_files/CUOItemInfo.cpp src/game/uo_files/CUOItemTypeRec.h src/game/uo_files/CUOMapBlock.h src/game/uo_files/CUOMapList.h - src/game/uo_files/CUOMapList.cpp src/game/uo_files/CUOMapMeter.h - src/game/uo_files/CUOMapMeter.cpp src/game/uo_files/CUOMobtypes.h - src/game/uo_files/CUOMobtypes.cpp src/game/uo_files/CUOMultiItemRec.h - src/game/uo_files/CUOMultiItemRec.cpp src/game/uo_files/CUOStaticItemRec.h - src/game/uo_files/CUOStaticItemRec.cpp src/game/uo_files/CUOTerrainInfo.h - src/game/uo_files/CUOTerrainInfo.cpp src/game/uo_files/CUOTerrainTypeRec.h src/game/uo_files/CUOTiledata.h - src/game/uo_files/CUOTiledata.cpp src/game/uo_files/CUOVersionBlock.h - src/game/uo_files/CUOVersionBlock.cpp src/game/uo_files/uofiles_enums.h src/game/uo_files/uofiles_enums_itemid.h src/game/uo_files/uofiles_enums_creid.h src/game/uo_files/uofiles_macros.h src/game/uo_files/uofiles_types.h ) -source_group(game\\uo_files FILES ${uofiles_SRCS}) +source_group(game\\uo_files FILES ${uofiles_H} ${uofiles_S}) -# Files containing 'background work' -set(common_SRCS +############################## +# Common +############################## +set(common_S src/common/sqlite/SQLite.cpp + src/common/common.cpp + src/common/CCacheableScriptFile.cpp + src/common/CDataBase.cpp + src/common/CException.cpp + src/common/CExpression.cpp + src/common/CFloatMath.cpp + src/common/CLanguageID.cpp + src/common/CLocalVarsExtra.cpp + src/common/CLog.cpp + src/common/CServerMap.cpp + src/common/CUID.cpp + src/common/CPointBase.cpp + src/common/CRect.cpp + src/common/CScript.cpp + src/common/CScriptContexts.cpp + src/common/CScriptObj.cpp + src/common/CScriptParserBufs.cpp + src/common/CScriptTriggerArgs.cpp + src/common/CSFileObj.cpp + src/common/CSFileObjContainer.cpp + src/common/CSVFile.cpp + src/common/CTextConsole.cpp + src/common/CUOClientVersion.cpp + src/common/CUOInstall.cpp + src/common/CVarDefMap.cpp + src/common/ListDefContMap.cpp +) +set(common_H src/common/sqlite/SQLite.h src/common/assertion.h src/common/basic_threading.h - src/common/common.cpp - src/common/common.h - src/common/CCacheableScriptFile.cpp src/common/CCacheableScriptFile.h - src/common/CDataBase.cpp src/common/CDataBase.h - src/common/CException.cpp src/common/CException.h - src/common/CExpression.cpp src/common/CExpression.h - src/common/CFloatMath.cpp src/common/CFloatMath.h - src/common/CLanguageID.cpp src/common/CLanguageID.h - src/common/CLocalVarsExtra.cpp src/common/CLocalVarsExtra.h - src/common/CLog.cpp src/common/CLog.h - src/common/CServerMap.cpp src/common/CServerMap.h - src/common/CUID.cpp src/common/CUID.h - src/common/CPointBase.cpp src/common/CPointBase.h - src/common/CRect.cpp src/common/CRect.h - src/common/CScript.cpp src/common/CScript.h - src/common/CScriptContexts.cpp src/common/CScriptContexts.h - src/common/CScriptObj.cpp src/common/CScriptObj.h - src/common/CScriptTriggerArgs.cpp + src/common/CScriptParserBufs.h src/common/CScriptTriggerArgs.h - src/common/CSFileObj.cpp src/common/CSFileObj.h - src/common/CSFileObjContainer.cpp src/common/CSFileObjContainer.h - src/common/CSVFile.cpp src/common/CSVFile.h - src/common/CTextConsole.cpp src/common/CTextConsole.h - src/common/CUOClientVersion.cpp src/common/CUOClientVersion.h - src/common/CUOInstall.cpp src/common/CUOInstall.h - src/common/CVarDefMap.cpp src/common/CVarDefMap.h src/common/datatypes.h - src/common/ListDefContMap.cpp src/common/ListDefContMap.h src/common/os_unix.h src/common/os_windows.h @@ -198,240 +209,300 @@ set(common_SRCS src/common/sphereversion.h src/common/target_info.h ) -source_group(common FILES ${common_SRCS}) +source_group(common FILES ${common_H} ${common_S}) + +############################## +# Windows CrashDump +############################## +set(crashdump_S + src/common/crashdump/crashdump.cpp +) +set(crashdump_H + src/common/crashdump/crashdump.h + src/common/crashdump/mingwdbghelp.h +) +source_group(common\\crashdump FILES ${crashdump_H} ${crashdump_S}) + +############################## +# Login / Cryptography +############################## +set(crypto_S + src/common/crypto/CBCrypt.cpp + src/common/crypto/CCrypto.cpp + src/common/crypto/CCryptoBlowFish.cpp + src/common/crypto/CCryptoLogin.cpp + src/common/crypto/CCryptoKeyCalc.cpp + src/common/crypto/CCryptoMD5Interface.cpp + src/common/crypto/CCryptoTwoFishInterface.cpp + src/common/crypto/CHuffman.cpp + src/common/crypto/CMD5.cpp +) +set(crypto_H + src/common/crypto/CBCrypt.h + src/common/crypto/CCrypto.h + src/common/crypto/CCryptoKeyCalc.h + src/common/crypto/CHuffman.h + src/common/crypto/CMD5.h + src/common/crypto/crypto_common.h +) +source_group(common\\crypto FILES ${crypto_H} ${crypto_S}) -set(resource_SRCS +############################## +# Sphere Resources +############################## +set(resource_S src/common/resource/CResourceDef.cpp - src/common/resource/CResourceDef.h src/common/resource/CResourceHolder.cpp - src/common/resource/CResourceHolder.h src/common/resource/CResourceID.cpp - src/common/resource/CResourceID.h src/common/resource/CResourceHash.cpp - src/common/resource/CResourceHash.h src/common/resource/CResourceLink.cpp - src/common/resource/CResourceLink.h src/common/resource/CResourceLock.cpp - src/common/resource/CResourceLock.h src/common/resource/CResourceRef.cpp - src/common/resource/CResourceRef.h src/common/resource/CResourceScript.cpp - src/common/resource/CResourceScript.h src/common/resource/CResourceSortedArrays.cpp - src/common/resource/CResourceSortedArrays.h src/common/resource/CResourceQty.cpp - src/common/resource/CResourceQty.h src/common/resource/CValueDefs.cpp +) +set(resource_H + src/common/resource/CResourceDef.h + src/common/resource/CResourceHolder.h + src/common/resource/CResourceID.h + src/common/resource/CResourceHash.h + src/common/resource/CResourceLink.h + src/common/resource/CResourceLock.h + src/common/resource/CResourceRef.h + src/common/resource/CResourceScript.h + src/common/resource/CResourceSortedArrays.h + src/common/resource/CResourceQty.h src/common/resource/CValueDefs.h ) -source_group(common\\resource FILES ${resource_SRCS}) +source_group(common\\resource FILES ${resource_H} ${resource_S}) -set(resourcesections_SRCS +############################## +# Scripted resource sections +############################## +set(resourcesections_S src/common/resource/sections/CDialogDef.cpp - src/common/resource/sections/CDialogDef.h src/common/resource/sections/CItemTypeDef.cpp - src/common/resource/sections/CItemTypeDef.h src/common/resource/sections/CRandGroupDef.cpp - src/common/resource/sections/CRandGroupDef.h src/common/resource/sections/CRegionResourceDef.cpp - src/common/resource/sections/CRegionResourceDef.h src/common/resource/sections/CResourceNamedDef.cpp - src/common/resource/sections/CResourceNamedDef.h src/common/resource/sections/CSkillClassDef.cpp - src/common/resource/sections/CSkillClassDef.h src/common/resource/sections/CSkillDef.cpp - src/common/resource/sections/CSkillDef.h src/common/resource/sections/CSpellDef.cpp - src/common/resource/sections/CSpellDef.h src/common/resource/sections/CWebPageDef.cpp +) +set(resourcesections_H + src/common/resource/sections/CDialogDef.h + src/common/resource/sections/CItemTypeDef.h + src/common/resource/sections/CRandGroupDef.h + src/common/resource/sections/CRegionResourceDef.h + src/common/resource/sections/CResourceNamedDef.h + src/common/resource/sections/CSkillClassDef.h + src/common/resource/sections/CSkillDef.h + src/common/resource/sections/CSpellDef.h src/common/resource/sections/CWebPageDef.h ) -source_group(common\\resource\\sections FILES ${resourcesections_SRCS}) +source_group(common\\resource\\sections FILES ${resourcesections_H} ${resourcesections_S}) -# Sphere library files -set(spherelibrary_SRCS +############################## +# Sphere Library files +############################## +set(spherelibrary_S src/common/sphere_library/CSAssoc.cpp - src/common/sphere_library/CSAssoc.h src/common/sphere_library/CSFile.cpp - src/common/sphere_library/CSFile.h src/common/sphere_library/CSFileList.cpp - src/common/sphere_library/CSFileList.h src/common/sphere_library/CSFileText.cpp - src/common/sphere_library/CSFileText.h src/common/sphere_library/CSMemBlock.cpp + src/common/sphere_library/CSObjCont.cpp + src/common/sphere_library/CSObjList.cpp + src/common/sphere_library/CSQueue.cpp + src/common/sphere_library/CSRand.cpp + src/common/sphere_library/CSString.cpp + src/common/sphere_library/CSTime.cpp + src/common/sphere_library/CSWindow.cpp + src/common/sphere_library/smutex.cpp + src/common/sphere_library/sresetevents.cpp + src/common/sphere_library/sstring.cpp + src/common/sphere_library/sstringobjs.cpp + src/common/sphere_library/stypecast.cpp +) +set(spherelibrary_H + src/common/sphere_library/CSAssoc.h + src/common/sphere_library/CSFile.h + src/common/sphere_library/CSFileList.h + src/common/sphere_library/CSFileText.h src/common/sphere_library/CSMemBlock.h src/common/sphere_library/CSObjArray.h - src/common/sphere_library/CSObjCont.cpp src/common/sphere_library/CSObjCont.h src/common/sphere_library/CSObjContRec.h - src/common/sphere_library/CSObjList.cpp src/common/sphere_library/CSObjList.h src/common/sphere_library/CSObjListRec.h src/common/sphere_library/CSObjSortArray.h src/common/sphere_library/CSPtrTypeArray.h src/common/sphere_library/CSReferenceCount.h src/common/sphere_library/CSTypedArray.h - src/common/sphere_library/CSQueue.cpp src/common/sphere_library/CSQueue.h - src/common/sphere_library/CSRand.cpp src/common/sphere_library/CSRand.h - src/common/sphere_library/CSString.cpp src/common/sphere_library/CSString.h - src/common/sphere_library/CSTime.cpp src/common/sphere_library/CSTime.h - src/common/sphere_library/CSWindow.cpp src/common/sphere_library/CSWindow.h src/common/sphere_library/scontainer_ops.h src/common/sphere_library/sfastmath.h src/common/sphere_library/smap.h src/common/sphere_library/smutex.h - src/common/sphere_library/smutex.cpp + src/common/sphere_library/sobjpool.h src/common/sphere_library/squeues.h - src/common/sphere_library/sresetevents.cpp src/common/sphere_library/sresetevents.h src/common/sphere_library/ssorted_vector.h src/common/sphere_library/sptr.h src/common/sphere_library/sptr_containers.h src/common/sphere_library/sstacks.h - src/common/sphere_library/sstring.cpp src/common/sphere_library/sstring.h - src/common/sphere_library/sstringobjs.cpp src/common/sphere_library/sstringobjs.h - src/common/sphere_library/stypecast.cpp src/common/sphere_library/stypecast.h ) -source_group(common\\sphere_library FILES ${spherelibrary_SRCS}) +source_group(common\\sphere_library FILES ${spherelibrary_H} ${spherelibrary_S}) -# Main game files. -set(game_SRCS +############################## +# Main game-related classes +############################## +set(game_S src/game/CBase.cpp - src/game/CBase.h src/game/CContainer.cpp - src/game/CContainer.h src/game/CComponent.cpp - src/game/CComponent.h src/game/CComponentProps.cpp - src/game/CComponentProps.h src/game/CEntity.cpp - src/game/CEntity.h src/game/CEntityProps.cpp - src/game/CEntityProps.h src/game/CObjBase.cpp - src/game/CObjBase.h src/game/CObjBaseTemplate.cpp - src/game/CObjBaseTemplate.h src/game/CPathFinder.cpp - src/game/CPathFinder.h src/game/CRegion.cpp - src/game/CRegion.h src/game/CRegionBase.cpp - src/game/CRegionBase.h src/game/CResourceCalc.cpp - src/game/CScriptProfiler.h src/game/CSector.cpp - src/game/CSector.h - src/game/CSectorEnviron.h src/game/CSectorEnviron.cpp src/game/CSectorTemplate.cpp - src/game/CSectorTemplate.h src/game/CSectorList.cpp - src/game/CSectorList.h src/game/CServer.cpp - src/game/CServer.h src/game/CServerConfig.cpp - src/game/CServerConfig.h src/game/CServerDef.cpp - src/game/CServerDef.h src/game/CServerTime.cpp - src/game/CServerTime.h - src/game/CStartLoc.h src/game/CTeleport.cpp - src/game/CTeleport.h src/game/CTimedFunction.cpp - src/game/CTimedFunction.h src/game/CTimedFunctionHandler.cpp - src/game/CTimedFunctionHandler.h src/game/CTimedObject.cpp - src/game/CTimedObject.h src/game/CWorld.cpp - src/game/CWorld.h src/game/CWorldCache.cpp - src/game/CWorldCache.h src/game/CWorldClock.cpp - src/game/CWorldClock.h src/game/CWorldComm.cpp - src/game/CWorldComm.h src/game/CWorldGameTime.cpp - src/game/CWorldGameTime.h src/game/CWorldImport.cpp src/game/CWorldMap.cpp - src/game/CWorldMap.h src/game/CWorldSearch.cpp - src/game/CWorldSearch.h src/game/CWorldTicker.cpp - src/game/CWorldTicker.h src/game/CWorldTickingList.cpp - src/game/CWorldTickingList.h src/game/CWorldTimedFunctions.cpp + src/game/spheresvr.cpp + src/game/triggers.cpp + src/game/CSectorEnviron.cpp +) +set(game_H + src/game/CBase.h + src/game/CContainer.h + src/game/CComponent.h + src/game/CComponentProps.h + src/game/CEntity.h + src/game/CEntityProps.h + src/game/CObjBase.h + src/game/CObjBaseTemplate.h + src/game/CPathFinder.h + src/game/CRegion.h + src/game/CRegionBase.h + src/game/CScriptProfiler.h + src/game/CSector.h + src/game/CSectorEnviron.h + src/game/CSectorTemplate.h + src/game/CSectorList.h + src/game/CServer.h + src/game/CServerConfig.h + src/game/CServerDef.h + src/game/CServerTime.h + src/game/CStartLoc.h + src/game/CTeleport.h + src/game/CTimedFunction.h + src/game/CTimedFunctionHandler.h + src/game/CTimedObject.h + src/game/CWorld.h + src/game/CWorldCache.h + src/game/CWorldClock.h + src/game/CWorldComm.h + src/game/CWorldGameTime.h + src/game/CWorldMap.h + src/game/CWorldSearch.h + src/game/CWorldTicker.h + src/game/CWorldTickingList.h src/game/CWorldTimedFunctions.h src/game/game_enums.h src/game/game_macros.h - src/game/spheresvr.cpp src/game/spheresvr.h src/game/triggers.h - src/game/triggers.cpp ) -source_group(game FILES ${game_SRCS}) +source_group(game FILES ${game_H} ${game_S}) -set(items_SRCS +############################## +# Item entities +############################## +set(items_S src/game/items/CItem.cpp - src/game/items/CItem.h src/game/items/CItemBase.cpp - src/game/items/CItemBase.h src/game/items/CItemCommCrystal.cpp - src/game/items/CItemCommCrystal.h src/game/items/CItemContainer.cpp - src/game/items/CItemContainer.h src/game/items/CItemCorpse.cpp - src/game/items/CItemCorpse.h src/game/items/CItemMap.cpp - src/game/items/CItemMap.h src/game/items/CItemMemory.cpp - src/game/items/CItemMemory.h src/game/items/CItemMessage.cpp - src/game/items/CItemMessage.h src/game/items/CItemMulti.cpp - src/game/items/CItemMulti.h src/game/items/CItemMultiCustom.cpp - src/game/items/CItemMultiCustom.h src/game/items/CItemPlant.cpp src/game/items/CItemScript.cpp - src/game/items/CItemScript.h src/game/items/CItemShip.cpp - src/game/items/CItemShip.h src/game/items/CItemScript.cpp - src/game/items/CItemScript.h src/game/items/CItemStone.cpp - src/game/items/CItemStone.h src/game/items/CItemVendable.cpp +) +set(items_H + src/game/items/CItem.h + src/game/items/CItemBase.h + src/game/items/CItemCommCrystal.h + src/game/items/CItemContainer.h + src/game/items/CItemCorpse.h + src/game/items/CItemMap.h + src/game/items/CItemMemory.h + src/game/items/CItemMessage.h + src/game/items/CItemMulti.h + src/game/items/CItemMultiCustom.h + src/game/items/CItemScript.h + src/game/items/CItemShip.h + src/game/items/CItemStone.h src/game/items/CItemVendable.h src/game/items/item_types.h ) -source_group(game\\items FILES ${items_SRCS}) +source_group(game\\items FILES ${items_H} ${items_S}) -set(chars_SRCS +############################## +# Chars entities +############################## +set(chars_S src/game/chars/CCharAct.cpp src/game/chars/CCharBase.cpp src/game/chars/CChar.cpp - src/game/chars/CChar.h src/game/chars/CCharAttacker.cpp - src/game/chars/CCharBase.h src/game/chars/CCharFight.cpp src/game/chars/CCharLOS.cpp src/game/chars/CCharMemory.cpp src/game/chars/CCharNotoriety.cpp src/game/chars/CCharNPC.cpp - src/game/chars/CCharNPC.h src/game/chars/CCharNPCAct.cpp src/game/chars/CCharNPCAct_Fight.cpp src/game/chars/CCharNPCAct_Magic.cpp @@ -439,84 +510,93 @@ set(chars_SRCS src/game/chars/CCharNPCPet.cpp src/game/chars/CCharNPCStatus.cpp src/game/chars/CCharPlayer.cpp - src/game/chars/CCharPlayer.h - src/game/chars/CCharRefArray.h src/game/chars/CCharRefArray.cpp src/game/chars/CCharSkill.cpp src/game/chars/CCharSpell.cpp src/game/chars/CCharStat.cpp src/game/chars/CCharStatus.cpp src/game/chars/CCharUse.cpp - src/game/chars/CStoneMember.h src/game/chars/CStoneMember.cpp ) -source_group(game\\chars FILES ${chars_SRCS}) +set(chars_H + src/game/chars/CChar.h + src/game/chars/CCharBase.h + src/game/chars/CCharNPC.h + src/game/chars/CCharPlayer.h + src/game/chars/CCharRefArray.h + src/game/chars/CStoneMember.h +) +source_group(game\\chars FILES ${chars_H} ${items_S}) -set(clients_SRCS +############################## +# Clients +############################## +set(clients_S src/game/clients/CAccount.cpp - src/game/clients/CAccount.h src/game/clients/CChat.cpp - src/game/clients/CChat.h src/game/clients/CChatChannel.cpp - src/game/clients/CChatChannel.h src/game/clients/CChatChanMember.cpp - src/game/clients/CChatChanMember.h src/game/clients/CClient.cpp src/game/clients/CClientDialog.cpp src/game/clients/CClientEvent.cpp - src/game/clients/CClient.h src/game/clients/CClientLog.cpp src/game/clients/CClientMsg.cpp src/game/clients/CClientMsg_AOSTooltip.cpp src/game/clients/CClientTarg.cpp - src/game/clients/CClientTooltip.h src/game/clients/CClientTooltip.cpp src/game/clients/CClientUse.cpp src/game/clients/CGlobalChatChanMember.cpp - src/game/clients/CGlobalChatChanMember.h src/game/clients/CGMPage.cpp - src/game/clients/CGMPage.h src/game/clients/CParty.cpp +) +set(clients_H + src/game/clients/CAccount.h + src/game/clients/CChat.h + src/game/clients/CChatChannel.h + src/game/clients/CChatChanMember.h + src/game/clients/CClient.h + src/game/clients/CClientTooltip.h + src/game/clients/CGlobalChatChanMember.h + src/game/clients/CGMPage.h src/game/clients/CParty.h ) -source_group(game\\clients FILES ${clients_SRCS}) +source_group(game\\clients FILES ${clients_H} ${clients_S}) -set(components_SRCS +############################## +# ECS: components +############################## +set(components_S src/game/components/subcomponents/CFactionDef.cpp - src/game/components/subcomponents/CFactionDef.h src/game/components/CCChampion.cpp - src/game/components/CCChampion.h src/game/components/CCItemDamageable.cpp - src/game/components/CCItemDamageable.h src/game/components/CCMultiMovable.cpp - src/game/components/CCMultiMovable.h src/game/components/CCPropsChar.cpp - src/game/components/CCPropsChar.h src/game/components/CCPropsItem.cpp - src/game/components/CCPropsItem.h src/game/components/CCPropsItemChar.cpp - src/game/components/CCPropsItemChar.h src/game/components/CCPropsItemEquippable.cpp - src/game/components/CCPropsItemEquippable.h src/game/components/CCPropsItemWeapon.cpp - src/game/components/CCPropsItemWeapon.h src/game/components/CCPropsItemWeaponRanged.cpp - src/game/components/CCPropsItemWeaponRanged.h src/game/components/CCSpawn.cpp - src/game/components/CCSpawn.h ) -source_group(game\\components FILES ${components_SRCS}) - -# CrashDump files -set(crashdump_SRCS - src/common/crashdump/crashdump.cpp - src/common/crashdump/crashdump.h - src/common/crashdump/mingwdbghelp.h +set(components_H + src/game/components/subcomponents/CFactionDef.h + src/game/components/CCChampion.h + src/game/components/CCItemDamageable.h + src/game/components/CCMultiMovable.h + src/game/components/CCPropsChar.h + src/game/components/CCPropsItem.h + src/game/components/CCPropsItemChar.h + src/game/components/CCPropsItemEquippable.h + src/game/components/CCPropsItemWeapon.h + src/game/components/CCPropsItemWeaponRanged.h + src/game/components/CCSpawn.h ) -source_group(common\\crashdump FILES ${crashdump_SRCS}) +source_group(game\\components FILES ${components_H} ${components_S}) -# Table definitions -set(tables_SRCS +############################## +# Table Definitions +############################## +set(tables_S src/tables/CBaseBaseDef_props.tbl src/tables/CChar_functions.tbl src/tables/CChar_props.tbl @@ -553,28 +633,57 @@ set(tables_SRCS src/tables/defmessages.tbl src/tables/triggers.tbl ) -source_group(tables FILES ${tables_SRCS}) +source_group(tables FILES ${tables_S}) + +############################## +# App Resources (Windows .rc) +############################## +set(app_resources_S + src/resources/SphereSvr.rc +) + +############################## +# Docs and Configs +############################## +set(docs_TEXT + Changelog.txt + src/sphere.ini + src/sphereCrypt.ini +) -set(app_resources_SRCS src/resources/SphereSvr.rc) -# Misc doc and *.ini files -set(docs_TEXT Changelog.txt src/sphere.ini src/sphereCrypt.ini) +set(SPHERE_HEADERS + ${game_H} + ${items_H} + ${chars_H} + ${clients_H} + ${components_H} + ${uofiles_H} + ${common_H} + ${resource_H} + ${resourcesections_H} + ${network_H} + ${crypto_H} + ${sphere_H} + ${crashdump_H} + ${spherelibrary_H} +) set(SPHERE_SOURCES - ${game_SRCS} - ${items_SRCS} - ${chars_SRCS} - ${clients_SRCS} - ${components_SRCS} - ${uofiles_SRCS} - ${common_SRCS} - ${resource_SRCS} - ${resourcesections_SRCS} - ${network_SRCS} - ${crypto_SRCS} - ${sphere_SRCS} - ${crashdump_SRCS} - ${spherelibrary_SRCS} - ${tables_SRCS} - ${app_resources_SRCS} + ${game_S} + ${items_S} + ${chars_S} + ${clients_S} + ${components_S} + ${uofiles_S} + ${common_S} + ${resource_S} + ${resourcesections_S} + ${network_S} + ${crypto_S} + ${sphere_S} + ${crashdump_S} + ${spherelibrary_S} + ${tables_S} + ${app_resources_S} ) diff --git a/src/common/CCacheableScriptFile.cpp b/src/common/CCacheableScriptFile.cpp index 2f6f949f8..9d92061d9 100644 --- a/src/common/CCacheableScriptFile.cpp +++ b/src/common/CCacheableScriptFile.cpp @@ -163,7 +163,9 @@ bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) // go past the end of this substring, there should be the delimiter/newline/etc, just skip it. lpctstr str_end = &str_start[len_to_copy]; _SkipOneEndline(str_end); + _fileContent->emplace_back(str_start, len_to_copy); + iStrLen += (str_end - len_to_copy - str_start); fFirstLine = false; @@ -186,7 +188,7 @@ bool CCacheableScriptFile::_Open(lpctstr ptcFilename, uint uiModeFlags) bool CCacheableScriptFile::Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CCacheableScriptFile::Open"); - MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(this, CCacheableScriptFile::_Open(ptcFilename, uiModeFlags)); } void CCacheableScriptFile::_Close() @@ -205,7 +207,7 @@ void CCacheableScriptFile::_Close() void CCacheableScriptFile::Close() { ADDTOCALLSTACK("CCacheableScriptFile::Close"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); _Close(); } @@ -222,7 +224,7 @@ bool CCacheableScriptFile::IsFileOpen() const { ADDTOCALLSTACK("CCacheableScriptFile::IsFileOpen"); - MT_SHARED_LOCK_SET; + MT_SHARED_LOCK_SET(this); if ( _useDefaultFile() ) MT_RETURN(CSFileText::_IsFileOpen()); @@ -235,12 +237,13 @@ bool CCacheableScriptFile::_IsEOF() const if ( _useDefaultFile() ) return CSFileText::_IsEOF(); - return (_fileContent->empty() || ((uint)_iCurrentLine == _fileContent->size()) ); + const size_t uiFileSize = _fileContent->size(); // might be faster than empty() on some implementations + return (!uiFileSize || ((uint)_iCurrentLine == uiFileSize) ); } bool CCacheableScriptFile::IsEOF() const { //ADDTOCALLSTACK("CCacheableScriptFile::IsEOF"); - MT_SHARED_LOCK_RETURN(_IsEOF()); + MT_SHARED_LOCK_RETURN(this, _IsEOF()); } tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) @@ -256,15 +259,17 @@ tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) //*pBuffer = '\0'; ASSERT(_fileContent); - if (_fileContent->empty() || ((uint)_iCurrentLine >= _fileContent->size())) + size_t uiSize = _fileContent->size(); // might be faster than empty() on some implementations + if (!uiSize || ((uint)_iCurrentLine >= uiSize)) return nullptr; const std::string_view cur_line = (*_fileContent)[_iCurrentLine]; + uiSize = cur_line.size(); ++_iCurrentLine; - if (cur_line.empty()) + if (!uiSize) return pBuffer; - size_t bytes_to_copy = cur_line.size(); + size_t bytes_to_copy = uiSize; if (bytes_to_copy >= size_t(sizemax)) bytes_to_copy = size_t(sizemax) - 1; //if (!cur_line.empty()) @@ -280,7 +285,7 @@ tchar * CCacheableScriptFile::_ReadString(tchar *pBuffer, int sizemax) tchar * CCacheableScriptFile::ReadString(tchar *pBuffer, int sizemax) { //ADDTOCALLSTACK_DEBUG("CCacheableScriptFile::ReadString"); - MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_ReadString(pBuffer, sizemax)); + MT_UNIQUE_LOCK_RETURN(this, CCacheableScriptFile::_ReadString(pBuffer, sizemax)); } void CCacheableScriptFile::_dupeFrom(CCacheableScriptFile *other) @@ -295,19 +300,19 @@ void CCacheableScriptFile::_dupeFrom(CCacheableScriptFile *other) } void CCacheableScriptFile::dupeFrom(CCacheableScriptFile *other) { - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); _dupeFrom(other); } bool CCacheableScriptFile::_HasCache() const { - if ((_fileContent == nullptr) /* || _fileContent->empty() Exclude this: might just be an empty file!*/) + if ((_fileContent == nullptr) /* || _fileContent->empty() Exclude this: might just be an empty file, which is legit!*/) return false; return true; } bool CCacheableScriptFile::HasCache() const { - MT_SHARED_LOCK_RETURN(_HasCache()); + MT_SHARED_LOCK_RETURN(this, _HasCache()); } bool CCacheableScriptFile::_useDefaultFile() const @@ -318,7 +323,7 @@ bool CCacheableScriptFile::_useDefaultFile() const * bool CCacheableScriptFile::useDefaultFile() const { - MT_SHARED_LOCK_RETURN(_useDefaultFile()); + MT_SHARED_LOCK_RETURN(this, _useDefaultFile()); }*/ int CCacheableScriptFile::_Seek(int iOffset, int iOrigin) @@ -342,7 +347,7 @@ int CCacheableScriptFile::_Seek(int iOffset, int iOrigin) int CCacheableScriptFile::Seek(int iOffset, int iOrigin) { ADDTOCALLSTACK("CCacheableScriptFile::Seek"); - MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(this, CCacheableScriptFile::_Seek(iOffset, iOrigin)); } int CCacheableScriptFile::_GetPosition() const @@ -356,5 +361,5 @@ int CCacheableScriptFile::_GetPosition() const int CCacheableScriptFile::GetPosition() const { ADDTOCALLSTACK("CCacheableScriptFile::GetPosition"); - MT_UNIQUE_LOCK_RETURN(CCacheableScriptFile::_GetPosition()); + MT_UNIQUE_LOCK_RETURN(this, CCacheableScriptFile::_GetPosition()); } diff --git a/src/common/CCacheableScriptFile.h b/src/common/CCacheableScriptFile.h index 3075f5da0..2931c0eab 100644 --- a/src/common/CCacheableScriptFile.h +++ b/src/common/CCacheableScriptFile.h @@ -37,10 +37,10 @@ public: virtual int GetPosition() const override; protected: virtual tchar * _ReadString(tchar *pBuffer, int sizemax) override; public: virtual tchar * ReadString(tchar *pBuffer, int sizemax) override; -protected: +protected: void _dupeFrom(CCacheableScriptFile *other); void dupeFrom(CCacheableScriptFile *other); - + protected: bool _HasCache() const; public: bool HasCache() const; diff --git a/src/common/CDataBase.cpp b/src/common/CDataBase.cpp index 352359d24..cbc694cfa 100644 --- a/src/common/CDataBase.cpp +++ b/src/common/CDataBase.cpp @@ -4,16 +4,23 @@ #include "../sphere/asyncdb.h" #include "resource/sections/CResourceNamedDef.h" // Needed because it was only forward declared. #include "CLog.h" -#include "CException.h" -#include "CExpression.h" +//#include "CException.h" // included in the precompiled header +//#include "CExpression.h" // included in the precompiled header #include "CScriptTriggerArgs.h" #include "CDataBase.h" +#include // mysql standard include +#include // this needs to be defined AFTER common.h -CDataBase::CDataBase() + +struct MySQLDataWrapper +{ + MYSQL *ptr; +}; + +CDataBase::CDataBase() : + _myData(std::make_unique()), m_fConnected(false) { - m_fConnected = false; - _myData = nullptr; } CDataBase::~CDataBase() @@ -38,8 +45,8 @@ bool CDataBase::Connect(const char *user, const char *password, const char *base return false; } - _myData = mysql_init(_myData ? _myData : nullptr); - if ( !_myData ) + _myData->ptr = mysql_init(_myData->ptr ? _myData->ptr : nullptr); + if ( !_myData->ptr ) return false; int portnum = 0; @@ -53,12 +60,12 @@ bool CDataBase::Connect(const char *user, const char *password, const char *base host = pcTemp; } - if ( !mysql_real_connect(_myData, host, user, password, base, portnum, nullptr, CLIENT_MULTI_STATEMENTS ) ) + if ( !mysql_real_connect(_myData->ptr, host, user, password, base, portnum, nullptr, CLIENT_MULTI_STATEMENTS ) ) { - const char *error = mysql_error(_myData); + const char *error = mysql_error(_myData->ptr); g_Log.Event(LOGM_NOCONTEXT|LOGL_ERROR, "MariaDB connect fail with error: %s\n", error); - mysql_close(_myData); - _myData = nullptr; + mysql_close(_myData->ptr); + _myData->ptr = nullptr; return false; } @@ -81,8 +88,8 @@ void CDataBase::Close() { ADDTOCALLSTACK("CDataBase::Close"); SimpleThreadLock lock(m_connectionMutex); - mysql_close(_myData); - _myData = nullptr; + mysql_close(_myData->ptr); + _myData->ptr = nullptr; m_fConnected = false; } @@ -91,10 +98,10 @@ bool CDataBase::query(const char *query, CVarDefMap & mapQueryResult) ADDTOCALLSTACK("CDataBase::query"); mapQueryResult.Clear(); mapQueryResult.SetNumNew("NUMROWS", 0); - + if ( !isConnected() ) return false; - + int result; MYSQL_RES * m_res = nullptr; const char * myErr = nullptr; @@ -105,28 +112,28 @@ bool CDataBase::query(const char *query, CVarDefMap & mapQueryResult) * finish the query -and- retrieve the results */ SimpleThreadLock lock(m_connectionMutex); - result = mysql_query(_myData, query); - + result = mysql_query(_myData->ptr, query); + if ( result == 0 ) { - m_res = mysql_store_result(_myData); + m_res = mysql_store_result(_myData->ptr); if ( m_res == nullptr ) return false; } else { - myErr = mysql_error(_myData); + myErr = mysql_error(_myData->ptr); } } - + if ( m_res != nullptr ) { MYSQL_FIELD * fields = mysql_fetch_fields(m_res); int num_fields = mysql_num_fields(m_res); - + mapQueryResult.SetNum("NUMROWS", mysql_num_rows(m_res)); mapQueryResult.SetNum("NUMCOLS", num_fields); - + char key[12]; char **trow = nullptr; int rownum = 0; @@ -139,13 +146,13 @@ bool CDataBase::query(const char *query, CVarDefMap & mapQueryResult) char *z = trow[i]; if (z == nullptr) //We need to check if the row empty, and return char 0 as return, because SetStr clean up \0 chars from vardef and causes the Sphere crash. z = ∅ - + if ( !rownum ) { mapQueryResult.SetStr(Str_FromI_Fast(i, key, sizeof(key), 10), true, z); mapQueryResult.SetStr(fields[i].name, true, z); } - + snprintf(zStore, Str_TempLength(), "%d.%d", rownum, i); mapQueryResult.SetStr(zStore, true, z); snprintf(zStore, Str_TempLength(), "%d.%s", rownum, fields[i].name); @@ -160,10 +167,10 @@ bool CDataBase::query(const char *query, CVarDefMap & mapQueryResult) { g_Log.Event(LOGM_NOCONTEXT|LOGL_ERROR, "MariaDB query \"%s\" failed due to \"%s\"\n", query, ( *myErr ? myErr : "unknown reason")); } - + if (( result == CR_SERVER_GONE_ERROR ) || ( result == CR_SERVER_LOST )) Close(); - + return false; } @@ -189,12 +196,12 @@ bool CDataBase::exec(const char *query) { // connection can only handle one query at a time, so we need to lock until we finish SimpleThreadLock lock(m_connectionMutex); - result = mysql_query(_myData, query); + result = mysql_query(_myData->ptr, query); if (result == 0) { // even though we don't want (or expect) any result data, we must retrieve // is anyway otherwise we will lose our connection to the server - MYSQL_RES* res = mysql_store_result(_myData); + MYSQL_RES* res = mysql_store_result(_myData->ptr); if (res != nullptr) mysql_free_result(res); @@ -202,7 +209,7 @@ bool CDataBase::exec(const char *query) } else { - const char *myErr = mysql_error(_myData); + const char *myErr = mysql_error(_myData->ptr); g_Log.Event(LOGM_NOCONTEXT|LOGL_ERROR, "MariaDB query \"%s\" failed due to \"%s\"\n", query, ( *myErr ? myErr : "unknown reason")); } @@ -244,11 +251,11 @@ bool CDataBase::addQuery(bool isQuery, lpctstr theFunction, lpctstr theQuery) } } -void CDataBase::addQueryResult(CSString & theFunction, CScriptTriggerArgs * theResult) +void CDataBase::addQueryResult(CSString & theFunction, CScriptTriggerArgsPtr theResult) { SimpleThreadLock stlThelock(m_resultMutex); - m_QueryArgs.push(FunctionArgsPair_t(theFunction, theResult)); + m_QueryArgs.push(FunctionArgsPair_t(theFunction, std::move(theResult))); } bool CDataBase::_OnTick() @@ -268,7 +275,7 @@ bool CDataBase::_OnTick() if ( isConnected() ) // currently connected - just check that the link is alive { SimpleThreadLock lock(m_connectionMutex); - const int iPingRet = mysql_ping(_myData); + const int iPingRet = mysql_ping(_myData->ptr); if ( iPingRet ) { g_Log.EventError("MariaDB server link has been lost (error code: %d). Trying to reattach to it.\n", iPingRet); @@ -287,17 +294,14 @@ bool CDataBase::_OnTick() FunctionArgsPair_t currentPair; { SimpleThreadLock lock(m_resultMutex); - currentPair = m_QueryArgs.front(); + currentPair = m_QueryArgs.front(); m_QueryArgs.pop(); } - if ( !g_Serv.r_Call(currentPair.first, &g_Serv, currentPair.second) ) + if ( !g_Serv.r_Call(currentPair.first.GetBuffer(), currentPair.second, &g_Serv) ) { // error } - - ASSERT(currentPair.second != nullptr); - delete currentPair.second; } return true; @@ -438,7 +442,7 @@ bool CDataBase::r_WriteVal(lpctstr ptcKey, CSString &sVal, CTextConsole *pSrc, b tchar * escapedString = Str_GetTemp(); SimpleThreadLock lock(m_connectionMutex); - if ( isConnected() && mysql_real_escape_string(_myData, escapedString, ptcKey, (uint)(strlen(ptcKey))) ) + if ( isConnected() && mysql_real_escape_string(_myData->ptr, escapedString, ptcKey, (uint)(strlen(ptcKey))) ) { sVal = escapedString; } diff --git a/src/common/CDataBase.h b/src/common/CDataBase.h index d166cd70d..22ff94b82 100644 --- a/src/common/CDataBase.h +++ b/src/common/CDataBase.h @@ -12,12 +12,8 @@ #include "CVarDefMap.h" #include -#include // mysql standard include -#include // this needs to be defined AFTER common.h - - #define MIN_MARIADB_VERSION_ALLOW 30002 - +struct MySQLDataWrapper; class CDataBase : public CScriptObj { @@ -40,7 +36,7 @@ class CDataBase : public CScriptObj bool queryf(CVarDefMap & mapQueryResult, char *fmt, ...) SPHERE_PRINTFARGS(3,4); bool exec(const char *query); // executes query (pretty faster) for ALTER, UPDATE, INSERT, DELETE, ... bool execf(char *fmt, ...) SPHERE_PRINTFARGS(2,3); - void addQueryResult(CSString & theFunction, CScriptTriggerArgs * theResult); + void addQueryResult(CSString & theFunction, CScriptTriggerArgsPtr theResult); // set / get / info methods bool isConnected(); @@ -62,17 +58,18 @@ class CDataBase : public CScriptObj static lpctstr const sm_szVerbKeys[]; private: - typedef std::pair FunctionArgsPair_t; + typedef std::pair FunctionArgsPair_t; typedef std::queue QueueFunction_t; protected: - bool m_fConnected; // are we online? - MYSQL *_myData; // mySQL link - QueueFunction_t m_QueryArgs; + std::unique_ptr _myData; // mySQL link + bool m_fConnected; // are we online? + QueueFunction_t m_QueryArgs; private: SimpleMutex m_connectionMutex; SimpleMutex m_resultMutex; + bool addQuery(bool isQuery, lpctstr theFunction, lpctstr theQuery); }; diff --git a/src/common/CException.cpp b/src/common/CException.cpp index f796eab80..2e8d8dd29 100644 --- a/src/common/CException.cpp +++ b/src/common/CException.cpp @@ -1,5 +1,5 @@ -#include "CException.h" +//#include "CException.h" // included in the precompiled header #ifdef WINDOWS_SHOULD_EMIT_CRASH_DUMP #include "crashdump/crashdump.h" @@ -25,6 +25,7 @@ int IsDebuggerPresent(void) return 0; ssize_t num_read = read(status_fd, buf, sizeof(buf)-1); + close(status_fd); if (num_read > 0) { @@ -97,7 +98,7 @@ void RaiseImmediateAbort(int iErrCode) UnreferencedParameter(iErrCode); #ifdef _DEBUG - STDERR_LOG("RaiseImmediateAbort with code %d.\n", iErrCode); + stderrLog("RaiseImmediateAbort with code %d.\n", iErrCode); #endif EXC_NOTIFY_DEBUGGER; @@ -325,7 +326,7 @@ static void Signal_Terminate(int sig = 0) noexcept // If shutdown is initialized if ((sig == SIGABRT) && IsAbortImmediate()) { // No clean ending. Abort right now. - STDERR_LOG("FATAL: Immediate abort requested."); + stderrLog("FATAL: Immediate abort requested."); #if defined(__GNUC__) || defined(__clang__) __builtin_trap(); diff --git a/src/common/CException.h b/src/common/CException.h index 1de7652c4..dde06502a 100644 --- a/src/common/CException.h +++ b/src/common/CException.h @@ -133,7 +133,11 @@ class CAssert : public CSError #endif // _EXC_CAUGHT -#define _EXC_CAUGHT static_cast(ThreadHolder::get().current())->signalExceptionCaught() +#if defined THREAD_TRACK_CALLSTACK && defined _EXCEPTIONS_DEBUG +# define _EXC_CAUGHT static_cast(ThreadHolder::get().current())->signalExceptionCaught() +#else +# define _EXC_CAUGHT +#endif /*--- Main (non SUB) macros ---*/ diff --git a/src/common/CExpression.cpp b/src/common/CExpression.cpp index 096ff5022..a7611c3ff 100644 --- a/src/common/CExpression.cpp +++ b/src/common/CExpression.cpp @@ -1,24 +1,113 @@ +//#include "CExpression.h" // included in the precompiled header #include "../game/CServerConfig.h" #include "sphere_library/CSRand.h" -//#include "CException.h" +///include "CException.h" // included in the precompiled header #include "CLog.h" -#include "CExpression.h" +#include "../sphere/threads.h" #include #include #include -tchar CExpression::sm_szMessages[DEFMSG_QTY][DEFMSG_MAX_LEN] = + +///////////////////////////////////////////////////////////////////////// +// - CExprGlobals + +tchar CExprGlobals::sm_szDefMessages[DEFMSG_QTY][m_kiDefmsgMaxLen] = { #define MSG(a,b) b, #include "../tables/defmessages.tbl" }; -lpctstr const CExpression::sm_szMsgNames[DEFMSG_QTY] = +lpctstr const CExprGlobals::sm_szDefMsgNames[DEFMSG_QTY] = { #define MSG(a,b) #a, #include "../tables/defmessages.tbl" }; +CExprGlobals::CExprGlobals() +{ + m_VarResDefs. Reserve(0x1000); + m_VarDefs. Reserve(0x100); + m_VarGlobals. Reserve(0x10); +} + +void CExprGlobals::UpdateDefMsgDependentData() +{ + // Method to be re-evaluated. + // At the moment, it's not actually useful, if g_Cfg.GetDefaultMsg returns the pointer to the string + // CExprGlobals::sm_szDefMessages. The memory location does never change, but we might want to modify this behavior, + // or use this function for other global data which is cached in this class. + + //std::lock_guard _lock_me(MT_CMUTEX); + auto gwriter = g_ExprGlobals.mtEngineLockedWriter(); + auto& vardefs = gwriter->m_VarDefs; + + // TODO: get rid of this associative system... what about using a plain simple map/hash map? + + m_SkillTitles_Ninjitsu = std::array + {{ + { "", INT32_MIN }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_NEOPHYTE], (int)(vardefs.GetKeyNum("SKILLTITLE_NEOPHYTE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_NOVICE], (int)(vardefs.GetKeyNum("SKILLTITLE_NOVICE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_APPRENTICE], (int)(vardefs.GetKeyNum("SKILLTITLE_APPRENTICE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_JOURNEYMAN], (int)(vardefs.GetKeyNum("SKILLTITLE_JOURNEYMAN")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_EXPERT], (int)(vardefs.GetKeyNum("SKILLTITLE_EXPERT")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_ADEPT], (int)(vardefs.GetKeyNum("SKILLTITLE_ADEPT")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_MASTER], (int)(vardefs.GetKeyNum("SKILLTITLE_MASTER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_GRANDMASTER], (int)(vardefs.GetKeyNum("SKILLTITLE_GRANDMASTER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_ELDER_NINJITSU], (int)(vardefs.GetKeyNum("SKILLTITLE_ELDER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_LEGENDARY_NINJITSU], (int)(vardefs.GetKeyNum("SKILLTITLE_LEGENDARY")) }, + { nullptr, INT32_MAX } + }}; + + m_SkillTitles_Bushido = std::array + {{ + { "", INT32_MIN }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_NEOPHYTE], (int)(vardefs.GetKeyNum("SKILLTITLE_NEOPHYTE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_NOVICE], (int)(vardefs.GetKeyNum("SKILLTITLE_NOVICE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_APPRENTICE], (int)(vardefs.GetKeyNum("SKILLTITLE_APPRENTICE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_JOURNEYMAN], (int)(vardefs.GetKeyNum("SKILLTITLE_JOURNEYMAN")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_EXPERT], (int)(vardefs.GetKeyNum("SKILLTITLE_EXPERT")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_ADEPT], (int)(vardefs.GetKeyNum("SKILLTITLE_ADEPT")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_MASTER], (int)(vardefs.GetKeyNum("SKILLTITLE_MASTER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_GRANDMASTER], (int)(vardefs.GetKeyNum("SKILLTITLE_GRANDMASTER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_ELDER_BUSHIDO], (int)(vardefs.GetKeyNum("SKILLTITLE_ELDER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_LEGENDARY_BUSHIDO], (int)(vardefs.GetKeyNum("SKILLTITLE_LEGENDARY")) }, + { nullptr, INT32_MAX } + }}; + + m_SkillTitles_Generic = std::array + {{ + { "", INT32_MIN }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_NEOPHYTE], (int)(vardefs.GetKeyNum("SKILLTITLE_NEOPHYTE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_NOVICE], (int)(vardefs.GetKeyNum("SKILLTITLE_NOVICE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_APPRENTICE], (int)(vardefs.GetKeyNum("SKILLTITLE_APPRENTICE")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_JOURNEYMAN], (int)(vardefs.GetKeyNum("SKILLTITLE_JOURNEYMAN")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_EXPERT], (int)(vardefs.GetKeyNum("SKILLTITLE_EXPERT")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_ADEPT], (int)(vardefs.GetKeyNum("SKILLTITLE_ADEPT")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_MASTER], (int)(vardefs.GetKeyNum("SKILLTITLE_MASTER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_GRANDMASTER], (int)(vardefs.GetKeyNum("SKILLTITLE_GRANDMASTER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_ELDER], (int)(vardefs.GetKeyNum("SKILLTITLE_ELDER")) }, + { sm_szDefMessages[DEFMSG_SKILLTITLE_LEGENDARY], (int)(vardefs.GetKeyNum("SKILLTITLE_LEGENDARY")) }, + { nullptr, INT32_MAX } + }}; +} + +lpctstr CExprGlobals::SkillTitle(SKILL_TYPE skill, uint uiVal) const +{ + // TODO: CValStr::FindName is hack-ish as hell, please use another way of storing and getting this stuff, like maps. + switch (skill) + { + case SKILL_NINJITSU: + return m_SkillTitles_Ninjitsu[0].FindName(uiVal); + case SKILL_BUSHIDO: + return m_SkillTitles_Bushido[0].FindName(uiVal); + default: + break; + } + return m_SkillTitles_Generic[0].FindName(uiVal); +} + ///////////////////////////////////////////////////////////////////////// // - String expressions parsers (separate and evaluate arguments) @@ -425,14 +514,14 @@ int Str_ParseCmdsAdv(tchar * pszCmdLine, tchar ** ppCmd, int iMax, const tchar * bool IsValidResourceDef( lpctstr ptcTest ) { - return (nullptr != g_Exp.m_VarResDefs.CheckParseKey( ptcTest )); + return (nullptr != g_ExprGlobals.mtEngineLockedReader()->m_VarResDefs.CheckParseKey( ptcTest )); } bool IsValidGameObjDef( lpctstr ptcTest ) { if (!IsSimpleNumberString(ptcTest)) { - const CVarDefCont * pVarBase = g_Exp.m_VarResDefs.CheckParseKey( ptcTest ); + const CVarDefCont * pVarBase = g_ExprGlobals.mtEngineLockedReader()->m_VarResDefs.CheckParseKey( ptcTest ); if ( pVarBase == nullptr ) return false; @@ -533,122 +622,195 @@ int Calc_GetSCurve( int iValDiff, int iVariance ) return iChance; } +// --- + CExpression::CExpression() noexcept : + _pBufs(std::make_unique()), _iGetVal_Reentrant(0) { } -llong CExpression::GetSingle( lpctstr & pszArgs ) +CExpression& CExpression::GetExprParser() // static { - ADDTOCALLSTACK("CExpression::GetSingle"); - // Parse just a single expression without any operators or ranges. - ASSERT(pszArgs); - GETNONWHITESPACE( pszArgs ); + auto thread = static_cast(ThreadHolder::get().current()); + return *(thread->m_pExpr.get()); + /* + // If we need to use it at startup (or deep shutdown?) when there's no AbstractSphereThread instance. + static CExpression expr_thread_unsafe; + return !thread + ? expr_thread_unsafe + : *(thread->m_pExpr.get()); + */ +} - lpctstr ptcStartingString = pszArgs; - if (pszArgs[0]=='.') - ++pszArgs; +int64 CExpression::GetSingle(lpctstr & refStrExpr) +{ + ADDTOCALLSTACK("CExpression::GetSingle"); + ASSERT(refStrExpr); + GETNONWHITESPACE(refStrExpr); - if ( pszArgs[0] == '0' ) // leading '0' = hex value. - { - // A hex value. - if ( pszArgs[1] == '.' ) // leading 0. means it really is decimal. - { - pszArgs += 2; - goto try_dec; - } + tchar ptcOrigExpr[SCRIPT_MAX_LINE_LEN]; + Str_CopyLimitNull(ptcOrigExpr, refStrExpr, sizeof(ptcOrigExpr)); - lpctstr pStart = pszArgs; - llong val = 0; - ushort ndigits = 0; - while (true) - { - tchar ch = *pszArgs; - if ( IsDigit(ch) ) - ch -= '0'; - else - { - ch = static_cast(tolower(ch)); - if ( ch > 'f' || ch < 'a' ) - { - if ( ch == '.' && pStart[0] != '0' ) // ok i'm confused. it must be decimal. - { - pszArgs = pStart; - goto try_dec; - } - break; - } - ch -= 'a' - 10; - ++ ndigits; - } - val *= 0x10; - val += ch; - ++ pszArgs; - } - if (ndigits <= 8) - return (llong)(int)val; - return val; - } - /* - // We could just use this, but it doesn't "eat" the string pointer. - if ( pszArgs[0] == '.' || IsDigit(pszArgs[0]) ) + // Differences between GetSingle and cstr_to_num (sstring.cpp) in parsing numbers: + // - The first "consumes" the string (advances the pointer) as it is parsed. + // - The first supports only base 16 and 10. + // - The first stops when finding a '.' during hex parsing. The second stops and warns. + + // Legacy: allow/skip a leading '.' before parsing numbers + if (refStrExpr[0] == '.') + ++refStrExpr; + + if (refStrExpr[0] == '0' && refStrExpr[1] != '.') { - std::optional iVal = Str_ToLL(pszArgs, 0, true); - if (!iVal) + // HEX PATH: consume the leading '0' marker, then scan [0-9 A-F a-f] until a non-hex char or '.' + ++refStrExpr; + + uint64 uiVal = 0; // accumulates significant nibbles only + uint uiSig = 0; // count of significant hex digits (max 16) + bool fSeenNonZero = false; + bool fOverflow = false; + + lpctstr p = refStrExpr; + for (;; ++p) { - g_Log.EventDebug("Invalid conversion to number for the string '%s'?\n", pszArgs); + const tchar ch = *p; + uint v; + if (ch >= '0' && ch <= '9') v = (uint)(ch - '0'); + else if (ch >= 'A' && ch <= 'F') v = (uint)(ch - 'A' + 10); + else if (ch >= 'a' && ch <= 'f') v = (uint)(ch - 'a' + 10); + else break; // stop on non-hex (including '.') + + if (!fSeenNonZero) + { + if (v != 0) + { + fSeenNonZero = true; + uiSig = 1; + uiVal = v; // first non-zero nibble + } + // leading zeros before first non-zero nibble do not affect width or value + } + else + { + if (uiSig == 16) + { + // More than 16 significant nibbles; flag overflow, keep consuming token for caller. + fOverflow = true; + } + else + { + uiVal = (uiVal << 4) | v; // safe: uiSig < 16 guarantees no shift past 60 bits here + ++uiSig; + } + } + } + + // Consume the entire hex token (up to, not including, the first non-hex char) + refStrExpr = p; + + // If no significant nibble after the marker, the numeric value is zero (e.g., "0", "0000") + if (!fSeenNonZero) return 0; + + if (fOverflow) + { + g_Log.EventWarn("Hexadecimal value parsing will overflow 64 bits: %s.\n", ptcOrigExpr); + return -1; + } + + // Decide width by significant hex digits: + // ≤ 8 → signed 32-bit reinterpretation, then widen; 9..16 → signed 64-bit reinterpretation + if (uiSig <= 8) + { + const uint32 u32 = static_cast(uiVal); + const int32 s32 = static_cast(u32); // two's complement reinterpretation + return static_cast(s32); + } + else + { + // two's-complement 64-bit reinterpretation without implementation-defined casts + if (uiVal & (1ull << 63)) + { + const uint64 mag = (~uiVal) + 1; // absolute magnitude + const int64 neg = -static_cast(mag);// well-defined negate in 64-bit domain + return neg; + } + return (llong)(int64)uiVal; } - return iVal.value(); } - */ - else if ( pszArgs[0] == '.' || IsDigit(pszArgs[0]) ) - { - // A decimal number -try_dec: - llong iVal = 0; - for ( ; ; ++pszArgs ) - { - if ( *pszArgs == '.' ) - continue; // just skip this. - if ( ! IsDigit(*pszArgs) ) - break; - iVal *= 10; - iVal += (llong)(*pszArgs) - '0'; - } - return iVal; - } - else if ( ! _ISCSYMF(pszArgs[0]) ) + else if ((refStrExpr[0] >= '0' && refStrExpr[0] <= '9') || + (refStrExpr[0] == '.' && (refStrExpr[1] >= '0' && refStrExpr[1] <= '9'))) + { + // DECIMAL PATH: digits, optionally with '.' separators that are ignored + // Overflow guard for INT64_MAX (9223372036854775807) + constexpr int64 LIM10 = INT64_MAX / 10; // 922337203685477580 + constexpr int LIMDG = (int)(INT64_MAX % 10); // 7 + + int64 val = 0; + bool overflow = false; + + lpctstr p = refStrExpr; + for (;; ++p) + { + const tchar ch = *p; + if (ch == '.') + continue; // skip grouping separators + if (ch < '0' || ch > '9') + break; + + const int d = ch - '0'; + if (!overflow && (val > LIM10 || (val == LIM10 && d > LIMDG))) + { + overflow = true; // keep consuming the whole token for the caller + } + else if (!overflow) + { + val = val * 10 + d; // safe: guarded to avoid signed overflow + } + } + + // Consume the entire decimal token + refStrExpr = p; + + if (overflow) + { + g_Log.EventWarn("Decimal value parsing will overflow 64 bits: %s.\n", ptcOrigExpr); + return -1; + } + return (llong)val; + } + else if ( ! _ISCSYMF(refStrExpr[0]) ) { - //#pragma region maths // MSVC specific + //#pragma region maths // MSVC specific // some sort of math op ? - switch ( pszArgs[0] ) + switch ( refStrExpr[0] ) { case '{': - ++pszArgs; - return GetRangeNumber( pszArgs ); + ++refStrExpr; + return GetRangeNumber( refStrExpr ); case '[': case '(': // Parse out a sub expression. - ++pszArgs; - return GetVal( pszArgs ); + ++refStrExpr; + return GetVal( refStrExpr ); case '+': - ++pszArgs; + ++refStrExpr; break; - case '-': - ++pszArgs; - return -GetSingle( pszArgs ); + case '-': + ++refStrExpr; + return -GetSingle( refStrExpr ); case '~': // Bitwise not. - ++pszArgs; - return ~GetSingle( pszArgs ); + ++refStrExpr; + return ~GetSingle( refStrExpr ); case '!': // boolean not. - ++pszArgs; - if ( pszArgs[0] == '=' ) // odd condition such as (!=x) which is always true of course. + ++refStrExpr; + if ( refStrExpr[0] == '=' ) // odd condition such as (!=x) which is always true of course. { - ++pszArgs; // so just skip it. and compare it to 0 - return GetSingle( pszArgs ); + ++refStrExpr; // so just skip it. and compare it to 0 + return GetSingle( refStrExpr ); } - return !GetSingle( pszArgs ); + return !GetSingle( refStrExpr ); case ';': // seperate field. case ',': // seperate field. case '\0': @@ -661,15 +823,15 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) { // Symbol or intrinsinc function ? - INTRINSIC_TYPE iIntrinsic = (INTRINSIC_TYPE) FindTableHeadSorted( pszArgs, sm_IntrinsicFunctions, ARRAY_COUNT(sm_IntrinsicFunctions)-1 ); + INTRINSIC_TYPE iIntrinsic = (INTRINSIC_TYPE) FindTableHeadSorted( refStrExpr, sm_IntrinsicFunctions, ARRAY_COUNT(sm_IntrinsicFunctions)-1 ); if ( iIntrinsic >= 0 ) { size_t iLen = strlen(sm_IntrinsicFunctions[iIntrinsic]); - if ( strchr("( ", pszArgs[iLen]) ) + if ( strchr("( ", refStrExpr[iLen]) ) { - pszArgs += (iLen + 1); + refStrExpr += (iLen + 1); tchar * pszArgsNext; - Str_Parse( const_cast(pszArgs), &(pszArgsNext), ")" ); + Str_Parse( const_cast(refStrExpr), &(pszArgsNext), ")" ); tchar * ppCmd[5]; llong iResult; @@ -679,10 +841,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) { case INTRINSIC_ID: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = ResGetIndex((dword)GetVal(pszArgs)); + iResult = ResGetIndex((dword)GetVal(refStrExpr)); } else { @@ -694,7 +856,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_MAX: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 0; else @@ -706,7 +868,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_MIN: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 0; else @@ -721,9 +883,9 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) iCount = 0; iResult = 0; - if ( *pszArgs ) + if ( *refStrExpr ) { - llong iArgument = GetVal(pszArgs); + llong iArgument = GetVal(refStrExpr); if ( iArgument <= 0 ) { DEBUG_ERR(( "Exp_GetVal: (x)Log(%lld) is %s\n", iArgument, (!iArgument ? "infinite" : "undefined") )); @@ -732,21 +894,21 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) { iCount = 1; - if ( strchr(pszArgs, ',') ) + if ( strchr(refStrExpr, ',') ) { ++iCount; - SKIP_ARGSEP(pszArgs); - if ( !strcmpi(pszArgs, "e") ) + SKIP_ARGSEP(refStrExpr); + if ( !strcmpi(refStrExpr, "e") ) { iResult = (llong)log( (double)iArgument ); } - else if ( !strcmpi(pszArgs, "pi") ) + else if ( !strcmpi(refStrExpr, "pi") ) { iResult = (llong)(log( (double)iArgument ) / log( M_PI ) ); } else { - llong iBase = GetVal(pszArgs); + llong iBase = GetVal(refStrExpr); if ( iBase <= 0 ) { DEBUG_ERR(( "Exp_GetVal: (%lld)Log(%lld) is %s\n", iBase, iArgument, (!iBase ? "infinite" : "undefined") )); @@ -765,10 +927,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_NAPIERPOW: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)exp( (double)GetVal( pszArgs ) ); + iResult = (llong)exp( (double)GetVal( refStrExpr ) ); } else { @@ -783,9 +945,9 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) iCount = 0; iResult = 0; - if ( *pszArgs ) + if ( *refStrExpr ) { - llong iTosquare = GetVal(pszArgs); + llong iTosquare = GetVal(refStrExpr); if (iTosquare >= 0) { @@ -805,10 +967,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_SIN: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)sin( (double)GetVal( pszArgs ) ); + iResult = (llong)sin( (double)GetVal( refStrExpr ) ); } else { @@ -820,10 +982,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_ARCSIN: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)asin( (double)GetVal( pszArgs ) ); + iResult = (llong)asin( (double)GetVal( refStrExpr ) ); } else { @@ -835,10 +997,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_COS: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)cos( (double)GetVal( pszArgs ) ); + iResult = (llong)cos( (double)GetVal( refStrExpr ) ); } else { @@ -850,10 +1012,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_ARCCOS: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)acos( (double)GetVal( pszArgs ) ); + iResult = (llong)acos( (double)GetVal( refStrExpr ) ); } else { @@ -865,10 +1027,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_TAN: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)tan( (double)GetVal( pszArgs ) ); + iResult = (llong)tan( (double)GetVal( refStrExpr ) ); } else { @@ -880,10 +1042,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_ARCTAN: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = (llong)atan( (double)GetVal( pszArgs ) ); + iResult = (llong)atan( (double)GetVal( refStrExpr ) ); } else { @@ -895,7 +1057,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_StrIndexOf: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 3, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 3, "," ); if ( iCount < 2 ) iResult = -1; else @@ -904,7 +1066,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_STRMATCH: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 0; else @@ -913,7 +1075,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_STRREGEX: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 0; else @@ -929,7 +1091,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_RANDBELL: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 0; else @@ -938,10 +1100,10 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_STRASCII: { - if ( *pszArgs ) + if ( *refStrExpr ) { iCount = 1; - iResult = pszArgs[0]; + iResult = refStrExpr[0]; } else { @@ -952,7 +1114,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_RAND: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount <= 0 ) iResult = 0; else @@ -961,16 +1123,16 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) if ( iCount == 2 ) { int64 val2 = GetVal( ppCmd[1] ); - iResult = g_Rand.GetLLVal2( val1, val2 ); + iResult = g_Rand.GetLLVal2( val1, val2 ); } else - iResult = g_Rand.GetLLVal(val1); + iResult = g_Rand.GetLLVal(val1); } } break; case INTRINSIC_STRCMP: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 1; else @@ -979,7 +1141,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_STRCMPI: { - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 2, "," ); if ( iCount < 2 ) iResult = 1; else @@ -989,26 +1151,26 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_STRLEN: { iCount = 1; - iResult = strlen(pszArgs); + iResult = strlen(refStrExpr); } break; case INTRINSIC_ISOBSCENE: { iCount = 1; - iResult = g_Cfg.IsObscene( pszArgs ); + iResult = g_Cfg.IsObscene( refStrExpr ); } break; case INTRINSIC_ISNUMBER: { iCount = 1; - SKIP_NONNUM( pszArgs ); - iResult = IsStrNumeric( pszArgs ); + SKIP_NONNUM( refStrExpr ); + iResult = IsStrNumeric( refStrExpr ); } break; case INTRINSIC_QVAL: { // Here is handled the intrinsic QVAL form: QVAL(VALUE1,VALUE2,LESSTHAN,EQUAL,GREATERTHAN) - iCount = Str_ParseCmds( const_cast(pszArgs), ppCmd, 5, "," ); + iCount = Str_ParseCmds( const_cast(refStrExpr), ppCmd, 5, "," ); if (iCount < 3) { iResult = 0; @@ -1035,7 +1197,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) case INTRINSIC_ABS: { iCount = 1; - iResult = llabs(GetVal(pszArgs)); + iResult = llabs(GetVal(refStrExpr)); } break; default: @@ -1044,7 +1206,7 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) break; } - pszArgs = pszArgsNext; + refStrExpr = pszArgsNext; if ( !iCount ) { @@ -1057,40 +1219,48 @@ llong CExpression::GetSingle( lpctstr & pszArgs ) } // Must be a symbol of some sort ? - lpctstr ptcArgsOriginal = pszArgs; - llong llVal; - if ( m_VarGlobals.GetParseVal_Advance( pszArgs, &llVal ) ) // VAR. - return llVal; - if ( m_VarResDefs.GetParseVal( ptcArgsOriginal, &llVal ) ) // RESDEF. - return llVal; - if ( m_VarDefs.GetParseVal( ptcArgsOriginal, &llVal ) ) // DEF. - return llVal; + + //[[maybe_unused]] auto _ = g_ExprGlobals.mtEngineGetLockShared(); + //auto globals_reader = g_ExprGlobals.unsafeReader(); + auto reader = g_ExprGlobals.mtEngineLockedReader(); + + lpctstr ptcArgsOriginal = refStrExpr; + int64 iVal; + // VAR. + if ( reader->m_VarGlobals.GetParseVal_Advance( refStrExpr, &iVal ) ) + return iVal; + // RESDEF. + if ( reader->m_VarResDefs.GetParseVal( ptcArgsOriginal, &iVal ) ) + return iVal; + // DEF. + if ( reader->m_VarDefs.GetParseVal( ptcArgsOriginal, &iVal ) ) + return iVal; } //#pragma endregion intrinsics // MSVC specific // hard end ! Error of some sort. - if (ptcStartingString[0] != '\0') + if (ptcOrigExpr[0] != '\0') { tchar szTag[EXPRESSION_MAX_KEY_LEN]; - GetIdentifierString(szTag, ptcStartingString); - const lpctstr ptcLast = (ptcStartingString[0] == '<') ? ">'" : "'"; - DEBUG_ERR(("Undefined symbol '%s' [Evaluated expression: '%s%s].\n", szTag, ptcStartingString, ptcLast)); + GetIdentifierString(szTag, ptcOrigExpr); + //const lpctstr ptcLast = (ptcOrigExpr[0] == '<') ? ">'" : "'"; + g_Log.EventError("Undefined symbol '%s' [Evaluated expression: '%s'].\n", szTag, ptcOrigExpr); } else { - DEBUG_ERR(("Undefined symbol (empty parameter?).\n")); - } + g_Log.EventError("Undefined symbol (empty parameter?).\n"); + } return 0; } -llong CExpression::GetValMath( llong llVal, lpctstr & pExpr ) +int64 CExpression::GetValMath(int64 iVal, lpctstr & refStrExpr ) { ADDTOCALLSTACK("CExpression::GetValMath"); - GETNONWHITESPACE(pExpr); + GETNONWHITESPACE(refStrExpr); // Look for math type operator and eventually apply it to the second operand (which we evaluate here if a valid operator is found). - llong llValSecond; - switch ( pExpr[0] ) + int64 iValSecond; + switch ( refStrExpr[0] ) { case '\0': break; @@ -1098,163 +1268,163 @@ llong CExpression::GetValMath( llong llVal, lpctstr & pExpr ) case ')': // expression end markers. case '}': case ']': - ++pExpr; // consume this. + ++refStrExpr; // consume this. break; case '+': - ++pExpr; - llValSecond = GetVal(pExpr); - llVal += llValSecond; + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal += iValSecond; break; case '-': - llValSecond = GetVal(pExpr); + iValSecond = GetVal(refStrExpr); //++pExpr; No need to consume the negative sign, we need to keep it! - llVal += llValSecond; // a subtraction is an addiction with a negative number. + iVal += iValSecond; // a subtraction is an addiction with a negative number. break; case '*': - ++pExpr; - llValSecond = GetVal(pExpr); - llVal *= llValSecond; + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal *= iValSecond; break; case '|': - ++pExpr; - if ( pExpr[0] == '|' ) // boolean ? + ++refStrExpr; + if ( refStrExpr[0] == '|' ) // boolean ? { - ++pExpr; - llValSecond = GetVal(pExpr); - llVal = (llValSecond || llVal); + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal = (iValSecond || iVal); } else // bitwise { - llValSecond = GetVal(pExpr); - llVal |= llValSecond; + iValSecond = GetVal(refStrExpr); + iVal |= iValSecond; } break; case '&': - ++pExpr; - if ( pExpr[0] == '&' ) // boolean ? + ++refStrExpr; + if ( refStrExpr[0] == '&' ) // boolean ? { - ++pExpr; - llValSecond = GetVal(pExpr); - llVal = (llValSecond && llVal); // tricky stuff here. logical ops must come first or possibly not get processed. + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal = (iValSecond && iVal); // tricky stuff here. logical ops must come first or possibly not get processed. } else // bitwise { - llValSecond = GetVal(pExpr); - llVal &= llValSecond; + iValSecond = GetVal(refStrExpr); + iVal &= iValSecond; } break; case '/': - ++pExpr; - llValSecond = GetVal(pExpr); - if (!llValSecond) + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + if (!iValSecond) { g_Log.EventError("Evaluating math: Divide by 0\n"); break; } - llVal /= llValSecond; + iVal /= iValSecond; break; case '%': - ++pExpr; - llValSecond = GetVal(pExpr); - if (!llValSecond) + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + if (!iValSecond) { g_Log.EventError("Evaluating math: Modulo 0\n"); break; } - llVal %= llValSecond; + iVal %= iValSecond; break; case '^': - ++pExpr; - llValSecond = GetVal(pExpr); - llVal ^= llValSecond; + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal ^= iValSecond; break; case '>': // boolean - ++pExpr; - if ( pExpr[0] == '=' ) // boolean ? + ++refStrExpr; + if ( refStrExpr[0] == '=' ) // boolean ? { - ++pExpr; - llValSecond = GetVal(pExpr); - llVal = ( llVal >= llValSecond ); + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal = ( iVal >= iValSecond ); } - else if ( pExpr[0] == '>' ) // shift + else if ( refStrExpr[0] == '>' ) // shift { - ++pExpr; - llValSecond = GetVal(pExpr); - llVal >>= llValSecond; + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal >>= iValSecond; } else { - llValSecond = GetVal(pExpr); - llVal = (llVal > llValSecond); + iValSecond = GetVal(refStrExpr); + iVal = (iVal > iValSecond); } break; case '<': // boolean - ++pExpr; - if ( pExpr[0] == '=' ) // boolean ? + ++refStrExpr; + if ( refStrExpr[0] == '=' ) // boolean ? { - ++pExpr; - llValSecond = GetVal(pExpr); - llVal = ( llVal <= llValSecond ); + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal = ( iVal <= iValSecond ); } - else if ( pExpr[0] == '<' ) // shift + else if ( refStrExpr[0] == '<' ) // shift { - ++pExpr; - llValSecond = GetVal(pExpr); - llVal <<= llValSecond; + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal <<= iValSecond; } else { - llValSecond = GetVal(pExpr); - llVal = (llVal < llValSecond); + iValSecond = GetVal(refStrExpr); + iVal = (iVal < iValSecond); } break; case '!': - ++pExpr; - if ( pExpr[0] != '=' ) + ++refStrExpr; + if ( refStrExpr[0] != '=' ) break; // boolean ! is handled as a single expresion. - ++pExpr; - llValSecond = GetVal(pExpr); - llVal = ( llVal != llValSecond ); + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal = ( iVal != iValSecond ); break; case '=': // boolean - while ( pExpr[0] == '=' ) - ++pExpr; - llValSecond = GetVal(pExpr); - llVal = ( llVal == llValSecond ); + while ( refStrExpr[0] == '=' ) + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + iVal = ( iVal == iValSecond ); break; case '@': - ++pExpr; - llValSecond = GetVal(pExpr); - if (llVal < 0) + ++refStrExpr; + iValSecond = GetVal(refStrExpr); + if (iVal < 0) { - llVal = cexpression_power(llVal, llValSecond); + iVal = cexpression_power(iVal, iValSecond); break; } - else if ((llVal == 0) && (llValSecond <= 0)) //The information from https://en.cppreference.com/w/cpp/numeric/math/pow says if both input are 0, it can cause errors too. + else if ((iVal == 0) && (iValSecond <= 0)) //The information from https://en.cppreference.com/w/cpp/numeric/math/pow says if both input are 0, it can cause errors too. { g_Log.EventError("Power of zero with zero or negative exponent is undefined.\n"); break; } - llVal = cexpression_power(llVal, llValSecond); + iVal = cexpression_power(iVal, iValSecond); break; } - return llVal; + return iVal; } -llong CExpression::GetVal( lpctstr & pExpr ) +int64 CExpression::GetVal(lpctstr & refStrExpr ) { // This function moves the pointer forward, so you can retrieve the value only once! @@ -1280,58 +1450,58 @@ llong CExpression::GetVal( lpctstr & pExpr ) // { animal_colors 1 no_colors 1 } // weighted range // { red_colors 1 {34 39} 1 } // same (red_colors expands to a range) - if ( pExpr == nullptr ) + if ( refStrExpr == nullptr ) return 0; if (_iGetVal_Reentrant >= 128 ) { - g_Log.EventError( "Deadlock detected while parsing '%s'. Fix the error in your scripts.\n", pExpr ); + g_Log.EventError( "Deadlock detected while parsing '%s'. Fix the error in your scripts.\n", refStrExpr ); return 0; } ++_iGetVal_Reentrant; // Get the first operand value: it may be a number or an expression - llong llVal = GetSingle(pExpr); + int64 iVal = GetSingle(refStrExpr); // Check if there is an operator (mathematical or logical), in that case apply it to the second operand (which we evaluate again with GetSingle). - llVal = GetValMath(llVal, pExpr); + iVal = GetValMath(iVal, refStrExpr); --_iGetVal_Reentrant; - return llVal; + return iVal; } -int CExpression::GetRangeVals(lpctstr & pExpr, int64 * piVals, int iMaxQty, bool bNoWarn) +int CExpression::GetRangeVals(lpctstr & refStrExpr, int64 * piVals, int iMaxQty, bool fNoWarn) { ADDTOCALLSTACK("CExpression::GetRangeVals"); // Get a list of values. - if (pExpr == nullptr) + if (refStrExpr == nullptr) return 0; ASSERT(piVals); int iQty = 0; - while (pExpr[0] != '\0') + while (refStrExpr[0] != '\0') { - if (pExpr[0] == ';') // seperate field - is this used anymore? + if (refStrExpr[0] == ';') // seperate field - is this used anymore? return iQty; - if (pExpr[0] == ',') - ++pExpr; + if (refStrExpr[0] == ',') + ++refStrExpr; - piVals[iQty] = GetSingle(pExpr); + piVals[iQty] = GetSingle(refStrExpr); if (++iQty >= iMaxQty) return iQty; - GETNONWHITESPACE(pExpr); + GETNONWHITESPACE(refStrExpr); // Look for math type operator. - switch (pExpr[0]) + switch (refStrExpr[0]) { case ')': // expression end markers. case '}': case ']': - ++pExpr; // consume this and end. + ++refStrExpr; // consume this and end. return iQty; case '+': @@ -1343,45 +1513,59 @@ int CExpression::GetRangeVals(lpctstr & pExpr, int64 * piVals, int iMaxQty, bool case '>': case '|': case '&': - piVals[iQty - 1] = GetValMath(piVals[iQty - 1], pExpr); + piVals[iQty - 1] = GetValMath(piVals[iQty - 1], refStrExpr); return iQty; } } - if (!bNoWarn) + if (!fNoWarn) g_Log.EventError("Range isn't closed by a '}' character\n"); return iQty; } -int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSubexprData)[32], int iMaxQty) // static +CExpression::PrvBuffersPool::CSubExprStatesArenaPool_t::UniquePtr_t +CExpression::GetConditionalSubexpressions( + lptstr& refStrExpr, + CExpression::PrvBuffersPool::CSubExprStatesArenaPool_t& bufs_arena) { ADDTOCALLSTACK("CExpression::GetConditionalSubexpressions"); // Get the start and end pointers for each logical subexpression (delimited by brackets or by logical operators || and &&) inside a conditional statement (IF/ELIF/ELSEIF and QVAL). // Parse from left to start (like it was always done in Sphere). // Start and end pointers are inclusive (pointed values are valid chars, the end pointer doesn't necessarily point to '\0'). - if (pExpr == nullptr) - return 0; - //ASSERT(pSubexprPos); + // Take a struct which holds a "block" (array) of 'sm_kuiMaxConditionalSubexprsPerExpr' amount of prebuilt CExpression::CScriptSubExprState. + auto pSubexprsArena = bufs_arena.acquireUnique(); + memset(pSubexprsArena.get(), 0, sizeof(CSubExprStatesArena)); - //memset((void*)&pSubexprPos, 0, ARRAY_COUNT(pSubexprPos)); - int iSubexprQty = 0; // number of subexpressions - using SType = SubexprData::Type; - while (pExpr[0] != '\0') + if (refStrExpr == nullptr) + { + DEBUG_ASSERT(false); + return pSubexprsArena; + } + + static constexpr auto s_kuiMaxSubexpressionsPerExpr = CSubExprStatesArena::sm_kuiMaxConditionalSubexprsPerExpr; + using SubexprState_t = CExpression::CScriptSubExprState; + using SubexprType_t = SubexprState_t::Type; + + SubexprState_t* parsingSubexprsStates = pSubexprsArena.get()->m_subexprs; + uint& uiSubexprQty = pSubexprsArena.get()->m_uiQty; // number of subexpressions + DEBUG_ASSERT(uiSubexprQty == 0); + + while (refStrExpr[0] != '\0') { - if (++iSubexprQty >= iMaxQty) + if (++uiSubexprQty >= s_kuiMaxSubexpressionsPerExpr) { - g_Log.EventWarn("Exceeded maximum allowed number of subexpressions (%d). Parsing halted.\n", iMaxQty); - return iSubexprQty; + g_Log.EventWarn("Exceeded maximum allowed number of subexpressions (%u). Parsing halted.\n", s_kuiMaxSubexpressionsPerExpr); + return pSubexprsArena; } - GETNONWHITESPACE(pExpr); - SubexprData& sCurSubexpr = psSubexprData[iSubexprQty - 1]; - tchar ch = pExpr[0]; + GETNONWHITESPACE(refStrExpr); + SubexprState_t& sCurSubexpr = parsingSubexprsStates[uiSubexprQty - 1]; + tchar ch = refStrExpr[0]; // Init the data for the current subexpression and set the position of the first character of the subexpression. - sCurSubexpr = {pExpr, nullptr, SType::None, 0}; + sCurSubexpr = {refStrExpr, nullptr, SubexprType_t::None, 0}; // -- What's an expression and what's a subexpression. // An expression can contain a single statement, a single operation (enclosed, or not, by curly brackets), like: IF or IF == 1. @@ -1405,17 +1589,17 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube { // Remember that i'm interested only in the special case of subexpressions preceded by '!'. // If it's inside the subexpression, it will already be handled correctly. - ptcTopLevelNegation = pExpr; - ++pExpr; - GETNONWHITESPACE(pExpr); - ch = *pExpr; + ptcTopLevelNegation = refStrExpr; + ++refStrExpr; + GETNONWHITESPACE(refStrExpr); + ch = *refStrExpr; } // Helper lambda functions for the next section. - auto findLastClosingBracket = [](lptstr pExpr_) -> lptstr + auto findLastservClosingBracket = [](lptstr pExpr_) -> lptstr { - // Returns a pointer to the last closing bracket in the string. - // If the last character in the string (ignoring comments) is not ')', it means that, if we find a closing bracket, + // Returns a pointer to the last servClosing bracket in the string. + // If the last character in the string (ignoring comments) is not ')', it means that, if we find a servClosing bracket, // it's past other characters, so there's other valid text after the ')'. // Eg: IF (1+2) > 10. The ')' is not at the end of the line, because there's the remaining part of the script. ASSERT(*pExpr_ != '\0'); @@ -1469,11 +1653,11 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube // This ensures that we begin every parsing loop without any open curly bracket. // Start of a expression within curved brackets? - lptstr ptcCurSubexprStart = pExpr; - lptstr ptcTopBracket = (ch == '(') ? pExpr : nullptr; + lptstr ptcCurSubexprStart = refStrExpr; + lptstr ptcTopBracket = (ch == '(') ? refStrExpr : nullptr; // -- Done with preliminar expression analysis. Now look for subexpressions. - lptstr ptcLastClosingBracket = nullptr; // Needs to be preserved in the subexpression parsing. + lptstr ptcLastservClosingBracket = nullptr; // Needs to be preserved in the subexpression parsing. while (true) { // This loop parses a single subexpression. Remember that we checked for a negation prefix like !( ) in the block before. @@ -1485,30 +1669,30 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube // We could have encountered one of the situations below and already found the end of the subexpression, or we could need to find it here. if (sCurSubexpr.ptcEnd == nullptr) { - sCurSubexpr.ptcEnd = pExpr; - if (ptcTopBracket && ptcLastClosingBracket) + sCurSubexpr.ptcEnd = refStrExpr; + if (ptcTopBracket && ptcLastservClosingBracket) { - lptstr ptcLineLastClosingBracket = findLastClosingBracket(ptcCurSubexprStart); - // ptcLastClosingBracket: the last closing bracket found while parsing the subexpression (might not be at the end of the line). - // ptcExprLastClosingBracket: the last closing bracket ')', if any, of the string. The function used does NOT check if that's a valid closing bracket - // (eg. if in the string for every opening bracket there is a closing bracket). - if (iSubexprQty == 1) + lptstr ptcLineLastservClosingBracket = findLastservClosingBracket(ptcCurSubexprStart); + // ptcLastservClosingBracket: the last servClosing bracket found while parsing the subexpression (might not be at the end of the line). + // ptcExprLastservClosingBracket: the last servClosing bracket ')', if any, of the string. The function used does NOT check if that's a valid servClosing bracket + // (eg. if in the string for every opening bracket there is a servClosing bracket). + if (uiSubexprQty == 1) { - if (nullptr == ptcLineLastClosingBracket) + if (nullptr == ptcLineLastservClosingBracket) { - // There are other valid characters after the closing curly bracket, so leave ptcEnd unchanged, to the end of the string. + // There are other valid characters after the servClosing curly bracket, so leave ptcEnd unchanged, to the end of the string. ; } - else if (ptcLastClosingBracket == ptcLineLastClosingBracket) + else if (ptcLastservClosingBracket == ptcLineLastservClosingBracket) { // I'm here because the whole expression is enclosed by parentheses // + 1 because i want to point to the character after the ')', even if it's the string terminator. - sCurSubexpr.ptcEnd = ptcLastClosingBracket + 1; - sCurSubexpr.uiType |= SubexprData::TopParenthesizedExpr; + sCurSubexpr.ptcEnd = ptcLastservClosingBracket + 1; + sCurSubexpr.uiType |= SubexprState_t::TopParenthesizedExpr; } else { - sCurSubexpr.ptcEnd = ptcLastClosingBracket; + sCurSubexpr.ptcEnd = ptcLastservClosingBracket; } } // else: // The starting bracket encloses only a part of the expression @@ -1519,11 +1703,11 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube else if (ch == '(') { - if (ptcCurSubexprStart == pExpr) + if (ptcCurSubexprStart == refStrExpr) { // Start of a subexpression delimited by brackets (it can be preceded by an operator like '!', handled before). - // Now i want only to see where's the matching closing bracket. - sCurSubexpr.ptcStart = pExpr; + // Now i want only to see where's the matching servClosing bracket. + sCurSubexpr.ptcStart = refStrExpr; } else { @@ -1547,36 +1731,36 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube } // Just skip what's enclosed in the subexpression. - ptcLastClosingBracket = skipBracketedSubexpression(pExpr); - if (ptcLastClosingBracket != nullptr) - pExpr = ptcLastClosingBracket; + ptcLastservClosingBracket = skipBracketedSubexpression(refStrExpr); + if (ptcLastservClosingBracket != nullptr) + refStrExpr = ptcLastservClosingBracket; else { g_Log.EventError("Expression started with '(' but isn't closed by a ')' character.\n"); - sCurSubexpr.ptcEnd = pExpr - 1; // Position of the char just before the last ')' of the bracketed subexpression -> this eats away the last closing bracket - return iSubexprQty; + sCurSubexpr.ptcEnd = refStrExpr - 1; // Position of the char just before the last ')' of the bracketed subexpression -> this eats away the last servClosing bracket + return pSubexprsArena; } // Okay, i've eaten the expression in brackets, now fall through the rest of the loop and continue. } - else if ((ch == '|') && (pExpr[1] == '|')) + else if ((ch == '|') && (refStrExpr[1] == '|')) { // Logical two-way OR operator: || if (sCurSubexpr.ptcEnd == nullptr) - sCurSubexpr.ptcEnd = pExpr; - sCurSubexpr.uiType = SType::Or | (sCurSubexpr.uiType & ~SType::None); - pExpr += 2u; // Skip the second char of the operator + sCurSubexpr.ptcEnd = refStrExpr; + sCurSubexpr.uiType = SubexprType_t::Or | (sCurSubexpr.uiType & ~SubexprType_t::None); + refStrExpr += 2u; // Skip the second char of the operator break; // End of subexpr... } - else if ((ch == '&') && (pExpr[1] == '&')) + else if ((ch == '&') && (refStrExpr[1] == '&')) { // Logical two-way AND operator: && if (sCurSubexpr.ptcEnd == nullptr) - sCurSubexpr.ptcEnd = pExpr; - sCurSubexpr.uiType = SType::And | (sCurSubexpr.uiType & ~SType::None); - pExpr += 2u; // Skip the second char of the operator + sCurSubexpr.ptcEnd = refStrExpr; + sCurSubexpr.uiType = SubexprType_t::And | (sCurSubexpr.uiType & ~SubexprType_t::None); + refStrExpr += 2u; // Skip the second char of the operator break; // End of subexpr... } @@ -1587,32 +1771,34 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube if (ch == '<') { // This can be: <, <= or the start of a bracketed expression < > - if (pExpr[1] == '=') + if (refStrExpr[1] == '=') { - sCurSubexpr.uiType = SType::BinaryNonLogical | (sCurSubexpr.uiType & ~SType::None); - pExpr += 1u; + sCurSubexpr.uiType = SubexprType_t::BinaryNonLogical | (sCurSubexpr.uiType & ~SubexprType_t::None); + refStrExpr += 1u; } else { - const ushort prevSubexprType = ((iSubexprQty == 1) ? (ushort)SType::None : psSubexprData[iSubexprQty - 2].uiType); - if ((prevSubexprType & SType::None)) + const ushort prevSubexprType = ((uiSubexprQty == 1) + ? (ushort)SubexprType_t::None + : parsingSubexprsStates[uiSubexprQty - 2].uiType); + if ((prevSubexprType & SubexprType_t::None)) { // This subexpr is not preceded by a two-way operator, so probably i'm an operator: skip me. - sCurSubexpr.uiType = SType::BinaryNonLogical | (sCurSubexpr.uiType & ~SType::None); + sCurSubexpr.uiType = SubexprType_t::BinaryNonLogical | (sCurSubexpr.uiType & ~SubexprType_t::None); // This is not a whole logical subexpression but a single operand, or piece/fragment of the current arithmetic subexpr. } else { // This subexpr is preceded by a two-way operator, so probably i'm not another operator, rather a < > expression. - lptstr pExprSkipped = pExpr; + lptstr pExprSkipped = refStrExpr; Str_SkipEnclosedAngularBrackets(pExprSkipped); - if (pExpr != pExprSkipped) + if (refStrExpr != pExprSkipped) { // I actually have something enclosed in angular brackets. - // The function above moves the pointer after the last closing bracket '>', but we want to point here to it, not the character after. - pExpr = pExprSkipped; - ch = *pExpr; + // The function above moves the pointer after the last servClosing bracket '>', but we want to point here to it, not the character after. + refStrExpr = pExprSkipped; + ch = *refStrExpr; continue; // This allows us to skip the "ch = *(++pExpr);" below, we don't want to advance further the pointer. } } @@ -1621,28 +1807,28 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube } else if (ch == '>') { - if (pExpr[1] == '=') + if (refStrExpr[1] == '=') { - sCurSubexpr.uiType = SType::BinaryNonLogical | (sCurSubexpr.uiType & ~SType::None); - pExpr += 1u; + sCurSubexpr.uiType = SubexprType_t::BinaryNonLogical | (sCurSubexpr.uiType & ~SubexprType_t::None); + refStrExpr += 1u; } else { - sCurSubexpr.uiType = SType::BinaryNonLogical | (sCurSubexpr.uiType & ~SType::None); + sCurSubexpr.uiType = SubexprType_t::BinaryNonLogical | (sCurSubexpr.uiType & ~SubexprType_t::None); } } // End of arithmetic subexpression parsing. } - ch = *(++pExpr); + ch = *(++refStrExpr); } // End of the subexpression while loop } // End of the main while loop // Now that we found the subexpressions, prepare them for their evaluation. lptstr ptcStart, ptcEnd; - for (int i = 0; i < iSubexprQty; ++i) + for (uint i = 0; i < uiSubexprQty; ++i) { - SubexprData& sCurSubexpr = psSubexprData[i]; + SubexprState_t& sCurSubexpr = parsingSubexprsStates[i]; ptcStart = sCurSubexpr.ptcStart; ptcEnd = sCurSubexpr.ptcEnd; @@ -1656,7 +1842,7 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube ) { // We have logical operators inside, so it's a nested subexpression. - sCurSubexpr.uiType |= SType::MaybeNestedSubexpr; + sCurSubexpr.uiType |= SubexprType_t::MaybeNestedSubexpr; break; } } @@ -1676,10 +1862,9 @@ int CExpression::GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSube sCurSubexpr.ptcEnd = ptcEnd; } - return iSubexprQty; + return pSubexprsArena; } - static constexpr int kiRangeMaxArgs = 96; static int GetRangeArgsPos(lpctstr & pExpr, lpctstr (&pArgPos)[kiRangeMaxArgs][2], bool fIgnoreMissingEndBracket) { @@ -1767,7 +1952,7 @@ static int GetRangeArgsPos(lpctstr & pExpr, lpctstr (&pArgPos)[kiRangeMaxArgs][2 return iQty; } -int64 CExpression::GetRangeNumber(lpctstr & pExpr) +int64 CExpression::GetRangeNumber(lpctstr & refStrExpr) { ADDTOCALLSTACK("CExpression::GetRangeNumber"); @@ -1779,7 +1964,7 @@ int64 CExpression::GetRangeNumber(lpctstr & pExpr) // of the elements and then only the element which was randomly chosen. lpctstr pElementsStart[kiRangeMaxArgs][2] {}; - int iQty = GetRangeArgsPos( pExpr, pElementsStart, false ); // number of arguments (not of value-weight couples) + int iQty = GetRangeArgsPos( refStrExpr, pElementsStart, false ); // number of arguments (not of value-weight couples) if (iQty == 0) return 0; @@ -1791,18 +1976,20 @@ int64 CExpression::GetRangeNumber(lpctstr & pExpr) return 0; } - tchar pToParse[THREAD_STRING_LENGTH]; + tchar ptcToParse[THREAD_STRING_LENGTH]; if (iQty == 1) // It's just a simple value { ASSERT(pElementsStart[0] != nullptr); // Copy the value in a new string - const size_t iToParseLen = (pElementsStart[0][1] - pElementsStart[0][0]); - memcpy((void*)pToParse, pElementsStart[0][0], iToParseLen * sizeof(tchar)); - pToParse[iToParseLen] = '\0'; + const size_t uiToParseLen = std::min( + size_t(THREAD_STRING_LENGTH-1), + size_t(pElementsStart[0][1] - pElementsStart[0][0])); + memcpy((void*)ptcToParse, pElementsStart[0][0], uiToParseLen * sizeof(tchar)); + ptcToParse[uiToParseLen] = '\0'; - lptstr pToParseCasted = static_cast(pToParse); + lptstr pToParseCasted = static_cast(ptcToParse); return GetSingle(pToParseCasted); } @@ -1812,28 +1999,32 @@ int64 CExpression::GetRangeNumber(lpctstr & pExpr) ASSERT(pElementsStart[1] != nullptr); // Copy the first element in a new string - size_t iToParseLen = (pElementsStart[0][1] - pElementsStart[0][0]); - memcpy((void*)pToParse, pElementsStart[0][0], iToParseLen * sizeof(tchar)); - pToParse[iToParseLen] = '\0'; + size_t uiToParseLen = std::min( + size_t(THREAD_STRING_LENGTH-1), + size_t(pElementsStart[0][1] - pElementsStart[0][0])); + memcpy((void*)ptcToParse, pElementsStart[0][0], uiToParseLen * sizeof(tchar)); + ptcToParse[uiToParseLen] = '\0'; - lptstr pToParseCasted = static_cast(pToParse); - llong llValFirst = GetSingle(pToParseCasted); + lptstr pToParseCasted = static_cast(ptcToParse); + int64 iValFirst = GetSingle(pToParseCasted); // Copy the second element in a new string - iToParseLen = (pElementsStart[1][1] - pElementsStart[1][0]); - memcpy((void*)pToParse, pElementsStart[1][0], iToParseLen * sizeof(tchar)); - pToParse[iToParseLen] = '\0'; + uiToParseLen = std::min( + size_t(THREAD_STRING_LENGTH-1), + size_t(pElementsStart[1][1] - pElementsStart[1][0])); + memcpy((void*)ptcToParse, pElementsStart[1][0], uiToParseLen * sizeof(tchar)); + ptcToParse[uiToParseLen] = '\0'; - pToParseCasted = static_cast(pToParse); - llong llValSecond = GetSingle(pToParseCasted); + pToParseCasted = static_cast(ptcToParse); + int64 iValSecond = GetSingle(pToParseCasted); - if (llValSecond < llValFirst) // the first value has to be < than the second before passing it to g_Rand.GetLLVal2 + if (iValSecond < iValFirst) // the first value has to be < than the second before passing it to g_Rand.GetLLVal2 { - const llong llValTemp = llValFirst; - llValFirst = llValSecond; - llValSecond = llValTemp; + const int64 iValTemp = iValFirst; + iValFirst = iValSecond; + iValSecond = iValTemp; } - return g_Rand.GetLLVal2(llValFirst, llValSecond); + return g_Rand.GetLLVal2(iValFirst, iValSecond); } // First get the total of the weights @@ -1845,11 +2036,13 @@ int64 CExpression::GetRangeNumber(lpctstr & pExpr) // break; // Shouldn't really happen... // Copy the weight element in a new string - const size_t iToParseLen = (pElementsStart[i][1] - pElementsStart[i][0]); - memcpy((void*)pToParse, pElementsStart[i][0], iToParseLen * sizeof(tchar)); - pToParse[iToParseLen] = '\0'; + const size_t uiToParseLen = std::min( + size_t(THREAD_STRING_LENGTH-1), + size_t(pElementsStart[i][1] - pElementsStart[i][0])); + memcpy((void*)ptcToParse, pElementsStart[i][0], uiToParseLen * sizeof(tchar)); + ptcToParse[uiToParseLen] = '\0'; - lptstr pToParseCasted = static_cast(pToParse); + lptstr pToParseCasted = static_cast(ptcToParse); llWeights[i] = GetSingle(pToParseCasted); // GetSingle changes the pointer value, so i need to work with a copy if ( ! llWeights[i] ) // having a weight of 0 is very strange ! @@ -1858,7 +2051,7 @@ int64 CExpression::GetRangeNumber(lpctstr & pExpr) } // Now roll the dice to see what value to pick - llTotalWeight = g_Rand.GetLLVal(llTotalWeight) + 1; + llTotalWeight = g_Rand.GetLLVal(llTotalWeight) + 1; // Now loop to that value int i = 1; @@ -1871,18 +2064,19 @@ int64 CExpression::GetRangeNumber(lpctstr & pExpr) ASSERT(i < iQty); i -= 1; // pick the value instead of the weight - const size_t iToParseLen = (pElementsStart[i][1] - pElementsStart[i][0]); - // Copy the value element in a new string ASSERT(nullptr != pElementsStart[i][0]); - memcpy((void*)pToParse, pElementsStart[i][0], iToParseLen * sizeof(tchar)); - pToParse[iToParseLen] = '\0'; - - lptstr pToParseCasted = static_cast(pToParse); - return GetSingle(pToParseCasted); + const size_t uiToParseLen = std::min( + size_t(THREAD_STRING_LENGTH-1), + size_t(pElementsStart[i][1] - pElementsStart[i][0])); + memcpy((void*)ptcToParse, pElementsStart[i][0], uiToParseLen * sizeof(tchar)); + ptcToParse[uiToParseLen] = '\0'; + + lptstr ptcToParseCasted = static_cast(ptcToParse); + return GetSingle(ptcToParseCasted); } -CSString CExpression::GetRangeString(lpctstr & pExpr) +CSString CExpression::GetRangeString(lpctstr & refStrExpr) { ADDTOCALLSTACK("CExpression::GetRangeString"); @@ -1893,14 +2087,14 @@ CSString CExpression::GetRangeString(lpctstr & pExpr) // of the elements and then only the element which was randomly chosen. lpctstr pElementsStart[kiRangeMaxArgs][2]{}; - int iQty = GetRangeArgsPos( pExpr, pElementsStart, true ); // number of arguments (not of value-weight couples) + int iQty = GetRangeArgsPos( refStrExpr, pElementsStart, true ); // number of arguments (not of value-weight couples) if (iQty <= 0) return {}; if (iQty == 1) // It's just a simple value { ASSERT(pElementsStart[0] != nullptr); - const int iToParseLen = int(pElementsStart[0][1] - pElementsStart[0][0]); + const int iToParseLen = int(size_t(pElementsStart[0][1] - pElementsStart[0][0])); return CSString(pElementsStart[0][0], iToParseLen - 1); } @@ -1921,7 +2115,7 @@ CSString CExpression::GetRangeString(lpctstr & pExpr) // break; // Shouldn't really happen... // Copy the weight element in a new string - const size_t iToParseLen = (pElementsStart[i][1] - pElementsStart[i][0]); + const size_t iToParseLen = size_t(pElementsStart[i][1] - pElementsStart[i][0]); memcpy((void*)pToParse, pElementsStart[i][0], iToParseLen * sizeof(tchar)); pToParse[iToParseLen] = '\0'; lptstr pToParseCasted = reinterpret_cast(pToParse); @@ -1951,6 +2145,586 @@ CSString CExpression::GetRangeString(lpctstr & pExpr) ASSERT(i < iQty); i -= 1; // pick the value instead of the weight - const int iToParseLen = int(pElementsStart[i][1] - pElementsStart[i][0]); + const int iToParseLen = int(size_t(pElementsStart[i][1] - pElementsStart[i][0])); return CSString(pElementsStart[i][0], iToParseLen); } + +bool CExpression::EvaluateConditionalSingle( + CScriptSubExprState& refSubExprState, CScriptExprContext& refExprContext, + CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc) +{ + ADDTOCALLSTACK("CExpression::EvaluateConditionalSingle"); + + ASSERT(refSubExprState.ptcStart); + ASSERT(refSubExprState.ptcEnd); + ASSERT(refExprContext._pScriptObjI != nullptr); + + using SType = CScriptSubExprState::Type; + bool fVal; + lptstr ptcSubexpr; + + // Evaluate the subexpression body + if (refExprContext._iEvaluate_Conditional_Reentrant >= 16) + { + g_Log.EventError("Exceeding the limit of 16 subexpressions. Further parsing is halted.\n"); + return false; + } + ++ refExprContext._iEvaluate_Conditional_Reentrant; + + // Is this conditional expression is fully enclosed by brackets ? + const bool fFullyEnclosed = (refSubExprState.uiType & SType::TopParenthesizedExpr); + + // Length to copy: include the last valid char (i'm not copying the subsequent char, which can be another char or '\0' + ASSERT(refSubExprState.ptcEnd >= refSubExprState.ptcStart); + size_t len = std::min(Str_TempLength() - 1U, size_t(refSubExprState.ptcEnd - refSubExprState.ptcStart)); + if (len == 0) + { + g_Log.EventError("Empty subexpression. Defaulting its value to false.\n"); + return false; + } + + lptstr ptcParsingStart = refSubExprState.ptcStart; + if (fFullyEnclosed) + { + -- len; // Exclude the servClosing bracket ')'. + ASSERT(len > 0); + + // In this case, we need to start parsing after the opening parenthesis '('; if we start before it and the subexpr is marked with MaybeNestedSubexpr, + // Evaluate_Conditional will again return the same subexpression fully enclosed by parenthesis, and we'll have a deadlock. + // Remember that sdata.uiNonAssociativeOffset is the distance between the open bracket '(' and the non-associative operator (negation operator '!'). + // The string might start with said non-associative operator. + ptcParsingStart += 1; + len -= 1; + } + + ASSERT(len < Str_TempLength()); + ptcSubexpr = Str_GetTemp(); + memcpy(ptcSubexpr, ptcParsingStart, len); + ptcSubexpr[len] = '\0'; + + const bool fNested = (refSubExprState.uiType & SType::MaybeNestedSubexpr); + if (fNested) + { + // Probably this subexpression has other conditional subexpressions inside. + fVal = EvaluateConditionalWhole(ptcSubexpr, refExprContext, pScriptArgs, pSrc); + } + else + { + // If an expression is enclosed by parentheses, ParseScriptText needs to read both the open and the closed one, we cannot + // pass the string starting with the character after the '('. + ParseScriptText(ptcSubexpr, refExprContext, pScriptArgs, pSrc, 0); + fVal = bool(Exp_GetLLVal(ptcSubexpr)); + } + + -- refExprContext._iEvaluate_Conditional_Reentrant; + + + // Apply non-associative operators preceding the subexpression + if (refSubExprState.uiNonAssociativeOffset) + { + ptcSubexpr = refSubExprState.ptcStart - refSubExprState.uiNonAssociativeOffset; + ASSERT(!IsWhitespace(*ptcSubexpr)); + while (const tchar chOperator = *ptcSubexpr) + { + if (chOperator == '!') + fVal = !fVal; + else if (IsWhitespace(chOperator)) + ; // Allowed, skip it + else + break; + ++ptcSubexpr; + } + } + + return fVal; +} + +bool CExpression::EvaluateConditionalWhole(lptstr ptcExpr, CScriptExprContext& refExprContext, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc) +{ + ADDTOCALLSTACK("CExpression::EvaluateConditionalWhole"); + ASSERT(refExprContext._pScriptObjI != nullptr); + ASSERT(pScriptArgs); + + lptstr ptcExprDbg = ptcExpr; + const auto pSubexprArena = GetConditionalSubexpressions(ptcExprDbg, _pBufs.get()->m_poolCScriptExprSubStatesPool); // number of arguments + const uint uiQty = pSubexprArena->m_uiQty; + CScriptSubExprState* parsingSubexprsStates = pSubexprArena->m_subexprs; + + if (uiQty == 0) + return 0; + + using SType = CScriptSubExprState::Type; + + if (uiQty == 1) + { + // We don't have subexpressions, but only a simple expression. + CScriptSubExprState& sCur = parsingSubexprsStates[0]; + ASSERT((sCur.uiType & SType::None) || (sCur.uiType & SType::BinaryNonLogical)); + + const bool fVal = EvaluateConditionalSingle(sCur, refExprContext, pScriptArgs, pSrc); + return fVal; + } + + // We have some subexpressions, connected between them by logical operators. + + bool fWholeExprVal = false; + for (uint i = 0; i < uiQty; ++i) + { + CScriptSubExprState& sCur = parsingSubexprsStates[i]; + ASSERT(sCur.uiType != SType::Unknown); + + if (i == 0) + { + fWholeExprVal = EvaluateConditionalSingle(sCur, refExprContext, pScriptArgs, pSrc); + continue; + } + + CScriptSubExprState& sPrev = parsingSubexprsStates[i - 1]; + if (sPrev.uiType & SType::Or) + { + if (fWholeExprVal) + return true; + + const bool fVal = EvaluateConditionalSingle(sCur, refExprContext, pScriptArgs, pSrc); + fWholeExprVal = fWholeExprVal || fVal; + } + else if (sPrev.uiType & SType::And) + { + if (!fWholeExprVal) + return false; + + const bool fVal = EvaluateConditionalSingle(sCur, refExprContext, pScriptArgs, pSrc); + fWholeExprVal = (i == 1) ? fVal : (fWholeExprVal && fVal); + } + + if (sCur.uiType & SType::None) + { + ASSERT(i == uiQty - 1); // It should be the last subexpression + ASSERT((sPrev.uiType & SType::Or) || (sPrev.uiType & SType::And)); + } + } + + return fWholeExprVal; +} + +static void EvaluateConditionalQval_ParseArg(tchar* ptcSrc, tchar** ptcDest, lpctstr ptcSep) +{ + ASSERT(ptcSep && *ptcSep); + + // Check if we are encountering a a nested QVAL? + tchar* ptcBracketPos = nullptr; + tchar* ptcSepPos = nullptr; + for (tchar* ptcLine = ptcSrc; *ptcLine != '\0';) + { + const tchar ch = *ptcLine; + if ((ch != '<') && (ch != *ptcSep)) + { + ++ptcLine; + continue; + } + + if ((ch == '<') && !ptcBracketPos) + { + tchar* ptcTest = ptcLine + 1; + GETNONWHITESPACE(ptcTest); + if (!strnicmp("QVAL", ptcTest, 4)) + { + ptcBracketPos = ptcLine; + ptcLine = ptcTest + 3; + } + } + else if ((ch == *ptcSep) && !ptcSepPos) + { + ptcSepPos = ptcLine; + } + + if (!ptcBracketPos || !ptcSepPos) + { + ++ptcLine; + continue; + } + + if (ptcSepPos < ptcBracketPos) + { + // The separator we have found is before the nested QVAL. + ptcSrc = ptcSepPos; + break; + } + + // Found a nested QVAL. Skip it, otherwise we'll catch the wrong separator + Str_SkipEnclosedAngularBrackets(ptcBracketPos); + if (ptcBracketPos <= ptcLine) + ++ptcLine; + else + ptcSrc = ptcLine = ptcBracketPos; + + ptcBracketPos = ptcSepPos = nullptr; + } + + Str_Parse(ptcSrc, ptcDest, ptcSep); +} + +bool CExpression::EvaluateConditionalQval( + lpctstr ptcKey, CSString& refStrVal, + CScriptExprContext& pContext, + CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc) +{ + // Do a switch ? type statement + ADDTOCALLSTACK("CExpression::EvaluateConditionalQval"); + ASSERT(pContext._pScriptObjI != nullptr); + + // Do NOT work on the original arguments, it WILL fuck up the original string! + tchar* ptcArgs = Str_GetTemp(); + Str_CopyLimitNull(ptcArgs, ptcKey, Str_TempLength()); + + // We only partially evaluated the QVAL parameters (it's a special case), so we need to parse the expressions (still have angular brackets at this stage) + tchar* ppCmds[3]; + ppCmds[0] = ptcArgs; + + // Get the condition + EvaluateConditionalQval_ParseArg(ppCmds[0], &(ppCmds[1]), "?"); + + // Get the first and second retvals + EvaluateConditionalQval_ParseArg(ppCmds[1], &(ppCmds[2]), ":"); + + // Complete evaluation of the condition + // (do that in another string, since it may overwrite the arguments, which are written later in the same string). + tchar* ptcTemp = Str_GetTemp(); + Str_CopyLimitNull(ptcTemp, ppCmds[0], Str_TempLength()); + ParseScriptText(ptcTemp, pContext, pScriptArgs, pSrc, 0); + const bool fCondition = Exp_GetLLVal(ptcTemp); + + // Get the retval we want + // (we might as well work on the transformed original string, since at this point we don't care if we corrupt other arguments) + ptcTemp = ppCmds[(fCondition ? 1 : 2)]; + ParseScriptText(ptcTemp, pContext, pScriptArgs, pSrc, 0); + + refStrVal = ptcTemp; + if (refStrVal.IsEmpty()) + refStrVal.Clear(); + return true; +} + +// TODO: fully move out the QVAL evaluation from here? +int CExpression::ParseScriptText( + tchar * ptcResponse, + CScriptExprContext& pContext, + CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, + int iFlags) +{ + ADDTOCALLSTACK("CScriptObj::ParseScriptText"); + //ASSERT(ptcResponse[0] != ' '); // Not needed: i remove whitespaces and invalid characters here. + ASSERT(pContext._pScriptObjI != nullptr); + ASSERT(pScriptArgs); + + // Take in a line of text that may have fields that can be replaced with operators here. + // ARGS: + // iFlags & 1: Use HTML-compatible delimiters (%). Inside those, angular brackets are allowed to do nested evaluations. + // iFlags & 2: Don't allow recusive bracket count. + // iFlags & 4: Just parsing a nested QVAL. + // NOTE: + // html will have opening + // RETURN: + // iFlags & 4: Position of the ending bracket/delimiter of a QVAL statement. + // Otherwise: New length of the string. + + // Recursion control variables. + // _iParseScriptText_Reentrant = 0; + // _fParseScriptText_Brackets = false; // Am i evaluating a statement? (Am i inside < > brackets of a statement i am currently evaluating?) + + const bool fNoRecurseBrackets = ((iFlags & 2) != 0); + + // General purpose variables. + const bool fHTML = ((iFlags & 1) != 0); + + // If we are parsing a string from a HTML file, we are using '%' as a delimiter for Sphere expressions, since < > are reserved characters in HTML. + const tchar chBegin = fHTML ? '%' : '<'; + const tchar chEnd = fHTML ? '%' : '>'; + + // Variables used to handle the QVAL special case and do lazy evaluation, instead of fully evaluating the whole string on the first pass. + // As an aftertought QVAL parsing could have been moved into a separate function, but it's intricate enough and it's working, so let's leave as it is...' + enum class QvalStatus { None, Condition, Returns, End } eQval = QvalStatus::None; + int iQvalOpenBrackets = 0; + + size_t uiSubstitutionBegin = 0; + int i = 0; + EXC_TRY("ParseScriptText Main Loop"); + for ( i = 0; ptcResponse[i] != '\0'; ++i) + { + const tchar ch = ptcResponse[i]; + + // Are we looking for the current statement start? + if ( !pContext._fParseScriptText_Brackets) // not in brackets + { + if ( ch == chBegin ) // found the start ! + { + const tchar chNext = ptcResponse[i + 1]; + if ((chNext != '<') && !IsAlnum(chNext)) + continue; // Ignore this, it might be a operator like <= + if ((chBegin == '<') && (chNext == '<')) + { + // Is a << operator? I want a whitespace after the operator. + if ((ptcResponse[i + 2] != '\0') && (ptcResponse[i + 3] != '\0') && IsWhitespace(ptcResponse[i + 2])) + { + lpctstr ptcOpTest = &(ptcResponse[4]); + if (*ptcOpTest != '\0') + { + GETNONWHITESPACE(ptcOpTest); + if (*ptcOpTest != '\0') // There's more text to parse + { + // I guess i have sufficient proof: skip, it's a << operator + i += 2; // Skip < and the whitespace + continue; + } + } + } + } + + // Set the statement start + ASSERT(i >= 0); + uiSubstitutionBegin = (size_t)i; + pContext._fParseScriptText_Brackets = true; + + // Set-up to process special statements: is it a QVAL? + const bool fIsQval = !strnicmp(ptcResponse + i + 1, "QVAL", 4); + if (fIsQval) + { + ++iQvalOpenBrackets; + eQval = QvalStatus::Condition; + + i += 4; + } + } + + continue; + } + + // Are we inside a QVAL and are we searching where its condition end? + if ((ch == '?') && (eQval == QvalStatus::Condition)) + { + // Now we keep the bracket count to find the servClosing bracket for the QVAL statement. + eQval = QvalStatus::Returns; + continue; + } + + // Handle possibly recursive angular brackets (i'm already inside an open bracket) + if (pContext._fParseScriptText_Brackets && (ch == '<')) + { + const tchar chNext = ptcResponse[i + 1]; + if (chNext == '<') + { + // Nested angular brackets? like: <> + lptstr ptcTestNested = ptcResponse + i; + lpctstr ptcTestOrig = ptcTestNested; + Str_SkipEnclosedAngularBrackets(ptcTestNested); + // If i have matching servClosing brackets, so it must be nested angular brackets. + if (ptcTestNested == ptcTestOrig) + { + // Otherwise, it might be the << operator. + + // This shouldn't be necessary... but + /* + // Is a << operator? I want a whitespace after the operator. + if ((ptcResponse[i + 2] != '\0') && (ptcResponse[i + 3] != '\0') && IsWhitespace(ptcResponse[i + 2])) + { + lpctstr ptcOpTest = &(ptcResponse[4]); + if (*ptcOpTest != '\0') + { + GETNONWHITESPACE(ptcOpTest); + if (*ptcOpTest != '\0') // There's more text to parse + { + // I guess i have sufficient proof: skip, it's a << operator + i += 2; // Skip < and the whitespace + pContext._fParseScriptText_Brackets = false; + continue; + } + } + } + // Print an error! I thought it was a << operator but it is not! What's happening here ?! + */ + + ++i; + continue; + } + } + + // Detect nested QVALs + if (eQval != QvalStatus::None) + { + const bool fIsQval = !strnicmp(ptcResponse + i + 1, "QVAL", 4); + if (fIsQval) + { + // Nested QVAL... Needs to be evaluated separately, but we only want to know where it ends. + ASSERT(pContext._fParseScriptText_Brackets == true); + ++ pContext._iParseScriptText_Reentrant; + pContext._fParseScriptText_Brackets = false; + + tchar* ptcRecurseParse = ptcResponse + i; + const int iLen = ParseScriptText(ptcRecurseParse, pContext, pScriptArgs, pSrc, 4); + + pContext._fParseScriptText_Brackets = true; + -- pContext._iParseScriptText_Reentrant; + + i += iLen; + continue; + } + + // At this point, we shouldn't face nested QVALs. + + // I'm inside a QVAL. I can be parsing the condition or the return values. + if (eQval == QvalStatus::Returns) // I'm after its condition (so after '?'), thus i'm parsing the return values. + ++iQvalOpenBrackets; + + // Halt here the evaluation of the stuff inside this open bracket, since i don't want to know what's inside. + continue; + } + + if (pContext._iParseScriptText_Reentrant > 32 ) + { + EXC_SET_BLOCK("recursive brackets limit"); + ASSERT_ALWAYS(pContext._iParseScriptText_Reentrant < 32); + } + + ASSERT(pContext._fParseScriptText_Brackets == true); + ++pContext._iParseScriptText_Reentrant; + pContext._fParseScriptText_Brackets = false; + + // Parse what's inside the open bracket + tchar* ptcRecurseParse = ptcResponse + i; + const int iLen = ParseScriptText(ptcRecurseParse, pContext, pScriptArgs, pSrc, 2 ); + + pContext._fParseScriptText_Brackets = true; + --pContext._iParseScriptText_Reentrant; + + i += iLen; + continue; + } + + // At this point i'm sure that ahead we won't find other open angular brackets, we may find their servClosing one or just plain text. + if ( ch == chEnd ) + { + // servClosing bracket found: should we evaluate what's inside the brackets? + if (eQval != QvalStatus::None) + { + // Special handling for QVAL + if (eQval == QvalStatus::Returns) + { + // I'm after the '?' symbol in QVAL. We are searching for the servClosing bracket. + --iQvalOpenBrackets; + + if (iQvalOpenBrackets == 0) + { + // End of the QVAL statement. + if (iFlags & 04) + { + // I was just checking for the QVAL statement end. + ASSERT(pContext._fParseScriptText_Brackets == true); + pContext._fParseScriptText_Brackets = false; + return i; + } + + // Proceed, so we can execute it (do not 'continue'). + eQval = QvalStatus::End; + } + else + { + // Still inside QVAL, just go ahead. + continue; + } + } + else + { + // I'm before the '?' symbol in QVAL and i'm still searching for it, so we know when the conditional expression ends + continue; // Ignore brackets, i want only the ? symbol. + } + } + + + // If i'm here it means that finally i'm at the end of the statement inside brackets. + pContext._fParseScriptText_Brackets = false; // Close the statement. + + if ((eQval == QvalStatus::End) && (iQvalOpenBrackets != 0)) + { + // I had an incomplete QVAL statement. + g_Log.EventError("QVAL parameters after '?' have unmatched '%c'.\n", ((iQvalOpenBrackets < 0) ? '<' : '>')); + } + + // Complete the evaluation of our string + //-- Write to our temporary sVal the evaluated script + EXC_SET_BLOCK("writeval"); + + ptcResponse[i] = '\0'; // Needed for r_WriteVal + lpctstr ptcKey = ptcResponse + uiSubstitutionBegin + 1; // move past the opening bracket + + CSString sVal; + bool fRes; + if (eQval != QvalStatus::None) + { + // Separate evaluation for QVAL. I may need additional script context for it (pScriptArgs isn't available in r_WriteVal). + EXC_SET_BLOCK("writeval qval"); + ptcKey += 4; // Skip the letters QVAL and pass only the arguments + fRes = EvaluateConditionalQval(ptcKey, sVal, pContext, pScriptArgs, pSrc); + eQval = QvalStatus::None; + } + else + { + // Standard evaluation for everything else + EXC_SET_BLOCK("writeval generic"); + ASSERT(pContext._pScriptObjI); + fRes = pContext._pScriptObjI->r_WriteVal(ptcKey, sVal, pSrc); + if (fRes == false) + { + EXC_SET_BLOCK("writeval args"); + // write the value of functions or triggers variables/objects like ARGO, ARGN1/2/3, LOCALs... + if ((pScriptArgs != nullptr) && pScriptArgs->r_WriteVal(ptcKey, sVal, pSrc)) + fRes = true; + } + } + + + if ( fRes == false ) + { + DEBUG_ERR(( "Can't resolve <%s>.\n", ptcKey )); + // Just in case this really is a <= operator ? + ptcResponse[i] = chEnd; // it's the char we overwrote with '\0' + } + + if (fHTML && sVal.IsEmpty()) + { + sVal = " "; + } + + //-- In the output string, substitute the raw substring with its parsed value + EXC_SET_BLOCK("mem shifting"); + + const size_t uiWriteValLen = sVal.GetLength(); + + // Make room for the obtained value, moving to left (if it's shorter than the scripted statement) or right (if longer) the string characters after it. + tchar* ptcDest = ptcResponse + uiSubstitutionBegin + uiWriteValLen; // + iWriteValLen because we need to leave the space for the replacing keyword + const tchar * const ptcLeftover = ptcResponse + i + 1; // End of the statement we just evaluated + const size_t uiLeftoverLen = strlen(ptcLeftover) + 1; + memmove(ptcDest, ptcLeftover, uiLeftoverLen); + + // Insert the obtained value in the room we created. + ptcDest = ptcResponse + uiSubstitutionBegin; + memcpy(ptcDest, sVal.GetBuffer(), uiWriteValLen); + + // This can be negative. + i = (int)(uiSubstitutionBegin + uiWriteValLen) - 1; + + if (fNoRecurseBrackets) // just do this one then bail out. + { + pContext._fParseScriptText_Brackets = false; + return i; + } + } + } + EXC_CATCH; + + EXC_DEBUG_START; + g_Log.EventDebug("response '%s' source addr '0%p' flags '%d' args '%p'\n", ptcResponse, static_cast(pSrc), iFlags, static_cast(pScriptArgs.get())); + EXC_DEBUG_END; + + pContext._fParseScriptText_Brackets = false; + return i; +} diff --git a/src/common/CExpression.h b/src/common/CExpression.h index 759142318..b6e44a256 100644 --- a/src/common/CExpression.h +++ b/src/common/CExpression.h @@ -10,8 +10,12 @@ #ifndef _INC_CEXPRSSION_H #define _INC_CEXPRSSION_H +#include "sphere_library/CSAssoc.h" +#include "sphere_library/sobjpool.h" +#include "CScriptParserBufs.h" #include "CVarDefMap.h" #include "ListDefContMap.h" +#include #undef ISWHITESPACE @@ -55,6 +59,16 @@ inline bool IsWhitespace(const T ch) noexcept { #define VARDEF_FLOAT_MAXBUFFERSIZE 82 +struct CScriptExprContext +{ + CScriptObj *_pScriptObjI; + + // Recursion counters and state variables + short _iEvaluate_Conditional_Reentrant{}; + short _iParseScriptText_Reentrant{}; + bool _fParseScriptText_Brackets{}; +}; + enum DEFMSG_TYPE { #define MSG(a,b) DEFMSG_##a, @@ -122,69 +136,137 @@ static lpctstr constexpr sm_IntrinsicFunctions[INTRINSIC_QTY+1] = nullptr }; -struct SubexprData + +enum SKILL_TYPE : int; + +struct CExprGlobals { - lptstr ptcStart, ptcEnd; - enum Type : ushort - { - Unknown = 0, - // Powers of two - MaybeNestedSubexpr = 0x1 << 0, // 001 - TopParenthesizedExpr = 0x1 << 1, // 002 - None = 0x1 << 2, // 004 - BinaryNonLogical = 0x1 << 3, // 008 - And = 0x1 << 4, // 010 - Or = 0x1 << 5 // 020 - }; - ushort uiType; - ushort uiNonAssociativeOffset; // How much bytes/characters before the start is (if any) the first non-associative operator preceding the subexpression. +#ifdef MT_ENGINES + MT_CMUTEX_DEF; +#endif + static const char *m_sClassName; + + CVarDefMap m_VarResDefs; // Defined variables in sorted order (RESDEF/RESDEF0). + CVarDefMap m_VarDefs; // Defined variables in sorted order (DEF/DEF0). + CVarDefMap m_VarGlobals; // Global variables (VAR/VAR0) + CListDefMap m_ListGlobals; // Global lists + CListDefMap m_ListInternals; // Internal lists + +private: + friend class CServerConfig; + friend class CScriptObj; // For DEFMSG.* + + // Defined default messages (DEFMESSAGEs) + static constexpr ushort m_kiDefmsgMaxLen = 128; + static tchar sm_szDefMessages[DEFMSG_QTY][m_kiDefmsgMaxLen]; // like: "You put %s to %s" + static lpctstr const sm_szDefMsgNames[DEFMSG_QTY]; // like: "put_it" + + static constexpr ushort m_kiSkillTitlesQty = 12; + std::array m_SkillTitles_Ninjitsu; + std::array m_SkillTitles_Bushido; + std::array m_SkillTitles_Generic; + +public: + CExprGlobals(); + ~CExprGlobals() = default; + + void UpdateDefMsgDependentData(); + + lpctstr SkillTitle(SKILL_TYPE skill, uint uiVal) const; }; -extern class CExpression +extern sl::GuardedAccess g_ExprGlobals; // Declared in spheresvr.cpp + + +// Main SphereScript parsing utilities +class CExpression { - short _iGetVal_Reentrant; + struct CScriptSubExprState + { + lptstr ptcStart, ptcEnd; + enum Type : ushort + { + Unknown = 0, + // Powers of two + MaybeNestedSubexpr = 0x1 << 0, // 001 + TopParenthesizedExpr = 0x1 << 1, // 002 + None = 0x1 << 2, // 004 + BinaryNonLogical = 0x1 << 3, // 008 + And = 0x1 << 4, // 010 + Or = 0x1 << 5 // 020 + }; + ushort uiType; + ushort uiNonAssociativeOffset; // How much bytes/characters before the start is (if any) the first non-associative operator preceding the subexpression. + }; + + struct CSubExprStatesArena + { + static constexpr uint sm_kuiMaxConditionalSubexprsPerExpr = 32; + + CScriptSubExprState m_subexprs[sm_kuiMaxConditionalSubexprsPerExpr]; + uint m_uiQty; + }; + + struct PrvBuffersPool + { + // A pool of arenas. + static constexpr uint sm_subexpr_pool_size = 1'000; + static constexpr bool sm_allow_fallback_objects = false; + using CSubExprStatesArenaPool_t = sl::ObjectPool; + CSubExprStatesArenaPool_t m_poolCScriptExprSubStatesPool; + }; + + std::unique_ptr _pBufs; + int _iGetVal_Reentrant; public: static const char *m_sClassName; - CVarDefMap m_VarResDefs; // Defined variables in sorted order (RESDEF/RESDEF0). - CVarDefMap m_VarDefs; // Defined variables in sorted order (DEF/DEF0). - CVarDefMap m_VarGlobals; // Global variables (VAR/VAR0) - CListDefMap m_ListGlobals; // Global lists - CListDefMap m_ListInternals; // Internal lists - - // defined default messages -#define DEFMSG_MAX_LEN 128 - static tchar sm_szMessages[DEFMSG_QTY][DEFMSG_MAX_LEN]; // like: "You put %s to %s" - static lpctstr const sm_szMsgNames[DEFMSG_QTY]; // like: "put_it" -public: // Evaluate using the stuff we know. - llong GetSingle(lpctstr & pExpr); - - llong GetValMath(llong llVal, lpctstr & pExpr); - llong GetVal(lpctstr & pExpr); + int64 GetSingle(lpctstr & refStrExpr); - int GetRangeVals(lpctstr& pExpr, int64* piVals, int iMaxQty, bool bNoWarn = false); - int64 GetRangeNumber(lpctstr& pExpr); // Evaluate a { } range - CSString GetRangeString(lpctstr& pExpr); // STRRANDRANGE + int64 GetValMath(int64 iVal, lpctstr & refStrExpr); + int64 GetVal(lpctstr & refStrExpr); - static int GetConditionalSubexpressions(lptstr& pExpr, SubexprData(&psSubexprData)[32], int iMaxQty); + int GetRangeVals(lpctstr& refStrExpr, int64* piVals, int iMaxQty, bool fNoWarn = false); + int64 GetRangeNumber(lpctstr& refStrExpr); // Evaluate a { } range + CSString GetRangeString(lpctstr& refStrExpr); // STRRANDRANGE // Strict G++ Prototyping produces an error when not casting char*& to const char*& // So this is a rather lazy and const-UNsafe workaround - inline llong GetSingle(lptstr &pArgs) { - return GetSingle(const_cast(pArgs)); + inline int64 GetSingle(lptstr &refArgs) { + return GetSingle(const_cast(refArgs)); } - inline llong GetVal(lptstr& pArgs) { - return GetVal(const_cast(pArgs)); + inline int64 GetVal(lptstr& refArgs) { + return GetVal(const_cast(refArgs)); } - inline int GetRangeVals(lptstr &pExpr, int64 * piVals, int iMaxQty, bool bNoWarn = false) { - return GetRangeVals(const_cast(pExpr), piVals, iMaxQty, bNoWarn); + inline int GetRangeVals(lptstr &refStrExpr, int64 * piVals, int iMaxQty, bool fNoWarn = false) { + return GetRangeVals(const_cast(refStrExpr), piVals, iMaxQty, fNoWarn); } - inline int64 GetRangeNumber(lptstr &pArgs) { - return GetRangeNumber(const_cast(pArgs)); + inline int64 GetRangeNumber(lptstr &refStrArgs) { + return GetRangeNumber(const_cast(refStrArgs)); } + /* + * @brief Do the first-level parsing of a script line and eventually replace requested values got by r_WriteVal. + */ + int ParseScriptText( tchar * pszResponse, CScriptExprContext& refExprContext, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, int iFlags = 0 ); + + [[nodiscard]] + bool EvaluateConditionalWhole(lptstr ptcExpression, CScriptExprContext& refExprContext, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); + +private: + // Arguments inside conditional statements: IF, ELIF, ELSEIF + [[nodiscard]] + bool EvaluateConditionalSingle(CScriptSubExprState& refSubExprState, CScriptExprContext& refExprContext, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); + + [[nodiscard]] + bool EvaluateConditionalQval(lpctstr ptcKey, CSString& refStrVal, CScriptExprContext& refContext, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); + + [[nodiscard]] + PrvBuffersPool::CSubExprStatesArenaPool_t::UniquePtr_t + GetConditionalSubexpressions(lptstr& refStrExpr, PrvBuffersPool::CSubExprStatesArenaPool_t& bufs_arena); + public: CExpression() noexcept; @@ -192,7 +274,11 @@ extern class CExpression CExpression(const CExpression& copy) = delete; CExpression& operator=(const CExpression& other) = delete; -} g_Exp; + + // One per thread + [[nodiscard]] + static CExpression& GetExprParser(); +}; uint GetIdentifierString( tchar * szTag, lpctstr pszArgs ); @@ -273,33 +359,33 @@ int Calc_GetLog2( uint iVal ); int Calc_GetSCurve( int iValDiff, int iVariance ); int Calc_GetBellCurve( int iValDiff, int iVariance ); -#define Exp_GetSingle( pa ) static_cast (g_Exp.GetSingle( pa )) -#define Exp_GetUSingle( pa ) static_cast (g_Exp.GetSingle( pa )) -#define Exp_GetLLSingle( pa ) g_Exp.GetSingle( pa ) -#define Exp_GetDWSingle( pa ) static_cast (g_Exp.GetSingle( pa )) - -#define Exp_GetRange( pa ) static_cast (g_Exp.GetRangeNumber( pa )) -#define Exp_GetLLRange( pa ) g_Exp.GetRangeNumber( pa ) - -#define Exp_GetCVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetUCVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetSVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetUSVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetUVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetLLVal( pa ) g_Exp.GetVal( pa ) -#define Exp_GetULLVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetBVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetWVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetDWVal( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_Get8Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_Get16Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_Get32Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_Get64Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetU8Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetU16Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetU32Val( pa ) static_cast (g_Exp.GetVal( pa )) -#define Exp_GetU64Val( pa ) static_cast (g_Exp.GetVal( pa )) +#define Exp_GetSingle( pa ) static_cast (CExpression::GetExprParser().GetSingle( pa )) +#define Exp_GetUSingle( pa ) static_cast (CExpression::GetExprParser().GetSingle( pa )) +#define Exp_GetLLSingle( pa ) CExpression::GetExprParser().GetSingle( pa ) +#define Exp_GetDWSingle( pa ) static_cast (CExpression::GetExprParser().GetSingle( pa )) + +#define Exp_GetRange( pa ) static_cast (CExpression::GetExprParser().GetRangeNumber( pa )) +#define Exp_GetLLRange( pa ) CExpression::GetExprParser().GetRangeNumber( pa ) + +#define Exp_GetCVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetUCVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetSVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetUSVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetUVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetLLVal( pa ) CExpression::GetExprParser().GetVal( pa ) +#define Exp_GetULLVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetBVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetWVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetDWVal( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_Get8Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_Get16Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_Get32Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_Get64Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetU8Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetU16Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetU32Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) +#define Exp_GetU64Val( pa ) static_cast (CExpression::GetExprParser().GetVal( pa )) #if INTPTR_MAX == INT32_MAX #define Exp_GetSTVal Exp_GetU32Val diff --git a/src/common/CFloatMath.cpp b/src/common/CFloatMath.cpp index 8b0238ce6..60b90b567 100644 --- a/src/common/CFloatMath.cpp +++ b/src/common/CFloatMath.cpp @@ -5,75 +5,75 @@ #include "../game/CServerConfig.h" #include "../sphere/threads.h" #include "sphere_library/CSRand.h" -#include "CExpression.h" +//#include "CExpression.h" // included in the precompiled header #include "CLog.h" #include "CFloatMath.h" -CSString CFloatMath::FloatMath(lpctstr& Expr) +CSString CFloatMath::FloatMath(lpctstr& ptcRefExpr) { ADDTOCALLSTACK("CFloatMath::FloatMath"); char szReal[VARDEF_FLOAT_MAXBUFFERSIZE]; - snprintf(szReal, VARDEF_FLOAT_MAXBUFFERSIZE, "%f", MakeFloatMath(Expr)); + snprintf(szReal, VARDEF_FLOAT_MAXBUFFERSIZE, "%f", MakeFloatMath(ptcRefExpr)); return CSString(szReal); } static thread_local short int _iReentrant_Count = 0; -realtype CFloatMath::MakeFloatMath( lpctstr & Expr ) +realtype CFloatMath::MakeFloatMath(lpctstr & ptcRefExpr ) { ADDTOCALLSTACK("CFloatMath::MakeFloatMath"); - if ( ! Expr ) + if ( ! ptcRefExpr ) return 0; - GETNONWHITESPACE( Expr ); + GETNONWHITESPACE( ptcRefExpr ); ++_iReentrant_Count; if ( _iReentrant_Count > 128 ) { - DEBUG_WARN(( "Deadlock detected while parsing '%s'. Fix the error in your scripts.\n", Expr )); + DEBUG_WARN(( "Deadlock detected while parsing '%s'. Fix the error in your scripts.\n", ptcRefExpr )); --_iReentrant_Count; return 0; } //DEBUG_ERR(("Expr: '%s' GetSingle(Expr) '%f' GetValMath(GetSingle(Expr), Expr) '%f'\n",Expr,GetSingle(Expr),GetValMath(GetSingle(Expr), Expr))); - realtype dVal = GetValMath(GetSingle(Expr), Expr); + realtype dVal = GetValMath(GetSingle(ptcRefExpr), ptcRefExpr); --_iReentrant_Count; return dVal; } -realtype CFloatMath::GetValMath( realtype dVal, lpctstr & pExpr ) +realtype CFloatMath::GetValMath(realtype dVal, lpctstr & ptcRefExpr ) { ADDTOCALLSTACK("CFloatMath::GetValMath"); //DEBUG_ERR(("GetValMath dVal %f pExpr %s\n",dVal,pExpr)); - GETNONWHITESPACE(pExpr); + GETNONWHITESPACE(ptcRefExpr); // Look for math type operator. - switch ( pExpr[0] ) + switch ( ptcRefExpr[0] ) { case '\0': break; case ')': // expression end markers. case '}': case ']': - ++pExpr; // consume this. + ++ptcRefExpr; // consume this. break; case '+': - ++pExpr; - dVal += MakeFloatMath( pExpr ); + ++ptcRefExpr; + dVal += MakeFloatMath( ptcRefExpr ); break; case '-': - ++pExpr; - dVal -= MakeFloatMath( pExpr ); + ++ptcRefExpr; + dVal -= MakeFloatMath( ptcRefExpr ); break; case '*': - ++pExpr; - dVal *= MakeFloatMath( pExpr ); + ++ptcRefExpr; + dVal *= MakeFloatMath( ptcRefExpr ); break; case '/': - ++pExpr; + ++ptcRefExpr; { - realtype dTempVal = MakeFloatMath( pExpr ); + realtype dTempVal = MakeFloatMath( ptcRefExpr ); if ( ! dTempVal ) { g_Log.EventError("Evaluating float math: Divide by 0\n"); @@ -83,21 +83,21 @@ realtype CFloatMath::GetValMath( realtype dVal, lpctstr & pExpr ) } break; case '!': - ++pExpr; - if ( pExpr[0] != '=' ) + ++ptcRefExpr; + if ( ptcRefExpr[0] != '=' ) break; // boolean ! is handled as a single expresion. - ++pExpr; - dVal = ( dVal != MakeFloatMath( pExpr )); + ++ptcRefExpr; + dVal = ( dVal != MakeFloatMath( ptcRefExpr )); break; case '=': // boolean - while ( pExpr[0] == '=' ) - ++pExpr; - dVal = ( dVal == MakeFloatMath( pExpr )); + while ( ptcRefExpr[0] == '=' ) + ++ptcRefExpr; + dVal = ( dVal == MakeFloatMath( ptcRefExpr )); break; case '@': - ++pExpr; + ++ptcRefExpr; { - realtype dTempVal = MakeFloatMath( pExpr ); + realtype dTempVal = MakeFloatMath( ptcRefExpr ); if ( (dVal == 0) && (dTempVal <= 0) ) { DEBUG_ERR(( "Float_MakeFloatMath: Power of zero with zero or negative exponent is undefined\n" )); @@ -109,81 +109,81 @@ realtype CFloatMath::GetValMath( realtype dVal, lpctstr & pExpr ) break; //Following operations are not allowed with Double case '|': - ++pExpr; - if ( pExpr[0] == '|' ) // boolean ? + ++ptcRefExpr; + if ( ptcRefExpr[0] == '|' ) // boolean ? { - ++pExpr; - dVal = ( MakeFloatMath( pExpr ) || dVal ); + ++ptcRefExpr; + dVal = ( MakeFloatMath( ptcRefExpr ) || dVal ); } else // bitwise DEBUG_ERR(("Operator '%s' is not allowed with floats.\n","|")); break; case '&': - ++pExpr; - if ( pExpr[0] == '&' ) // boolean ? + ++ptcRefExpr; + if ( ptcRefExpr[0] == '&' ) // boolean ? { - ++pExpr; - dVal = ( MakeFloatMath( pExpr ) && dVal ); // tricky stuff here. logical ops must come first or possibly not get processed. + ++ptcRefExpr; + dVal = ( MakeFloatMath( ptcRefExpr ) && dVal ); // tricky stuff here. logical ops must come first or possibly not get processed. } else // bitwise DEBUG_ERR(("Operator '%s' is not allowed with floats.\n","&")); break; case '%': - ++pExpr; + ++ptcRefExpr; DEBUG_ERR(("Operator '%s' is not allowed with floats.\n","%")); break; case '^': - ++pExpr; + ++ptcRefExpr; DEBUG_ERR(("Operator '%s' is not allowed with floats.\n","^")); break; case '>': // boolean - ++pExpr; - if ( pExpr[0] == '=' ) // boolean ? + ++ptcRefExpr; + if ( ptcRefExpr[0] == '=' ) // boolean ? { - ++pExpr; - dVal = ( dVal >= MakeFloatMath( pExpr )); + ++ptcRefExpr; + dVal = ( dVal >= MakeFloatMath( ptcRefExpr )); } - else if ( pExpr[0] == '>' ) // shift + else if ( ptcRefExpr[0] == '>' ) // shift { - ++pExpr; + ++ptcRefExpr; DEBUG_ERR(("Operator '%s' is not allowed with floats.\n",">>")); } else { - dVal = ( dVal > MakeFloatMath( pExpr )); + dVal = ( dVal > MakeFloatMath( ptcRefExpr )); } break; case '<': // boolean - ++pExpr; - if ( pExpr[0] == '=' ) // boolean ? + ++ptcRefExpr; + if ( ptcRefExpr[0] == '=' ) // boolean ? { - ++pExpr; - dVal = ( dVal <= MakeFloatMath( pExpr )); + ++ptcRefExpr; + dVal = ( dVal <= MakeFloatMath( ptcRefExpr )); } - else if ( pExpr[0] == '<' ) // shift + else if ( ptcRefExpr[0] == '<' ) // shift { - ++pExpr; + ++ptcRefExpr; DEBUG_ERR(("Operator '%s' is not allowed with floats.\n","<<")); } else { - dVal = ( dVal < MakeFloatMath( pExpr )); + dVal = ( dVal < MakeFloatMath( ptcRefExpr )); } break; } return dVal; } -realtype CFloatMath::GetSingle( lpctstr & pArgs ) +realtype CFloatMath::GetSingle( lpctstr & ptcRefArgs ) { ADDTOCALLSTACK("CFloatMath::GetSingle"); - //DEBUG_ERR(("GetSingle pArgs %s\n",pArgs)); - GETNONWHITESPACE( pArgs ); - const size_t uiArgsCopySize = strlen(pArgs) + 1; - char * pArgsCopy = new char[uiArgsCopySize]; - Str_CopyLimitNull(pArgsCopy, pArgs, uiArgsCopySize); + //DEBUG_ERR(("GetSingle ptcRefArgs %s\n",ptcRefArgs)); + GETNONWHITESPACE( ptcRefArgs ); + const size_t uiArgsCopySize = strlen(ptcRefArgs) + 1; + char * ptcRefArgsCopy = new char[uiArgsCopySize]; + Str_CopyLimitNull(ptcRefArgsCopy, ptcRefArgs, uiArgsCopySize); /*bool IsNum = true; // Old Ellessar's code without support for negative numbers - for( char ch = tolower(*pArgs); ch; ch = tolower(*(++pArgs)) ) + for( char ch = tolower(*ptcRefArgs); ch; ch = tolower(*(++ptcRefArgs)) ) { if (( IsDigit( ch ) ) || ( ch == '.' ) || ( ch == ',' )) continue; @@ -195,7 +195,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) break; }*/ bool IsNum = false; - for (tchar ch = static_cast(tolower(*pArgs)); ch; ch = static_cast(tolower(*(++pArgs)))) + for (tchar ch = static_cast(tolower(*ptcRefArgs)); ch; ch = static_cast(tolower(*(++ptcRefArgs)))) { if (( IsDigit( ch ) ) || ( ch == '.' ) || ( ch == ',' )) { @@ -213,53 +213,53 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) if ( IsNum ) { char * pEnd; - realtype ret = strtod(pArgsCopy,&pEnd); - //DEBUG_ERR(("IsNum: '%d' pArgsCopy '%s' Ret: '%f'\n",IsNum,pArgsCopy,strtod(pArgsCopy,&pEnd))); - delete[] pArgsCopy; + realtype ret = strtod(ptcRefArgsCopy,&pEnd); + //DEBUG_ERR(("IsNum: '%d' ptcRefArgsCopy '%s' Ret: '%f'\n",IsNum,ptcRefArgsCopy,strtod(ptcRefArgsCopy,&pEnd))); + delete[] ptcRefArgsCopy; return( ret ); } - delete[] pArgsCopy; - switch ( pArgs[0] ) + delete[] ptcRefArgsCopy; + switch ( ptcRefArgs[0] ) { case '{': - // ++pArgs; - // return( GetRangeNumber( pArgs )); + // ++ptcRefArgs; + // return( GetRangeNumber( ptcRefArgs )); case '[': case '(': // Parse out a sub expression. - ++pArgs; - return( MakeFloatMath( pArgs )); + ++ptcRefArgs; + return( MakeFloatMath( ptcRefArgs )); case '+': - ++pArgs; + ++ptcRefArgs; break; case '-': - ++pArgs; - return( -GetSingle( pArgs )); + ++ptcRefArgs; + return( -GetSingle( ptcRefArgs )); case '~': // Bitwise not. - ++pArgs; + ++ptcRefArgs; DEBUG_ERR(("Operator '~' is not allowed with floats.\n")); return 0; case '!': // boolean not. - ++pArgs; - if ( pArgs[0] == '=' ) // odd condition such as (!=x) which is always true of course. + ++ptcRefArgs; + if ( ptcRefArgs[0] == '=' ) // odd condition such as (!=x) which is always true of course. { - ++pArgs; // so just skip it. and compare it to 0 - return( GetSingle( pArgs )); + ++ptcRefArgs; // so just skip it. and compare it to 0 + return( GetSingle( ptcRefArgs )); } - return( !GetSingle( pArgs )); + return( !GetSingle( ptcRefArgs )); case ';': // seperate field. case ',': // seperate field. case '\0': return 0; } - INTRINSIC_TYPE iIntrinsic = (INTRINSIC_TYPE) FindTableHeadSorted( pArgs, sm_IntrinsicFunctions, ARRAY_COUNT(sm_IntrinsicFunctions)-1 ); + INTRINSIC_TYPE iIntrinsic = (INTRINSIC_TYPE) FindTableHeadSorted( ptcRefArgs, sm_IntrinsicFunctions, ARRAY_COUNT(sm_IntrinsicFunctions)-1 ); if ( iIntrinsic >= 0 ) { size_t iLen = strlen(sm_IntrinsicFunctions[iIntrinsic]); - if ( strchr("( ", pArgs[iLen]) ) + if ( strchr("( ", ptcRefArgs[iLen]) ) { - pArgs += (iLen + 1); - tchar * pArgsNext; - Str_Parse( const_cast(pArgs), &(pArgsNext), ")" ); + ptcRefArgs += (iLen + 1); + tchar * ptcRefArgsNext; + Str_Parse( const_cast(ptcRefArgs), &(ptcRefArgsNext), ")" ); tchar * ppCmd[5]; realtype rResult; @@ -271,10 +271,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) { case INTRINSIC_ID: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - rResult = ResGetIndex(int(MakeFloatMath(pArgs))); // ResGetIndex + rResult = ResGetIndex(int(MakeFloatMath(ptcRefArgs))); // ResGetIndex } else { @@ -286,7 +286,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_MAX: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 0; else @@ -302,7 +302,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_MIN: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 0; else @@ -318,7 +318,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_LOGARITHM: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 3, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 3, "," ); if ( iCount < 1 ) { rResult = 0; @@ -362,10 +362,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_NAPIERPOW: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - rResult = exp(MakeFloatMath(pArgs)); + rResult = exp(MakeFloatMath(ptcRefArgs)); } else { @@ -379,9 +379,9 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) { iCount = 0; - if ( *pArgs ) + if ( *ptcRefArgs ) { - realtype dTosquare = MakeFloatMath(pArgs); + realtype dTosquare = MakeFloatMath(ptcRefArgs); if (dTosquare >= 0) { @@ -405,10 +405,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_SIN: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - realtype dArgument = MakeFloatMath(pArgs); + realtype dArgument = MakeFloatMath(ptcRefArgs); rResult = sin(dArgument * M_PI / 180); } else @@ -421,10 +421,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_ARCSIN: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - realtype dArgument = MakeFloatMath(pArgs); + realtype dArgument = MakeFloatMath(ptcRefArgs); rResult = asin(dArgument) * 180 / M_PI; } else @@ -437,10 +437,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_COS: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - realtype dArgument = MakeFloatMath(pArgs); + realtype dArgument = MakeFloatMath(ptcRefArgs); rResult = cos(dArgument * M_PI / 180); } else @@ -453,10 +453,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_ARCCOS: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - realtype dArgument = MakeFloatMath(pArgs); + realtype dArgument = MakeFloatMath(ptcRefArgs); rResult = acos(dArgument) * 180 / M_PI; } else @@ -469,10 +469,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_TAN: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - realtype dArgument = MakeFloatMath(pArgs); + realtype dArgument = MakeFloatMath(ptcRefArgs); rResult = tan(dArgument * M_PI / 180); } else @@ -485,10 +485,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_ARCTAN: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - realtype dArgument = MakeFloatMath(pArgs); + realtype dArgument = MakeFloatMath(ptcRefArgs); rResult = atan(dArgument) * 180 / M_PI; } else @@ -502,7 +502,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_StrIndexOf: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 3, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 3, "," ); if ( iCount < 2 ) rResult = -1; else @@ -514,7 +514,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_STRMATCH: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 0; else @@ -523,7 +523,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_STRREGEX: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 0; else @@ -539,7 +539,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_RANDBELL: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 0; else @@ -552,10 +552,10 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_STRASCII: { - if ( *pArgs ) + if ( *ptcRefArgs ) { iCount = 1; - rResult = pArgs[0]; + rResult = ptcRefArgs[0]; } else { @@ -566,7 +566,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_RAND: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount <= 0 ) rResult = 0; else @@ -586,7 +586,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_STRCMP: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 1; else @@ -595,7 +595,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_STRCMPI: { - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 2, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 2, "," ); if ( iCount < 2 ) rResult = 1; else @@ -605,26 +605,26 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) case INTRINSIC_STRLEN: { iCount = 1; - rResult = (realtype)strlen(pArgs); + rResult = (realtype)strlen(ptcRefArgs); } break; case INTRINSIC_ISOBSCENE: { iCount = 1; - rResult = g_Cfg.IsObscene( pArgs ); + rResult = g_Cfg.IsObscene( ptcRefArgs ); } break; case INTRINSIC_ISNUMBER: { iCount = 1; - SKIP_NONNUM( pArgs ); - rResult = IsStrNumeric( pArgs ); + SKIP_NONNUM( ptcRefArgs ); + rResult = IsStrNumeric( ptcRefArgs ); } break; case INTRINSIC_QVAL: { // Here is handled the intrinsic QVAL form: QVAL(VALUE1,VALUE2,LESSTHAN,EQUAL,GREATERTHAN) - iCount = Str_ParseCmds( const_cast(pArgs), ppCmd, 5, "," ); + iCount = Str_ParseCmds( const_cast(ptcRefArgs), ppCmd, 5, "," ); if (iCount < 3) { rResult = 0; @@ -659,7 +659,7 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) break; } - pArgs = pArgsNext; + ptcRefArgs = ptcRefArgsNext; if ( iCount <= 0 ) { @@ -672,13 +672,18 @@ realtype CFloatMath::GetSingle( lpctstr & pArgs ) } } } - llong llVal; - if ( g_Exp.m_VarGlobals.GetParseVal( pArgs, &llVal ) ) - return (int)llVal; - if ( g_Exp.m_VarResDefs.GetParseVal( pArgs, &llVal ) ) - return (int)llVal; - if ( g_Exp.m_VarDefs.GetParseVal( pArgs, &llVal ) ) - return (int)llVal; + + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + int64 iVal; + // VAR. + if ( gReader->m_VarGlobals.GetParseVal( ptcRefArgs, &iVal ) ) + return static_cast(static_cast(iVal)); + // RESDEF. + if ( gReader->m_VarResDefs.GetParseVal( ptcRefArgs, &iVal ) ) + return static_cast(static_cast(iVal)); + // DEF. + if ( gReader->m_VarDefs.GetParseVal( ptcRefArgs, &iVal ) ) + return static_cast(static_cast(iVal)); return 0; } diff --git a/src/common/CFloatMath.h b/src/common/CFloatMath.h index fef632401..13635aabe 100644 --- a/src/common/CFloatMath.h +++ b/src/common/CFloatMath.h @@ -12,18 +12,18 @@ struct CFloatMath { // Expression Parsing - static CSString FloatMath( lpctstr & Expr ); + static CSString FloatMath( lpctstr & ptcRefExpr ); private: - static realtype MakeFloatMath( lpctstr & Expr ); + static realtype MakeFloatMath( lpctstr & ptcRefExpr ); static realtype GetRandVal( realtype dQty ); static realtype GetRandVal2( realtype dMin, realtype dMax ); //Does not work as it should, would be too slow, and nobody needs that /*static realtype GetRangeNumber( lpctstr & pExpr ); static int GetRangeVals( lpctstr & pExpr, realtype * piVals, short int iMaxQty );*/ - static realtype GetValMath( realtype dVal, lpctstr & pExpr ); - static realtype GetSingle( lpctstr & pArgs ); + static realtype GetValMath( realtype dVal, lpctstr & ptcRefExpr ); + static realtype GetSingle(lpctstr & ptcRefArgs ); }; -#endif // _INC_CFLOATMATH_H \ No newline at end of file +#endif // _INC_CFLOATMATH_H diff --git a/src/common/CLocalVarsExtra.cpp b/src/common/CLocalVarsExtra.cpp index 54ee9c263..7e6f6494b 100644 --- a/src/common/CLocalVarsExtra.cpp +++ b/src/common/CLocalVarsExtra.cpp @@ -1,5 +1,5 @@ #include "../sphere/threads.h" -#include "CExpression.h" +//#include "CExpression.h" // included in the precompiled header #include "CLog.h" #include "CLocalVarsExtra.h" @@ -33,7 +33,7 @@ bool CLocalFloatVars::Set( const char* VarName, const char* VarValue ) return Insert(VarName, VarValue, true); } -bool CLocalFloatVars::Insert( const char* VarName, const char* VarValue, bool fForceSet) +bool CLocalFloatVars::Insert( const char* VarName, const char* VarValue, bool fForceSet) { ADDTOCALLSTACK("CLocalFloatVars::Insert"); if (!VarValue || !VarName) @@ -119,4 +119,4 @@ bool CLocalObjMap::Insert( ushort Number, CObjBase * pObj, bool fForceSet ) void CLocalObjMap::Clear() { m_ObjMap.clear(); -} \ No newline at end of file +} diff --git a/src/common/CLog.cpp b/src/common/CLog.cpp index 364dadae5..846ac4739 100644 --- a/src/common/CLog.cpp +++ b/src/common/CLog.cpp @@ -3,7 +3,7 @@ #include "../sphere/threads.h" #include "../game/CServer.h" #include "sphere_library/sstringobjs.h" -#include "CException.h" +//#include "CException.h" // included in the precompiled header #include "CScript.h" #include "CLog.h" @@ -107,7 +107,7 @@ const CScript * CLog::_SetScriptContext( const CScript * pScriptContext ) const CScript * CLog::SetScriptContext(const CScript * pScriptContext) { - MT_UNIQUE_LOCK_RETURN(CLog::_SetScriptContext(pScriptContext)); + MT_UNIQUE_LOCK_RETURN(this, CLog::_SetScriptContext(pScriptContext)); } const CScriptObj * CLog::_SetObjectContext( const CScriptObj * pObjectContext ) @@ -119,7 +119,7 @@ const CScriptObj * CLog::_SetObjectContext( const CScriptObj * pObjectContext ) const CScriptObj * CLog::SetObjectContext(const CScriptObj * pObjectContext) { - MT_UNIQUE_LOCK_RETURN(CLog::_SetObjectContext(pObjectContext)); + MT_UNIQUE_LOCK_RETURN(this, CLog::_SetObjectContext(pObjectContext)); } bool CLog::SetFilePath( lpctstr pszName ) @@ -216,7 +216,7 @@ bool CLog::_OpenLog( lpctstr pszBaseDirName ) // name set previously. bool CLog::OpenLog(lpctstr pszBaseDirName) // name set previously. { ADDTOCALLSTACK("CLog::OpenLog"); - MT_UNIQUE_LOCK_RETURN(CLog::_OpenLog(pszBaseDirName)); + MT_UNIQUE_LOCK_RETURN(this, CLog::_OpenLog(pszBaseDirName)); } int CLog::EventStr( dword dwMask, lpctstr pszMsg, ConsoleTextColor iLogColor) noexcept @@ -277,7 +277,7 @@ int CLog::EventStr( dword dwMask, lpctstr pszMsg, ConsoleTextColor iLogColor) no // Print to screen. if ( !(dwMask & LOGF_LOGFILE_ONLY) ) { - if ( !(dwMask & LOGM_INIT) && !g_Serv.IsLoading() ) + if ( !(dwMask & LOGM_INIT) && !g_Serv.IsLoadingGeneric() ) { g_Serv.PrintStr(CTCOL_YELLOW, szTime ); } @@ -310,7 +310,7 @@ int CLog::EventStr( dword dwMask, lpctstr pszMsg, ConsoleTextColor iLogColor) no // Print to log file. if ( !(dwMask & LOGF_CONSOLE_ONLY) ) { - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); if ( datetime.GetDay() != m_dateStamp.GetDay()) { @@ -349,7 +349,7 @@ int CLog::EventStr( dword dwMask, lpctstr pszMsg, ConsoleTextColor iLogColor) no // Not much we can do about this iRet = 0; GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); - STDERR_LOG("CLog::EventStr failed to print: '%s'.\n", pszMsg); + stderrLog("CLog::EventStr failed to print: '%s'.\n", pszMsg); } return iRet; diff --git a/src/common/CPointBase.cpp b/src/common/CPointBase.cpp index 7c20098d1..10821a167 100644 --- a/src/common/CPointBase.cpp +++ b/src/common/CPointBase.cpp @@ -1,8 +1,10 @@ #include "../common/sphere_library/scontainer_ops.h" -#include "../common/CExpression.h" +#include "../common/sphere_library/sfastmath.h" +//#include "../common/CExpression.h" // included in the precompiled header #include "../game/items/CItem.h" #include "../game/uo_files/CUOMultiItemRec.h" #include "../game/uo_files/CUOStaticItemRec.h" +#include "../game/uo_files/CUOMapList.h" #include "../game/CSector.h" #include "../game/CServer.h" #include "../game/CWorldMap.h" @@ -12,8 +14,6 @@ #include "CPointBase.h" #include -#include "../game/uo_files/CUOMapList.h" - static_assert(sizeof(CPointBase) == sizeof(CPointMap), "CPointBase and CPointMap have to have the same size. Was a virtual method added?"); @@ -96,17 +96,6 @@ lpctstr const CPointBase::sm_szLoadKeys[PT_QTY+1] = nullptr }; -CPointBase::CPointBase() noexcept : - m_x(-1), m_y(-1), m_z(0), m_map(0) // Same thing as calling InitPoint(), but without this extra cuntion call -{ - //InitPoint(); -} - -CPointBase::CPointBase(short x, short y, char z, uchar map) noexcept : - m_x(x), m_y(y), m_z(z), m_map(map) -{ -} - bool CPointBase::operator== ( const CPointBase & pt ) const noexcept { return( m_x == pt.m_x && m_y == pt.m_y && m_z == pt.m_z && m_map == pt.m_map ); @@ -214,9 +203,7 @@ int CPointBase::GetDist( const CPointBase & pt ) const noexcept // Distance betw //ADDTOCALLSTACK_DEBUG("CPointBase::GetDist"); // Get the basic 2d distance. - if ( !pt.IsValidPoint() || (pt.m_map != m_map)) - return INT16_MAX; - return GetDistBase(pt); + return (!pt.IsValidPoint() || (pt.m_map != m_map)) ? INT16_MAX : GetDistBase(pt); } int CPointBase::GetDistSightBase( const CPointBase & pt ) const noexcept // Distance between points based on UO sight @@ -233,9 +220,7 @@ int CPointBase::GetDistSightBase( const CPointBase & pt ) const noexcept // Dist int CPointBase::GetDistSight( const CPointBase & pt ) const noexcept // Distance between points based on UO sight { - if ( !pt.IsValidPoint() ) - return INT16_MAX; - if ( pt.m_map != m_map ) + if (!pt.IsValidPoint() || (pt.m_map != m_map)) return INT16_MAX; //const int dx = abs(m_x - pt.m_x); @@ -281,39 +266,71 @@ int CPointBase::GetDist3D( const CPointBase & pt ) const noexcept // Distance be } } -bool CPointBase::IsValidXY() const noexcept +// Difference between IsCharValid and IsValidPoint: the first returns false if x or y == 0. +bool CPointBase::IsCharValid() const noexcept { - if ( (m_x < 0) || (m_y < 0) ) - return false; - if ( (m_x >= g_MapList.GetMapSizeX(m_map)) || (m_y >= g_MapList.GetMapSizeY(m_map)) ) - return false; - return true; + // 0 for X or Y is invalid. Z is valid if (-127, 127), excluding the extremes. + const uint32_t sx = g_MapList.GetMapSizeX(m_map) - 1; // -1 because 0 is not valid. + const uint32_t sy = g_MapList.GetMapSizeY(m_map) - 1; + constexpr uint32_t sz = 2 * UO_SIZE_Z; // exclude -128, -127, 127 + + const uint32_t ux = static_cast(static_cast(m_x) - 1); // -1 because 0 is not valid + const uint32_t uy = static_cast(static_cast(m_y) - 1); + const uint32_t uz = static_cast(static_cast(m_z) + UO_SIZE_Z); + + return (ux < sx) && (uy < sy) && (uz > 0) && (uz < sz); } -bool CPointBase::IsCharValid() const noexcept +bool CPointBase::IsValidPoint() const noexcept { - if ( (m_z <= -UO_SIZE_Z) || (m_z >= UO_SIZE_Z) ) - return false; - if ((m_x <= 0) || (m_y <= 0)) - return false; - if ((m_x >= g_MapList.GetMapSizeX(m_map)) || (m_y >= g_MapList.GetMapSizeY(m_map))) - return false; - return true; + // 0 for X or Y is valid. Z is valid if (-127, 127), excluding the extremes. + const uint32_t sx = g_MapList.GetMapSizeX(m_map); + const uint32_t sy = g_MapList.GetMapSizeY(m_map); + constexpr uint32_t sz = 2 * UO_SIZE_Z; // exclude -128, -127, 127 + + // Cast to unsigned - negative values wrap to large numbers + const uint32_t ux = static_cast(static_cast(m_x)); + const uint32_t uy = static_cast(static_cast(m_y)); + const uint32_t uz = static_cast(static_cast(m_z) + UO_SIZE_Z); + + return (ux < sx) && (uy < sy) && (uz > 0) && (uz < sz); +} + +bool CPointBase::IsValidXY() const noexcept +{ + // 0 for X or Y is valid. + const uint32 sx = g_MapList.GetMapSizeX(m_map); + const uint32 sy = g_MapList.GetMapSizeY(m_map); + + const uint32 ux = uint32(uint16(m_x)); + const uint32 uy = uint32(uint16(m_y)); + + // Two unsigned compares fold both < 0 and >= size checks. 0 is valid x or y, + return ux < sx && uy < sy; } void CPointBase::ValidatePoint() noexcept { + const short iMaxX = (short)g_MapList.GetMapSizeX(m_map); + const short iMaxY = (short)g_MapList.GetMapSizeY(m_map); + + m_x = sl::fmath::sMax(m_x, (short)0); + m_x = (m_x < iMaxX) ? m_x : (iMaxX - 1); + + m_y = sl::fmath::sMax(m_y, (short)0); + m_y = (m_y < iMaxY) ? m_y : (iMaxY - 1); + + /* if ( m_x < 0 ) m_x = 0; - const short iMaxX = (short)g_MapList.GetMapSizeX(m_map); if (m_x >= iMaxX) m_x = iMaxX - 1; if ( m_y < 0 ) m_y = 0; - const short iMaxY = (short)g_MapList.GetMapSizeY(m_map); if (m_y >= iMaxY) m_y = iMaxY - 1; + */ } bool CPointBase::IsSame2D( const CPointBase & pt ) const @@ -512,7 +529,12 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const if (pMultiItem->m_visible == 0) continue; - const CPointMap ptTest((word)(ptMulti.m_x + pMultiItem->m_dx), (word)(ptMulti.m_y + pMultiItem->m_dy), (char)(ptMulti.m_z + pMultiItem->m_dz), this->m_map); + const CPointMap ptTest( + (word)(ptMulti.m_x + pMultiItem->m_dx), + (word)(ptMulti.m_y + pMultiItem->m_dy), + (char)(ptMulti.m_z + pMultiItem->m_dz), + this->m_map); + if (GetDist(ptTest) > 0) continue; @@ -615,7 +637,12 @@ bool CPointBase::r_WriteVal( lpctstr ptcKey, CSString & sVal ) const break; if (pMultiItem->m_visible == 0) continue; - CPointMap ptTest((word)(ptMulti.m_x + pMultiItem->m_dx), (word)(ptMulti.m_y + pMultiItem->m_dy), (char)(ptMulti.m_z + pMultiItem->m_dz), this->m_map); + + const CPointMap ptTest( + (word)(ptMulti.m_x + pMultiItem->m_dx), + (word)(ptMulti.m_y + pMultiItem->m_dy), + (char)(ptMulti.m_z + pMultiItem->m_dz), + this->m_map); if (GetDist(ptTest) > 0) continue; @@ -827,30 +854,26 @@ bool CPointBase::r_LoadVal( lpctstr ptcKey, lpctstr pszArgs ) return true; } - DIR_TYPE CPointBase::GetDir( const CPointBase & pt, DIR_TYPE DirDefault ) const // Direction to point pt { ADDTOCALLSTACK_DEBUG("CPointBase::GetDir"); // Get the 2D direction between points. - const int dx = (m_x-pt.m_x); - const int dy = (m_y-pt.m_y); + /* + const int dx = (m_x-pt.m_x); + const int dy = (m_y-pt.m_y); const int ax = abs(dx); const int ay = abs(dy); - if ( ay > ax ) { if ( ! ax ) - { return(( dy > 0 ) ? DIR_N : DIR_S ); - } - int slope = ay / ax; + + const int slope = ay / ax; if ( slope > 2 ) return(( dy > 0 ) ? DIR_N : DIR_S ); if ( dx > 0 ) // westish - { return(( dy > 0 ) ? DIR_NW : DIR_SW ); - } return(( dy > 0 ) ? DIR_NE : DIR_SE ); } else @@ -861,24 +884,66 @@ DIR_TYPE CPointBase::GetDir( const CPointBase & pt, DIR_TYPE DirDefault ) const return( DirDefault ); // here ? return(( dx > 0 ) ? DIR_W : DIR_E ); } - int slope = ax / ay; + const int slope = ax / ay; if ( slope > 2 ) return(( dx > 0 ) ? DIR_W : DIR_E ); if ( dy > 0 ) - { return(( dx > 0 ) ? DIR_NW : DIR_NE ); - } return(( dx > 0 ) ? DIR_SW : DIR_SE ); } + */ + + const int dx = m_x - pt.m_x; + const int dy = m_y - pt.m_y; + + // Early exit if identical point + if ((dx | dy) == 0) //(dx == 0 && dy == 0) + return DirDefault; + + // Absolute values + const int ax = dx < 0 ? -dx : dx; + const int ay = dy < 0 ? -dy : dy; + + // Axis‐dominance and “steepness” test + const bool steep = ay > ax; + const bool extreme = steep + ? (ay > ax * 2) + : (ax > ay * 2); + + // Cardinal direction if “extreme”, else diagonal + // Use boolean-to-int multipliers (true→1, false→0) + const int north = (dy > 0); + const int south = (dy < 0); + const int east = (dx < 0); + const int west = (dx > 0); + + const DIR_TYPE dir_card = enum_alias_cast( + steep + ? ( north * DIR_N + south * DIR_S ) + : ( east * DIR_E + west * DIR_W )); + //const DIR_TYPE card = steep + // ? (north ? DIR_N : DIR_S) + // : (east ? DIR_W : DIR_E); + + const DIR_TYPE dir_diag = enum_alias_cast( + north * east * DIR_NE + + north * west * DIR_NW + + south * east * DIR_SE + + south * west * DIR_SW); + //const DIR_TYPE diag = north + // ? (east ? DIR_NE : DIR_NW) + // : (east ? DIR_SE : DIR_SW); + + return extreme ? dir_card : dir_diag; } int CPointBase::StepLinePath( const CPointBase & ptSrc, int iSteps ) { ADDTOCALLSTACK("CPointBase::StepLinePath"); // Take x steps toward this point. - int dx = m_x - ptSrc.m_x; - int dy = m_y - ptSrc.m_y; - int iDist2D = GetDist( ptSrc ); + const int dx = m_x - ptSrc.m_x; + const int dy = m_y - ptSrc.m_y; + const int iDist2D = GetDist( ptSrc ); if ( ! iDist2D ) return 0; @@ -1000,11 +1065,11 @@ CSector * CPointBase::GetSector() const { g_Log.Event(LOGL_ERROR, "Point(%d,%d): trying to get a sector for point on map #%d out of bounds for this map(%d,%d). Defaulting to sector 0 of the map.\n", m_x, m_y, m_map, g_MapList.GetMapSizeX(m_map), g_MapList.GetMapSizeY(m_map)); - return CWorldMap::GetSector(m_map, 0); + return CWorldMap::GetSectorByIndex(m_map, 0); } // Get the world Sector we are in. - return CWorldMap::GetSector(m_map, m_x, m_y); + return CWorldMap::GetSectorByCoordsUnchecked(m_map, m_x, m_y); } CRegion * CPointBase::GetRegion( dword dwType ) const @@ -1017,10 +1082,7 @@ CRegion * CPointBase::GetRegion( dword dwType ) const return nullptr; const CSector *pSector = GetSector(); - if ( pSector ) - return pSector->GetRegion(*this, dwType); - - return nullptr; + return !pSector ? nullptr : pSector->GetRegion(*this, dwType); } size_t CPointBase::GetRegions( dword dwType, CRegionLinks *pRLinks ) const @@ -1032,10 +1094,7 @@ size_t CPointBase::GetRegions( dword dwType, CRegionLinks *pRLinks ) const return 0; const CSector *pSector = GetSector(); - if ( pSector ) - return pSector->GetRegions(*this, dwType, pRLinks); - - return 0; + return !pSector ? 0 : pSector->GetRegions(*this, dwType, pRLinks); } int CPointBase::GetPointSortIndex() const noexcept diff --git a/src/common/CPointBase.h b/src/common/CPointBase.h index 4e0670468..544392241 100644 --- a/src/common/CPointBase.h +++ b/src/common/CPointBase.h @@ -23,7 +23,7 @@ DIR_TYPE GetDirTurn( DIR_TYPE dir, int offset ); struct CPointBase // Non initialized 3d point. { private: - friend class GlobalInitializer; + friend struct GlobalInitializer; static void InitRuntimeDefaultValues(); public: @@ -42,19 +42,27 @@ struct CPointBase // Non initialized 3d point. CPointBase& InitPoint() noexcept; CPointBase& ZeroPoint() noexcept; - CPointBase() noexcept; - - // This destructor (and the ones of the child classes) are willingly NOT virtual. - // If the class had any virtual method, its size will increase of at least 4-8 bytes (size of a pointer to the virtual table). - // It matters the most because CPointBase is used in the union inside CItem. Increasing the size of CPointBase will increase the size of the union. - // Moreover, it will disalign the addresses of the data inside the other structs of the union. - // Last but not least, remember that deleting an inheriting class without a virtual destructor will delete only a part of the class! It will not call the topmost destructor! - ~CPointBase() noexcept = default; + inline CPointBase() noexcept : + m_x(-1), m_y(-1), m_z(0), m_map(0) // Same thing as calling InitPoint(), but without this extra function call + { + //InitPoint(); + } - CPointBase(short x, short y, char z = 0, uchar map = 0) noexcept; - CPointBase(const CPointBase&) noexcept = default; + inline CPointBase(short x, short y, char z, uchar map) noexcept : + m_x(x), m_y(y), m_z(z), m_map(map) + { + } + CPointBase(const CPointBase&) noexcept = default; CPointBase(CPointBase&&) noexcept = default; + // This destructor (and the ones of the child classes) are willingly NOT virtual. + // If the class had any virtual method, its size will increase of at least 4-8 bytes (size of a pointer to the virtual table). + // It matters the most because CPointBase is used in the union inside CItem. Increasing the size of CPointBase will increase the size of the union. + // Moreover, it will disalign the addresses of the data inside the other structs of the union. + // Last but not least, remember that deleting an inheriting class without a virtual destructor will delete only a part of the class! It will not call the topmost destructor! + ~CPointBase() noexcept = default; + + CPointBase& operator = (const CPointBase&) noexcept = default; bool operator == ( const CPointBase & pt ) const noexcept; bool operator != ( const CPointBase & pt ) const noexcept; @@ -65,19 +73,14 @@ struct CPointBase // Non initialized 3d point. [[nodiscard]] int GetDist( const CPointBase & pt ) const noexcept; // Distance between points [[nodiscard]] int GetDistSightBase( const CPointBase & pt ) const noexcept; // Distance between points based on UO sight [[nodiscard]] int GetDistSight( const CPointBase & pt ) const noexcept; // Distance between points based on UO sight - [[nodiscard]] int GetDist3D( const CPointBase & pt ) const noexcept; // 3D Distance between points + [[nodiscard]] int GetDist3D( const CPointBase & pt ) const noexcept; // 3D Distance between points - [[nodiscard]] inline bool IsValidZ() const noexcept - { - return ((m_z > -127 /* -UO_SIZE_Z */) && (m_z < 127 /* UO_SIZE_Z */)); - } + //[[nodiscard]] inline bool IsValidZ() const noexcept { + // return ((m_z > -127 /* -UO_SIZE_Z */) && (m_z < 127 /* UO_SIZE_Z */)); + //} [[nodiscard]] bool IsValidXY() const noexcept; [[nodiscard]] bool IsCharValid() const noexcept; - [[nodiscard]] inline bool IsValidPoint() const noexcept - { - // Called a LOT of times, it's worth inlining it. - return (IsValidXY() && IsValidZ()); - } + [[nodiscard]] bool IsValidPoint() const noexcept; void ValidatePoint() noexcept; diff --git a/src/common/CRect.cpp b/src/common/CRect.cpp index 589fe09ac..cad104411 100644 --- a/src/common/CRect.cpp +++ b/src/common/CRect.cpp @@ -1,5 +1,6 @@ #include "../game/uo_files/CUOMapList.h" #include "../game/CSectorList.h" +#include "sphere_library/sfastmath.h" #include "CLog.h" #include "CRect.h" @@ -65,51 +66,51 @@ void CRect::UnionRect( const CRect & rect ) DEBUG_ERR(("Uniting regions from different maps!\n")); } } -bool CRect::IsInside( const CRect & rect ) const noexcept + +bool CRect::IsInside( int x, int y, int map ) const noexcept +{ + // NON inclusive rect! Is the point in the rectangle ? + return( IsInsideX(x) && IsInsideY(y) && ( m_map == map )); +} + +bool CRect::IsInside(const CRect & rect) const noexcept { // Is &rect inside me ? // ASSUME: Normalized rect - if ( rect.m_map != m_map ) - return false; - if ( rect.m_left < m_left ) - return false; - if ( rect.m_top < m_top ) - return false; - if ( rect.m_right > m_right ) - return false; - if ( rect.m_bottom > m_bottom ) - return false; - return true; + return (rect.m_map == m_map) && + (rect.m_left >= m_left) && + (rect.m_top >= m_top) && + (rect.m_right <= m_right)&& + (rect.m_bottom<= m_bottom); } -bool CRect::IsOverlapped( const CRect & rect ) const noexcept +bool CRect::IsOverlapped(const CRect & rect) const noexcept { // are the 2 rects overlapped at all ? // NON inclusive rect. // ASSUME: Normalized rect - // if ( rect.m_map != m_map ) return false; - if ( rect.m_left >= m_right ) - return false; - if ( rect.m_top >= m_bottom ) - return false; - if ( rect.m_right <= m_left ) - return false; - if ( rect.m_bottom <= m_top ) - return false; - return true; + return (rect.m_left < m_right) && + (rect.m_top < m_bottom) && + (rect.m_right > m_left) && + (rect.m_bottom> m_top); } bool CRect::IsEqual( const CRect & rect ) const noexcept { - return m_left == rect.m_left && - m_top == rect.m_top && - m_right == rect.m_right && - m_bottom == rect.m_bottom && - m_map == rect.m_map; + return m_left == rect.m_left && + m_top == rect.m_top && + m_right == rect.m_right && + m_bottom == rect.m_bottom && + m_map == rect.m_map; } void CRect::NormalizeRect() noexcept { + sl::fmath::sSortPair(m_top, m_bottom); + sl::fmath::sSortPair(m_left, m_right); + m_map = g_MapList.IsMapSupported(m_map) ? m_map : 0; + +/* if ( m_bottom < m_top ) { const int wtmp = m_bottom; @@ -124,6 +125,7 @@ void CRect::NormalizeRect() noexcept } if ( !g_MapList.IsMapSupported(m_map) ) m_map = 0; +*/ } void CRect::SetRect( int left, int top, int right, int bottom, int map ) noexcept @@ -138,6 +140,12 @@ void CRect::SetRect( int left, int top, int right, int bottom, int map ) noexcep void CRect::NormalizeRectMax( int cx, int cy ) noexcept { + m_left = sl::fmath::sMax(0, m_left); + m_top = sl::fmath::sMax(0, m_top); + m_right = sl::fmath::sMin(cx, m_right); + m_bottom = sl::fmath::sMin(cy, m_bottom); + + /* if ( m_left < 0 ) m_left = 0; if ( m_top < 0 ) @@ -146,6 +154,7 @@ void CRect::NormalizeRectMax( int cx, int cy ) noexcept m_right = cx; if ( m_bottom > cy ) m_bottom = cy; + */ } size_t CRect::Read( lpctstr pszVal ) @@ -215,14 +224,14 @@ lpctstr CRect::Write() const return Write( Str_GetTemp(), (uint)Str_TempLength() ); } -CPointBase CRect::GetCenter() const +CPointBase CRect::GetCenter() const noexcept { - CPointBase pt; - pt.m_x = (short)(( m_left + m_right ) / 2); - pt.m_y = (short)((m_top + m_bottom) / 2); - pt.m_z = 0; - pt.m_map = (uchar)(m_map); - return pt; + return CPointBase( + (short)((m_left + m_right) / 2), + (short)((m_top + m_bottom) / 2), + 0, + (uchar)(m_map) + ); } CPointBase CRect::GetRectCorner( DIR_TYPE dir ) const @@ -277,44 +286,94 @@ CPointBase CRect::GetRectCorner( DIR_TYPE dir ) const return pt; } -CSector * CRect::GetSector( int i ) const noexcept // ge all the sectors that make up this rect. +// get the n-th sector that makes up this rect. +CSector * CRect::GetSectorAtIndex( int i ) const noexcept { - //ADDTOCALLSTACK_DEBUG("CRect::GetSector"); - // get all the CSector(s) that overlap this rect. - // RETURN: nullptr = no more - - // Align new rect. - const CSectorList* pSectors = CSectorList::Get(); - - const int iSectorSize = pSectors->GetSectorSize(m_map); - CRectMap rect; - rect.m_left = m_left & ~(iSectorSize-1); - rect.m_right = ( m_right | (iSectorSize-1)) + 1; - rect.m_top = m_top & ~(iSectorSize-1); - rect.m_bottom = ( m_bottom | (iSectorSize-1)) + 1; - rect.m_map = m_map; - rect.NormalizeRectMax(); - - const int iSectorCols = pSectors->GetSectorCols(m_map); - const int width = (rect.GetWidth()) / iSectorSize; - const int height = (rect.GetHeight()) / iSectorSize; -#ifdef _DEBUG - ASSERT(width <= iSectorCols); - const int iSectorRows = pSectors->GetSectorRows(m_map); - ASSERT(height <= iSectorRows); -#endif + //ADDTOCALLSTACK_DEBUG("CRect::GetSectorAtIndexWithHints"); + // get all the CSector(s) that overlap this rect. + // RETURN: nullptr = no more - const int iBase = (( rect.m_top / iSectorSize) * iSectorCols) + ( rect.m_left / iSectorSize ); + if (g_MapList.IsMapSupported(m_map) == false) + return nullptr; - if ( i >= ( height * width )) - { - if ( ! i ) - return pSectors->GetSector(m_map, iBase); - return nullptr; - } + SectIndexingHints hints = PrecomputeSectorIndexingHints(); + return GetSectorAtIndexWithHints(i, hints); +} + +// get the n-th sector that makes up this rect. +CSector * CRect::GetSectorAtIndexWithHints(int i, SectIndexingHints hints) const noexcept +{ + //if (g_MapList.IsMapSupported(m_map) == false) + // return nullptr; + + const CSectorList &pSectors = CSectorList::Get(); + + if (i >= hints.iRectSectorCount) + return i ? nullptr : pSectors.GetSectorByIndexUnchecked(m_map, hints.iBaseSectorIndex); + + // From now on we need only height and sectorcols. We'll use precomputed data when checking + // incremental sector indices for the same CRect. + + // Column-major + // col = i / height + // row = i % height + // indexoffset = col * sectorRows + row + // Row-major + const int row = i / hints.iRectWidth; + const int col = i % hints.iRectWidth; + const int iSectorIndexOffset = CSectorList::CalcSectorIndex(hints.iRectMapSectorCols, col, row); + //const int iSectorIndexOffset = row * hints.iRectMapSectorCols + col; + const int iSectorIndex = hints.iBaseSectorIndex + iSectorIndexOffset; + return pSectors.GetSectorByIndexUnchecked(m_map, iSectorIndex); +} + +CRect::SectIndexingHints +CRect::PrecomputeSectorIndexingHints() const noexcept +{ + const CSectorList &pSectors = CSectorList::Get(); + const MapSectorsData* pSecData = pSectors.GetMapSectorData(m_map); + //ASSERT(pSecData); + + const int iSectorSize = pSecData->iSectorSize; + const int iSectorCols = pSecData->iSectorColumns; + const uint uiSectorShift = pSecData->uiSectorSizeDivShift; + + // Align new rect. + // CRectMap(int left, int top, int right, int bottom, int map) + CRectMap rect( + (m_left & ~(iSectorSize-1)), // aligns the left boundary down to the nearest multiple of iSectorSize. + (m_top & ~(iSectorSize-1)), + (m_right | (iSectorSize-1)) + 1, // rounds the right boundary up to cover the full last sector. + (m_bottom | (iSectorSize-1)) + 1, + m_map + ); + //g_Log.EventDebug("Precomputing hints. Starting rect: %d,%d, %d,%d. Aligned rect: %d,%d, %d,%d.\n", + // m_left, m_top, m_right, m_bottom, rect.m_left, rect.m_top, rect.m_right, rect.m_bottom); + rect.NormalizeRectMax(); + + //const int width = (rect.GetWidth()) / iSectorSize; + //const int height = (rect.GetHeight()) / iSectorSize; + // Bit-shifting is faster than plain division. We can use it because sector size is guaranteed to be a multiple of 2. + const int width = rect.GetWidth() >> uiSectorShift; + const int height = rect.GetHeight() >> uiSectorShift; + +#ifdef _DEBUG + const int iSectorRows = pSecData->iSectorRows; + ASSERT(width <= iSectorCols); + ASSERT(height <= iSectorRows); +#endif - const int indexoffset = (( i / width ) * iSectorCols) + ( i % width ); - return pSectors->GetSector(m_map, iBase + indexoffset); + const int baseRow = rect.m_top >> uiSectorShift; + const int baseCol = rect.m_left >> uiSectorShift; + const int iBase = CSectorList::CalcSectorIndex(iSectorCols, baseCol, baseRow); + + return SectIndexingHints { + .iBaseSectorIndex = iBase, + .iRectWidth = width, + .iRectHeight = height, + .iRectSectorCount = width * height, + .iRectMapSectorCols = iSectorCols + }; } @@ -338,12 +397,9 @@ CRectMap::CRectMap(int left, int top, int right, int bottom, int map) noexcept : bool CRectMap::IsValid() const noexcept { const int iSizeX = GetWidth(); - if ( (iSizeX < 0) || (iSizeX > g_MapList.GetMapSizeX(m_map)) ) - return false; const int iSizeY = GetHeight(); - if ( (iSizeY < 0) || (iSizeY > g_MapList.GetMapSizeY(m_map)) ) - return false; - return true; + return (iSizeX >= 0 && iSizeX <= g_MapList.GetMapSizeX(m_map)) && + (iSizeY >= 0 && iSizeY <= g_MapList.GetMapSizeY(m_map)); } void CRectMap::NormalizeRect() noexcept diff --git a/src/common/CRect.h b/src/common/CRect.h index 629cbb479..680448dd8 100644 --- a/src/common/CRect.h +++ b/src/common/CRect.h @@ -21,27 +21,38 @@ struct CRect // Basic rectangle, similar to _WIN32 RECT (May not be on the map) int m_bottom; // South ( NON INCLUSIVE !) int m_map; + + struct SectIndexingHints + { + int iBaseSectorIndex; // Sector index at origin, top-left x,y coords of the rect + int iRectWidth; // x1 - x: how much x units (columns) are inside the rect + int iRectHeight; // y1 - y: how much y units (rows) are inside the rect + int iRectSectorCount; // How much sectors are inside this rect + int iRectMapSectorCols; // Number of sectors columns (X) per each row in the map (so, map max X) + }; + + void SetRectEmpty() noexcept; - CRect() noexcept; - CRect(int left, int top, int right, int bottom, int map) noexcept; - CRect(const CRect&) noexcept = default; - CRect(CRect&&) noexcept = default; + CRect() noexcept; + CRect(int left, int top, int right, int bottom, int map) noexcept; + constexpr CRect(const CRect&) noexcept = default; + constexpr CRect(CRect&&) noexcept = default; virtual ~CRect() noexcept = default; - CRect& operator = (const CRect&) = default; - const CRect& operator += (const CRect& rect); + CRect& operator = (const CRect&) = default; + const CRect& operator += (const CRect& rect); - inline int GetWidth() const noexcept + constexpr int GetWidth() const noexcept { return( m_right - m_left ); } - inline int GetHeight() const noexcept + constexpr int GetHeight() const noexcept { return( m_bottom - m_top ); } - inline bool IsRectEmpty() const noexcept + constexpr bool IsRectEmpty() const noexcept { return( m_left >= m_right || m_top >= m_bottom ); } @@ -49,36 +60,38 @@ struct CRect // Basic rectangle, similar to _WIN32 RECT (May not be on the map) void OffsetRect( int x, int y ); void UnionPoint( int x, int y ); - inline bool IsInsideX( int x ) const + constexpr bool IsInsideX( int x ) const noexcept { // non-inclusive return( x >= m_left && x < m_right ); } - inline bool IsInsideY( int y ) const + constexpr bool IsInsideY( int y ) const noexcept { // non-inclusive return( y >= m_top && y < m_bottom ); } - inline bool IsInside( int x, int y, int map ) const - { - // NON inclusive rect! Is the point in the rectangle ? - return( IsInsideX(x) && IsInsideY(y) && ( m_map == map )); - } - inline bool IsInside2d( const CPointBase & pt ) const + inline bool IsInside2d( const CPointBase & pt ) const noexcept { // NON inclusive rect! Is the point in the rectangle ? return( IsInside( pt.m_x, pt.m_y, pt.m_map ) ); } - void UnionRect( const CRect & rect ); - bool IsInside( const CRect & rect ) const noexcept; + bool IsInside( int x, int y, int map ) const noexcept; + bool IsInside( const CRect & rect ) const noexcept; + void UnionRect( const CRect & rect ); bool IsOverlapped( const CRect & rect ) const noexcept; bool IsEqual( const CRect & rect ) const noexcept; virtual void NormalizeRect() noexcept; void NormalizeRectMax( int cx, int cy ) noexcept; - CPointBase GetCenter() const; + CPointBase GetCenter() const noexcept; CPointBase GetRectCorner( DIR_TYPE dir ) const; - CSector * GetSector( int i ) const noexcept; // ge all the sectors that make up this rect. + + // get all the sectors that make up this rect. + CSector * GetSectorAtIndex( int i ) const noexcept; + + // get all the sectors that make up this rect (using cached precomputed data, that's faster if the use case allows its usage). + CSector * GetSectorAtIndexWithHints(int i, SectIndexingHints hints) const noexcept; + SectIndexingHints PrecomputeSectorIndexingHints() const noexcept; void SetRect( int left, int top, int right, int bottom, int map ) noexcept; @@ -90,23 +103,23 @@ struct CRect // Basic rectangle, similar to _WIN32 RECT (May not be on the map) struct CRectMap : public CRect { CRectMap() noexcept = default; - CRectMap(int left, int top, int right, int bottom, int map) noexcept; - CRectMap(const CRectMap&) noexcept = default; - CRectMap(CRectMap&&) noexcept = default; + CRectMap(int left, int top, int right, int bottom, int map) noexcept; + constexpr CRectMap(const CRectMap&) noexcept = default; + constexpr CRectMap(CRectMap&&) noexcept = default; // special copy constructors, valid because CRectMap hasn't additional members compared to CRect, it only has more methods - CRectMap(const CRect& rect) noexcept : CRectMap(static_cast(rect)) {} - CRectMap(CRect&& rect) noexcept : CRectMap(static_cast(rect)) {} + constexpr CRectMap(const CRect& rect) noexcept : CRectMap(static_cast(rect)) {} + constexpr CRectMap(CRect&& rect) noexcept : CRectMap(static_cast(rect)) {} - CRectMap& operator=(const CRectMap&) noexcept = default; - CRectMap& operator=(const CRect& rect) noexcept + CRectMap& operator=(const CRectMap&) noexcept = default; + CRectMap& operator=(const CRect& rect) noexcept { return CRectMap::operator=(static_cast(rect)); } virtual ~CRectMap() noexcept = default; - bool IsValid() const noexcept; + bool IsValid() const noexcept; virtual void NormalizeRect() noexcept override; void NormalizeRectMax() noexcept; diff --git a/src/common/CSFileObj.cpp b/src/common/CSFileObj.cpp index 42ad26989..4e9245a6c 100644 --- a/src/common/CSFileObj.cpp +++ b/src/common/CSFileObj.cpp @@ -1,8 +1,8 @@ #include "../sphere/threads.h" #include "CScript.h" #include "CLog.h" -#include "CException.h" -#include "CExpression.h" +//#include "CException.h" // included in the precompiled header +//#include "CExpression.h" // included in the precompiled header #include "CSFileObj.h" diff --git a/src/common/CSFileObjContainer.cpp b/src/common/CSFileObjContainer.cpp index b9a1146d4..bf2bdbd60 100644 --- a/src/common/CSFileObjContainer.cpp +++ b/src/common/CSFileObjContainer.cpp @@ -1,7 +1,7 @@ #include "../game/CServerTime.h" #include "../sphere/threads.h" -#include "CException.h" -#include "CExpression.h" +//#include "CException.h" // included in the precompiled header +//#include "CExpression.h" // included in the precompiled header #include "CScript.h" #include "CSFileObj.h" #include "CSFileObjContainer.h" @@ -265,7 +265,7 @@ bool CSFileObjContainer::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsol return pObj->r_WriteVal(ptcKey, sVal, pSrc); } } - + return false; } diff --git a/src/common/CSVFile.cpp b/src/common/CSVFile.cpp index da0f6c041..4f6ef1d55 100644 --- a/src/common/CSVFile.cpp +++ b/src/common/CSVFile.cpp @@ -1,6 +1,6 @@ #include "../sphere/threads.h" -#include "CExpression.h" +//#include "CExpression.h" // included in the precompiled header #include "CSVFile.h" #include "common.h" @@ -24,12 +24,12 @@ CSVFile::~CSVFile() int CSVFile::GetColumnCount() const { ADDTOCALLSTACK("CSVFile::GetColumnCount"); - MT_SHARED_LOCK_RETURN(_iColumnCount); + MT_SHARED_LOCK_RETURN(this, _iColumnCount); } int CSVFile::GetCurrentRow() const { ADDTOCALLSTACK("CSVFile::GetCurrentRow"); - MT_SHARED_LOCK_RETURN(_iCurrentRow); + MT_SHARED_LOCK_RETURN(this, _iCurrentRow); } bool CSVFile::_Open(lpctstr ptcFilename, uint uiModeFlags) @@ -89,7 +89,7 @@ bool CSVFile::_Open(lpctstr ptcFilename, uint uiModeFlags) bool CSVFile::Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CSVFile::Open"); - MT_UNIQUE_LOCK_RETURN(CSVFile::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(this, CSVFile::_Open(ptcFilename, uiModeFlags)); } int CSVFile::_ReadRowContent(tchar ** ppOutput, int rowIndex, int columns) @@ -140,6 +140,6 @@ bool CSVFile::_ReadNextRowContent(CSVRowData& target) bool CSVFile::ReadNextRowContent(CSVRowData& target) { ADDTOCALLSTACK("CSVFile::ReadNextRowContent"); - MT_UNIQUE_LOCK_RETURN(CSVFile::_ReadNextRowContent(target)); + MT_UNIQUE_LOCK_RETURN(this, CSVFile::_ReadNextRowContent(target)); } diff --git a/src/common/CScript.cpp b/src/common/CScript.cpp index e83ed9086..8224bce78 100644 --- a/src/common/CScript.cpp +++ b/src/common/CScript.cpp @@ -3,8 +3,8 @@ #include "../common/CLog.h" #include "../sphere/threads.h" -#include "CException.h" -#include "CExpression.h" +//#include "CException.h" // included in the precompiled header +//#include "CExpression.h" // included in the precompiled header #include "CScript.h" #include "common.h" @@ -301,35 +301,36 @@ CScriptKey::CScriptKey( tchar * ptcKey, tchar * ptcArg ) : /////////////////////////////////////////////////////////////// // -CScriptKeyAlloc -tchar * CScriptKeyAlloc::_GetKeyBufferRaw( size_t iLen ) +tchar * CScriptKeyAlloc::_GetKeyBufferRaw() { //ADDTOCALLSTACK_DEBUG("CScriptKeyAlloc::_GetKeyBufferRaw"); // iLen = length of the string we want to hold. - if ( iLen > SCRIPT_MAX_LINE_LEN ) - iLen = SCRIPT_MAX_LINE_LEN; - ++iLen; // add null. - - if ( m_Mem.GetDataLength() < iLen ) - m_Mem.Alloc( iLen ); + if (!m_TextBuf) + m_TextBuf = CScriptParserBufs::GetScriptKeyArgBufPtr(); m_pszKey = m_pszArg = GetKeyBuffer(); m_pszKey[0] = '\0'; return m_pszKey; } /* -tchar * CScriptKeyAlloc::GetKeyBufferRaw( size_t iLen ) +tchar * CScriptKeyAlloc::GetKeyBufferRaw() { ADDTOCALLSTACK("CScriptKeyAlloc::GetKeyBufferRaw"); - MT_UNIQUE_LOCK_RETURN(CScriptKeyAlloc::_GetKeyBufferRaw(iLen)); + MT_UNIQUE_LOCK_RETURN(this, CScriptKeyAlloc::_GetKeyBufferRaw()); } */ +void CScriptKeyAlloc::_FreeKeyBuffer() +{ + m_TextBuf.reset(); +} + tchar * CScriptKeyAlloc::GetKeyBuffer() { // Get the buffer the key is in. - ASSERT(m_Mem.GetData()); - return reinterpret_cast(m_Mem.GetData()); + ASSERT(m_TextBuf); + return reinterpret_cast(m_TextBuf.get()->data()); } bool CScriptKeyAlloc::ParseKey( lpctstr ptcKey ) @@ -338,17 +339,17 @@ bool CScriptKeyAlloc::ParseKey( lpctstr ptcKey ) // Skip leading white space if ( ! ptcKey ) { - _GetKeyBufferRaw(0); + _GetKeyBufferRaw(); return false; } GETNONWHITESPACE( ptcKey ); - tchar * pBuffer = _GetKeyBufferRaw( strlen( ptcKey )); + tchar * pBuffer = _GetKeyBufferRaw(); ASSERT(pBuffer); - size_t iLen = m_Mem.GetDataLength(); - Str_CopyLimitNull( pBuffer, ptcKey, iLen ); + size_t iLen = sizeof(CScriptKeyArgBuf); + Str_CopyLimitNull( pBuffer, ptcKey, iLen ); Str_Parse( pBuffer, &m_pszArg ); return true; @@ -363,25 +364,25 @@ bool CScriptKeyAlloc::ParseKey( lpctstr ptcKey, lpctstr pszVal ) if ( ! lenkey ) return ParseKey(pszVal); - ASSERT( lenkey < SCRIPT_MAX_LINE_LEN-2 ); + ASSERT( lenkey < sizeof(CScriptKeyArgBuf) - 2 ); size_t lenval = 0; if ( pszVal ) lenval = strlen( pszVal ); - m_pszKey = _GetKeyBufferRaw( lenkey + lenval + 1 ); + m_pszKey = _GetKeyBufferRaw(); if (ptcKey != m_pszKey) { // Invalid key, or not yet inited. - Str_CopyLimitNull(m_pszKey, ptcKey, m_Mem.GetDataLength()); + Str_CopyLimitNull(m_pszKey, ptcKey, sizeof(CScriptKeyArgBuf)); } m_pszArg = m_pszKey + lenkey; if ( pszVal ) { ++m_pszArg; - lenval = m_Mem.GetDataLength(); + lenval = sizeof(CScriptKeyArgBuf); Str_CopyLimitNull( m_pszArg, pszVal, lenval - lenkey ); } @@ -514,7 +515,7 @@ bool CScript::_Open( lpctstr ptcFilename, uint uiFlags ) bool CScript::Open( lpctstr ptcFilename, uint uiFlags ) { ADDTOCALLSTACK("CScript::Open"); - MT_UNIQUE_LOCK_RETURN(CScript::_Open(ptcFilename, uiFlags)); + MT_UNIQUE_LOCK_RETURN(this, CScript::_Open(ptcFilename, uiFlags)); } bool CScript::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file @@ -525,8 +526,8 @@ bool CScript::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened // ARGS: // fRemoveBlanks = Don't report any blank lines, (just keep reading) - tchar* ptcBuf = _GetKeyBufferRaw(SCRIPT_MAX_LINE_LEN); - while ( CCacheableScriptFile::_ReadString( ptcBuf, SCRIPT_MAX_LINE_LEN )) + tchar* ptcBuf = _GetKeyBufferRaw(); + while ( CCacheableScriptFile::_ReadString( ptcBuf, sizeof(CScriptKeyArgBuf) )) { ++m_iLineNum; if ( fRemoveBlanks ) @@ -542,7 +543,7 @@ bool CScript::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened } bool CScript::ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file { - MT_UNIQUE_LOCK_RETURN(CScript::_ReadTextLine(fRemoveBlanks)); + MT_UNIQUE_LOCK_RETURN(this, CScript::_ReadTextLine(fRemoveBlanks)); } bool CScript::FindTextHeader( lpctstr pszName ) // Find a section in the current script @@ -550,7 +551,7 @@ bool CScript::FindTextHeader( lpctstr pszName ) // Find a section in the current ADDTOCALLSTACK("CScript::FindTextHeader"); // RETURN: false = EOF reached. ASSERT(pszName); - ASSERT( ! IsBinaryMode()); + ASSERT(!_IsBinaryMode()); SeekToBegin(); @@ -585,7 +586,7 @@ int CScript::_Seek( int iOffset, int iOrigin ) int CScript::Seek( int iOffset, int iOrigin ) { ADDTOCALLSTACK("CScript::Seek"); - MT_UNIQUE_LOCK_RETURN(CScript::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(this, CScript::_Seek(iOffset, iOrigin)); } bool CScript::FindNextSection() @@ -800,12 +801,16 @@ void CScript::_Close() ADDTOCALLSTACK("CScript::_Close"); // EndSection(); CCacheableScriptFile::_Close(); + + // If i don't have to use this to read/parse stuff anymore (example: after i have cached a script file), just free the text buffer. + // It will be created again if necessary by _GetKeyBufferRaw(). + m_TextBuf.reset(); } void CScript::Close() { ADDTOCALLSTACK("CScript::Close"); - // EndSection(); - CCacheableScriptFile::Close(); + MT_UNIQUE_LOCK_SET(this); + CScript::_Close(); } void CScript::CloseForce() @@ -821,7 +826,7 @@ bool CScript::_SeekContext(const CScriptLineContext &LineContext ) } bool CScript::SeekContext( CScriptLineContext const& LineContext ) { - MT_UNIQUE_LOCK_RETURN(CScript::_SeekContext(LineContext)); + MT_UNIQUE_LOCK_RETURN(this, CScript::_SeekContext(LineContext)); } CScriptLineContext CScript::_GetContext() const @@ -832,19 +837,20 @@ CScriptLineContext CScript::_GetContext() const LineContext.m_iOffset = _GetPosition(); return LineContext; */ - return {m_iLineNum, _GetPosition()}; + return {_GetPosition(), m_iLineNum}; } CScriptLineContext CScript::GetContext() const { - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); /* CScriptLineContext LineContext; LineContext.m_iLineNum = m_iLineNum; LineContext.m_iOffset = _GetPosition(); return LineContext; */ - return {m_iLineNum, _GetPosition()}; + auto ret = CScriptLineContext(_GetPosition(), m_iLineNum); + return ret; } bool CScript::WriteSection( lpctstr ptcSection, ... ) @@ -869,7 +875,7 @@ bool CScript::WriteSection( lpctstr ptcSection, ... ) offset += Str_CopyLimitNull(tsHeader.buffer() + offset, tsSectionFormatted.buffer(), tsHeader.capacity() - 2); offset += Str_ConcatLimitNull(tsHeader.buffer() + offset, "]\n", tsHeader.capacity() - offset); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); _Write(tsHeader.buffer(), int(offset)); return true; diff --git a/src/common/CScript.h b/src/common/CScript.h index bb21601a0..1c2928404 100644 --- a/src/common/CScript.h +++ b/src/common/CScript.h @@ -6,8 +6,8 @@ #ifndef _INC_CSCRIPT_H #define _INC_CSCRIPT_H -#include "sphere_library/CSMemBlock.h" #include "CScriptContexts.h" +#include "CScriptParserBufs.h" #include "CCacheableScriptFile.h" @@ -78,11 +78,13 @@ class CScriptKeyAlloc : public CScriptKey { // Dynamic allocated script key. protected: - CSMemLenBlock m_Mem; // the buffer to hold data read. + static constexpr size_t sm_TextBufMaxSize = sizeof(CScriptKeyArgBuf); + CScriptKeyArgBufPtr m_TextBuf; // the buffer to hold data read. protected: - tchar * _GetKeyBufferRaw( size_t iSize ); - //tchar * GetKeyBufferRaw( size_t iSize ); + tchar * _GetKeyBufferRaw(); + //tchar * GetKeyBufferRaw(); + void _FreeKeyBuffer(); size_t ParseKeyEnd(); public: diff --git a/src/common/CScriptContexts.cpp b/src/common/CScriptContexts.cpp index 65916366e..23809dd33 100644 --- a/src/common/CScriptContexts.cpp +++ b/src/common/CScriptContexts.cpp @@ -36,14 +36,14 @@ void CScriptFileContext::_OpenScript( const CScript * pScriptContext ) void CScriptFileContext::OpenScript( const CScript * pScriptContext ) { ADDTOCALLSTACK("CScriptFileContext::OpenScript"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); _OpenScript(pScriptContext); } void CScriptFileContext::Close() { ADDTOCALLSTACK("CScriptFileContext::Close"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); _Close(); } void CScriptFileContext::_Close() diff --git a/src/common/CScriptContexts.h b/src/common/CScriptContexts.h index 46c0bba96..c1d3292ba 100644 --- a/src/common/CScriptContexts.h +++ b/src/common/CScriptContexts.h @@ -21,10 +21,10 @@ struct CScriptLineContext void Init(); bool IsValid() const; - CScriptLineContext() noexcept : + constexpr CScriptLineContext() noexcept : m_iOffset(-1), m_iLineNum(-1) {}; - CScriptLineContext(int iOffset, int iLineNum) noexcept : + constexpr CScriptLineContext(int iOffset, int iLineNum) noexcept : m_iOffset(iOffset), m_iLineNum(iLineNum) {}; }; diff --git a/src/common/CScriptObj.cpp b/src/common/CScriptObj.cpp index 5698c2d45..6450d2211 100644 --- a/src/common/CScriptObj.cpp +++ b/src/common/CScriptObj.cpp @@ -17,11 +17,10 @@ #include "sphere_library/CSRand.h" #include "resource/sections/CResourceNamedDef.h" #include "resource/CResourceLock.h" -#include "CFloatMath.h" -#include "CExpression.h" +//#include "CExpression.h" // included in the precompiled header +//#include "CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "CSFileObjContainer.h" -#include "CScriptTriggerArgs.h" -#include +#include "CFloatMath.h" #ifdef _WIN32 # include @@ -221,7 +220,7 @@ bool CScriptObj::r_CanCall(size_t uiFunctionIndex) // static return true; } -bool CScriptObj::r_Call( lpctstr pszFunction, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * psVal, TRIGRET_TYPE * piRet ) +bool CScriptObj::r_Call( lpctstr pszFunction, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * psVal, TRIGRET_TYPE * piRet ) { ADDTOCALLSTACK("CScriptObj::r_Call (FunctionName)"); @@ -229,10 +228,10 @@ bool CScriptObj::r_Call( lpctstr pszFunction, CTextConsole * pSrc, CScriptTrigge if ( !r_CanCall(index) ) return false; - return r_Call(index, pSrc, pArgs, psVal, piRet); + return r_Call(index, pScriptArgs, pSrc, psVal, piRet); } -bool CScriptObj::r_Call( size_t uiFunctionIndex, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * psVal, TRIGRET_TYPE * piRet ) +bool CScriptObj::r_Call( size_t uiFunctionIndex, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * psVal, TRIGRET_TYPE * piRet ) { ADDTOCALLSTACK("CScriptObj::r_Call (FunctionIndex)"); EXC_TRY("Call by index"); @@ -289,7 +288,7 @@ bool CScriptObj::r_Call( size_t uiFunctionIndex, CTextConsole * pSrc, CScriptTri TIME_PROFILE_START; } - TRIGRET_TYPE iRet = OnTriggerRun(sFunction, TRIGRUN_SECTION_TRUE, pSrc, pArgs, psVal); + TRIGRET_TYPE iRet = OnTriggerRun(sFunction, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, psVal); if ( IsSetEF(EF_Script_Profiler) ) { @@ -355,29 +354,33 @@ bool CScriptObj::r_LoadVal( CScript & s ) const bool fZero = (index == SSC_VAR0); bool fQuoted = false; lpctstr ptcArg = s.GetArgStr(&fQuoted); - g_Exp.m_VarGlobals.SetStr(ptcKey + (fZero ? 5 : 4), fQuoted, ptcArg, fZero); + g_ExprGlobals.mtEngineLockedWriter()->m_VarGlobals.SetStr(ptcKey + (fZero ? 5 : 4), fQuoted, ptcArg, fZero); return true; } - case SSC_LIST: - if ( !g_Exp.m_ListGlobals.r_LoadVal(ptcKey + 5, s) ) + case SSC_LIST: + if ( ! g_ExprGlobals.mtEngineLockedWriter()->m_ListGlobals.r_LoadVal(ptcKey + 5, s) ) DEBUG_ERR(("Unable to process command '%s %s'\n", ptcKey, s.GetArgRaw())); return true; case SSC_DEFMSG: + { ptcKey += 7; - for ( long l = 0; l < DEFMSG_QTY; ++l ) + // TODO: we really should be using a sorted vector or an hash map... + auto rw = g_ExprGlobals.mtEngineLockedWriter(); + for ( int l = 0; l < DEFMSG_QTY; ++l ) { - if ( !strcmpi(ptcKey, g_Exp.sm_szMsgNames[l]) ) + if ( !strcmpi(ptcKey, rw->sm_szDefMsgNames[l]) ) { bool fQuoted = false; - tchar * args = s.GetArgStr(&fQuoted); - Str_CopyLimitNull(g_Exp.sm_szMessages[l], args, sizeof(g_Exp.sm_szMessages[0])); + tchar * args = s.GetArgStr(&fQuoted); + Str_CopyLimitNull(rw->sm_szDefMessages[l], args, CExprGlobals::m_kiDefmsgMaxLen); return true; } } g_Log.Event(LOGM_INIT|LOGL_ERROR, "Setting not used message override named '%s'\n", ptcKey); return false; + } } return true; EXC_CATCH; @@ -542,7 +545,7 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc lpctstr ptcArg = ptcKey + 1; if ( r_WriteVal(ptcArg, sVal, pSrc) ) { - if ( *sVal != '-' ) + if ( *sVal != '-' ) sVal.FormatLLVal(Str_ToLL(sVal.GetBuffer()).value_or(0)); return true; } @@ -649,35 +652,41 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc case SSC_VAR: // "VAR." = get/set a system wide variable. { - const CVarDefCont * pVar = g_Exp.m_VarGlobals.GetKey(ptcKey); - sVal = CVarDefCont::GetValStrZeroed(pVar, fZero); + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + const CVarDefCont * pVar = gReader->m_VarGlobals.GetKey(ptcKey); + sVal = CVarDefCont::GetValStrZeroed(pVar, fZero); } return true; - case SSC_DEFLIST: - g_Exp.m_ListInternals.r_Write(pSrc, ptcKey, sVal); + case SSC_DEFLIST: + g_ExprGlobals.mtEngineLockedReader()->m_ListInternals.r_Write(pSrc, ptcKey, sVal); return true; - case SSC_LIST: - if (!g_Exp.m_ListGlobals.r_Write(pSrc, ptcKey, sVal)) + case SSC_LIST: + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + if (! gReader->m_ListGlobals.r_Write(pSrc, ptcKey, sVal)) sVal = "-1"; + } return true; case SSC_DEF0: fZero = true; FALLTHROUGH; case SSC_DEF: - { - const CVarDefCont * pVar = g_Exp.m_VarDefs.GetKey(ptcKey); - if ( pVar ) - sVal = pVar->GetValStr(); - else if ( fZero ) - sVal.SetValFalse(); - } - return true; + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + const CVarDefCont * pVar = gReader->m_VarDefs.GetKey(ptcKey); + if ( pVar ) + sVal = pVar->GetValStr(); + else if ( fZero ) + sVal.SetValFalse(); + } + return true; case SSC_RESDEF0: fZero = true; FALLTHROUGH; case SSC_RESDEF: { - const CVarDefCont * pVar = g_Exp.m_VarResDefs.GetKey(ptcKey); + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + const CVarDefCont * pVar = gReader->m_VarResDefs.GetKey(ptcKey); if ( pVar ) sVal = pVar->GetValStr(); else if ( fZero ) @@ -777,7 +786,7 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc return true; case SSC_STRRANDRANGE: - sVal = g_Exp.GetRangeString(ptcKey); + sVal = CExpression::GetExprParser().GetRangeString(ptcKey); return true; case SSC_StrPos: @@ -1002,10 +1011,10 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc for (tchar *iSeperator = iSep + strlen(iSep) - 1; iSeperator > iSep; --iSeperator) *iSeperator = '\0'; - tchar *pArgs = Str_UnQuote(ppArgs[0]); + tchar *pScriptArgs = Str_UnQuote(ppArgs[0]); sVal.Clear(); tchar *ppCmd[255]; - int count = Str_ParseCmdsAdv(pArgs, ppCmd, ARRAY_COUNT(ppCmd), iSep); //Remove unnecessary chars from seperator to avoid issues. + int count = Str_ParseCmdsAdv(pScriptArgs, ppCmd, ARRAY_COUNT(ppCmd), iSep); //Remove unnecessary chars from seperator to avoid issues. tchar *ppArrays[2]; //Getting range of array index... @@ -1026,14 +1035,12 @@ bool CScriptObj::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc { if (iValue > count) return false; - else if (iValue == iValueEnd) { + else if (iValue == iValueEnd) sVal.Format(ppCmd[iValue - 1]); - } else { sVal.Add(ppCmd[iValue - 1]); - int64 i = iValue + 1; - for ( ; i <= iValueEnd; ++i) + for (int64 i = iValue + 1 ; i <= iValueEnd; ++i) { sVal.Add(iSep); sVal.Add(ppCmd[i - 1]); @@ -1282,7 +1289,7 @@ bool CScriptObj::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command f { ptcKey = s.GetArgStr(); SKIP_SEPARATORS(ptcKey); - g_Exp.m_VarGlobals.ClearKeys(ptcKey); + g_ExprGlobals.mtEngineLockedWriter()->m_VarGlobals.ClearKeys(ptcKey); return true; } @@ -1495,578 +1502,7 @@ bool CScriptObj::r_Load( CScript & s ) return true; } - -bool CScriptObj::_Evaluate_Conditional_EvalSingle(SubexprData& sdata, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, std::shared_ptr pContext) -{ - ADDTOCALLSTACK("CScriptObj::_Evaluate_Conditional_EvalSingle"); - ASSERT(sdata.ptcStart); - ASSERT(sdata.ptcEnd); - using SType = SubexprData::Type; - bool fVal; - lptstr ptcSubexpr; - - // Evaluate the subexpression body - if (pContext->_iEvaluate_Conditional_Reentrant >= 16) - { - g_Log.EventError("Exceeding the limit of 16 subexpressions. Further parsing is halted.\n"); - return false; - } - ++ pContext->_iEvaluate_Conditional_Reentrant; - - // Is this conditional expression is fully enclosed by brackets ? - const bool fFullyEnclosed = (sdata.uiType & SType::TopParenthesizedExpr); - - // Length to copy: include the last valid char (i'm not copying the subsequent char, which can be another char or '\0' - ASSERT(sdata.ptcEnd >= sdata.ptcStart); - size_t len = std::min(Str_TempLength() - 1U, size_t(sdata.ptcEnd - sdata.ptcStart)); - if (len == 0) - { - g_Log.EventError("Empty subexpression. Defaulting its value to false.\n"); - return false; - } - - lptstr ptcParsingStart = sdata.ptcStart; - if (fFullyEnclosed) - { - -- len; // Exclude the closing bracket ')'. - ASSERT(len > 0); - - // In this case, we need to start parsing after the opening parenthesis '('; if we start before it and the subexpr is marked with MaybeNestedSubexpr, - // Evaluate_Conditional will again return the same subexpression fully enclosed by parenthesis, and we'll have a deadlock. - // Remember that sdata.uiNonAssociativeOffset is the distance between the open bracket '(' and the non-associative operator (negation operator '!'). - // The string might start with said non-associative operator. - ptcParsingStart += 1; - len -= 1; - } - - ASSERT(len < Str_TempLength()); - ptcSubexpr = Str_GetTemp(); - memcpy(ptcSubexpr, ptcParsingStart, len); - ptcSubexpr[len] = '\0'; - - const bool fNested = (sdata.uiType & SType::MaybeNestedSubexpr); - if (fNested) - { - // Probably this subexpression has other conditional subexpressions inside. - fVal = Evaluate_Conditional(ptcSubexpr, pSrc, pArgs); - } - else - { - // If an expression is enclosed by parentheses, ParseScriptText needs to read both the open and the closed one, we cannot - // pass the string starting with the character after the '('. - ParseScriptText(ptcSubexpr, pSrc, 0, pArgs); - fVal = bool(Exp_GetLLVal(ptcSubexpr)); - } - - -- pContext->_iEvaluate_Conditional_Reentrant; - - - // Apply non-associative operators preceding the subexpression - if (sdata.uiNonAssociativeOffset) - { - ptcSubexpr = sdata.ptcStart - sdata.uiNonAssociativeOffset; - ASSERT(!IsWhitespace(*ptcSubexpr)); - while (const tchar chOperator = *ptcSubexpr) - { - if (chOperator == '!') - fVal = !fVal; - else if (IsWhitespace(chOperator)) - ; // Allowed, skip it - else - break; - ++ptcSubexpr; - } - } - - return fVal; -} - -bool CScriptObj::Evaluate_Conditional(lptstr ptcExpr, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, std::shared_ptr pContext) -{ - ADDTOCALLSTACK("CScriptObj::Evaluate_Conditional"); - - //g_Log.EventDebug("\nEvaluating conditional expression: \"%s\"\n", ptcExpr); - - SubexprData psSubexprData[32]{}; - lptstr ptcExprDbg = ptcExpr; - const int iQty = CExpression::GetConditionalSubexpressions(ptcExprDbg, psSubexprData, ARRAY_COUNT(psSubexprData)); // number of arguments - - /*g_Log.EventDebug("---Qty: %d\n", iQty); - for (int i = 0; i < iQty; ++i) - g_Log.EventDebug("---Subexpr %d: \"%.*s\"\n", i, (psSubexprData[i].ptcEnd - psSubexprData[i].ptcStart), psSubexprData[i].ptcStart); - */ - - if (iQty == 0) - return 0; - - using SType = SubexprData::Type; - - if (iQty == 1) - { - // We don't have subexpressions, but only a simple expression. - SubexprData& sCur = psSubexprData[0]; - ASSERT((sCur.uiType & SType::None) || (sCur.uiType & SType::BinaryNonLogical)); - - const bool fVal = _Evaluate_Conditional_EvalSingle(sCur, pSrc, pArgs, pContext); - return fVal; - } - - // We have some subexpressions, connected between them by logical operators. - - bool fWholeExprVal = false; - for (int i = 0; i < iQty; ++i) - { - SubexprData& sCur = psSubexprData[i]; - ASSERT(sCur.uiType != SType::Unknown); - - if (i == 0) - { - fWholeExprVal = _Evaluate_Conditional_EvalSingle(sCur, pSrc, pArgs, pContext); - continue; - } - - SubexprData& sPrev = psSubexprData[i - 1]; - if (sPrev.uiType & SType::Or) - { - if (fWholeExprVal) - return true; - - const bool fVal = _Evaluate_Conditional_EvalSingle(sCur, pSrc, pArgs, pContext); - fWholeExprVal = fWholeExprVal || fVal; - } - else if (sPrev.uiType & SType::And) - { - if (!fWholeExprVal) - return false; - - const bool fVal = _Evaluate_Conditional_EvalSingle(sCur, pSrc, pArgs, pContext); - fWholeExprVal = (i == 1) ? fVal : (fWholeExprVal && fVal); - } - - - if (sCur.uiType & SType::None) - { - ASSERT(i == iQty - 1); // It should be the last subexpression - ASSERT((sPrev.uiType & SType::Or) || (sPrev.uiType & SType::And)); - } - - } - - return fWholeExprVal; -} - -static void Evaluate_QvalConditional_ParseArg(tchar* ptcSrc, tchar** ptcDest, lpctstr ptcSep) -{ - ASSERT(ptcSep && *ptcSep); - - // Check if we are encountering a a nested QVAL? - tchar* ptcBracketPos = nullptr; - tchar* ptcSepPos = nullptr; - for (tchar* ptcLine = ptcSrc; *ptcLine != '\0';) - { - const tchar ch = *ptcLine; - if ((ch != '<') && (ch != *ptcSep)) - { - ++ptcLine; - continue; - } - - if ((ch == '<') && !ptcBracketPos) - { - tchar* ptcTest = ptcLine + 1; - GETNONWHITESPACE(ptcTest); - if (!strnicmp("QVAL", ptcTest, 4)) - { - ptcBracketPos = ptcLine; - ptcLine = ptcTest + 3; - } - } - else if ((ch == *ptcSep) && !ptcSepPos) - { - ptcSepPos = ptcLine; - } - - if (!ptcBracketPos || !ptcSepPos) - { - ++ptcLine; - continue; - } - - if (ptcSepPos < ptcBracketPos) - { - // The separator we have found is before the nested QVAL. - ptcSrc = ptcSepPos; - break; - } - - // Found a nested QVAL. Skip it, otherwise we'll catch the wrong separator - Str_SkipEnclosedAngularBrackets(ptcBracketPos); - if (ptcBracketPos <= ptcLine) - ++ptcLine; - else - ptcSrc = ptcLine = ptcBracketPos; - - ptcBracketPos = ptcSepPos = nullptr; - } - - Str_Parse(ptcSrc, ptcDest, ptcSep); -} - -bool CScriptObj::Evaluate_QvalConditional(lpctstr ptcKey, CSString& sVal, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, std::shared_ptr pContext) -{ - ADDTOCALLSTACK("CScriptObj::Evaluate_QvalConditional"); - // Do a switch ? type statement - - // Do NOT work on the original arguments, it WILL fuck up the original string! - tchar* ptcArgs = Str_GetTemp(); - Str_CopyLimitNull(ptcArgs, ptcKey, Str_TempLength()); - - // We only partially evaluated the QVAL parameters (it's a special case), so we need to parse the expressions (still have angular brackets at this stage) - tchar* ppCmds[3]; - ppCmds[0] = ptcArgs; - - // Get the condition - Evaluate_QvalConditional_ParseArg(ppCmds[0], &(ppCmds[1]), "?"); - - // Get the first and second retvals - Evaluate_QvalConditional_ParseArg(ppCmds[1], &(ppCmds[2]), ":"); - - // Complete evaluation of the condition - // (do that in another string, since it may overwrite the arguments, which are written later in the same string). - tchar* ptcTemp = Str_GetTemp(); - Str_CopyLimitNull(ptcTemp, ppCmds[0], Str_TempLength()); - ParseScriptText(ptcTemp, pSrc, 0, pArgs, pContext); - const bool fCondition = Exp_GetLLVal(ptcTemp); - - // Get the retval we want - // (we might as well work on the transformed original string, since at this point we don't care if we corrupt other arguments) - ptcTemp = ppCmds[(fCondition ? 1 : 2)]; - ParseScriptText(ptcTemp, pSrc, 0, pArgs, pContext); - - sVal = ptcTemp; - if (sVal.IsEmpty()) - sVal.Clear(); - return true; -} - -int CScriptObj::ParseScriptText(tchar * ptcResponse, CTextConsole * pSrc, int iFlags, CScriptTriggerArgs * pArgs, std::shared_ptr pContext) -{ - ADDTOCALLSTACK("CScriptObj::ParseScriptText"); - //ASSERT(ptcResponse[0] != ' '); // Not needed: i remove whitespaces and invalid characters here. - - // Take in a line of text that may have fields that can be replaced with operators here. - // ARGS: - // iFlags & 1: Use HTML-compatible delimiters (%). Inside those, angular brackets are allowed to do nested evaluations. - // iFlags & 2: Don't allow recusive bracket count. - // iFlags & 4: Just parsing a nested QVAL. - // NOTE: - // html will have opening - // RETURN: - // iFlags & 4: Position of the ending bracket/delimiter of a QVAL statement. - // Otherwise: New length of the string. - - // Recursion control variables. - // _iParseScriptText_Reentrant = 0; - // _fParseScriptText_Brackets = false; // Am i evaluating a statement? (Am i inside < > brackets of a statement i am currently evaluating?) - - ASSERT(pContext->_fParseScriptText_Brackets == false); - const bool fNoRecurseBrackets = ((iFlags & 2) != 0); - - // General purpose variables. - const bool fHTML = ((iFlags & 1) != 0); - - // If we are parsing a string from a HTML file, we are using '%' as a delimiter for Sphere expressions, since < > are reserved characters in HTML. - const tchar chBegin = fHTML ? '%' : '<'; - const tchar chEnd = fHTML ? '%' : '>'; - - // Variables used to handle the QVAL special case and do lazy evaluation, instead of fully evaluating the whole string on the first pass. - // As an aftertought QVAL parsing could have been moved into a separate function, but it's intricate enough and it's working, so let's leave as it is...' - enum class QvalStatus { None, Condition, Returns, End } eQval = QvalStatus::None; - int iQvalOpenBrackets = 0; - - size_t uiSubstitutionBegin = 0; - int i = 0; - EXC_TRY("ParseScriptText Main Loop"); - for ( i = 0; ptcResponse[i] != '\0'; ++i) - { - const tchar ch = ptcResponse[i]; - - // Are we looking for the current statement start? - if ( !pContext->_fParseScriptText_Brackets) // not in brackets - { - if ( ch == chBegin ) // found the start ! - { - const tchar chNext = ptcResponse[i + 1]; - if ((chNext != '<') && !IsAlnum(chNext)) - continue; // Ignore this, it might be a operator like <= - if ((chBegin == '<') && (chNext == '<')) - { - // Is a << operator? I want a whitespace after the operator. - if ((ptcResponse[i + 2] != '\0') && (ptcResponse[i + 3] != '\0') && IsWhitespace(ptcResponse[i + 2])) - { - lpctstr ptcOpTest = &(ptcResponse[4]); - if (*ptcOpTest != '\0') - { - GETNONWHITESPACE(ptcOpTest); - if (*ptcOpTest != '\0') // There's more text to parse - { - // I guess i have sufficient proof: skip, it's a << operator - i += 2; // Skip < and the whitespace - continue; - } - } - } - } - - // Set the statement start - ASSERT(i >= 0); - uiSubstitutionBegin = (size_t)i; - pContext->_fParseScriptText_Brackets = true; - - // Set-up to process special statements: is it a QVAL? - const bool fIsQval = !strnicmp(ptcResponse + i + 1, "QVAL", 4); - if (fIsQval) - { - ++iQvalOpenBrackets; - eQval = QvalStatus::Condition; - - i += 4; - } - } - - continue; - } - - // Are we inside a QVAL and are we searching where its condition end? - if ((ch == '?') && (eQval == QvalStatus::Condition)) - { - // Now we keep the bracket count to find the closing bracket for the QVAL statement. - eQval = QvalStatus::Returns; - continue; - } - - // Handle possibly recursive angular brackets (i'm already inside an open bracket) - if (pContext->_fParseScriptText_Brackets && (ch == '<')) - { - const tchar chNext = ptcResponse[i + 1]; - if (chNext == '<') - { - // Nested angular brackets? like: <> - lptstr ptcTestNested = ptcResponse + i; - lpctstr ptcTestOrig = ptcTestNested; - Str_SkipEnclosedAngularBrackets(ptcTestNested); - // If i have matching closing brackets, so it must be nested angular brackets. - if (ptcTestNested == ptcTestOrig) - { - // Otherwise, it might be the << operator. - - // This shouldn't be necessary... but - /* - // Is a << operator? I want a whitespace after the operator. - if ((ptcResponse[i + 2] != '\0') && (ptcResponse[i + 3] != '\0') && IsWhitespace(ptcResponse[i + 2])) - { - lpctstr ptcOpTest = &(ptcResponse[4]); - if (*ptcOpTest != '\0') - { - GETNONWHITESPACE(ptcOpTest); - if (*ptcOpTest != '\0') // There's more text to parse - { - // I guess i have sufficient proof: skip, it's a << operator - i += 2; // Skip < and the whitespace - pContext->_fParseScriptText_Brackets = false; - continue; - } - } - } - // Print an error! I thought it was a << operator but it is not! What's happening here ?! - */ - - ++i; - continue; - } - } - - // Detect nested QVALs - if (eQval != QvalStatus::None) - { - const bool fIsQval = !strnicmp(ptcResponse + i + 1, "QVAL", 4); - if (fIsQval) - { - // Nested QVAL... Needs to be evaluated separately, but we only want to know where it ends. - ASSERT(pContext->_fParseScriptText_Brackets == true); - ++ pContext->_iParseScriptText_Reentrant; - pContext->_fParseScriptText_Brackets = false; - - tchar* ptcRecurseParse = ptcResponse + i; - const int iLen = ParseScriptText(ptcRecurseParse, pSrc, 4, pArgs); - - pContext->_fParseScriptText_Brackets = true; - -- pContext->_iParseScriptText_Reentrant; - - i += iLen; - continue; - } - - // At this point, we shouldn't face nested QVALs. - - // I'm inside a QVAL. I can be parsing the condition or the return values. - if (eQval == QvalStatus::Returns) // I'm after its condition (so after '?'), thus i'm parsing the return values. - ++iQvalOpenBrackets; - - // Halt here the evaluation of the stuff inside this open bracket, since i don't want to know what's inside. - continue; - } - - if (pContext->_iParseScriptText_Reentrant > 32 ) - { - EXC_SET_BLOCK("recursive brackets limit"); - ASSERT_ALWAYS(pContext->_iParseScriptText_Reentrant < 32); - } - - ASSERT(pContext->_fParseScriptText_Brackets == true); - ++pContext->_iParseScriptText_Reentrant; - pContext->_fParseScriptText_Brackets = false; - - // Parse what's inside the open bracket - tchar* ptcRecurseParse = ptcResponse + i; - const int iLen = ParseScriptText(ptcRecurseParse, pSrc, 2, pArgs ); - - pContext->_fParseScriptText_Brackets = true; - --pContext->_iParseScriptText_Reentrant; - - i += iLen; - continue; - } - - // At this point i'm sure that ahead we won't find other open angular brackets, we may find their closing one or just plain text. - if ( ch == chEnd ) - { - // Closing bracket found: should we evaluate what's inside the brackets? - if (eQval != QvalStatus::None) - { - // Special handling for QVAL - if (eQval == QvalStatus::Returns) - { - // I'm after the '?' symbol in QVAL. We are searching for the closing bracket. - --iQvalOpenBrackets; - - if (iQvalOpenBrackets == 0) - { - // End of the QVAL statement. - if (iFlags & 04) - { - // I was just checking for the QVAL statement end. - ASSERT(pContext->_fParseScriptText_Brackets == true); - pContext->_fParseScriptText_Brackets = false; - return i; - } - - // Proceed, so we can execute it (do not 'continue'). - eQval = QvalStatus::End; - } - else - { - // Still inside QVAL, just go ahead. - continue; - } - } - else - { - // I'm before the '?' symbol in QVAL and i'm still searching for it, so we know when the conditional expression ends - continue; // Ignore brackets, i want only the ? symbol. - } - } - - - // If i'm here it means that finally i'm at the end of the statement inside brackets. - pContext->_fParseScriptText_Brackets = false; // Close the statement. - - if ((eQval == QvalStatus::End) && (iQvalOpenBrackets != 0)) - { - // I had an incomplete QVAL statement. - g_Log.EventError("QVAL parameters after '?' have unmatched '%c'.\n", ((iQvalOpenBrackets < 0) ? '<' : '>')); - } - - // Complete the evaluation of our string - //-- Write to our temporary sVal the evaluated script - EXC_SET_BLOCK("writeval"); - - ptcResponse[i] = '\0'; // Needed for r_WriteVal - lpctstr ptcKey = ptcResponse + uiSubstitutionBegin + 1; // move past the opening bracket - - CSString sVal; - bool fRes; - if (eQval != QvalStatus::None) - { - // Separate evaluation for QVAL. I may need additional script context for it (pArgs isn't available in r_WriteVal). - EXC_SET_BLOCK("writeval qval"); - ptcKey += 4; // Skip the letters QVAL and pass only the arguments - fRes = Evaluate_QvalConditional(ptcKey, sVal, pSrc, pArgs, pContext); - eQval = QvalStatus::None; - } - else - { - // Standard evaluation for everything else - EXC_SET_BLOCK("writeval generic"); - fRes = r_WriteVal(ptcKey, sVal, pSrc); - if (fRes == false) - { - EXC_SET_BLOCK("writeval args"); - // write the value of functions or triggers variables/objects like ARGO, ARGN1/2/3, LOCALs... - if ((pArgs != nullptr) && pArgs->r_WriteVal(ptcKey, sVal, pSrc)) - fRes = true; - } - } - - - if ( fRes == false ) - { - DEBUG_ERR(( "Can't resolve <%s>.\n", ptcKey )); - // Just in case this really is a <= operator ? - ptcResponse[i] = chEnd; // it's the char we overwrote with '\0' - } - - if (fHTML && sVal.IsEmpty()) - { - sVal = " "; - } - - //-- In the output string, substitute the raw substring with its parsed value - EXC_SET_BLOCK("mem shifting"); - - const size_t uiWriteValLen = sVal.GetLength(); - - // Make room for the obtained value, moving to left (if it's shorter than the scripted statement) or right (if longer) the string characters after it. - tchar* ptcDest = ptcResponse + uiSubstitutionBegin + uiWriteValLen; // + iWriteValLen because we need to leave the space for the replacing keyword - const tchar * const ptcLeftover = ptcResponse + i + 1; // End of the statement we just evaluated - const size_t uiLeftoverLen = strlen(ptcLeftover) + 1; - memmove(ptcDest, ptcLeftover, uiLeftoverLen); - - // Insert the obtained value in the room we created. - ptcDest = ptcResponse + uiSubstitutionBegin; - memcpy(ptcDest, sVal.GetBuffer(), uiWriteValLen); - - // This can be negative. - i = (int)(uiSubstitutionBegin + uiWriteValLen) - 1; - - if (fNoRecurseBrackets) // just do this one then bail out. - { - pContext->_fParseScriptText_Brackets = false; - return i; - } - } - } - EXC_CATCH; - - EXC_DEBUG_START; - g_Log.EventDebug("response '%s' source addr '0%p' flags '%d' args '%p'\n", ptcResponse, static_cast(pSrc), iFlags, static_cast(pArgs)); - EXC_DEBUG_END; - - pContext->_fParseScriptText_Brackets = false; - return i; -} - -bool CScriptObj::Execute_Call(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs) +bool CScriptObj::Execute_Call(CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc) { ADDTOCALLSTACK("CScriptObj::Execute_Call"); bool fRes = false; @@ -2077,7 +1513,7 @@ bool CScriptObj::Execute_Call(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs // Parse object references, src.* is not parsed // by r_GetRef so do it manually - r_GetRef(const_cast(static_cast(argRaw)), pRef); + r_GetRef(const_cast(reinterpret_cast(argRaw)), pRef); if (!strnicmp("SRC.", argRaw, 4)) { argRaw += 4; @@ -2099,33 +1535,33 @@ bool CScriptObj::Execute_Call(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs if (z && *z) { - int64 iN1 = pArgs->m_iN1; - int64 iN2 = pArgs->m_iN2; - int64 iN3 = pArgs->m_iN3; - CScriptObj* pO1 = pArgs->m_pO1; - CSString s1 = pArgs->m_s1; - CSString s1_raw = pArgs->m_s1_buf_vec; - pArgs->m_v.clear(); - pArgs->Init(z); - - fRes = pRef->r_Call(argRaw, pSrc, pArgs, &sVal); - - pArgs->m_iN1 = iN1; - pArgs->m_iN2 = iN2; - pArgs->m_iN3 = iN3; - pArgs->m_pO1 = pO1; - pArgs->m_s1 = s1; - pArgs->m_s1_buf_vec = s1_raw; - pArgs->m_v.clear(); + int64 iN1 = pScriptArgs->m_iN1; + int64 iN2 = pScriptArgs->m_iN2; + int64 iN3 = pScriptArgs->m_iN3; + CScriptObj* pO1 = pScriptArgs->m_pO1; + CSString s1 = pScriptArgs->m_s1; + CSString s1_raw = pScriptArgs->m_s1_buf_vec; + pScriptArgs->m_v.clear(); + pScriptArgs->Init(z); + + fRes = pRef->r_Call(argRaw, pScriptArgs, pSrc, &sVal); + + pScriptArgs->m_iN1 = iN1; + pScriptArgs->m_iN2 = iN2; + pScriptArgs->m_iN3 = iN3; + pScriptArgs->m_pO1 = pO1; + pScriptArgs->m_s1 = s1; + pScriptArgs->m_s1_buf_vec = s1_raw; + pScriptArgs->m_v.clear(); } else - fRes = pRef->r_Call(argRaw, pSrc, pArgs, &sVal); + fRes = pRef->r_Call(argRaw, pScriptArgs, pSrc, &sVal); } return fRes; } -bool CScriptObj::Execute_FullTrigger(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs) +bool CScriptObj::Execute_FullTrigger(CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc) { ADDTOCALLSTACK("CScriptObj::Execute_FullTrigger"); bool fRes = false; @@ -2134,6 +1570,7 @@ bool CScriptObj::Execute_FullTrigger(CScript& s, CTextConsole* pSrc, CScriptTrig tchar* ptcTmp = Str_GetTemp(); Str_CopyLimitNull(ptcTmp, s.GetArgRaw(), Str_TempLength()); int iArgQty = Str_ParseCmds(ptcTmp, piCmd, ARRAY_COUNT(piCmd), " ,\t"); + CScriptObj* pRef = this; if (iArgQty == 2) { @@ -2171,29 +1608,29 @@ bool CScriptObj::Execute_FullTrigger(CScript& s, CTextConsole* pSrc, CScriptTrig if (z && *z) { - int64 iN1 = pArgs->m_iN1; - int64 iN2 = pArgs->m_iN2; - int64 iN3 = pArgs->m_iN3; - CScriptObj* pO1 = pArgs->m_pO1; - CSString s1 = pArgs->m_s1; - CSString s1_raw = pArgs->m_s1_buf_vec; - pArgs->m_v.clear(); - pArgs->Init(z); - - tRet = pRef->OnTrigger(ptcTmp, pSrc, pArgs); - - pArgs->m_iN1 = iN1; - pArgs->m_iN2 = iN2; - pArgs->m_iN3 = iN3; - pArgs->m_pO1 = pO1; - pArgs->m_s1 = s1; - pArgs->m_s1_buf_vec = s1_raw; - pArgs->m_v.clear(); + int64 iN1 = pScriptArgs->m_iN1; + int64 iN2 = pScriptArgs->m_iN2; + int64 iN3 = pScriptArgs->m_iN3; + CScriptObj* pO1 = pScriptArgs->m_pO1; + CSString s1 = pScriptArgs->m_s1; + CSString s1_raw = pScriptArgs->m_s1_buf_vec; + pScriptArgs->m_v.clear(); + pScriptArgs->Init(z); + + tRet = pRef->OnTrigger(ptcTmp, pScriptArgs, pSrc); + + pScriptArgs->m_iN1 = iN1; + pScriptArgs->m_iN2 = iN2; + pScriptArgs->m_iN3 = iN3; + pScriptArgs->m_pO1 = pO1; + pScriptArgs->m_s1 = s1; + pScriptArgs->m_s1_buf_vec = s1_raw; + pScriptArgs->m_v.clear(); } else - tRet = pRef->OnTrigger(ptcTmp, pSrc, pArgs); + tRet = pRef->OnTrigger(ptcTmp, pScriptArgs, pSrc); - pArgs->m_VarsLocal.SetNum("return", tRet, false); + pScriptArgs->m_VarsLocal.SetNum("return", tRet, false); fRes = (tRet > 0) ? 1 : 0; } @@ -2218,7 +1655,7 @@ bool CScriptObj::OnTriggerFind( CScript & s, lpctstr pszTrigName ) return false; } -TRIGRET_TYPE CScriptObj::OnTriggerScript( CScript & s, lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CScriptObj::OnTriggerScript( CScript & s, lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc) { ADDTOCALLSTACK("CScriptObj::OnTriggerScript"); // look for exact trigger matches @@ -2279,7 +1716,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerScript( CScript & s, lpctstr pszTrigName, CTex TIME_PROFILE_START; } - TRIGRET_TYPE iRet = OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs); + TRIGRET_TYPE iRet = OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc); if ( IsSetEF(EF_Script_Profiler) && pTrig != nullptr ) { @@ -2287,7 +1724,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerScript( CScript & s, lpctstr pszTrigName, CTex TIME_PROFILE_END; llTicksStart = llTicksEnd - llTicksStart; pTrig->total += llTicksStart; - pTrig->average = (pTrig->total/pTrig->called); + pTrig->average = (pTrig->total / pTrig->called); if ( pTrig->max < llTicksStart ) pTrig->max = llTicksStart; if (( pTrig->min > llTicksStart ) || ( !pTrig->min )) @@ -2298,11 +1735,12 @@ TRIGRET_TYPE CScriptObj::OnTriggerScript( CScript & s, lpctstr pszTrigName, CTex return iRet; } -TRIGRET_TYPE CScriptObj::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs) +TRIGRET_TYPE CScriptObj::OnTrigger( lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc) { UnreferencedParameter(pszTrigName); UnreferencedParameter(pSrc); - UnreferencedParameter(pArgs); + UnreferencedParameter(pScriptArgs); + ASSERT(false); // I shouldn't get here? return( TRIGRET_RET_DEFAULT ); } @@ -2344,7 +1782,6 @@ enum SK_TYPE : int }; - lpctstr const CScriptObj::sm_szScriptKeys[SK_QTY+1] = { "BEGIN", @@ -2381,16 +1818,21 @@ lpctstr const CScriptObj::sm_szScriptKeys[SK_QTY+1] = nullptr }; -TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult) +TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult) { ADDTOCALLSTACK("CScriptObj::OnTriggerLoopGeneric"); // loop from start here to the ENDFOR // See WebPageScriptList for dealing with Arrays. - CScriptLineContext StartContext = s.GetContext(); + ASSERT(pScriptArgs); + + CScriptLineContext StartContext = s.GetContext(); CScriptLineContext EndContext(StartContext); int LoopsMade = 0; + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptExprContext context; + if (iType & 8) // WHILE { TemporaryString tsConditionBuf; @@ -2406,14 +1848,16 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol tchar* ptcCond = tsConditionBuf.buffer(); Str_CopyLimitNull(ptcCond, tsOrig.buffer(), tsConditionBuf.capacity()); - ParseScriptText(ptcCond, pSrc, 0, pArgs); + + context = {._pScriptObjI = this}; + expr_parser.ParseScriptText(ptcCond, context, pScriptArgs, pSrc, 0); if (!Exp_GetLLVal(ptcCond)) break; - pArgs->m_VarsLocal.SetNum("_WHILE", iWhile, false); + pScriptArgs->m_VarsLocal.SetNum("_WHILE", iWhile, false); ++iWhile; - TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { EndContext = StartContext; @@ -2430,7 +1874,8 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol } else { - ParseScriptText(s.GetArgStr(), pSrc, 0, pArgs); + context = {._pScriptObjI = this}; + expr_parser.ParseScriptText(s.GetArgStr(), context, pScriptArgs, pSrc, 0); } @@ -2482,8 +1927,8 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol if (g_Cfg.m_iMaxLoopTimes && (LoopsMade >= g_Cfg.m_iMaxLoopTimes)) goto toomanyloops; - pArgs->m_VarsLocal.SetNum(sLoopVar, i, false); - TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + pScriptArgs->m_VarsLocal.SetNum(sLoopVar, i, false); + TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { EndContext = StartContext; @@ -2505,8 +1950,8 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol if (g_Cfg.m_iMaxLoopTimes && (LoopsMade >= g_Cfg.m_iMaxLoopTimes)) goto toomanyloops; - pArgs->m_VarsLocal.SetNum(sLoopVar, i, false); - TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + pScriptArgs->m_VarsLocal.SetNum(sLoopVar, i, false); + TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { EndContext = StartContext; @@ -2553,7 +1998,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol CItem* pItem = AreaItems->GetItem(); if (pItem == nullptr) break; - TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { EndContext = StartContext; @@ -2585,7 +2030,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol continue; if ((iType & 0x20) && (pChar->m_pPlayer == nullptr)) // FORPLAYERS continue; - TRIGRET_TYPE iRet = pChar->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pChar->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { EndContext = StartContext; @@ -2664,7 +2109,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol goto toomanyloops; // Execute script on this object - TRIGRET_TYPE iRet = pObj->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pObj->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { EndContext = StartContext; @@ -2699,7 +2144,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol char funcname[1024]; Str_CopyLimitNull(funcname, ptcArgs, sizeof(funcname)); - TRIGRET_TYPE iRet = CWorldTimedFunctions::Loop(funcname, LoopsMade, StartContext, s, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = CWorldTimedFunctions::Loop(funcname, LoopsMade, StartContext, s, pScriptArgs, pSrc, pResult); if ((iRet != TRIGRET_ENDIF) && (iRet != TRIGRET_CONTINUE)) return iRet; } @@ -2715,7 +2160,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol if (EndContext.m_iOffset <= StartContext.m_iOffset) { // just skip to the end. - TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); if (iRet != TRIGRET_ENDIF) return iRet; } @@ -2725,7 +2170,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopGeneric(CScript& s, int iType, CTextConsol return TRIGRET_ENDIF; } -TRIGRET_TYPE CScriptObj::OnTriggerLoopForCharSpecial(CScript& s, SK_TYPE iCmd, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult) +TRIGRET_TYPE CScriptObj::OnTriggerLoopForCharSpecial(CScript& s, SK_TYPE iCmd, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult) { ADDTOCALLSTACK("CScriptObj::OnTriggerLoopForCharSpecial"); TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; @@ -2735,29 +2180,32 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopForCharSpecial(CScript& s, SK_TYPE iCmd, C { if (s.HasArgs()) { - ParseScriptText(s.GetArgRaw(), pSrc, 0, pArgs); + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptExprContext context = {._pScriptObjI = this}; + + expr_parser.ParseScriptText(s.GetArgRaw(), context, pScriptArgs, pSrc, 0); if (iCmd == SK_FORCHARLAYER) - iRet = pCharThis->OnCharTrigForLayerLoop(s, pSrc, pArgs, pResult, (LAYER_TYPE)s.GetArgVal()); + iRet = pCharThis->OnCharTrigForLayerLoop(s, pScriptArgs, pSrc, pResult, (LAYER_TYPE)s.GetArgVal()); else - iRet = pCharThis->OnCharTrigForMemTypeLoop(s, pSrc, pArgs, pResult, s.GetArgWVal()); + iRet = pCharThis->OnCharTrigForMemTypeLoop(s, pScriptArgs, pSrc, pResult, s.GetArgWVal()); } else { g_Log.EventError("FORCHAR[layer/memorytype] called on char 0%" PRIx32 " (%s) without arguments.\n", (dword)(pCharThis->GetUID()), pCharThis->GetName()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); } } else { g_Log.EventError("FORCHAR[layer/memorytype] called on non-char object '%s'.\n", GetName()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); } return iRet; } -TRIGRET_TYPE CScriptObj::OnTriggerLoopForCont(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult) +TRIGRET_TYPE CScriptObj::OnTriggerLoopForCont(CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult) { ADDTOCALLSTACK("CScriptObj::OnTriggerLoopForCont"); TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; @@ -2772,7 +2220,10 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopForCont(CScript& s, CTextConsole* pSrc, CS TemporaryString tsOrigValue; tchar* ptcOrigValue = tsOrigValue.buffer(); Str_ConcatLimitNull(ptcOrigValue, ppArgs[0], tsOrigValue.capacity()); - ParseScriptText(ptcOrigValue, pSrc, 0, pArgs); + + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptExprContext context = {._pScriptObjI = this}; + expr_parser.ParseScriptText(ptcOrigValue, context, pScriptArgs, pSrc, 0); CUID pCurUid(Exp_GetDWVal(ptcOrigValue)); if (pCurUid.IsValidUID()) @@ -2785,101 +2236,110 @@ TRIGRET_TYPE CScriptObj::OnTriggerLoopForCont(CScript& s, CTextConsole* pSrc, CS CScriptLineContext StartContext = s.GetContext(); CScriptLineContext EndContext = StartContext; - iRet = pContThis->OnGenericContTriggerForLoop(s, pSrc, pArgs, pResult, StartContext, EndContext, ppArgs[1] != nullptr ? Exp_GetVal(ppArgs[1]) : 255); + iRet = pContThis->OnGenericContTriggerForLoop(s, pScriptArgs, pSrc, pResult, StartContext, EndContext, ppArgs[1] != nullptr ? Exp_GetVal(ppArgs[1]) : 255); } else { g_Log.EventError("FORCONT called on invalid uid/invalid container (UID: 0%x).\n", pCurUid.GetObjUID()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); } } else { g_Log.EventError("FORCONT called with invalid arguments (UID: 0%x, LEVEL: %s).\n", pCurUid.GetObjUID(), (ppArgs[1] && *ppArgs[1]) ? ppArgs[1] : "255"); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); } } else { g_Log.EventError("FORCONT called with insufficient arguments.\n"); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); } } else { g_Log.EventError("FORCONT called without arguments.\n"); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); } return iRet; } -TRIGRET_TYPE CScriptObj::OnTriggerLoopForContSpecial(CScript& s, SK_TYPE iCmd, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult) +TRIGRET_TYPE CScriptObj::OnTriggerLoopForContSpecial(CScript& s, SK_TYPE iCmd, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult) { - ADDTOCALLSTACK("CScriptObj::OnTriggerLoopForContSpecial"); - TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; + ADDTOCALLSTACK("CScriptObj::OnTriggerLoopForContSpecial"); + TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; - CObjBase* pObjCont = dynamic_cast (this); - CContainer* pCont = dynamic_cast (this); - if (pObjCont && pCont) - { - if (s.HasArgs()) - { - lpctstr ptcKey = s.GetArgRaw(); - SKIP_SEPARATORS(ptcKey); + CObjBase* pObjCont = dynamic_cast (this); + CContainer* pCont = dynamic_cast (this); + if (!pObjCont || !pCont) + { + g_Log.EventError("FORCONT[id/type] called on non-container object '%s'.\n", GetName()); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); + return iRet; + } - tchar* ppArgs[2]; + if (!s.HasArgs()) + { + g_Log.EventError("FORCONT[id/type] called on container 0%x without arguments.\n", (dword)pObjCont->GetUID()); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); + return iRet; + } - if (Str_ParseCmds(const_cast(ptcKey), ppArgs, ARRAY_COUNT(ppArgs), " \t,") >= 1) - { - TemporaryString tsParsedArg0; - Str_CopyLimitNull(tsParsedArg0.buffer(), ppArgs[0], tsParsedArg0.capacity()); - if ((ParseScriptText(tsParsedArg0.buffer(), pSrc, 0, pArgs) > 0)) - { - TemporaryString tsParsedArg1; - if (ppArgs[1] != nullptr) - { - Str_CopyLimitNull(tsParsedArg1.buffer(), ppArgs[1], tsParsedArg0.capacity()); - if (ParseScriptText(tsParsedArg1.buffer(), pSrc, 0, pArgs) <= 0) - goto forcont_incorrect_args; - } + lptstr ptcKey = s.GetArgRaw(); + SKIP_SEPARATORS(ptcKey); - CScriptLineContext StartContext = s.GetContext(); - CScriptLineContext EndContext(StartContext); - lpctstr ptcParsedArg1 = tsParsedArg1.buffer(); - iRet = pCont->OnContTriggerForLoop(s, pSrc, pArgs, pResult, StartContext, EndContext, - g_Cfg.ResourceGetID(((iCmd == SK_FORCONTID) ? RES_ITEMDEF : RES_TYPEDEF), tsParsedArg0.buffer()), - 0, ((ppArgs[1] != nullptr) ? Exp_GetVal(ptcParsedArg1) : 255)); - } - else - { - forcont_incorrect_args: - g_Log.EventError("FORCONT[id/type] called on container 0%x with incorrect arguments.\n", (dword)pObjCont->GetUID()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); - } - } - else - { - g_Log.EventError("FORCONT[id/type] called on container 0%x with incorrect arguments.\n", (dword)pObjCont->GetUID()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); - } - } - else - { - g_Log.EventError("FORCONT[id/type] called on container 0%x without arguments.\n", (dword)pObjCont->GetUID()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); - } - } - else - { - g_Log.EventError("FORCONT[id/type] called on non-container object '%s'.\n", GetName()); - iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); - } + tchar* ppArgs[2]; + if (Str_ParseCmds(ptcKey, ppArgs, ARRAY_COUNT(ppArgs), " \t,") < 1) + { + g_Log.EventError("FORCONT[id/type] called on container 0%x with incorrect arguments.\n", (dword)pObjCont->GetUID()); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); + return iRet; + } - return iRet; + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptExprContext context = {._pScriptObjI = this}; + + TemporaryString tsParsedArg0; + Str_CopyLimitNull(tsParsedArg0.buffer(), ppArgs[0], tsParsedArg0.capacity()); + if (expr_parser.ParseScriptText(tsParsedArg0.buffer(), context, pScriptArgs, pSrc, 0) <= 0) + { + g_Log.EventError("FORCONT[id/type] called on container 0%x with incorrect argument 0.\n", (dword)pObjCont->GetUID()); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); + return iRet; + } + + context = {._pScriptObjI = this}; + TemporaryString tsParsedArg1; + if (ppArgs[1] != nullptr) + { + Str_CopyLimitNull(tsParsedArg1.buffer(), ppArgs[1], tsParsedArg0.capacity()); + if (expr_parser.ParseScriptText(tsParsedArg1.buffer(), context, pScriptArgs, pSrc, 0) <= 0) + { + g_Log.EventError("FORCONT[id/type] called on container 0%x with incorrect argument 1.\n", (dword)pObjCont->GetUID()); + iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); + return iRet; + } + } + + CScriptLineContext StartContext = s.GetContext(); + CScriptLineContext EndContext(StartContext); + lpctstr ptcParsedArg1 = tsParsedArg1.buffer(); + + const RES_TYPE ridExpectedType = ((iCmd == SK_FORCONTID) ? RES_ITEMDEF : RES_TYPEDEF); + const CResourceID &rid = g_Cfg.ResourceGetID(ridExpectedType, tsParsedArg0.buffer()); + + constexpr dword dwArg = 0; + const int iDescendLevels = ((ppArgs[1] != nullptr) ? Exp_GetVal(ptcParsedArg1) : 255); + iRet = pCont->OnContTriggerForLoop( + s, pScriptArgs, pSrc, pResult, + StartContext, EndContext, + rid, dwArg, iDescendLevels); + + return iRet; } -TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult ) +TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult ) { ADDTOCALLSTACK("CScriptObj::OnTriggerRun"); // ARGS: @@ -2893,16 +2353,12 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo // DEBUGCHECK( this == g_Log.m_pObjectContext ); // all scripts should have args for locals to work. - std::unique_ptr argsEmpty; - if ( !pArgs ) - { - argsEmpty = std::make_unique(); - pArgs = argsEmpty.get(); - } + + ASSERT(pScriptArgs); static constexpr uint g_reentrant_OnTriggerRun_limit = 75; static thread_local size_t g_reentrant_OnTriggerRun = 0; - auto clean_return = [](const TRIGRET_TYPE ret) -> TRIGRET_TYPE { + auto clean_return = [](const TRIGRET_TYPE ret) noexcept -> TRIGRET_TYPE { g_reentrant_OnTriggerRun -= 1; return ret; }; @@ -2917,7 +2373,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo // Script execution is always not threaded action EXC_TRY("TriggerRun"); - bool fSectionFalse = (trigrun == TRIGRUN_SECTION_FALSE || trigrun == TRIGRUN_SINGLE_FALSE); + const bool fSectionFalse = (trigrun == TRIGRUN_SECTION_FALSE || trigrun == TRIGRUN_SINGLE_FALSE); if ( trigrun == TRIGRUN_SECTION_EXEC || trigrun == TRIGRUN_SINGLE_EXEC ) // header was already read in. goto jump_in; @@ -2963,7 +2419,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo EXC_SET_BLOCK("if statement"); do { - iRet = OnTriggerRun( s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult ); + iRet = OnTriggerRun( s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult ); } while ( iRet == TRIGRET_ELSEIF || iRet == TRIGRET_ELSE ); break; case SK_WHILE: @@ -2984,7 +2440,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo case SK_DOSWITCH: case SK_BEGIN: EXC_SET_BLOCK("begin/loop cycle"); - iRet = OnTriggerRun( s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult ); + iRet = OnTriggerRun( s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult ); break; default: break; @@ -3004,34 +2460,34 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo case SK_CONTINUE: return clean_return(TRIGRET_CONTINUE); - case SK_FORITEM: EXC_SET_BLOCK("foritem"); iRet = OnTriggerLoopGeneric(s, 1, pSrc, pArgs, pResult); break; - case SK_FORCHAR: EXC_SET_BLOCK("forchar"); iRet = OnTriggerLoopGeneric(s, 2, pSrc, pArgs, pResult); break; - case SK_FORCLIENTS: EXC_SET_BLOCK("forclients"); iRet = OnTriggerLoopGeneric(s, 0x12, pSrc, pArgs, pResult); break; - case SK_FOROBJ: EXC_SET_BLOCK("forobjs"); iRet = OnTriggerLoopGeneric(s, 3, pSrc, pArgs, pResult); break; - case SK_FORPLAYERS: EXC_SET_BLOCK("forplayers"); iRet = OnTriggerLoopGeneric(s, 0x22, pSrc, pArgs, pResult); break; - case SK_FOR: EXC_SET_BLOCK("for"); iRet = OnTriggerLoopGeneric(s, 4, pSrc, pArgs, pResult); break; - case SK_WHILE: EXC_SET_BLOCK("while"); iRet = OnTriggerLoopGeneric(s, 8, pSrc, pArgs, pResult); break; - case SK_FORINSTANCE:EXC_SET_BLOCK("forinstance"); iRet = OnTriggerLoopGeneric(s, 0x40, pSrc, pArgs, pResult); break; - case SK_FORTIMERF: EXC_SET_BLOCK("fortimerf"); iRet = OnTriggerLoopGeneric(s, 0x100,pSrc, pArgs, pResult); break; + case SK_FORITEM: EXC_SET_BLOCK("foritem"); iRet = OnTriggerLoopGeneric(s, 1, pScriptArgs, pSrc, pResult); break; + case SK_FORCHAR: EXC_SET_BLOCK("forchar"); iRet = OnTriggerLoopGeneric(s, 2, pScriptArgs, pSrc, pResult); break; + case SK_FORCLIENTS: EXC_SET_BLOCK("forclients"); iRet = OnTriggerLoopGeneric(s, 0x12, pScriptArgs, pSrc, pResult); break; + case SK_FOROBJ: EXC_SET_BLOCK("forobjs"); iRet = OnTriggerLoopGeneric(s, 3, pScriptArgs, pSrc, pResult); break; + case SK_FORPLAYERS: EXC_SET_BLOCK("forplayers"); iRet = OnTriggerLoopGeneric(s, 0x22, pScriptArgs, pSrc, pResult); break; + case SK_FOR: EXC_SET_BLOCK("for"); iRet = OnTriggerLoopGeneric(s, 4, pScriptArgs, pSrc, pResult); break; + case SK_WHILE: EXC_SET_BLOCK("while"); iRet = OnTriggerLoopGeneric(s, 8, pScriptArgs, pSrc, pResult); break; + case SK_FORINSTANCE:EXC_SET_BLOCK("forinstance"); iRet = OnTriggerLoopGeneric(s, 0x40, pScriptArgs, pSrc, pResult); break; + case SK_FORTIMERF: EXC_SET_BLOCK("fortimerf"); iRet = OnTriggerLoopGeneric(s, 0x100,pScriptArgs, pSrc, pResult); break; case SK_FORCHARLAYER: case SK_FORCHARMEMORYTYPE: { EXC_SET_BLOCK("forchar[layer/memorytype]"); - iRet = OnTriggerLoopForCharSpecial(s, iCmd, pSrc, pArgs, pResult); + iRet = OnTriggerLoopForCharSpecial(s, iCmd, pScriptArgs, pSrc, pResult); } break; case SK_FORCONT: { EXC_SET_BLOCK("forcont"); - iRet = OnTriggerLoopForCont(s, pSrc, pArgs, pResult); + iRet = OnTriggerLoopForCont(s, pScriptArgs, pSrc, pResult); } break; case SK_FORCONTID: case SK_FORCONTTYPE: { EXC_SET_BLOCK("forcont[id/type]"); - iRet = OnTriggerLoopForContSpecial(s, iCmd, pSrc, pArgs, pResult); + iRet = OnTriggerLoopForContSpecial(s, iCmd, pScriptArgs, pSrc, pResult); } break; case SK_IF: @@ -3044,20 +2500,23 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo { // Parse out any variables in it. (may act like a verb sometimes?) EXC_SET_BLOCK("parsing"); + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptExprContext context = {._pScriptObjI = this}; + if( strchr(s.GetKey(), '<') ) { EXC_SET_BLOCK("parsing <> in a key"); TemporaryString tsBuf; - Str_CopyLimitNull(tsBuf.buffer(), s.GetKey(), tsBuf.capacity()); - Str_ConcatLimitNull(tsBuf.buffer(), " ", tsBuf.capacity()); - Str_ConcatLimitNull(tsBuf.buffer(), s.GetArgRaw(), tsBuf.capacity()); - ParseScriptText(tsBuf.buffer(), pSrc, 0, pArgs); + Str_CopyLimitNull (tsBuf.buffer(), s.GetKey(), tsBuf.capacity()); + Str_ConcatLimitNull(tsBuf.buffer(), " ", tsBuf.capacity()); + Str_ConcatLimitNull(tsBuf.buffer(), s.GetArgRaw(), tsBuf.capacity()); + expr_parser.ParseScriptText(tsBuf.buffer(), context, pScriptArgs, pSrc, 0); s.ParseKey(tsBuf.buffer()); } else { - ParseScriptText( s.GetArgRaw(), pSrc, 0, pArgs ); + expr_parser.ParseScriptText( s.GetArgRaw(), context, pScriptArgs, pSrc, 0); } } } @@ -3095,7 +2554,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo iVal = g_Rand.GetLLVal(iVal); for ( ; ; --iVal ) { - iRet = OnTriggerRun( s, (iVal == 0) ? TRIGRUN_SINGLE_TRUE : TRIGRUN_SINGLE_FALSE, pSrc, pArgs, pResult ); + iRet = OnTriggerRun( s, (iVal == 0) ? TRIGRUN_SINGLE_TRUE : TRIGRUN_SINGLE_FALSE, pScriptArgs, pSrc, pResult ); if ( iRet == TRIGRET_RET_DEFAULT ) continue; if ( iRet == TRIGRET_ENDIF ) @@ -3116,35 +2575,41 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo case SK_IF: { - EXC_SET_BLOCK("if statement"); - // At this point, we have to parse the conditional expression - const lptstr ptcArg = s.GetArgStr(); - bool fTrigger = Evaluate_Conditional(ptcArg, pSrc, pArgs); - bool fBeenTrue = false; - for (;;) - { - iRet = OnTriggerRun( s, fTrigger ? TRIGRUN_SECTION_TRUE : TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult ); - if (( iRet < TRIGRET_ENDIF ) || ( iRet >= TRIGRET_RET_HALFBAKED )) - return clean_return(iRet); - if ( iRet == TRIGRET_ENDIF ) - break; + EXC_SET_BLOCK("if statement"); + // At this point, we have to parse the conditional expression + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptExprContext context = {._pScriptObjI = this}; + + const lptstr ptcArg = s.GetArgStr(); + bool fTrigger = expr_parser.EvaluateConditionalWhole(ptcArg, context, pScriptArgs, pSrc); + bool fBeenTrue = false; + for (;;) + { + iRet = OnTriggerRun( s, fTrigger ? TRIGRUN_SECTION_TRUE : TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult ); + if (( iRet < TRIGRET_ENDIF ) || ( iRet >= TRIGRET_RET_HALFBAKED )) + return clean_return(iRet); + if ( iRet == TRIGRET_ENDIF ) + break; - fBeenTrue |= fTrigger; - if ( fBeenTrue ) - fTrigger = false; - else if ( iRet == TRIGRET_ELSE ) - fTrigger = true; - else if ( iRet == TRIGRET_ELSEIF ) - fTrigger = Evaluate_Conditional(s.GetArgStr(), pSrc, pArgs); - } - } - break; + fBeenTrue |= fTrigger; + if ( fBeenTrue ) + fTrigger = false; + else if ( iRet == TRIGRET_ELSE ) + fTrigger = true; + else if ( iRet == TRIGRET_ELSEIF ) + { + context = {._pScriptObjI = this}; + fTrigger = expr_parser.EvaluateConditionalWhole(s.GetArgStr(), context, pScriptArgs, pSrc); + } + } + } + break; case SK_BEGIN: // Do this block here. { EXC_SET_BLOCK("begin/loop cycle"); - iRet = OnTriggerRun( s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult ); + iRet = OnTriggerRun( s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult ); if ( iRet != TRIGRET_ENDIF ) return clean_return(iRet); } @@ -3152,18 +2617,18 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo default: EXC_SET_BLOCK("parsing standard statement"); - if ( !pArgs->r_Verb(s, pSrc) ) + if ( !pScriptArgs->r_Verb(s, pSrc) ) { bool fRes; if (!strnicmp(s.GetKey(), "CALL", 4)) { EXC_SET_BLOCK("call"); - fRes = Execute_Call(s, pSrc, pArgs); + fRes = Execute_Call(s, pScriptArgs, pSrc); } else if ( !strnicmp(s.GetKey(), "FullTrigger", 11 ) ) { EXC_SET_BLOCK("FullTrigger"); - fRes = Execute_FullTrigger(s, pSrc, pArgs); + fRes = Execute_FullTrigger(s, pScriptArgs, pSrc); } else { @@ -3184,12 +2649,12 @@ TRIGRET_TYPE CScriptObj::OnTriggerRun( CScript &s, TRIGRUN_TYPE trigrun, CTextCo EXC_DEBUG_START; g_Log.EventDebug("key '%s' runtype '%d' pargs '%p' ret '%s' [%p]\n", - s.GetKey(), trigrun, static_cast(pArgs), (pResult == nullptr ? "" : pResult->GetBuffer()), static_cast(pSrc)); + s.GetKey(), trigrun, static_cast(pScriptArgs.get()), (pResult == nullptr ? "" : pResult->GetBuffer()), static_cast(pSrc)); EXC_DEBUG_END; return clean_return(TRIGRET_RET_DEFAULT); } -TRIGRET_TYPE CScriptObj::OnTriggerRunVal( CScript &s, TRIGRUN_TYPE trigrun, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CScriptObj::OnTriggerRunVal( CScript &s, TRIGRUN_TYPE trigrun, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) { // Get the TRIGRET_TYPE that is returned by the script // This should be used instead of OnTriggerRun() when pReturn is not used @@ -3198,7 +2663,7 @@ TRIGRET_TYPE CScriptObj::OnTriggerRunVal( CScript &s, TRIGRUN_TYPE trigrun, CTex CSString sVal; TRIGRET_TYPE tr = TRIGRET_RET_DEFAULT; - OnTriggerRun( s, trigrun, pSrc, pArgs, &sVal ); + OnTriggerRun( s, trigrun, pScriptArgs, pSrc, &sVal ); lpctstr pszVal = sVal.GetBuffer(); if ( pszVal && *pszVal ) diff --git a/src/common/CScriptObj.h b/src/common/CScriptObj.h index df8b0e095..6b6362577 100644 --- a/src/common/CScriptObj.h +++ b/src/common/CScriptObj.h @@ -8,15 +8,16 @@ #include "../common/common.h" -struct SubexprData; class CScript; -class CScriptTriggerArgs; class CTextConsole; class CSFileText; class CSString; class CUID; class CChar; +class CScriptTriggerArgs; +using CScriptTriggerArgsPtr = std::shared_ptr; + enum SK_TYPE : int; @@ -36,7 +37,7 @@ enum TRIGRET_TYPE // trigger script returns. TRIGRET_RET_FALSE = 0, // default return. (script might not have been handled) TRIGRET_RET_TRUE = 1, TRIGRET_RET_DEFAULT, // we just came to the end of the script. - TRIGRET_ENDIF, + TRIGRET_ENDIF, TRIGRET_ELSE, TRIGRET_ELSEIF, TRIGRET_RET_HALFBAKED, @@ -46,14 +47,6 @@ enum TRIGRET_TYPE // trigger script returns. }; -struct ScriptedExprContext -{ - // Recursion counters and state variables - short _iEvaluate_Conditional_Reentrant; - short _iParseScriptText_Reentrant; - bool _fParseScriptText_Brackets; -}; - class CScriptObj { // This object can be scripted. (but might not be) @@ -91,12 +84,6 @@ class CScriptObj */ virtual bool r_WriteVal(lpctstr pKey, CSString& sVal, CTextConsole* pSrc = nullptr, bool fNoCallParent = false, bool fNoCallChildren = false); - /* - * @brief Do the first-level parsing of a script line and eventually replace requested values got by r_WriteVal. - */ - int ParseScriptText( tchar * pszResponse, CTextConsole * pSrc, int iFlags = 0, CScriptTriggerArgs * pArgs = nullptr, - std::shared_ptr pContext = std::make_shared() ); - /* * @brief Execute a script command. * Called when parsing a script section with OnTriggerRun or if issued by a CClient. @@ -125,35 +112,29 @@ class CScriptObj // FUNCTION methods static size_t r_GetFunctionIndex(lpctstr pszFunction); static bool r_CanCall(size_t uiFunctionIndex); - bool r_Call( lpctstr ptcFunction, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * psVal = nullptr, TRIGRET_TYPE * piRet = nullptr ); // Try to execute function - bool r_Call( size_t uiFunctionIndex, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * psVal = nullptr, TRIGRET_TYPE * piRet = nullptr ); // Try to execute function + bool r_Call( lpctstr ptcFunction, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * psVal = nullptr, TRIGRET_TYPE * piRet = nullptr ); // Try to execute function + bool r_Call( size_t uiFunctionIndex, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * psVal = nullptr, TRIGRET_TYPE * piRet = nullptr ); // Try to execute function // Generic section parsing - virtual TRIGRET_TYPE OnTrigger(lpctstr pszTrigName, CTextConsole* pSrc, CScriptTriggerArgs* pArgs = nullptr); + virtual TRIGRET_TYPE OnTrigger(lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); bool OnTriggerFind(CScript& s, lpctstr pszTrigName); - TRIGRET_TYPE OnTriggerScript(CScript& s, lpctstr pszTrigName, CTextConsole* pSrc, CScriptTriggerArgs* pArgs = nullptr); - TRIGRET_TYPE OnTriggerRun(CScript& s, TRIGRUN_TYPE trigger, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pReturn); - TRIGRET_TYPE OnTriggerRunVal(CScript& s, TRIGRUN_TYPE trigger, CTextConsole* pSrc, CScriptTriggerArgs* pArgs); + TRIGRET_TYPE OnTriggerScript(CScript& s, lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); + TRIGRET_TYPE OnTriggerRun(CScript& s, TRIGRUN_TYPE trigger, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pReturn); + TRIGRET_TYPE OnTriggerRunVal(CScript& s, TRIGRUN_TYPE trigger, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); // Special statements private: // While, standard for loop and some special for loops - TRIGRET_TYPE OnTriggerLoopGeneric(CScript& s, int iType, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult); - TRIGRET_TYPE OnTriggerLoopForCharSpecial(CScript& s, SK_TYPE iCmd, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult); - TRIGRET_TYPE OnTriggerLoopForCont(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult); - TRIGRET_TYPE OnTriggerLoopForContSpecial(CScript& s, SK_TYPE iCmd, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult); + TRIGRET_TYPE OnTriggerLoopGeneric(CScript& s, int iType, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult); + TRIGRET_TYPE OnTriggerLoopForCharSpecial(CScript& s, SK_TYPE iCmd, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult); + TRIGRET_TYPE OnTriggerLoopForCont(CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult); + TRIGRET_TYPE OnTriggerLoopForContSpecial(CScript& s, SK_TYPE iCmd, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult); // Special statements - bool _Evaluate_Conditional_EvalSingle(SubexprData& sdata, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, std::shared_ptr pContext); - bool Evaluate_Conditional(lptstr ptcExpression, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, - std::shared_ptr pContext = std::make_shared()); // IF, ELIF, ELSEIF - - bool Evaluate_QvalConditional(lpctstr ptcKey, CSString& sVal, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, std::shared_ptr pContext); - - bool Execute_Call(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs); - bool Execute_FullTrigger(CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs); + bool Execute_Call(CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); + bool Execute_FullTrigger(CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc); // Utilities @@ -168,6 +149,7 @@ class CScriptObj CScriptObj(const CScriptObj& copy) = delete; CScriptObj& operator=(const CScriptObj& other) = delete; + CScriptObj(CScriptObj&& move) = delete; }; #endif // _INC_CSCRIPTOBJ_H diff --git a/src/common/CScriptParserBufs.cpp b/src/common/CScriptParserBufs.cpp new file mode 100644 index 000000000..6bd4bd5a1 --- /dev/null +++ b/src/common/CScriptParserBufs.cpp @@ -0,0 +1,42 @@ +#include "CScriptParserBufs.h" +#include "CLog.h" + + +extern CScriptParserBufs g_ScriptParserBuffers; + +auto CScriptParserBufs::GetCScriptTriggerArgsPtr() -> CScriptTriggerArgsPtr +{ + auto& pool = g_ScriptParserBuffers.m_poolScriptTriggerArgs; + auto ptr = pool.acquireShared(); + if (!pool.isFromPool(ptr)) + { + static_assert(CScriptParserBufs::sm_allow_fallback_objects); +#ifdef _DEBUG + g_Log.EventDebug( + "Requesting CScriptTriggerArgs from an exhausted pool (max size: %" PRIu32 "). Alive new heap-allocated fallback objects: %" PRIu32 ".\n", + pool.sm_pool_size, pool.getFallbackCount()); +#endif + } + + ptr->Clear(); + return ptr; +} + +auto CScriptParserBufs::GetScriptKeyArgBufPtr() -> CScriptKeyArgBufPtr +{ + auto& pool = g_ScriptParserBuffers.m_poolScriptKeyArgBuffers; + auto ptr = pool.acquireUnique(); + if (!pool.isFromPool(ptr)) + { + static_assert(CScriptParserBufs::sm_allow_fallback_objects); +#ifdef _DEBUG + g_Log.EventDebug( + "Requesting CScriptKeyArgBuf from an exhausted pool (max size: %" PRIu32 "). Alive new heap-allocated fallback objects: %" PRIu32 ".\n", + pool.sm_pool_size, pool.getFallbackCount()); +#endif + } + + //ptr.get()->fill('\0'); + //memset(ptr.get(), 0, sizeof(CScriptKeyArgBuf)); + return ptr; +} diff --git a/src/common/CScriptParserBufs.h b/src/common/CScriptParserBufs.h new file mode 100644 index 000000000..048896241 --- /dev/null +++ b/src/common/CScriptParserBufs.h @@ -0,0 +1,46 @@ +#ifndef _INC_CSCRIPTPARSERBUFS_H +#define _INC_CSCRIPTPARSERBUFS_H + +#include "sphere_library/sobjpool.h" +#include "CScriptTriggerArgs.h" + + +/* +class CScriptTriggerArgsPtr : public std::shared_ptr +{ + CScriptTriggerArgsPtr(std::nullptr_t) = delete; +} +*/ + +using CScriptKeyArgBuf = std::array; + +struct CScriptParserBufs +{ + CScriptParserBufs() noexcept = default; + ~CScriptParserBufs() noexcept = default; + + static constexpr bool sm_allow_fallback_objects = true; + + // This is even more expensive to construct, so will definitely benefit a lot from having allocated, cached instances. + using PoolScriptTriggerArgs_t = sl::ObjectPool; + + // Not expensive to construct but frequent allocations and deallocations (like classic strings). + using PoolScriptKeyArgBuf_t = sl::ObjectPool; + + PoolScriptTriggerArgs_t m_poolScriptTriggerArgs; + PoolScriptKeyArgBuf_t m_poolScriptKeyArgBuffers; + + using CScriptTriggerArgsPtr = PoolScriptTriggerArgs_t::SharedPtr_t; + using CScriptKeyArgBufPtr = PoolScriptKeyArgBuf_t::UniquePtr_t; + +public: + [[nodiscard]] static CScriptTriggerArgsPtr GetCScriptTriggerArgsPtr(); + [[nodiscard]] static CScriptKeyArgBufPtr GetScriptKeyArgBufPtr(); +}; + +using CScriptTriggerArgsPtr = CScriptParserBufs::CScriptTriggerArgsPtr; +using CScriptKeyArgBufPtr = CScriptParserBufs::CScriptKeyArgBufPtr; + + + +#endif // _INC_CSCRIPTPARSERBUFS_H diff --git a/src/common/CScriptTriggerArgs.cpp b/src/common/CScriptTriggerArgs.cpp index f967d1d32..216a3aff6 100644 --- a/src/common/CScriptTriggerArgs.cpp +++ b/src/common/CScriptTriggerArgs.cpp @@ -1,14 +1,13 @@ #include "../game/CObjBase.h" #include "CLog.h" -#include "CException.h" -#include "CExpression.h" +//#include "CException.h" // included in the precompiled header +//#include "CExpression.h" // included in the precompiled header #include "CScriptTriggerArgs.h" -CScriptTriggerArgs::CScriptTriggerArgs() : - m_iN1(0), m_iN2(0), m_iN3(0) +CScriptTriggerArgs::CScriptTriggerArgs() noexcept : + m_iN1(0), m_iN2(0), m_iN3(0), m_pO1(nullptr) { - m_pO1 = nullptr; } CScriptTriggerArgs::CScriptTriggerArgs(lpctstr pszStr) @@ -16,25 +15,13 @@ CScriptTriggerArgs::CScriptTriggerArgs(lpctstr pszStr) Init(pszStr); } -CScriptTriggerArgs::CScriptTriggerArgs(CScriptObj* pObj) : +CScriptTriggerArgs::CScriptTriggerArgs(CScriptObj* pObj) noexcept : m_iN1(0), m_iN2(0), m_iN3(0), m_pO1(pObj) { } -CScriptTriggerArgs::CScriptTriggerArgs(int64 iVal1) : - m_iN1(iVal1), m_iN2(0), m_iN3(0) -{ - m_pO1 = nullptr; -} - -CScriptTriggerArgs::CScriptTriggerArgs(int64 iVal1, int64 iVal2, int64 iVal3) : - m_iN1(iVal1), m_iN2(iVal2), m_iN3(iVal3) -{ - m_pO1 = nullptr; -} - -CScriptTriggerArgs::CScriptTriggerArgs(int64 iVal1, int64 iVal2, CScriptObj* pObj) : - m_iN1(iVal1), m_iN2(iVal2), m_iN3(0), m_pO1(pObj) +CScriptTriggerArgs::CScriptTriggerArgs(int64 iVal1, int64 iVal2, int64 iVal3, CScriptObj* pObj) noexcept : + m_iN1(iVal1), m_iN2(iVal2), m_iN3(iVal3), m_pO1(pObj) { } @@ -129,20 +116,35 @@ void CScriptTriggerArgs::Init( lpctstr pszStr ) if ( IsDigit(*pszStr) || ((*pszStr == '-') && IsDigit(*(pszStr+1))) ) { - m_iN1 = Exp_GetSingle(pszStr); + auto& gExprParser = CExpression::GetExprParser(); + m_iN1 = gExprParser.GetSingle(pszStr); SKIP_ARGSEP( pszStr ); if ( IsDigit(*pszStr) || ((*pszStr == '-') && IsDigit(*(pszStr+1))) ) { - m_iN2 = Exp_GetSingle(pszStr); + m_iN2 = gExprParser.GetSingle(pszStr); SKIP_ARGSEP( pszStr ); if ( IsDigit(*pszStr) || ((*pszStr == '-') && IsDigit(*(pszStr+1))) ) { - m_iN3 = Exp_GetSingle(pszStr); + m_iN3 = gExprParser.GetSingle(pszStr); } } } } +void CScriptTriggerArgs::Init(CScriptObj* pObj) +{ + Clear(); + m_pO1 = pObj; +} + +void CScriptTriggerArgs::Init(int64 iVal1, int64 iVal2, int64 iVal3, CScriptObj* pObj) +{ + Clear(); + m_iN1 = iVal1; + m_iN2 = iVal2; + m_iN3 = iVal3; + m_pO1 = pObj; +} void CScriptTriggerArgs::GetArgNs(int64* iVar1, int64* iVar2, int64* iVar3) //Puts the ARGN's into the specified variables { diff --git a/src/common/CScriptTriggerArgs.h b/src/common/CScriptTriggerArgs.h index 72bd8afbd..21c9169cf 100644 --- a/src/common/CScriptTriggerArgs.h +++ b/src/common/CScriptTriggerArgs.h @@ -10,6 +10,7 @@ #include "CLocalVarsExtra.h" #include + class CScriptTriggerArgs : public CScriptObj { // All the args an event will need. @@ -34,25 +35,26 @@ class CScriptTriggerArgs : public CScriptObj CLocalObjMap m_VarObjs; // "REFx" = local object x public: - CScriptTriggerArgs(); + CScriptTriggerArgs() noexcept; explicit CScriptTriggerArgs(lpctstr pszStr); - explicit CScriptTriggerArgs(CScriptObj* pObj); - explicit CScriptTriggerArgs(int64 iVal1); - CScriptTriggerArgs(int64 iVal1, int64 iVal2, int64 iVal3 = 0); - CScriptTriggerArgs(int64 iVal1, int64 iVal2, CScriptObj* pObj); + explicit CScriptTriggerArgs(CScriptObj* pObj) noexcept; + CScriptTriggerArgs(int64 iVal1, int64 iVal2, int64 iVal3, CScriptObj* pObj) noexcept; virtual ~CScriptTriggerArgs() = default; -private: - CScriptTriggerArgs(const CScriptTriggerArgs& copy); - CScriptTriggerArgs& operator=(const CScriptTriggerArgs& other); + CScriptTriggerArgs(const CScriptTriggerArgs& copy) = delete; + CScriptTriggerArgs& operator=(const CScriptTriggerArgs& other) = delete; + CScriptTriggerArgs(CScriptTriggerArgs&& move) = delete; public: //Puts the ARGN's into the specified variables void GetArgNs(int64* iVar1 = nullptr, int64* iVar2 = nullptr, int64* iVar3 = nullptr); void Clear(); - void Init( lpctstr pszStr ); + void Init(lpctstr pszStr); + void Init(CScriptObj* pObj); + void Init(int64 iVal1, int64 iVal2, int64 iVal3, CScriptObj *pObj); + bool r_Verb( CScript & s, CTextConsole * pSrc ) override; bool r_LoadVal( CScript & s ) override; bool r_GetRef( lpctstr & ptcKey, CScriptObj * & pRef ) override; diff --git a/src/common/CServerMap.cpp b/src/common/CServerMap.cpp index 72a684785..7a34074e5 100644 --- a/src/common/CServerMap.cpp +++ b/src/common/CServerMap.cpp @@ -2,7 +2,7 @@ // CServerMap.cpp // -#include "CException.h" +//#include "CException.h" // included in the precompiled header #include "CUOInstall.h" #include "CServerMap.h" #include "../game/uo_files/CUOItemInfo.h" @@ -16,7 +16,6 @@ #include "../sphere/threads.h" - ////////////////////////////////////////////////////////////////// // -CServerMapBlockingState diff --git a/src/common/CUID.h b/src/common/CUID.h index fcd40e6aa..a11bec11e 100644 --- a/src/common/CUID.h +++ b/src/common/CUID.h @@ -33,25 +33,25 @@ class CUID // A unique system serial id. 4 bytes long dword m_dwInternalVal; public: - inline void InitUID() noexcept { + inline constexpr void InitUID() noexcept { m_dwInternalVal = UID_UNUSED; } // Use ClearUID only if the CUID is not used as a pure UID, but it can assume other kind of values. // Example: m_itFigurine.m_UID, m_itKey.m_UIDLock -> a MORE1/MORE2 == 0 is considered legit, also for many many item types MORE* isn't a UID. - inline void ClearUID() noexcept { + inline constexpr void ClearUID() noexcept { m_dwInternalVal = UID_PLAIN_CLEAR; } - inline CUID() noexcept + inline constexpr CUID() noexcept { InitUID(); } - inline CUID(const CUID& uid) noexcept + inline constexpr CUID(const CUID& uid) noexcept { SetPrivateUID(uid.GetPrivateUID()); } - inline explicit CUID(dword dwPrivateUID) noexcept + inline constexpr explicit CUID(dword dwPrivateUID) noexcept { // TODO: directly setting the private UID can led to unexpected results... // it's better to use SetObjUID in order to "filter" the raw value passed. @@ -81,13 +81,13 @@ class CUID // A unique system serial id. 4 bytes long return IsChar(m_dwInternalVal); } - inline bool IsObjDisconnected() const noexcept // Called very frequently + constexpr bool IsObjDisconnected() const noexcept // Called very frequently { // Not in the game world for some reason. return ((m_dwInternalVal & (UID_F_RESOURCE | UID_O_DISCONNECT)) == UID_O_DISCONNECT); } - bool IsObjTopLevel() const noexcept + constexpr bool IsObjTopLevel() const noexcept { // on the ground in the world. // might be static in client ? @@ -101,10 +101,10 @@ class CUID // A unique system serial id. 4 bytes long void RemoveObjFlags(dword dwFlags) noexcept; // Internal UID, which also has special flags not understood by the client. - inline void SetPrivateUID(dword dwVal) noexcept { + inline constexpr void SetPrivateUID(dword dwVal) noexcept { m_dwInternalVal = dwVal; } - inline dword GetPrivateUID() const noexcept { + inline constexpr dword GetPrivateUID() const noexcept { return m_dwInternalVal; } @@ -152,7 +152,10 @@ class CUID // A unique system serial id. 4 bytes long inline CChar* CharFind(bool fInvalidateBeingDeleted = false) const noexcept{ return CharFindFromUID(m_dwInternalVal, fInvalidateBeingDeleted); } - + }; +static constexpr CUID kUIDUninitialized(UID_UNUSED); +static constexpr CUID kUIDEmpty(UID_PLAIN_CLEAR); + #endif // _INC_CUID_H diff --git a/src/common/CUOClientVersion.cpp b/src/common/CUOClientVersion.cpp index e2c38bbb0..49df19943 100644 --- a/src/common/CUOClientVersion.cpp +++ b/src/common/CUOClientVersion.cpp @@ -18,12 +18,19 @@ uint CUOClientVersion::GetLegacyVersionNumber() const noexcept uint factor_revision = 100; uint factor_minor = 100'00; uint factor_major = 100'00'00; - if (m_revision > 100) + + if (m_build >= 100) + { + factor_revision *= 10; + factor_minor *= 10; + factor_major *= 10; + } + if (m_revision >= 100) { factor_minor *= 10; factor_major *= 10; } - if (m_minor > 100) + if (m_minor >= 100) { factor_major *= 10; } @@ -38,24 +45,36 @@ uint CUOClientVersion::GetLegacyVersionNumber() const noexcept std::string CUOClientVersion::GetVersionString() const noexcept { + if (m_major > 1000 || m_minor > 1000 || m_revision > 1000 || m_build > 1000 || m_build_sub > 1000) + return {}; // Invalid at best, a malicious attempt of writing past the buffer at worst. + std::string ret; - ret.reserve(30); + ret.resize(20); - if (*this >= CUOClientVersionConstants::kMinCliver_NewVersioning) + if ( *this >= CUOClientVersionConstants::kMinCliver_ModernVersioning + || *this <= CUOClientVersionConstants::kMinCliver_LetterVersioning) { snprintf(ret.data(), ret.capacity(), "%u.%u.%u.%u", m_major, m_minor, m_revision, m_build); } else { - const int iVer = snprintf(ret.data(), ret.capacity(), "%u.%u.%u", m_major, m_minor, m_revision); - if (m_revision) + // snprintf returns the number of characters that would have been written had n been sufficiently large, + // not counting the terminating null character, or a negative value if an encoding error occurred + int iWrittenChars = snprintf(ret.data(), ret.capacity(), "%u.%u.%u", m_major, m_minor, m_revision); + if (iWrittenChars < 0) + return {}; // ?! + + if (m_build) { - if (m_revision == 0) - ret[iVer] = '0'; - else - ret[iVer] = uchar(m_revision) + uchar('a' - 1); // 'a' = revision 1 - ret[iVer + 1] = '\0'; + ret[iWrittenChars] = uchar(m_build) + uchar('a' - 1); // 'a' = 'patch' 1 + ++iWrittenChars; } + if (m_build_sub) + { + // Append the number to the string buffer + Str_FromUI(m_build_sub, &ret[iWrittenChars], ret.capacity() - (size_t)iWrittenChars - 1); + } + // ret[iVer] = '\0'; // resize already zeroes everything } return ret; @@ -63,15 +82,17 @@ std::string CUOClientVersion::GetVersionString() const noexcept CUOClientVersion::CUOClientVersion(dword uiClientVersionNumber) noexcept : - m_extrachar(0) + m_build_sub(kuiBuildSubCatchAllVal) + // build_sub isn't encoded in the version number, so use a special value to inform + // that we got this CUOClientVersion from a ver number. { // launch some tests for this function - if (uiClientVersionNumber >= CUOClientVersionConstants::kMinCliver_NewVersioning.GetLegacyVersionNumber()) //MINCLIVER_NEWVERSIONING) + if (uiClientVersionNumber >= CUOClientVersionConstants::kMinCliver_ModernVersioning.GetLegacyVersionNumber()) //MINCLIVER_NEWVERSIONING) { // We should really ditch this number, giving the difficulty to parse (imagine that in scripts...). // We should just keep backwards compatibility with CLIVER/CLIVERREPORTED and provide just the string // Maybe CLIVERSTR/CLIVERREPORTEDSTR ? - if (uiClientVersionNumber > 100'00'000'00) + if (uiClientVersionNumber > 10'00'000'00) { // Two extra digits: it's a EC (one more digit used for the bigger major version) with a greater rev number (like 4.00.101.00) m_major = uiClientVersionNumber / 100'000'00; @@ -79,7 +100,7 @@ CUOClientVersion::CUOClientVersion(dword uiClientVersionNumber) noexcept : m_revision = (uiClientVersionNumber / 100 ) % 1000; } - else if (uiClientVersionNumber > 10'00'000'00) + else if (uiClientVersionNumber > 1'00'000'00) { // The extra digit can be used for a bigger major (EC, like 67.01.00.00) or rev number (CC, like 7.101.00.00) const bool fEC = ((uiClientVersionNumber / 1000'00) < kuiECMajorVerOffset); @@ -113,8 +134,8 @@ CUOClientVersion::CUOClientVersion(dword uiClientVersionNumber) noexcept : } } -CUOClientVersion::CUOClientVersion(lpctstr ptcVersion) noexcept : - m_major(0), m_minor(0), m_revision(0), m_build(0), m_extrachar(0) +CUOClientVersion::CUOClientVersion(lpctstr ptcVersion, bool fEnhancedClient) noexcept : + m_major(0), m_minor(0), m_revision(0), m_build(0), m_build_sub(0) { if ((ptcVersion == nullptr) || (*ptcVersion == '\0')) return; @@ -123,13 +144,22 @@ CUOClientVersion::CUOClientVersion(lpctstr ptcVersion) noexcept : Str_CopyLimitNull(ptcVersionBuf, ptcVersion, sizeof(ptcVersionBuf)); // Ranges algorithms not yet supported by Apple Clang... - // const ptrdiff_t count = std::ranges::count(std::string_view(ptcVersion), '.'); + // const size_t count = std::ranges::count(std::string_view(ptcVersion), '.'); const auto svVersion = std::string_view(ptcVersion); const auto count = std::count(svVersion.cbegin(), svVersion.cend(), '.'); if (count == 2) + { + if (fEnhancedClient) + { + g_Log.Event(LOGL_CRIT|LOGM_CLIENTS_LOG|LOGM_NOCONTEXT, "Wrong string passed to CUOClientVersion: Enhanced Client version but with old format?.\n"); + return; + } ApplyVersionFromStringOldFormat(ptcVersionBuf); + } else if (count == 3) - ApplyVersionFromStringNewFormat(ptcVersionBuf); + { + ApplyVersionFromStringNewFormat(ptcVersionBuf, fEnhancedClient); + } else { // Malformed string? @@ -140,18 +170,33 @@ CUOClientVersion::CUOClientVersion(lpctstr ptcVersion) noexcept : bool CUOClientVersion::operator ==(CUOClientVersion const& other) const noexcept { - //return (0 == memcmp(this, &other, sizeof(CUOClientVersion))); const bool fSameVerNum = (m_major == other.m_major) && (m_minor == other.m_minor) && (m_revision == other.m_revision) && (m_build == other.m_build); - return fSameVerNum && (m_extrachar == other.m_extrachar); + + // Ignore this comparison is m_build_sub is kuiBuildSubCatchAllVal + // (so, if one of the two CUOClientVersion was calculated from a version number, which doesn't carry this information) + const bool fSameBuildSub = + ((m_build_sub == kuiBuildSubCatchAllVal) || (other.m_build_sub == kuiBuildSubCatchAllVal)) + ? true : (m_build_sub == other.m_build_sub); + + return fSameVerNum && fSameBuildSub; } bool CUOClientVersion::operator > (CUOClientVersion const& other) const noexcept { if (m_major > other.m_major) return true; + + if (m_major < other.m_major) + return false; if (m_minor > other.m_minor) return true; + + if (m_minor < other.m_minor) + return false; if (m_revision > other.m_revision) return true; + + if (m_revision < other.m_revision) + return false; if (m_build > other.m_build) return true; return false; @@ -169,64 +214,95 @@ bool CUOClientVersion::operator <=(CUOClientVersion const& other) const noexcept return other > *this; } - void CUOClientVersion::ApplyVersionFromStringOldFormat(lptstr ptcVersion) noexcept { // Get version of old clients, which report the client version as ASCII string (eg: '5.0.2b') - byte uiLetter = 0; const size_t uiMax = strnlen(ptcVersion, 20); - for (size_t i = 0; i < uiMax; ++i) + size_t uiLetterPos = 0; + byte uiLetter = 0; + for (; uiLetterPos < uiMax; ++uiLetterPos) { - if (IsAlpha(ptcVersion[i])) + if (IsAlpha(ptcVersion[uiLetterPos])) { - uiLetter = uchar(ptcVersion[i] - 'a') + 1u; + uiLetter = uchar(ptcVersion[uiLetterPos] - 'a') + 1u; break; } } - tchar *piVer[3]; - Str_ParseCmds(ptcVersion, piVer, ARRAY_COUNT(piVer), "."); + tchar *piVer[3]{}; + lptstr ptcVersionParsed = ptcVersion; + Str_ParseCmds(ptcVersionParsed, piVer, ARRAY_COUNT(piVer), "."); // Don't rely on all values reported by client, because it can be easily faked. Injection users can report any // client version they want, and some custom clients may also report client version as "Custom" instead X.X.Xy if (!piVer[0] || !piVer[1] || !piVer[2] || (uiLetter > 26)) + { + g_Log.EventDebug("Invalid version string '%s' passed to CUOClientVersion::ApplyVersionFromStringOldFormat.\n", ptcVersion); return; + } m_major = uint(atoi(piVer[0])); m_minor = uint(atoi(piVer[1])); m_revision = uint(atoi(piVer[2])); m_build = uiLetter; + + ++ uiLetterPos; + if (ptcVersion[uiLetterPos] != '\0') + m_build_sub = uint(atoi(ptcVersion + uiLetterPos)); } -void CUOClientVersion::ApplyVersionFromStringNewFormat(lptstr ptcVersion) noexcept +void CUOClientVersion::ApplyVersionFromStringNewFormat(lptstr ptcVersion, bool fEnhancedClient) noexcept { // Get version of newer clients, which use only 4 numbers separated by dots (example: 6.0.1.1) + constexpr auto np = std::string_view::npos; const std::string_view sv(ptcVersion); + const size_t dot1 = sv.find_first_of('.', 0); + if (dot1 == np) + { + ret_err: + g_Log.EventDebug("Invalid version string '%s' passed to CUOClientVersion::ApplyVersionFromStringNewFormat.\n", ptcVersion); + return; + } + const size_t dot2 = sv.find_first_of('.', dot1 + 1); + if (dot2 == np) + goto ret_err; + const size_t dot3 = sv.find_first_of('.', dot2 + 1); - constexpr auto np = std::string_view::npos; - if (np == dot1 || np == dot2 || np == dot3) - return; + if (dot3 == np) + goto ret_err; - const std::string_view sv1(sv.data(), dot1 - 1); - const std::string_view sv2(sv1.data() + dot1, dot2 - 1); - const std::string_view sv3(sv2.data() + dot2, dot3 - 1); - const std::string_view sv4(sv3.data() + dot3); + const std::string_view sv1(sv.data(), dot1); + const std::string_view sv2(sv.data() + dot1 + 1, dot2 - dot1 - 1); + const std::string_view sv3(sv.data() + dot2 + 1, dot3 - dot2 - 1); + const std::string_view sv4(sv.data() + dot3 + 1); - std::optional val1, val2, val3, val4; bool ok = true; - ok = ok && (val1 = Str_ToU(sv1.data(), 10, true)).has_value(); - ok = ok && (val2 = Str_ToU(sv1.data(), 10, true)).has_value(); - ok = ok && (val3 = Str_ToU(sv1.data(), 10, true)).has_value(); - ok = ok && (val4 = Str_ToU(sv1.data(), 10, true)).has_value(); - if (!ok) - return; + try + { + std::optional val1, val2, val3, val4; + ok = ok && (val1 = Str_ToU(sv1.data(), 10, sv1.length(), false)).has_value(); + ok = ok && (val2 = Str_ToU(sv2.data(), 10, sv2.length(), false)).has_value(); + ok = ok && (val3 = Str_ToU(sv3.data(), 10, sv3.length(), false)).has_value(); + ok = ok && (val4 = Str_ToU(sv4.data(), 10, sv4.length(), false)).has_value(); + if (!ok) + return; + + m_major = val1.value(); + m_minor = val2.value(); + m_revision = val3.value(); + m_build = val4.value(); - m_major = val1.value(); - m_minor = val2.value(); - m_revision = val3.value(); - m_build = val4.value(); + if ((m_major < kuiECMajorVerOffset) && fEnhancedClient) + m_major += kuiECMajorVerOffset; + } + catch (std::bad_optional_access const&) + { + // Shouldn't really happen... + m_major = m_minor = m_revision = m_build = 0; + DEBUG_MSG(("std::bad_optional_access at CUOClientVersion::ApplyVersionFromStringNewFormat.\n")); + } } diff --git a/src/common/CUOClientVersion.h b/src/common/CUOClientVersion.h index 81ce47f0f..048264447 100644 --- a/src/common/CUOClientVersion.h +++ b/src/common/CUOClientVersion.h @@ -11,11 +11,12 @@ struct CUOClientVersion { - static constexpr uint kuiECMajorVerOffset = 63u; + static constexpr uint kuiECMajorVerOffset = 63u; + static constexpr uint kuiBuildSubCatchAllVal = 127; /* Members. */ uint m_major, m_minor, m_revision, m_build /* build or patch, different names for the same thing */; - char m_extrachar; // like 2.0.0[x] or 4.0.4b[2] + uint m_build_sub; // like 2.0.0[x] or 4.0.4b[2] /* Methods. */ bool Valid() const noexcept; @@ -28,23 +29,24 @@ struct CUOClientVersion // This explicit constructor has to be used for new version formats (without the trailing letter, thus clients >= 5.0.6.5, equivalent to > 5.0.6e), // or old versions formats without a letter as build number (it means that build number is 0, because build 'a' is 1). explicit constexpr - CUOClientVersion(const uint major, const uint minor, const uint revision, const uint build, const char extrachar = 0) noexcept : - m_major(major), m_minor(minor), m_revision(revision), m_build(build), m_extrachar(extrachar) + CUOClientVersion(const uint major, const uint minor, const uint revision, const uint build, const uint build_sub = 0) noexcept : + m_major(major), m_minor(minor), m_revision(revision), m_build(build), m_build_sub(build_sub) {} // Equivalent to ReportedCliVer! Offsets already applied! (like kuiECMajorVerOffset). // This explicit constructor has to be used for old version formats (with the trailing letter, thus clients < 5.0.6.5, equivalent to <= 5.0.6e), // if this version has a letter in place of a build number. explicit constexpr - CUOClientVersion(const uint major, const uint minor, const uint revision, const char build, const char extrachar = 0) noexcept : - m_major(major), m_minor(minor), m_revision(revision), m_build(build - 'a'), m_extrachar(extrachar) + CUOClientVersion(const uint major, const uint minor, const uint revision, const char build, const uint build_sub = 0) noexcept : + m_major(major), m_minor(minor), m_revision(revision), m_build(build - 'a' + 1), m_build_sub(build_sub) {} CUOClientVersion(dword uiClientVersionNumber) noexcept; - CUOClientVersion(lpctstr ptcVersionString) noexcept; + CUOClientVersion(lpctstr ptcVersionString, bool fEnhancedClient = false) noexcept; /* Operators. */ bool operator ==(CUOClientVersion const& other) const noexcept; + //bool operator == (auto const& other) const noexcept = delete; bool operator > (CUOClientVersion const& other) const noexcept; bool operator >=(CUOClientVersion const& other) const noexcept; bool operator < (CUOClientVersion const& other) const noexcept; @@ -53,7 +55,7 @@ struct CUOClientVersion /* Private methods. */ private: void ApplyVersionFromStringOldFormat(lptstr ptcVersion) noexcept; - void ApplyVersionFromStringNewFormat(lptstr ptcVersion) noexcept; + void ApplyVersionFromStringNewFormat(lptstr ptcVersion, bool fEnhancedClient) noexcept; }; @@ -78,6 +80,7 @@ struct CUOClientVersionConstants SCDECL kMinCliver_StatusV6 = CUOClientVersion(7u, 0u, 30u, 0u); // minimum client to receive v6 of 0x11 packet (7.0.30.0), old vernum 7003000 /* Client versions (behaviours) */ + SCDECL kMinCliver_LetterVersioning = CUOClientVersion(1u, 25u, 35u, 0u); // after this client, the versioning style will start to use letters for the last version number (patch) SCDECL kMinCliver_CheckWalkCode = CUOClientVersion(1u, 26u, 0u, 0u); // minimum client to use walk crypt codes for fastwalk prevention (obsolete), old vernum 1260000 SCDECL kMinCliver_PadCharList = CUOClientVersion(3u, 0u, 0u, 'j'); // minimum client to pad character list to at least 5 characters, old vernum 3000010 SCDECL kMinCliver_AutoAsync = CUOClientVersion(4u, 0u, 0u, 0u); // minimum client to auto-enable async networking, old vernum 4000000 @@ -85,7 +88,7 @@ struct CUOClientVersionConstants SCDECL kMinCliver_SkillCaps = CUOClientVersion(4u, 0u, 0u, 0u); // minimum client to send skill caps in 0x3A packet, old vernum 4000000 SCDECL kMinCliver_CloseDialog = CUOClientVersion(4u, 0u, 4u, 0u); // minimum client where close dialog does not trigger a client response, old vernum 4000400 SCDECL kMinCliver_CompressDialog = CUOClientVersion(5u, 0u, 0u, 0u); // minimum client to receive zlib compressed dialogs(5.0.0a), old vernum 5000000 - SCDECL kMinCliver_NewVersioning = CUOClientVersion(5u, 0u, 6u, 5u); // minimum client to use the new versioning format (after 5.0.6e it change to 5.0.6.5), old vernum 5000605 + SCDECL kMinCliver_ModernVersioning = CUOClientVersion(5u, 0u, 7u, 0u); // minimum client to use the new versioning format (after 5.0.6e it change to 5.0.7.0), old vernum 5000605 SCDECL kMinCliver_ItemGrid = CUOClientVersion(6u, 0u, 1u, 7u); // minimum client to use grid index (6.0.1.7), old vernum 6000107 SCDECL kMinCliver_NewChatSystemCC = CUOClientVersion(7u, 0u, 4u, 1u); // minimum client to use the new chat system (7.0.4.1) - classic client, old vernum 7000401 SCDECL kMinCliver_GlobalChat = CUOClientVersion(7u, 0u, 62u, 2u); // minimum client to use global chat system (7.0.62.2), old vernum 7006202 @@ -93,7 +96,7 @@ struct CUOClientVersionConstants SCDECL kMinCliver_MapWaypoint = CUOClientVersion(7u, 0u, 84u, 0u); // minimum client to use map waypoints on classic client (7.0.84.0), old vernum 7008400 // minimum client to use the new chat system (4.0.4.0) - enhanced client, old vernum 4000400 - SCDECL kMinCliverEC_NewChatSystem = CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 0u, 4u, 0u); + SCDECL kMinCliverEC_NewChatSystem = CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 0u, 4u, 0u); /* Client versions (packets) */ SCDECL kMinCliver_ReverseIP = CUOClientVersion(4u, 0u, 0u, 0u); // maximum client to reverse IP in 0xA8 packet, old vernum 4000000 diff --git a/src/common/CUOInstall.cpp b/src/common/CUOInstall.cpp index f09727661..6a588389a 100644 --- a/src/common/CUOInstall.cpp +++ b/src/common/CUOInstall.cpp @@ -1,7 +1,7 @@ #include "../game/uo_files/CUOMultiItemRec.h" #include "../game/CServerConfig.h" #include "../sphere/threads.h" -#include "CException.h" +//#include "CException.h" // included in the precompiled header #include "CLog.h" #include "CUOInstall.h" #include @@ -172,7 +172,7 @@ lpctstr CUOInstall::GetBaseFileName( VERFILE_TYPE i ) // static nullptr, "tiledata.mul", // Data about tiles in ART. name and flags, etc "animdata.mul", // - "hues.mul" // the 16 bit color pallete we use for everything. + "hues.mul" // the 16 bit color palette we use for everything. }; return ( i<0 || i>=VERFILE_QTY ) ? nullptr : sm_szFileNames[i]; @@ -266,10 +266,10 @@ VERFILE_TYPE CUOInstall::OpenFiles( ullong ullMask ) { if (g_MapList.IsInitialized(m) || (m == 0)) //Need at least a minimum of map0... (Ben) { - const int index = g_MapList.m_mapGeoData.maps[m].num; + const int index = g_MapList.m_mapGeoData.maps[m].iNum; if (index == -1) { - g_MapList.m_mapGeoData.maps[m].enabled = false; + g_MapList.m_mapGeoData.maps[m].fEnabled = false; continue; } @@ -406,9 +406,9 @@ VERFILE_TYPE CUOInstall::OpenFiles( ullong ullMask ) m_Statics[index].Close(); if (index == 1 && m_Maps[0].IsFileOpen()) - g_MapList.m_mapGeoData.maps[m].num = 0; + g_MapList.m_mapGeoData.maps[m].iNum = 0; else - g_MapList.m_mapGeoData.maps[m].id = 0; + g_MapList.m_mapGeoData.maps[m].iId = 0; } // mapdif and mapdifl are not required, but if one exists so should diff --git a/src/common/CUOInstall.h b/src/common/CUOInstall.h index 11328ffd4..b5e3195d4 100644 --- a/src/common/CUOInstall.h +++ b/src/common/CUOInstall.h @@ -79,7 +79,7 @@ extern struct CUOInstall bool ReadMulIndex(CSFile &file, dword id, CUOIndexRec &Index); bool ReadMulData(CSFile &file, const CUOIndexRec &Index, void * pData); - + public: CUOInstall(); @@ -105,7 +105,7 @@ extern class CVerDataMul void Unload(); void Load( CSFile & file ); bool FindVerDataBlock( VERFILE_TYPE type, dword id, CUOIndexRec & Index ) const; - + public: CVerDataMul(); ~CVerDataMul(); diff --git a/src/common/CVarDefMap.cpp b/src/common/CVarDefMap.cpp index 09d2b452b..a7134fc6c 100644 --- a/src/common/CVarDefMap.cpp +++ b/src/common/CVarDefMap.cpp @@ -2,7 +2,7 @@ #include "../common/CLog.h" #include "../game/CServer.h" #include "../game/CServerConfig.h" -#include "CExpression.h" +//#include "CExpression.h" // included in the precompiled header #include "CScript.h" #include "CTextConsole.h" #include "CVarDefMap.h" @@ -62,7 +62,7 @@ bool CVarDefContNum::r_WriteVal( lpctstr pKey, CSString & sVal, CTextConsole * p } CVarDefCont * CVarDefContNum::CopySelf() const -{ +{ return new CVarDefContNum( GetKey(), m_iVal ); } @@ -74,7 +74,7 @@ CVarDefCont * CVarDefContNum::CopySelf() const * ***************************************************************************/ -CVarDefContStr::CVarDefContStr( lpctstr ptcKey, lpctstr pszVal ) : m_sKey( ptcKey ), m_sVal( pszVal ) +CVarDefContStr::CVarDefContStr( lpctstr ptcKey, lpctstr pszVal ) : m_sKey( ptcKey ), m_sVal( pszVal ) { } @@ -88,7 +88,7 @@ int64 CVarDefContStr::GetValNum() const return( Exp_Get64Val(pszStr) ); } -void CVarDefContStr::SetValStr( lpctstr pszVal ) +void CVarDefContStr::SetValStr( lpctstr pszVal ) { const size_t uiLen = strlen(pszVal); if (uiLen <= SCRIPT_MAX_LINE_LEN/2) @@ -111,9 +111,9 @@ bool CVarDefContStr::r_WriteVal( lpctstr pKey, CSString & sVal, CTextConsole * p return true; } -CVarDefCont * CVarDefContStr::CopySelf() const -{ - return new CVarDefContStr( GetKey(), m_sVal ); +CVarDefCont * CVarDefContStr::CopySelf() const +{ + return new CVarDefContStr( GetKey(), m_sVal ); } @@ -142,11 +142,11 @@ lpctstr CVarDefMap::FindValStr( lpctstr pVal ) const for ( const CVarDefCont * pVarBase : m_Container ) { ASSERT( pVarBase ); - + const CVarDefContStr * pVarStr = dynamic_cast ( pVarBase ); if ( pVarStr == nullptr ) continue; - + if ( ! strcmpi( pVal, pVarStr->GetValStr())) return pVarBase->GetKey(); } @@ -322,6 +322,11 @@ size_t CVarDefMap::GetCount() const noexcept return m_Container.size(); } +void CVarDefMap::Reserve(size_t uiSize) +{ + m_Container.reserve(uiSize); +} + CVarDefContNum* CVarDefMap::SetNumNew( lpctstr pszName, int64 iVal ) { ADDTOCALLSTACK_DEBUG("CVarDefMap::SetNumNew"); @@ -401,17 +406,29 @@ CVarDefContNum* CVarDefMap::SetNum( lpctstr pszName, int64 iVal, bool fDeleteZer return SetNumNew( pszName, iVal ); CVarDefContNum * pVarNum = dynamic_cast ( pVarBase ); + const bool fShouldWarn = fWarnOverwrite && g_Serv.IsStartupLoadingScripts(); if ( pVarNum ) { - if ( fWarnOverwrite && !g_Serv.IsResyncing() && g_Serv.IsLoading() ) - DEBUG_WARN(( "Replacing existing VarNum '%s' with number: 0x%" PRIx64" \n", pVarBase->GetKey(), iVal )); - pVarNum->SetValNum( iVal ); + if ( fShouldWarn ) + { + g_Log.EventWarn( "Replacing existing VarNum '%s' with number: 0%" PRIx64 " (%" PRId64 ")\n", pVarBase->GetKey(), iVal, iVal ); +#ifdef _DEBUG + const int64 iOldVal = pVarNum->GetValNum(); + g_Log.EventDebug("Previous value: %0" PRIx64 "(%" PRId64 ")\n", iOldVal, iOldVal); +#endif + } + pVarNum->SetValNum( iVal ); } else { - if ( fWarnOverwrite && !g_Serv.IsResyncing() && g_Serv.IsLoading() ) - DEBUG_WARN(( "Replacing existing VarStr '%s' with number: 0x%" PRIx64" \n", pVarBase->GetKey(), iVal )); - return SetNumOverride( pszName, iVal ); + if ( fShouldWarn ) + { + g_Log.EventWarn( "Replacing existing VarStr '%s' with number: 0%" PRIx64" (%" PRId64 ")\n", pVarBase->GetKey(), iVal, iVal ); +#ifdef _DEBUG + g_Log.EventDebug("Previous value: '%s'\n", pVarNum->GetValStr()); +#endif + } + return SetNumOverride( pszName, iVal ); } return pVarNum; @@ -484,15 +501,27 @@ CVarDefCont* CVarDefMap::SetStr( lpctstr pszName, bool fQuoted, lpctstr pszVal, CVarDefContStr * pVarStr = dynamic_cast ( pVarBase ); if ( pVarStr ) { - if ( fWarnOverwrite && !g_Serv.IsResyncing() && g_Serv.IsLoading() ) - DEBUG_WARN(( "Replacing existing VarStr '%s' with string: '%s'\n", pVarBase->GetKey(), pszVal )); - pVarStr->SetValStr( pszVal ); + if ( fWarnOverwrite && !g_Serv.IsResyncing() && g_Serv.IsLoadingGeneric() ) + { + g_Log.EventWarn( "Replacing existing VarStr '%s' with string: '%s'\n", pVarBase->GetKey(), pszVal ); +#ifdef _DEBUG + g_Log.EventDebug("Previous value: '%s'\n", pVarStr->GetValStr()); +#endif + } + pVarStr->SetValStr( pszVal ); } else { - if ( fWarnOverwrite && !g_Serv.IsResyncing() && g_Serv.IsLoading() ) - DEBUG_WARN(( "Replacing existing VarNum '%s' with string: '%s'\n", pVarBase->GetKey(), pszVal )); - return SetStrOverride( pszName, pszVal ); + if ( fWarnOverwrite && !g_Serv.IsResyncing() && g_Serv.IsLoadingGeneric() ) + { + g_Log.EventWarn( "Replacing existing VarNum '%s' with string: '%s'\n", pVarBase->GetKey(), pszVal ); +#ifdef _DEBUG + const int64 iOldVal = pVarStr->GetValNum(); + g_Log.EventDebug("Previous value: 0%" PRIx64 " (%" PRId64 ")\n", iOldVal, iOldVal); + +#endif + } + return SetStrOverride( pszName, pszVal ); } return pVarStr; } @@ -505,7 +534,7 @@ CVarDefCont * CVarDefMap::GetKey( lpctstr ptcKey ) const if ( ptcKey ) { const size_t idx = m_Container.find_predicate(ptcKey, VarDefCompare); - + if ( idx != sl::scont_bad_index() ) pReturn = m_Container[idx]; } @@ -557,13 +586,13 @@ CVarDefCont * CVarDefMap::GetParseKey_Advance( lpctstr & pszArgs ) const return nullptr; } -bool CVarDefMap::GetParseVal_Advance( lpctstr & pszArgs, llong * pllVal ) const +bool CVarDefMap::GetParseVal_Advance( lpctstr & pszArgs, int64 * piVal ) const { ADDTOCALLSTACK_DEBUG("CVarDefMap::GetParseVal_Advance"); CVarDefCont * pVarBase = GetParseKey_Advance( pszArgs ); if ( pVarBase == nullptr ) return false; - *pllVal = pVarBase->GetValNum(); + *piVal = pVarBase->GetValNum(); return true; } @@ -668,7 +697,7 @@ void CVarDefMap::r_WritePrefix( CScript & s, lpctstr ptcPrefix, lpctstr ptcKeyEx const lpctstr ptcKey = pVar->GetKey(); if ( fHasExclude && !strcmpi(ptcKeyExclude, ptcKey)) continue; - + const CVarDefContNum * pVarNum = dynamic_cast(pVar); _WritePrefix(ptcKey); lpctstr ptcVal = pVar->GetValStr(); diff --git a/src/common/CVarDefMap.h b/src/common/CVarDefMap.h index 5f5bcc039..c008cc1eb 100644 --- a/src/common/CVarDefMap.h +++ b/src/common/CVarDefMap.h @@ -100,7 +100,7 @@ class CVarDefContStr : public CVarDefCont void SetValStr( lpctstr pszVal ); inline virtual lpctstr GetValStr() const override { - return m_sVal.GetBuffer(); + return m_sVal.GetBuffer(); } virtual int64 GetValNum() const override; virtual CVarDefCont * CopySelf() const override; @@ -142,14 +142,13 @@ class CVarDefMap bool CompareAll( const CVarDefMap * pArray ); void Clear(); size_t GetCount() const noexcept; + void Reserve(size_t uiSize); public: CVarDefMap() = default; ~CVarDefMap(); CVarDefMap & operator = ( const CVarDefMap & array ); - -private: - CVarDefMap(const CVarDefMap& copy); + CVarDefMap(const CVarDefMap& copy) = delete; public: lpctstr FindValNum( int64 iVal ) const; @@ -170,8 +169,8 @@ class CVarDefMap CVarDefCont * CheckParseKey( lpctstr pszArgs ) const; CVarDefCont * GetParseKey_Advance( lpctstr & pArgs ) const; inline CVarDefCont * GetParseKey( lpctstr pArgs ) const; - bool GetParseVal_Advance( lpctstr & pArgs, llong * pllVal ) const; - inline bool GetParseVal( lpctstr pArgs, llong * plVal ) const; + bool GetParseVal_Advance( lpctstr & pArgs, int64 * piVal ) const; + inline bool GetParseVal( lpctstr pArgs, int64 * plVal ) const; void DumpKeys( CTextConsole * pSrc, lpctstr pszPrefix = nullptr ) const; void ClearKeys(lpctstr mask = nullptr); @@ -205,9 +204,9 @@ CVarDefCont * CVarDefMap::GetParseKey(lpctstr pArgs) const return GetParseKey_Advance(pArgs); } -bool CVarDefMap::GetParseVal(lpctstr pArgs, llong * pllVal) const +bool CVarDefMap::GetParseVal(lpctstr pArgs, int64 *piVal) const { - return GetParseVal_Advance(pArgs, pllVal); + return GetParseVal_Advance(pArgs, piVal); } CVarDefMap::iterator CVarDefMap::begin() { return m_Container.begin(); } diff --git a/src/common/ListDefContMap.cpp b/src/common/ListDefContMap.cpp index 7d092eafb..53d1fe750 100644 --- a/src/common/ListDefContMap.cpp +++ b/src/common/ListDefContMap.cpp @@ -2,7 +2,7 @@ #include "../common/sphere_library/sstringobjs.h" #include "../common/CLog.h" #include "../sphere/threads.h" -#include "CExpression.h" +//#include "CExpression.h" // included in the precompiled header #include "CScript.h" #include "CTextConsole.h" #include "ListDefContMap.h" @@ -59,7 +59,7 @@ bool CListDefContNum::r_WriteVal( lpctstr pKey, CSString & sVal, CTextConsole * } CListDefContElem * CListDefContNum::CopySelf() const -{ +{ return new CListDefContNum( GetKey(), m_iVal ); } @@ -70,7 +70,7 @@ CListDefContElem * CListDefContNum::CopySelf() const * * ***************************************************************************/ -CListDefContStr::CListDefContStr( lpctstr ptcKey, lpctstr pszVal ) : CListDefContElem( ptcKey ), m_sVal( pszVal ) +CListDefContStr::CListDefContStr( lpctstr ptcKey, lpctstr pszVal ) : CListDefContElem( ptcKey ), m_sVal( pszVal ) { } @@ -84,8 +84,8 @@ int64 CListDefContStr::GetValNum() const return( Exp_Get64Val(pszStr) ); } -void CListDefContStr::SetValStr( lpctstr pszVal ) -{ +void CListDefContStr::SetValStr( lpctstr pszVal ) +{ m_sVal.Copy( pszVal ); } @@ -104,9 +104,9 @@ bool CListDefContStr::r_WriteVal( lpctstr pKey, CSString & sVal, CTextConsole * return true; } -CListDefContElem * CListDefContStr::CopySelf() const -{ - return new CListDefContStr( GetKey(), m_sVal ); +CListDefContElem * CListDefContStr::CopySelf() const +{ + return new CListDefContStr( GetKey(), m_sVal ); } /*************************************************************************** @@ -116,12 +116,12 @@ CListDefContElem * CListDefContStr::CopySelf() const * * ***************************************************************************/ -CListDefCont::CListDefCont( lpctstr ptcKey ) : m_Key( ptcKey ) -{ +CListDefCont::CListDefCont( lpctstr ptcKey ) : m_Key( ptcKey ) +{ } void CListDefCont::SetKey( lpctstr ptcKey ) -{ +{ m_Key = ptcKey; } @@ -135,7 +135,7 @@ CListDefContElem* CListDefCont::GetAt(size_t nIndex) const /* DefList::const_iterator it = m_listElements.begin(); std::advance(it, nIndex); - + if (it != m_listElements.end()) return (*it); return nullptr; @@ -444,7 +444,7 @@ CListDefCont* CListDefCont::CopySelf() ADDTOCALLSTACK("CListDefCont::CopySelf"); if (m_listElements.empty()) return nullptr; - + CListDefCont* pNewList = new CListDefCont(m_Key.GetBuffer()); if ( !pNewList ) return nullptr; @@ -704,7 +704,7 @@ CListDefCont* CListDefMap::AddList(lpctstr ptcKey) { ADDTOCALLSTACK("CListDefMap::AddList"); CListDefCont* pListBase = GetKey(ptcKey); - + if ( !pListBase && ptcKey && *ptcKey ) { pListBase = new CListDefCont(ptcKey); @@ -714,7 +714,7 @@ CListDefCont* CListDefMap::AddList(lpctstr ptcKey) return pListBase; } -void CListDefMap::DumpKeys( CTextConsole * pSrc, lpctstr pszPrefix ) +void CListDefMap::DumpKeys( CTextConsole * pSrc, lpctstr pszPrefix ) const { ADDTOCALLSTACK("CListDefMap::DumpKeys"); // List out all the keys. @@ -821,7 +821,7 @@ bool CListDefMap::r_LoadVal( lpctstr ptcKey, CScript & s ) DeleteKey(ppCmds[0]); pListBase = nullptr; } - + // Append: Sets each as a new element in LIST.xxx if ( !pListBase ) { @@ -848,7 +848,7 @@ bool CListDefMap::r_LoadVal( lpctstr ptcKey, CScript & s ) } else if ( !strnicmp(ppCmds[1], "sort", 4) ) { - // Re-orders LIST.xxx according to . (possible values are: no args , i , asc , iasc , desc , idesc) + // Re-orders LIST.xxx according to . (possible values are: no args , i , asc , iasc , desc , idesc) if ( !pListBase ) return false; @@ -967,7 +967,7 @@ bool CListDefMap::r_LoadVal( lpctstr ptcKey, CScript & s ) return false; } -bool CListDefMap::r_Write( CTextConsole *pSrc, lpctstr pszString, CSString& strVal ) +bool CListDefMap::r_Write( CTextConsole *pSrc, lpctstr pszString, CSString& strVal ) const { ADDTOCALLSTACK("CListDefMap::r_Write"); UnreferencedParameter(pSrc); @@ -1037,7 +1037,7 @@ bool CListDefMap::r_Write( CTextConsole *pSrc, lpctstr pszString, CSString& strV } -void CListDefMap::r_WriteSave( CScript& s ) +void CListDefMap::r_WriteSave( CScript& s ) const { ADDTOCALLSTACK("CListDefMap::r_WriteSave"); diff --git a/src/common/ListDefContMap.h b/src/common/ListDefContMap.h index 9df957509..a2053be3c 100644 --- a/src/common/ListDefContMap.h +++ b/src/common/ListDefContMap.h @@ -91,7 +91,7 @@ class CListDefContStr: public CListDefContElem public: inline lpctstr GetValStr() const { - return m_sVal.GetBuffer(); + return m_sVal.GetBuffer(); } void SetValStr( lpctstr pszVal ); int64 GetValNum() const; @@ -210,13 +210,13 @@ class CListDefMap CListDefCont* AddList(lpctstr ptcKey); - void DumpKeys( CTextConsole * pSrc, lpctstr pszPrefix = nullptr ); + void DumpKeys( CTextConsole * pSrc, lpctstr pszPrefix = nullptr ) const; void ClearKeys(lpctstr mask = nullptr); void DeleteKey( lpctstr key ); bool r_LoadVal( lpctstr ptcKey, CScript & s ) NONVIRTUAL; - bool r_Write( CTextConsole *pSrc, lpctstr pszString, CSString& strVal ) NONVIRTUAL; - void r_WriteSave( CScript& s ) NONVIRTUAL; + bool r_Write( CTextConsole *pSrc, lpctstr pszString, CSString& strVal ) const NONVIRTUAL; + void r_WriteSave( CScript& s ) const NONVIRTUAL; }; #endif // _INC_LISTDEFCONTMAP_H diff --git a/src/common/basic_threading.h b/src/common/basic_threading.h index 2f6e3e270..d7743e8d3 100644 --- a/src/common/basic_threading.h +++ b/src/common/basic_threading.h @@ -12,24 +12,28 @@ #include // As per standard, std::shared_mutex has NOT a noexcept contructor, nevertheless its implementations should actually not throw. +// -- Macros for direct mutex lock/unlock and mutex-guarded value returning. + +#define MT_DEFAULT_CMUTEX_TYPE std::shared_mutex + // Name of the Class Mutex -#define MT_CMUTEX _classMutex +#define MT_CMUTEX _classMutex // Use this in the class definition to add the class mutex. -#define MT_CMUTEX_DEF mutable std::shared_mutex MT_CMUTEX +#define MT_CMUTEX_DEF mutable MT_DEFAULT_CMUTEX_TYPE MT_CMUTEX // Read-Only: multiple threads can read the same resource -#define MT_SHARED_LOCK_SET std::shared_lock _shared_lock(this->MT_CMUTEX) +#define MT_SHARED_LOCK_SET(pClass) std::shared_lock _shared_lock((pClass)->MT_CMUTEX) // Locking is already done by MT_SHARED_LOCK_SET. Use it only if you know what you are doing! -#define MT_SHARED_LOCK _shared_lock.lock() +#define MT_SHARED_LOCK _shared_lock.lock() // Unlocking is done automatically then the function ends (when the lock created with MT_SHARED_LOCK_SET goes out of scope). Use it only if you know what you are doing! -#define MT_SHARED_UNLOCK _shared_lock.unlock() +#define MT_SHARED_UNLOCK _shared_lock.unlock() // Read/Write: exclusive access to a thread at a time -#define MT_UNIQUE_LOCK_SET std::unique_lock _unique_lock(this->MT_CMUTEX) +#define MT_UNIQUE_LOCK_SET(pClass) std::unique_lock _unique_lock((pClass)->MT_CMUTEX) // Locking is already done by MT_UNIQUE_LOCK_SET. Use it only if you know what you are doing! -#define MT_UNIQUE_LOCK _unique_lock.lock() +#define MT_UNIQUE_LOCK _unique_lock.lock() // Unlocking is done automatically then the function ends (when the lock created with MT_UNIQUE_LOCK_SET goes out of scope). Use it only if you know what you are doing! -#define MT_UNIQUE_UNLOCK _unique_lock.unlock() +#define MT_UNIQUE_UNLOCK _unique_lock.unlock() // Thread-safe return macro. If we directly do return without using a temporary storage variable, the return value will be stored in the returned register // after the thread lock is destroyed, so after the mutex is unlocked. In this way, we have a variable holding the value of the return expression, but before the mutex unlocking. @@ -40,42 +44,43 @@ // A single macro to lock the class mutex and return the value in a thread-safe way. // Do not use to return references! (It will return the address of the local-scoped, temporary variable _MT_RETURN_val) -#define MT_SHARED_LOCK_RETURN(x) \ +#define MT_SHARED_LOCK_RETURN(pClass, val) \ /* Creating and locking a mutex might THROW std::system_error ! */ \ - {MT_SHARED_LOCK_SET; \ - MT_RETURN(x);} + {MT_SHARED_LOCK_SET(pClass); \ + MT_RETURN(val);} -#define MT_UNIQUE_LOCK_RETURN(x) \ +#define MT_UNIQUE_LOCK_RETURN(pClass, val) \ /* Creating and locking a mutex might THROW std::system_error ! */ \ - {MT_UNIQUE_LOCK_SET; \ - MT_RETURN(x);} + {MT_UNIQUE_LOCK_SET(pClass); \ + MT_RETURN(val);} #if MT_ENGINES == 1 //_DEBUG - #define MT_ENGINE_SHARED_LOCK_SET MT_SHARED_LOCK_SET - #define MT_ENGINE_SHARED_LOCK MT_SHARED_LOCK - #define MT_ENGINE_SHARED_UNLOCK MT_SHARED_UNLOCK - #define MT_ENGINE_UNIQUE_LOCK_SET MT_UNIQUE_LOCK_SET - #define MT_ENGINE_UNIQUE_UNLOCK MT_UNIQUE_UNLOCK + #define MT_ENGINE_SHARED_LOCK_SET(pClass) MT_SHARED_LOCK_SET + #define MT_ENGINE_SHARED_LOCK MT_SHARED_LOCK + #define MT_ENGINE_SHARED_UNLOCK MT_SHARED_UNLOCK + #define MT_ENGINE_UNIQUE_LOCK_SET(pClass) MT_UNIQUE_LOCK_SET + #define MT_ENGINE_UNIQUE_UNLOCK MT_UNIQUE_UNLOCK - #define MT_ENGINE_RETURN(x) MT_RETURN(x) - #define MT_ENGINE_SHARED_LOCK_RETURN(x) MT_SHARED_LOCK_RETURN(x) - #define MT_ENGINE_UNIQUE_LOCK_RETURN(x) MT_UNIQUE_LOCK_RETURN(x) + #define MT_ENGINE_RETURN(x) MT_RETURN(x) + #define MT_ENGINE_SHARED_LOCK_RETURN(x) MT_SHARED_LOCK_RETURN(x) + #define MT_ENGINE_UNIQUE_LOCK_RETURN(x) MT_UNIQUE_LOCK_RETURN(x) #else - #define MT_ENGINE_SHARED_LOCK_SET (void)0 + #define MT_ENGINE_SHARED_LOCK_SET(pClass) (void)0 #define MT_ENGINE_SHARED_LOCK (void)0 #define MT_ENGINE_SHARED_UNLOCK (void)0 - #define MT_ENGINE_UNIQUE_LOCK_SET (void)0 + #define MT_ENGINE_UNIQUE_LOCK_SET(pClass) (void)0 #define MT_ENGINE_UNIQUE_LOCK (void)0 #define MT_ENGINE_UNIQUE_UNLOCK (void)0 - #define MT_ENGINE_RETURN(x) return (x) - #define MT_ENGINE_SHARED_LOCK_RETURN(x) return (x) - #define MT_ENGINE_UNIQUE_LOCK_RETURN(x) return (x) + #define MT_ENGINE_RETURN(x) return (x) + #define MT_ENGINE_SHARED_LOCK_RETURN(x) return (x) + #define MT_ENGINE_UNIQUE_LOCK_RETURN(x) return (x) #endif +// --- // Try acquiring a lock (try multiple times), to be used only for CRITICAL mutexes, that you pray will never fail (for an OS error) to be locked (not because they are already locked). If successiful, call func_, usually a lambda. If fails, program explodes (std::abort). #define RETRY_LOCK_ATTEMPTS_MAX 20 @@ -89,7 +94,7 @@ success_acquire_ = true; \ break; \ } catch (const std::system_error& e_) { \ - STDERR_LOG("[File '%s', line %d, function '%s']. Failed to acquire lock on attempt %u. Exc msg: '%s'.\n", \ + stderrLog("[File '%s', line %d, function '%s']. Failed to acquire lock on attempt %u. Exc msg: '%s'.\n", \ __FILE__, __LINE__, __func__, spin_attempt_ + 1, e_.what()); \ } \ } \ @@ -107,4 +112,152 @@ RETRY_LOCK_FOR_TASK_IMPL_(std::unique_lock, (mutex_name_), (lock_name_), RETRY_LOCK_ATTEMPTS_MAX, (retval_), (func_)) +// --- + +namespace sl +{ + +template +class GuardedAccess +{ +public: + using Mutex = MT_DEFAULT_CMUTEX_TYPE; + using SharedLock = std::shared_lock; + using UniqueLock = std::unique_lock; + + struct NoOpLock + { + NoOpLock(Mutex&) noexcept {} + ~NoOpLock() noexcept = default; + }; + using OptLock = +#if MT_ENGINES + SharedLock +#else + NoOpLock +#endif + ; + + // ---- Construct underlying T in-place + template + [[nodiscard]] + explicit GuardedAccess(Args&&... args) + : mutex_(), data_(std::forward(args)...) + {} + + // ----- Reader proxies ----- + class LockedReader + { + public: + [[nodiscard]] + LockedReader(const GuardedAccess& owner) + : lock_(owner.mutex_), ptr_(&owner.data_) + {} + RETURNS_NOTNULL + const T* operator->() const { return ptr_; } + const T& operator* () const { return *ptr_; } + private: + SharedLock lock_; + const T* ptr_; + }; + + class MTEngineLockedReader + { + public: + [[nodiscard]] + MTEngineLockedReader(const GuardedAccess& owner) + : +#if MT_ENGINES + locked_(owner) +#else + owner_(owner) +#endif + {} +#if MT_ENGINES + LockedReader operator-> () const { return locked_.operator->; } + LockedReader operator* () const { return locked_.operator*; } +#else + RETURNS_NOTNULL + const T* operator->() const { return &owner_.data_; } + const T& operator* () const { return owner_.data_; } +#endif + private: +#if MT_ENGINES + LockedReader locked_; +#else + const GuardedAccess& owner_; +#endif + }; + + // ----- Writer proxies ----- + class LockedWriter + { + public: + [[nodiscard]] + LockedWriter(GuardedAccess& owner) + : lock_(owner.mutex_), ptr_(&owner.data_) + {} + RETURNS_NOTNULL + T* operator->() { return ptr_; } + T& operator* () { return *ptr_; } + private: + UniqueLock lock_; + T* ptr_; + }; + + class MTEngineLockedWriter + { + public: + [[nodiscard]] + MTEngineLockedWriter(GuardedAccess& owner) + : +#if MT_ENGINES + locked_(owner) +#else + owner_(owner) +#endif + {} +#if MT_ENGINES + LockedWriter operator-> () { return locked_.operator->; } + LockedWriter operator* () { return locked_.operator*; } +#else + RETURNS_NOTNULL + T* operator->() { return &owner_.data_; } + T& operator* () { return owner_.data_; } +#endif + private: +#if MT_ENGINES + LockedWriter locked_; +#else + GuardedAccess& owner_; +#endif + }; + + // ---- Unsafe/raw accessors ---- + // No lock taken! You must lock manually. + T& unsafeWriter() { return data_; } + const T& unsafeReader() const { return data_; } + + // ---- Lock grabbing ---- + [[nodiscard]] auto getLockShared() const { return SharedLock(mutex_); } + [[nodiscard]] auto getLockUnique() { return UniqueLock(mutex_); } + + [[nodiscard]] auto mtEngineGetLockShared() const { return OptLock(mutex_); } + [[nodiscard]] auto mtEngineGetLockUnique() { return OptLock(mutex_); } + + // ---- Accessors ---- + [[nodiscard]] auto lockedReader() const { return LockedReader(*this); } + [[nodiscard]] auto lockedWriter() { return LockedWriter(*this); } + + [[nodiscard]] auto mtEngineLockedReader() const { return MTEngineLockedReader(*this); } + [[nodiscard]] auto mtEngineLockedWriter() { return MTEngineLockedWriter(*this); } + +private: + mutable Mutex mutex_; + T data_; +}; + + +} // namespace sl + #endif // _INC_BASIC_THREADING_H diff --git a/src/common/common.cpp b/src/common/common.cpp index e41b313aa..d1f148223 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -44,3 +44,13 @@ extern "C" return &g_osInfo; } #endif // !_WIN32 + +void stderrLog(lpctstr ptcFormat, ...) noexcept // SPHERE_PRINTFARGS(1,2); +{ + va_list vargs; + va_start(vargs, ptcFormat); + fprintf(stderr, ptcFormat, vargs); + va_end(vargs); + + fflush(stderr); +} diff --git a/src/common/common.h b/src/common/common.h index 3ae802333..ff0252b64 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -34,10 +34,6 @@ #include #include -#include "assertion.h" -#include "basic_threading.h" - - // On Windows, Clang with MSVC runtime defines _MSC_VER! (But also __clang__). #if !defined(_MSC_VER) || defined(__clang__) # define NON_MSVC_COMPILER 1 @@ -70,9 +66,19 @@ /* Coding helpers */ // Target arch. -#if defined(_WIN64) || (__SIZEOF_POINTER__ == 8) +#ifndef __SIZEOF_POINTER__ +# if defined(_WIN64) +# define __SIZEOF_POINTER__ 8 +# elif defined(_WIN32) +# define __SIZEOF_POINTER__ 4 +# else +# error "Can't detect the arch?" +# endif +#endif + +#if (__SIZEOF_POINTER__ == 8) # define ARCH_64 -#elif defined(_WIN32) || (__SIZEOF_POINTER__ == 4) +#elif (__SIZEOF_POINTER__ == 4) # define ARCH_32 #else # error "Can't detect the arch?" @@ -101,6 +107,9 @@ #define NOEXCEPT_NODEBUG noexcept #endif +#include "assertion.h" +#include "basic_threading.h" + // use to indicate that a function uses printf-style arguments, allowing GCC // to validate the format string and arguments: // a = 1-based index of format string @@ -135,7 +144,8 @@ //#define IsNegative(c) (((c) < 0) ? 1 : 0) template -constexpr bool IsNegative(T val) noexcept { +[[nodiscard]] constexpr + bool IsNegative(T val) noexcept { return (val < 0); } @@ -147,74 +157,96 @@ inline constexpr word dword_low_word(dword in) noexcept { } //#define HIWORD(l) ((word)((dword)(l) >> 16)) -inline constexpr word dword_hi_word(dword in) noexcept { +[[nodiscard]] inline constexpr + word dword_hi_word(dword in) noexcept { return (in >> 16); } //#define LOBYTE(w) ((byte)((dword)(w) & 0xff)) -inline constexpr byte word_low_byte(word in) noexcept { +[[nodiscard]] inline constexpr + byte word_low_byte(word in) noexcept { return (in & 0xFF); } //#define HIBYTE(w) ((byte)((dword)(w) >> 8)) -inline constexpr byte word_hi_byte(word in) noexcept { +[[nodiscard]] inline constexpr + byte word_hi_byte(word in) noexcept { return (in >> 8); } //#define MAKEWORD(low,high) ((word)(((byte)(low))|(((word)((byte)(high)))<<8))) -inline constexpr word make_word(byte low, byte high) noexcept { +[[nodiscard]] inline constexpr + word make_word(byte low, byte high) noexcept { return (word)low | ((word)high << 8); } //#define make_dword(low, high) ((dword)(((word)low) | (((dword)((word)high)) << 16))) -inline constexpr dword make_dword(word low, word high) noexcept { +[[nodiscard]] inline constexpr + dword make_dword(word low, word high) noexcept { return (dword)low | ((dword)high << 16); } //#define IMulDiv(a,b,c) (((((int)(a)*(int)(b)) + (int)(c / 2)) / (int)(c)) - (IsNegative((int)(a)*(int)(b)))) -constexpr int IMulDiv(const int a, const int b, const int c) noexcept +[[nodiscard]] constexpr + int IMulDiv(const int a, const int b, const int c) noexcept { const int ab = a*b; return ((ab + (c/2)) / c) - IsNegative(ab); } -constexpr uint UIMulDiv(const uint a, const uint b, const uint c) noexcept +[[nodiscard]] constexpr + uint UIMulDiv(const uint a, const uint b, const uint c) noexcept { const int ab = a * b; return ((ab + (c / 2)) / c) - IsNegative(ab); } //#define IMulDivLL(a,b,c) (((((llong)(a)*(llong)(b)) + (llong)(c / 2)) / (llong)(c)) - (IsNegative((llong)(a)*(llong)(b)))) -constexpr llong IMulDivLL(const llong a, const llong b, const llong c) noexcept +[[nodiscard]] constexpr + llong IMulDivLL(const llong a, const llong b, const llong c) noexcept { const llong ab = a*b; return ((ab + (c/2)) / c) - IsNegative(ab); } -constexpr realtype IMulDivRT(const realtype a, const realtype b, const realtype c) noexcept +[[nodiscard]] constexpr + realtype IMulDivRT(const realtype a, const realtype b, const realtype c) noexcept { const realtype ab = a*b; return ((ab + (c/2)) / c) - IsNegative(ab); } //#define IMulDivDown(a,b,c) (((a)*(b))/(c)) -constexpr int IMulDivDown(const int a, const int b, const int c) noexcept +[[nodiscard]] constexpr + int IMulDivDown(const int a, const int b, const int c) noexcept { return (a*b)/c; } -constexpr llong IMulDivDownLL(const llong a, const llong b, const llong c) noexcept +[[nodiscard]] constexpr + llong IMulDivDownLL(const llong a, const llong b, const llong c) noexcept { return (a*b)/c; } //#define sign(n) (((n) < 0) ? -1 : (((n) > 0) ? 1 : 0)) template +[[nodiscard]] constexpr T sign(const T n) noexcept { static_assert(std::is_arithmetic::value, "Invalid data type."); return ( (n < 0) ? -1 : ((n > 0) ? 1 : 0) ); } +[[nodiscard]] inline constexpr bool IsPowerOfTwo(unsigned int n) noexcept +{ + // n & (n - 1): This expression removes the lowest set bit in n. + // For powers of two, which have exactly one bit set (e.g., 2 is 10 in binary, 4 is 100, etc.), + // subtracting one yields a number where all lower bits are set to 1 (e.g., 2 - 1 = 1, 4 - 1 = 3), and the bitwise AND of these two numbers results in 0. + return n != 0 && (n & (n - 1)) == 0; +} +[[nodiscard]] inline constexpr bool IsPowerOfTwo(unsigned short n) noexcept { return n != 0 && (n & (n - 1)) == 0; } +[[nodiscard]] inline constexpr bool IsPowerOfTwo(unsigned char n) noexcept { return n != 0 && (n & (n - 1)) == 0; } + #define minimum(x,y) ((x)<(y)?(x):(y)) // NOT to be used with functions! Store the result of the function in a variable first, otherwise the function will be executed twice! #define maximum(x,y) ((x)>(y)?(x):(y)) // NOT to be used with functions! Store the result of the function in a variable first, otherwise the function will be executed twice! #define medium(x,y,z) ((x)>(y)?(x):((z)<(y)?(z):(y))) // NOT to be used with functions! Store the result of the function in a variable first, otherwise the function will be executed twice! @@ -249,6 +281,7 @@ constexpr T saturating_sub(T a, T b) noexcept { // Ensure that a constexpr value or a generic expression is evaluated at compile time. // Constexpr values are constants and cannot be mutated in the code. template +[[nodiscard]] consteval T as_consteval(T&& val_) noexcept { return val_; } @@ -260,7 +293,7 @@ consteval T as_consteval(T&& val_) noexcept { */ #undef UNREFERENCED_PARAMETER template -constexpr void UnreferencedParameter(T const&) noexcept { +constexpr void UnreferencedParameter([[maybe_unused]] T const& ) noexcept { ; } @@ -287,7 +320,7 @@ constexpr void UnreferencedParameter(T const&) noexcept { static_assert(!std::is_nothrow_invocable_v, #_func " function should be noexcept!") // For unrecoverable/unloggable errors. Should be used almost *never*. -#define STDERR_LOG(...) fprintf(stderr, __VA_ARGS__); fflush(stderr) +void stderrLog(lpctstr pszFormat, ...) noexcept SPHERE_PRINTFARGS(1,2); /* Sanitizers utilities */ diff --git a/src/common/crypto/CCrypto.cpp b/src/common/crypto/CCrypto.cpp index b6846f16b..eef0b3ef1 100644 --- a/src/common/crypto/CCrypto.cpp +++ b/src/common/crypto/CCrypto.cpp @@ -4,7 +4,7 @@ #include "../../sphere/threads.h" #include "../sphereproto.h" -#include "../CExpression.h" +//#include "../CExpression.h" // included in the precompiled header #include "../CScript.h" #include "../CLog.h" #include "../CUOClientVersion.h" @@ -17,7 +17,6 @@ extern "C" { #include "CMD5.h" - // =============================================================================================================== // --------------------------------------------------------------------------------------------------------------- // =============================================================================================================== @@ -221,10 +220,11 @@ CCrypto::CCrypto() //SetClientVerNumber(client_keys[0][2]); SetClientVerNumber(0u); - tf_cipher = new cipherInstance; - tf_key = new keyInstance; + tf_cipher = new cipherInstance; + tf_key = new keyInstance; m_md5_engine = new CMD5(); + m_MasterHi = m_MasterLo = 0; m_CryptMaskHi = m_CryptMaskLo = 0; m_seed = 0; m_ConnectType = CONNECT_NONE; @@ -233,6 +233,7 @@ CCrypto::CCrypto() m_gameBlockPos = 0; m_gameStreamPos = 0; m_md5_position = 0; + m_GameEnc = ENC_QTY; } CCrypto::~CCrypto() diff --git a/src/common/crypto/CCrypto.h b/src/common/crypto/CCrypto.h index dc45657bf..9e90a9c30 100644 --- a/src/common/crypto/CCrypto.h +++ b/src/common/crypto/CCrypto.h @@ -45,7 +45,7 @@ struct CCrypto dword m_CryptMaskHi; dword m_CryptMaskLo; dword m_seed; // seed ip we got from the client. - + CONNECT_TYPE m_ConnectType; CCryptoKeysHolder::CCryptoKey m_Key; // crypt key used by this CClient @@ -62,8 +62,8 @@ struct CCrypto void InitTwoFish(); bool DecryptTwoFish( byte * pOutput, const byte * pInput, size_t outLen, size_t inLen ); // --------------- EOF TwoFish ---------------------------- - - + + // -------------- Blow Fish ------------------------------ private: static bool sm_fBFishTablesReady; @@ -134,7 +134,7 @@ struct CCrypto bool LoginCryptStart( dword dwIP, const byte * pEvent, uint inLen ); bool GameCryptStart( dword dwIP, const byte * pEvent, uint inLen ); bool RelayGameCryptStart( byte * pOutput, const byte * pInput, uint outLen, uint inLen ); - + }; #endif // _INC_CCRYPTO_H diff --git a/src/common/crypto/CCryptoKeyCalc.cpp b/src/common/crypto/CCryptoKeyCalc.cpp index 3e6e9ea2e..f630b38a0 100644 --- a/src/common/crypto/CCryptoKeyCalc.cpp +++ b/src/common/crypto/CCryptoKeyCalc.cpp @@ -26,7 +26,7 @@ static GetEncryptionTypeForClient(CUOClientVersion ver) noexcept if (ver.m_major == 2) { if (ver.m_minor == 0 && ver.m_revision == 0 && ver.m_build == 0) - return (ver.m_extrachar == 'x') ? ENC_BTFISH : ENC_BFISH; // 2.0.0x or 2.0.0 + return (ver.m_build_sub == 23 /*'x'*/) ? ENC_BTFISH : ENC_BFISH; // 2.0.0x or 2.0.0 if (ver.m_minor == 0 && ver.m_revision <= 3) return ENC_BTFISH; } diff --git a/src/common/crypto/CCryptoLogin.cpp b/src/common/crypto/CCryptoLogin.cpp index 248d5b71b..a1a01392b 100644 --- a/src/common/crypto/CCryptoLogin.cpp +++ b/src/common/crypto/CCryptoLogin.cpp @@ -59,7 +59,7 @@ bool CCrypto::DecryptLogin( byte * pOutput, const byte * pInput, size_t outLen, + (MaskLo * m_MasterLo) - (m_CryptMaskHi * m_CryptMaskHi * 0x4c3a1353) + 0x16ef783f; - + // Old formula could cause undefined behavior /* m_CryptMaskHi = diff --git a/src/common/crypto/CMD5.h b/src/common/crypto/CMD5.h index 1cad81592..86d28a1af 100644 --- a/src/common/crypto/CMD5.h +++ b/src/common/crypto/CMD5.h @@ -1,5 +1,5 @@ /** - * @file CUID.h + * @file CMD5.h * @brief MD5 hashing. */ @@ -8,7 +8,7 @@ public domain code. */ -#ifndef _INC_CMD5_H +#ifndef _INC_CMD5_H #define _INC_CMD5_H #include "../datatypes.h" @@ -37,7 +37,7 @@ class CMD5 void digest( char * digest ) noexcept; // Get digest in a "numeric" form to be usable void numericDigest( uchar * digest ) noexcept; - + static void fastDigest(char * digest, const char * message) noexcept; }; diff --git a/src/common/os_unix.h b/src/common/os_unix.h index c69590695..c574e60f9 100644 --- a/src/common/os_unix.h +++ b/src/common/os_unix.h @@ -1,5 +1,5 @@ /** -* @file ox_unix.h +* @file os_unix.h * @brief Unix-specific declarations. */ diff --git a/src/common/resource/CResourceDef.cpp b/src/common/resource/CResourceDef.cpp index f14941743..9c4d4ab23 100644 --- a/src/common/resource/CResourceDef.cpp +++ b/src/common/resource/CResourceDef.cpp @@ -4,7 +4,7 @@ */ #include "../../sphere/threads.h" -#include "../CExpression.h" +#include "../CExpression.h" // included in the precompiled header #include "../CLog.h" #include "CResourceDef.h" @@ -40,10 +40,12 @@ bool CResourceDef::SetResourceName( lpctstr pszName ) } } - const CVarDefCont * pExistingVarKey = g_Exp.m_VarResDefs.GetKey( pszName ); const dword dwResPrivateUID = GetResourceID().GetPrivateUID(); const int iResIndex = GetResourceID().GetResIndex(); - CVarDefContNum* pVarKeyNum = nullptr; + CVarDefContNum* pVarKeyNum = nullptr; + + auto gwriter = g_ExprGlobals.mtEngineLockedWriter(); + const CVarDefCont * pExistingVarKey = gwriter->m_VarResDefs.GetKey( pszName ); if ( pExistingVarKey ) { const dword dwKeyVal = (dword)pExistingVarKey->GetValNum(); @@ -62,11 +64,11 @@ bool CResourceDef::SetResourceName( lpctstr pszName ) g_Log.EventWarn("DEFNAME=%s: redefinition (0%x!=0%x)\n", pszName, iKeyIndex, iResIndex); - pVarKeyNum = g_Exp.m_VarResDefs.SetNum( pszName, dwResPrivateUID ); + pVarKeyNum = gwriter->m_VarResDefs.SetNum( pszName, dwResPrivateUID ); } else { - pVarKeyNum = g_Exp.m_VarResDefs.SetNumNew( pszName, dwResPrivateUID ); + pVarKeyNum = gwriter->m_VarResDefs.SetNumNew( pszName, dwResPrivateUID ); } if ( pVarKeyNum == nullptr ) @@ -136,7 +138,8 @@ bool CResourceDef::MakeResourceName() size_t uiVar = 1; size_t uiLen = strlen( pbuf ); - for ( const CVarDefCont *pVarDef : g_Exp.m_VarResDefs ) + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + for ( const CVarDefCont *pVarDef : gReader->m_VarResDefs ) { // Is this a similar key? ptcKey = pVarDef->GetKey(); diff --git a/src/common/resource/CResourceHash.cpp b/src/common/resource/CResourceHash.cpp index 8af792106..1a9e7d5c2 100644 --- a/src/common/resource/CResourceHash.cpp +++ b/src/common/resource/CResourceHash.cpp @@ -3,6 +3,7 @@ * */ +#include "../CLog.h" #include "CResourceDef.h" #include "CResourceHash.h" @@ -48,16 +49,51 @@ int CResourceHashArray::_compare(std::unique_ptr const& pObjStored return -1; } +void CResourceHashArray::ManualSort() +{ + auto sorter = CResourceHashArraySorter(); + std::sort(this->begin(), this->end(), sorter); + auto it = this->cbegin(); + const auto itEnd = this->cend(); + while (true) + { + it = std::adjacent_find(it, itEnd); + if (it == itEnd) + break; + + const CResourceDef * pRes = it->get(); + g_Log.EventError("Duplicated CResourceDef '%s'.\n", pRes->GetResourceName()); + } +} + void CResourceHash::AddSortKey(CResourceID const& rid, CResourceDef* pNew) { ASSERT(rid.GetResPage() <= RES_PAGE_MAX); // RES_PAGE_ANY can be used only for search, you can't insert a rid with this special page - + auto& destArray = m_Array[GetHashArray(rid)]; ASSERT(destArray.find_sorted(rid) == sl::scont_bad_index()); destArray.emplace(pNew); } +void CResourceHash::AddUnsortedKey(CResourceID const& rid, CResourceDef* pNew) +{ + ASSERT(rid.GetResPage() <= RES_PAGE_MAX); // RES_PAGE_ANY can be used only for search, you can't insert a rid with this special page + + auto& destArray = m_Array[GetHashArray(rid)]; + + //ASSERT(destArray.find_sorted(rid) == sl::scont_bad_index()); + destArray.emplace_unsorted(pNew); +} + +void CResourceHash::SortStep() +{ + for (auto& arr : m_Array) + { + arr.ManualSort(); + } +} + void CResourceHash::SetAt(CResourceID const& rid, size_t index, CResourceDef* pNew) { ASSERT(rid.GetResPage() <= RES_PAGE_MAX); // RES_PAGE_ANY can be used only for search, you can't insert a rid with this special page diff --git a/src/common/resource/CResourceHash.h b/src/common/resource/CResourceHash.h index 6c6262e59..67b99d495 100644 --- a/src/common/resource/CResourceHash.h +++ b/src/common/resource/CResourceHash.h @@ -28,6 +28,8 @@ class CResourceHashArray : public sl::unique_ptr_sorted_vectorfind_predicate(rid, _compare); } //inline bool Contains(CResourceID const& rid) const { return (sl::scont_bad_index() != this->find_sorted(rid)); } }; @@ -42,7 +44,7 @@ struct CResourceHash CResourceHash& operator=(const CResourceHash&) = delete; private: - inline uint GetHashArray(const CResourceID& rid) const + constexpr inline uint GetHashArray(const CResourceID& rid) const { return (rid.GetResIndex() & 0x0F); } @@ -52,7 +54,7 @@ struct CResourceHash { return m_Array[GetHashArray(rid)].find_sorted(rid); } - inline sl::smart_ptr_view GetSmartPtrViewAt(const CResourceID& rid, size_t index) const + constexpr inline sl::smart_ptr_view GetSmartPtrViewAt(const CResourceID& rid, size_t index) const { return sl::smart_ptr_view(m_Array[GetHashArray(rid)][index]); } @@ -62,6 +64,10 @@ struct CResourceHash } void AddSortKey(CResourceID const& rid, CResourceDef* pNew); + + void AddUnsortedKey(CResourceID const& rid, CResourceDef* pNew); + void SortStep(); + void SetAt(CResourceID const& rid, size_t index, CResourceDef* pNew); //void ReplaceRid(CResourceID const& ridOld, CResourceDef* pNew); }; diff --git a/src/common/resource/CResourceHolder.cpp b/src/common/resource/CResourceHolder.cpp index f1630b6f7..a13c41123 100644 --- a/src/common/resource/CResourceHolder.cpp +++ b/src/common/resource/CResourceHolder.cpp @@ -1,11 +1,10 @@ -#include "../sphere_library/CSFileList.h" -#include "../CException.h" -#include "../CExpression.h" +//#include "../CException.h" // included in the precompiled header +#include "../CExpression.h" // included in the precompiled header #include "../CLog.h" #include "CResourceHolder.h" #include "CResourceHash.h" -#include "CResourceScript.h" +//#include "CResourceScript.h" //*************************************************** @@ -76,215 +75,7 @@ lpctstr const CResourceHolder::sm_szResourceBlocks[RES_QTY] = // static }; -//********************************************************* -// Resource Files - -CResourceScript * CResourceHolder::FindResourceFile( lpctstr pszPath ) -{ - ADDTOCALLSTACK("CResourceHolder::FindResourceFile"); - // Just match the titles ( not the whole path) - - lpctstr pszTitle = CScript::GetFilesTitle( pszPath ); - - for ( size_t i = 0; ; ++i ) - { - CResourceScript * pResFile = GetResourceFile(i); - if ( pResFile == nullptr ) - break; - lpctstr pszTitle2 = pResFile->GetFileTitle(); - if ( ! strcmpi( pszTitle2, pszTitle )) - return pResFile; - } - return nullptr; -} - -CResourceScript * CResourceHolder::AddResourceFile( lpctstr pszName ) -{ - ADDTOCALLSTACK("CResourceHolder::AddResourceFile"); - ASSERT(pszName != nullptr); - // Is this really just a dir name ? - - if (strlen(pszName) >= SPHERE_MAX_PATH) - throw CSError(LOGL_ERROR, 0, "Filename too long!"); - - tchar szName[SPHERE_MAX_PATH]; - Str_CopyLimitNull(szName, pszName, sizeof(szName)); - - tchar szTitle[SPHERE_MAX_PATH]; - lpctstr ptcTitle = CScript::GetFilesTitle(szName); - ASSERT_ALWAYS(strlen(ptcTitle) < sizeof(szTitle)); - Str_CopyLimitNull(szTitle, ptcTitle, sizeof(szTitle)); - - if ( szTitle[0] == '\0' ) - { - AddResourceDir( pszName ); - return nullptr; - } - - lpctstr pszExt = CScript::GetFilesExt( szTitle ); - if ( pszExt == nullptr ) - { - // No file extension provided, so append .scp to the filename - Str_ConcatLimitNull( szName, SPHERE_SCRIPT_EXT, sizeof(szName) ); - Str_ConcatLimitNull( szTitle, SPHERE_SCRIPT_EXT, sizeof(szTitle) ); - } - - if ( ! strnicmp( szTitle, SPHERE_FILE "tables", strlen(SPHERE_FILE "tables"))) - { - // Don't dupe this. - return nullptr; - } - - // Try to prevent dupes - CResourceScript * pNewRes = FindResourceFile(szTitle); - if ( pNewRes ) - return pNewRes; - - // Find correct path - pNewRes = new CResourceScript(); - if (! OpenResourceFind(static_cast(*pNewRes), szName)) - { - delete pNewRes; - return nullptr; - } - - m_ResourceFiles.emplace_back(pNewRes); - pNewRes->m_iResourceFileIndex = int(m_ResourceFiles.size() -1); - return pNewRes; -} - -void CResourceHolder::AddResourceDir( lpctstr pszDirName ) -{ - ADDTOCALLSTACK("CResourceHolder::AddResourceDir"); - if ( pszDirName[0] == '\0' ) - return; - - CSString sFilePath = CSFile::GetMergedFileName( pszDirName, "*" SPHERE_SCRIPT_EXT ); - - CSFileList filelist; - int iRet = filelist.ReadDir( sFilePath, false ); - if ( iRet < 0 ) - { - // also check script file path - sFilePath = CSFile::GetMergedFileName(m_sSCPBaseDir, sFilePath.GetBuffer()); - - iRet = filelist.ReadDir( sFilePath, true ); - if ( iRet < 0 ) - { - DEBUG_ERR(( "DirList=%d for '%s'\n", iRet, pszDirName )); - return; - } - } - - if ( iRet <= 0 ) // no files here. - return; - - CSStringListRec * psFile = filelist.GetHead(), *psFileNext = nullptr; - for ( ; psFile; psFile = psFileNext ) - { - psFileNext = psFile->GetNext(); - sFilePath = CSFile::GetMergedFileName( pszDirName, *psFile ); - AddResourceFile( sFilePath ); - } -} - -void CResourceHolder::LoadResourcesOpen( CScript * pScript ) -{ - ADDTOCALLSTACK("CResourceHolder::LoadResourcesOpen"); - // Load an already open resource file. - ASSERT(pScript); - ASSERT( pScript->HasCache() ); - - int iSections = 0; - while ( pScript->FindNextSection() ) - { - LoadResourceSection( pScript ); - ++iSections; - } - - if ( ! iSections ) - DEBUG_WARN(( "No resource sections in '%s'\n", pScript->GetFilePath())); -} - -bool CResourceHolder::LoadResources( CResourceScript * pScript ) -{ - ADDTOCALLSTACK("CResourceHolder::LoadResources"); - // Open the file then load it. - if ( pScript == nullptr ) - return false; - - if ( ! pScript->Open()) - { - g_Log.Event(LOGL_CRIT|LOGM_INIT, "[RESOURCES] '%s' not found...\n", pScript->GetFilePath()); - return false; - } - - g_Log.Event(LOGM_INIT, "Loading %s\n", pScript->GetFilePath()); - - LoadResourcesOpen( pScript ); - pScript->Close(); - pScript->CloseForce(); - return true; -} - -CResourceScript * CResourceHolder::LoadResourcesAdd( lpctstr pszNewFileName ) -{ - ADDTOCALLSTACK("CResourceHolder::LoadResourcesAdd"); - // Make sure this is added to my list of resource files - // And load it now. - - CResourceScript * pScript = AddResourceFile( pszNewFileName ); - if ( ! LoadResources(pScript) ) - return nullptr; - return pScript; -} - -bool CResourceHolder::OpenResourceFind( CScript &s, lpctstr pszFilename, bool fCritical ) -{ - ADDTOCALLSTACK("CResourceHolder::OpenResourceFind"); - // Open a single resource script file. - // Look in the specified path. - - if ( pszFilename == nullptr ) - pszFilename = s.GetFilePath(); - - // search the local dir or full path first. - if (CSFile::FileExists(pszFilename)) - { - if (s.Open(pszFilename, OF_READ | OF_NONCRIT)) - return true; - if (!fCritical) - return false; - } - - // next, check the script file path - CSString sPathName = CSFile::GetMergedFileName( m_sSCPBaseDir, pszFilename ); - if (CSFile::FileExists(sPathName)) - { - if (s.Open(sPathName, OF_READ | OF_NONCRIT)) - return true; - } - - // finally, strip the directory and re-check script file path - lpctstr pszTitle = CSFile::GetFilesTitle(pszFilename); - sPathName = CSFile::GetMergedFileName( m_sSCPBaseDir, pszTitle ); - if (CSFile::FileExists(sPathName)) - { - return s.Open(sPathName, OF_READ); - } - - g_Log.Event(LOGM_INIT|LOGL_ERROR, "Can't find file '%s' in any of the expected paths!.\n", pszFilename); - return false; -} - -bool CResourceHolder::LoadResourceSection( CScript * pScript ) -{ - ADDTOCALLSTACK("CResourceHolder::LoadResourceSection"); - UnreferencedParameter(pScript); - // Just stub this out for others for now. - return false; -} //********************************************************* // Resource Section Definitions @@ -294,13 +85,17 @@ lpctstr CResourceHolder::GetName() const return "CFG"; } -CResourceScript * CResourceHolder::GetResourceFile( size_t i ) +CResourceHolder::CResourceHolder() { - if ( ! m_ResourceFiles.IsValidIndex(i) ) - return nullptr; // All resource files we need to get blocks from later. - return m_ResourceFiles[i]; + // Avoid unnecessary continuous growing re-allocations at startup, just start with some preallocated space. + for (auto& arr : m_ResHash.m_Array) + { + arr.reserve(0x100); + } } + + CResourceID CResourceHolder::ResourceGetID_EatStr(RES_TYPE restype, lpctstr &ptcName, word wPage, bool fCanFail) { ADDTOCALLSTACK("CResourceHolder::ResourceGetID_EatStr"); diff --git a/src/common/resource/CResourceHolder.h b/src/common/resource/CResourceHolder.h index 003554723..69db54cad 100644 --- a/src/common/resource/CResourceHolder.h +++ b/src/common/resource/CResourceHolder.h @@ -36,21 +36,9 @@ class CResourceHolder : public CScriptObj sl::smart_ptr_view ResourceGetDefRefByName(RES_TYPE restype, lpctstr pszName, word wPage = 0); CResourceDef* ResourceGetDefByName(RES_TYPE restype, lpctstr pszName, word wPage = 0); - CResourceScript * AddResourceFile( lpctstr pszName ); - void AddResourceDir( lpctstr pszDirName ); - public: - CResourceScript * FindResourceFile( lpctstr pszTitle ); - CResourceScript * LoadResourcesAdd( lpctstr pszNewName ); - - void LoadResourcesOpen( CScript * pScript ); - bool LoadResources( CResourceScript * pScript ); static lpctstr GetResourceBlockName( RES_TYPE restype ); - virtual bool OpenResourceFind( CScript &s, lpctstr pszFilename, bool fCritical = true ); - virtual bool LoadResourceSection( CScript * pScript ) = 0; - - CResourceScript * GetResourceFile( size_t i ); CResourceID ResourceGetID_EatStr( RES_TYPE restype, lpctstr &pszName, word wPage = 0, bool fCanFail = false ); // this moves forward (changes!) the ptcName pointer! CResourceID ResourceGetID( RES_TYPE restype, lpctstr ptcName, word wPage = 0, bool fCanFail = false); CResourceID ResourceGetIDType( RES_TYPE restype, lpctstr pszName, word wPage = 0 ); @@ -63,7 +51,7 @@ class CResourceHolder : public CScriptObj public: lpctstr GetName() const; - CResourceHolder() = default; + CResourceHolder(); virtual ~CResourceHolder() = default; CResourceHolder(const CResourceHolder& copy) = delete; diff --git a/src/common/resource/CResourceLink.h b/src/common/resource/CResourceLink.h index 90124e963..ae8f4b6b4 100644 --- a/src/common/resource/CResourceLink.h +++ b/src/common/resource/CResourceLink.h @@ -43,7 +43,7 @@ class CResourceLink : public CResourceDef { ++_dwRefInstances; } - + void DelRefInstance(); bool IsLinked() const; // been loaded from the scripts ? diff --git a/src/common/resource/CResourceLock.cpp b/src/common/resource/CResourceLock.cpp index b99d1a8b8..07dbd74ab 100644 --- a/src/common/resource/CResourceLock.cpp +++ b/src/common/resource/CResourceLock.cpp @@ -33,7 +33,7 @@ bool CResourceLock::_Open(lpctstr ptcUnused, uint uiUnused) bool CResourceLock::Open(lpctstr ptcUnused, uint uiUnused) { ADDTOCALLSTACK("CResourceLock::Open"); - MT_UNIQUE_LOCK_RETURN(CResourceLock::_Open(ptcUnused, uiUnused)); + MT_UNIQUE_LOCK_RETURN(this, CResourceLock::_Open(ptcUnused, uiUnused)); } void CResourceLock::_Close() @@ -60,7 +60,7 @@ void CResourceLock::_Close() void CResourceLock::Close() { ADDTOCALLSTACK("CResourceLock::Close"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); CResourceLock::_Close(); } @@ -75,10 +75,10 @@ bool CResourceLock::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the EXC_TRY("_ReadTextLine"); ASSERT(m_pLock); - ASSERT( ! IsBinaryMode() ); + ASSERT( ! _IsBinaryMode() ); - tchar* ptcBuf = _GetKeyBufferRaw(SCRIPT_MAX_LINE_LEN); - while ( CCacheableScriptFile::_ReadString( ptcBuf, SCRIPT_MAX_LINE_LEN )) + tchar* ptcBuf = _GetKeyBufferRaw(); + while ( CCacheableScriptFile::_ReadString( ptcBuf, sm_TextBufMaxSize )) { m_pLock->m_iLineNum = ++m_iLineNum; // share this with original open. if ( fRemoveBlanks ) @@ -96,7 +96,7 @@ bool CResourceLock::_ReadTextLine( bool fRemoveBlanks ) // Read a line from the bool CResourceLock::ReadTextLine( bool fRemoveBlanks ) // Read a line from the opened script file { //ADDTOCALLSTACK_DEBUG("CResourceLock::ReadTextLine"); - MT_UNIQUE_LOCK_RETURN(CResourceLock::_ReadTextLine(fRemoveBlanks)); + MT_UNIQUE_LOCK_RETURN(this, CResourceLock::_ReadTextLine(fRemoveBlanks)); } int CResourceLock::OpenLock( CResourceScript * pLock, CScriptLineContext context ) diff --git a/src/common/resource/CResourceQty.cpp b/src/common/resource/CResourceQty.cpp index 5d03f5458..9e3e2cf92 100644 --- a/src/common/resource/CResourceQty.cpp +++ b/src/common/resource/CResourceQty.cpp @@ -8,7 +8,7 @@ #include "../../game/chars/CChar.h" #include "../../game/CObjBase.h" #include "../../sphere/threads.h" -#include "../CExpression.h" +#include "../CExpression.h" // included in the precompiled header #include "../CLog.h" #include "CResourceQty.h" diff --git a/src/common/resource/CResourceRef.cpp b/src/common/resource/CResourceRef.cpp index d1a20cacb..cd0d1b3b5 100644 --- a/src/common/resource/CResourceRef.cpp +++ b/src/common/resource/CResourceRef.cpp @@ -4,7 +4,7 @@ */ #include "../../game/CServerConfig.h" -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header #include "../CScript.h" #include "CResourceRef.h" diff --git a/src/common/resource/CResourceScript.cpp b/src/common/resource/CResourceScript.cpp index 6069b3bff..c0db21cd1 100644 --- a/src/common/resource/CResourceScript.cpp +++ b/src/common/resource/CResourceScript.cpp @@ -10,6 +10,16 @@ #include "../CLog.h" #include "CResourceScript.h" +CResourceScript::CResourceScript(lpctstr pszFileName) // explicit +{ + _Init(); + _SetFilePath(pszFileName); +} + +CResourceScript::CResourceScript() +{ + _Init(); +} bool CResourceScript::_CheckForChange() { @@ -44,7 +54,12 @@ bool CResourceScript::_CheckForChange() bool CResourceScript::CheckForChange() { ADDTOCALLSTACK("CResourceScript::CheckForChange"); - MT_UNIQUE_LOCK_RETURN(CResourceScript::_CheckForChange()); + MT_UNIQUE_LOCK_RETURN(this, CResourceScript::_CheckForChange()); +} + +bool CResourceScript::IsFirstCheck() const noexcept +{ + return (m_dwSize == UINT32_MAX && !m_dateChange.IsTimeValid()); } void CResourceScript::ReSync() @@ -55,7 +70,7 @@ void CResourceScript::ReSync() _fCacheToBeUpdated = true; if ( !Open() ) return; - g_Cfg.LoadResourcesOpen( this ); + g_Cfg.LoadResourcesOpen( this, true ); Close(); } @@ -74,9 +89,9 @@ bool CResourceScript::Open( lpctstr pszFilename, uint wFlags ) if ( CheckForChange() ) { // what should we do about it ? reload it of course ! - g_Serv.SetServerMode(SERVMODE_ResyncLoad); - g_Cfg.LoadResourcesOpen( this ); - g_Serv.SetServerMode(SERVMODE_Run); + g_Serv.SetServerMode(ServMode::ResyncLoad); + g_Cfg.LoadResourcesOpen( this, true ); + g_Serv.SetServerMode(ServMode::Run); } } ASSERT(HasCache()); @@ -99,8 +114,8 @@ void CResourceScript::Close() // Close it later when we know it has not been used for a bit. if ( ! IsFileOpen()) return; - --m_iOpenCount; + -- m_iOpenCount; if ( ! m_iOpenCount ) { // Just leave it open for caching purposes diff --git a/src/common/resource/CResourceScript.h b/src/common/resource/CResourceScript.h index 9fa4da247..b1243e153 100644 --- a/src/common/resource/CResourceScript.h +++ b/src/common/resource/CResourceScript.h @@ -31,15 +31,8 @@ class CResourceScript : public CScript public: static const char *m_sClassName; - explicit CResourceScript(lpctstr pszFileName) - { - _Init(); - _SetFilePath(pszFileName); - } - CResourceScript() - { - _Init(); - } + explicit CResourceScript(lpctstr pszFileName); + CResourceScript(); virtual ~CResourceScript() = default; private: bool _CheckForChange(); @@ -49,10 +42,7 @@ public: bool CheckForChange(); CResourceScript(const CResourceScript& copy) = delete; CResourceScript& operator=(const CResourceScript& other) = delete; - bool IsFirstCheck() const noexcept - { - return (m_dwSize == UINT32_MAX && !m_dateChange.IsTimeValid()); - } + bool IsFirstCheck() const noexcept; void ReSync(); virtual bool Open( lpctstr pszFilename = nullptr, uint wFlags = OF_READ ) override; virtual void Close() override; diff --git a/src/common/resource/CResourceSortedArrays.h b/src/common/resource/CResourceSortedArrays.h index 95b6daf70..618ece4c7 100644 --- a/src/common/resource/CResourceSortedArrays.h +++ b/src/common/resource/CResourceSortedArrays.h @@ -85,7 +85,7 @@ template class CObjUniquePtrNameSortVector final : public sl::unique_ptr_sorted_vector<_ObjType, CObjUniquePtrNameVectorSorter<_ObjType>> { public: - //static const char *m_sClassName; + //static const char *m_sClassName; inline size_t find_sorted(lpctstr ptcKey) const noexcept { return this->find_predicate(ptcKey, CObjUniquePtrNameVectorSorter<_ObjType>::_compare); } inline bool ContainsKey(lpctstr ptcKey) const noexcept { return (sl::scont_bad_index() != this->find_sorted(ptcKey)); } }; diff --git a/src/common/resource/CValueDefs.cpp b/src/common/resource/CValueDefs.cpp index fe1def35a..65cb07c87 100644 --- a/src/common/resource/CValueDefs.cpp +++ b/src/common/resource/CValueDefs.cpp @@ -1,6 +1,6 @@ #include "../../sphere/threads.h" #include "../sphere_library/CSRand.h" -#include "../CExpression.h" +#include "../CExpression.h" // included in the precompiled header #include "CValueDefs.h" @@ -31,7 +31,7 @@ bool CValueRangeDef::Load( tchar * pszDef ) // it can be a range (with format {lo# hi#}) or a single value, even without brackets, // so we don't need a warning if GetRangeVals doesn't find the brackets int64 piVal[2]; - int iQty = g_Exp.GetRangeVals( pszDef, piVal, ARRAY_COUNT(piVal), true); + int iQty = CExpression::GetExprParser().GetRangeVals( pszDef, piVal, ARRAY_COUNT(piVal), true); if ( iQty <= 0 ) return false; diff --git a/src/common/resource/sections/CDialogDef.cpp b/src/common/resource/sections/CDialogDef.cpp index 4ef0dc7be..038aed079 100644 --- a/src/common/resource/sections/CDialogDef.cpp +++ b/src/common/resource/sections/CDialogDef.cpp @@ -1,10 +1,10 @@ -#include "../../../game/chars/CChar.h" +#include "../../../game/chars/CChar.h" // needed, even if clangd says the opposite #include "../../../game/clients/CClient.h" #include "../../../game/CObjBase.h" #include "../../../sphere/threads.h" -#include "../../CException.h" -#include "../../CExpression.h" -#include "../../CScriptTriggerArgs.h" +//#include "../../CException.h" // included in the precompiled header +//#include "../../CExpression.h" // included in the precompiled header +//#include "../../CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../CResourceLock.h" #include "CDialogDef.h" @@ -131,8 +131,9 @@ bool CDialogDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on t { // RES_FUNCTION call CSString sVal; - CScriptTriggerArgs Args(s.GetArgRaw()); - if ( r_Call(uiFunctionIndex, pSrc, &Args, &sVal) ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(s.GetArgRaw()); + if ( r_Call(uiFunctionIndex, pScriptArgs, pSrc, &sVal) ) return true; } @@ -141,6 +142,7 @@ bool CDialogDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on t return m_pObj->r_Verb(s, pSrc); } + auto &gExprParser = CExpression::GetExprParser(); lptstr ptcArgs = s.GetArgStr(); //g_Log.EventDebug("Dialog index %d, KEY %s ARG %s.\n", index, ptcKey, ptcArgs); @@ -150,25 +152,33 @@ bool CDialogDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on t GETNONWHITESPACE(ptcArgs_); }; - const auto _CalcRelative = [](lptstr& ptcArgs_, int &iCoordBase_) -> int + const auto _CalcRelative = [&gExprParser](lptstr& ptcArgs_, int &iCoordBase_) -> int { int c; - if ( *ptcArgs_ == '-' && IsSpace(ptcArgs_[1])) + if ( *ptcArgs_ == '-' && IsSpace(ptcArgs_[1])) c = iCoordBase_, ++ptcArgs_; else if ( *ptcArgs_ == '+' ) - c = iCoordBase_ + Exp_GetSingle( ++ptcArgs_ ); + c = iCoordBase_ + n64_narrow_n32_checked(gExprParser.GetSingle(++ptcArgs_ )); else if ( *ptcArgs_ == '-' ) - c = iCoordBase_ - Exp_GetSingle( ++ptcArgs_ ); + c = iCoordBase_ - n64_narrow_n32_checked(gExprParser.GetSingle(++ptcArgs_ )); else if ( *ptcArgs_ == '*' ) - iCoordBase_ = c = iCoordBase_ + Exp_GetSingle( ++ptcArgs_ ); + iCoordBase_ = c = iCoordBase_ + n64_narrow_n32_checked(gExprParser.GetSingle( ++ptcArgs_ )); else - c = Exp_GetSingle( ptcArgs_ ); + c = n64_narrow_n32_checked(gExprParser.GetSingle( ptcArgs_ )); return c; }; -# define GET_ABSOLUTE(c) _SkipAll(ptcArgs); int c = Exp_GetSingle(ptcArgs); -# define GET_EVAL(c) _SkipAll(ptcArgs); int c = Exp_GetVal(ptcArgs); -# define GET_RELATIVE(c, base) _SkipAll(ptcArgs); int c = _CalcRelative(ptcArgs, base); +# define GET_RELATIVE(c, base) \ + _SkipAll(ptcArgs); \ + const int c = _CalcRelative(ptcArgs, base); + +# define GET_ABSOLUTE(c) \ + _SkipAll(ptcArgs); \ + const int c = n64_narrow_n32_checked(gExprParser.GetSingle(ptcArgs), false); + +# define GET_EVAL(c) \ + _SkipAll(ptcArgs); \ + const int c = n64_narrow_n32_checked(gExprParser.GetVal(ptcArgs), false); switch( index ) { @@ -430,17 +440,17 @@ bool CDialogDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on t if ( *ptcArgs == '-' && (IsSpace( ptcArgs[1] ) || !ptcArgs[1]) ) ++ptcArgs; else if ( *ptcArgs == '*' ) - m_iOriginX += Exp_GetSingle( ++ptcArgs ); + m_iOriginX += n64_narrow_n32_checked(gExprParser.GetSingle( ++ptcArgs ), false); else - m_iOriginX = Exp_GetSingle( ptcArgs ); + m_iOriginX = n64_narrow_n32_checked(gExprParser.GetSingle( ptcArgs ), false); _SkipAll( ptcArgs ); if ( *ptcArgs == '-' && (IsSpace( ptcArgs[1] ) || !ptcArgs[1]) ) ++ptcArgs; else if ( *ptcArgs == '*' ) - m_iOriginY += Exp_GetSingle( ++ptcArgs ); + m_iOriginY += n64_narrow_n32_checked(gExprParser.GetSingle( ++ptcArgs ), false); else - m_iOriginY = Exp_GetSingle( ptcArgs ); + m_iOriginY = n64_narrow_n32_checked(gExprParser.GetSingle( ptcArgs ), false); return true; } @@ -552,11 +562,13 @@ bool CDialogDef::GumpSetup( int iPage, CClient * pClient, CObjBase * pObjSrc, lp m_wPage = (word)(iPage); m_fNoDispose = false; - CScriptTriggerArgs Args(iPage, 0, pObjSrc); - //DEBUG_ERR(("Args.m_s1_buf_vec %s Args.m_s1 %s Arguments 0x%x\n",Args.m_s1_buf_vec, Args.m_s1, Arguments)); + CExpression& expr_parser = CExpression::GetExprParser(); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iPage, 0, 0, pObjSrc); + //DEBUG_ERR(("pScriptArgs->m_s1_buf_vec %s pScriptArgs->m_s1 %s Arguments 0x%x\n",pScriptArgs->m_s1_buf_vec, pScriptArgs->m_s1, Arguments)); if (Arguments) { - Args.m_s1_buf_vec = Args.m_s1 = Arguments; + pScriptArgs->m_s1_buf_vec = pScriptArgs->m_s1 = Arguments; } // read text first @@ -564,7 +576,8 @@ bool CDialogDef::GumpSetup( int iPage, CClient * pClient, CObjBase * pObjSrc, lp { while ( s.ReadKey()) { - m_pObj->ParseScriptText( s.GetKeyBuffer(), pClient->GetChar() ); + CScriptExprContext scpContext{._pScriptObjI = m_pObj}; + expr_parser.ParseScriptText( s.GetKeyBuffer(), scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pClient->GetChar() ); m_sText.emplace_back(false) = s.GetKey(); } } @@ -582,13 +595,14 @@ bool CDialogDef::GumpSetup( int iPage, CClient * pClient, CObjBase * pObjSrc, lp // starting x,y location. int64 iSizes[2]; tchar * pszBuf = s.GetKeyBuffer(); - m_pObj->ParseScriptText( pszBuf, pClient->GetChar() ); + CScriptExprContext scpContext{._pScriptObjI = m_pObj}; + expr_parser.ParseScriptText( pszBuf, scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pClient->GetChar() ); Str_ParseCmds( pszBuf, iSizes, ARRAY_COUNT(iSizes) ); m_x = (int)(iSizes[0]); m_y = (int)(iSizes[1]); - const auto trigRet = OnTriggerRunVal( s, TRIGRUN_SECTION_TRUE, pClient->GetChar(), &Args ); + const auto trigRet = OnTriggerRunVal( s, TRIGRUN_SECTION_TRUE, pScriptArgs, pClient->GetChar() ); m_sText.shrink_to_fit(); m_sControls.shrink_to_fit(); diff --git a/src/common/resource/sections/CItemTypeDef.cpp b/src/common/resource/sections/CItemTypeDef.cpp index ccd232c0c..5b123ae54 100644 --- a/src/common/resource/sections/CItemTypeDef.cpp +++ b/src/common/resource/sections/CItemTypeDef.cpp @@ -1,8 +1,8 @@ #include "../../../game/CServerConfig.h" #include "../../../game/CWorld.h" #include "../../../sphere/threads.h" -#include "../../CException.h" -#include "../../CExpression.h" +//#include "../../CException.h" // included in the precompiled header +//#include "../../CExpression.h" // included in the precompiled header #include "CItemTypeDef.h" int CItemTypeDef::GetItemType() const diff --git a/src/common/resource/sections/CRandGroupDef.cpp b/src/common/resource/sections/CRandGroupDef.cpp index 346cb7ce9..8f0a78e11 100644 --- a/src/common/resource/sections/CRandGroupDef.cpp +++ b/src/common/resource/sections/CRandGroupDef.cpp @@ -3,8 +3,8 @@ #include "../../../game/CServerConfig.h" #include "../../../game/triggers.h" #include "../../sphere_library/CSRand.h" -#include "../../CException.h" -#include "../../CExpression.h" +//#include "../../CException.h" // included in the precompiled header +//#include "../../CExpression.h" // included in the precompiled header #include "CRegionResourceDef.h" #include "CRandGroupDef.h" @@ -258,7 +258,7 @@ size_t CRandGroupDef::GetRandMemberIndex( CChar * pCharSrc, bool fTrigger ) cons if (IsTrigUsed(TRIGGER_RESOURCETEST)) { - if (fTrigger && pOreDef->OnTrigger("@ResourceTest", pCharSrc, nullptr) == TRIGRET_RET_TRUE) + if (fTrigger && pOreDef->OnTrigger("@ResourceTest", CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharSrc) == TRIGRET_RET_TRUE) continue; } } diff --git a/src/common/resource/sections/CRegionResourceDef.cpp b/src/common/resource/sections/CRegionResourceDef.cpp index 074752d74..03ec6845a 100644 --- a/src/common/resource/sections/CRegionResourceDef.cpp +++ b/src/common/resource/sections/CRegionResourceDef.cpp @@ -1,7 +1,7 @@ #include "../../../sphere/threads.h" #include "../../../game/items/CItemBase.h" #include "../../../game/CServerConfig.h" -#include "../../CException.h" +//#include "../../CException.h" // included in the precompiled header #include "../CResourceLock.h" #include "CRegionResourceDef.h" @@ -37,7 +37,7 @@ lpctstr const CRegionResourceDef::sm_szTrigName[RRTRIG_QTY+1] = // static }; -TRIGRET_TYPE CRegionResourceDef::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CRegionResourceDef::OnTrigger(lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc) { ADDTOCALLSTACK("CRegionResourceDef::OnTrigger"); // Attach some trigger to the cchar. (PC or NPC) @@ -46,7 +46,7 @@ TRIGRET_TYPE CRegionResourceDef::OnTrigger( lpctstr pszTrigName, CTextConsole * CResourceLock s; if ( ResourceLock( s )) { - TRIGRET_TYPE iRet = CScriptObj::OnTriggerScript( s, pszTrigName, pSrc, pArgs ); + TRIGRET_TYPE iRet = CScriptObj::OnTriggerScript( s, pszTrigName, pScriptArgs, pSrc); return iRet; } return TRIGRET_RET_DEFAULT; diff --git a/src/common/resource/sections/CRegionResourceDef.h b/src/common/resource/sections/CRegionResourceDef.h index f5415d6a2..002a55315 100644 --- a/src/common/resource/sections/CRegionResourceDef.h +++ b/src/common/resource/sections/CRegionResourceDef.h @@ -58,7 +58,7 @@ class CRegionResourceDef : public CResourceLink public: virtual bool r_LoadVal( CScript & s ) override; virtual bool r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc = nullptr, bool fNoCallParent = false, bool fNoCallChildren = false ) override; - virtual TRIGRET_TYPE OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) override; + virtual TRIGRET_TYPE OnTrigger( lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) override; }; #endif // _INC_CREGIONRESOURCEDEF_H diff --git a/src/common/resource/sections/CSkillClassDef.cpp b/src/common/resource/sections/CSkillClassDef.cpp index 251021e57..5019b9db2 100644 --- a/src/common/resource/sections/CSkillClassDef.cpp +++ b/src/common/resource/sections/CSkillClassDef.cpp @@ -1,6 +1,6 @@ #include "../../../sphere/threads.h" #include "../../../game/CServerConfig.h" -#include "../../CException.h" +//#include "../../CException.h" // included in the precompiled header #include "CSkillClassDef.h" enum SCC_TYPE diff --git a/src/common/resource/sections/CSkillDef.cpp b/src/common/resource/sections/CSkillDef.cpp index 621a75f53..72c0259bc 100644 --- a/src/common/resource/sections/CSkillDef.cpp +++ b/src/common/resource/sections/CSkillDef.cpp @@ -1,5 +1,5 @@ #include "../../../sphere/threads.h" -#include "../../CException.h" +//#include "../../CException.h" // included in the precompiled header #include "../../CScript.h" #include "CSkillDef.h" diff --git a/src/common/resource/sections/CSpellDef.cpp b/src/common/resource/sections/CSpellDef.cpp index 9bc0d5ed8..9d777c516 100644 --- a/src/common/resource/sections/CSpellDef.cpp +++ b/src/common/resource/sections/CSpellDef.cpp @@ -1,8 +1,8 @@ #include "../../../game/CServerConfig.h" #include "../../../game/game_macros.h" #include "../../../game/uo_files/uofiles_enums.h" -#include "../../CException.h" -#include "../../CExpression.h" +//#include "../../CException.h" // included in the precompiled header +//#include "../../CExpression.h" // included in the precompiled header #include "CSpellDef.h" diff --git a/src/common/resource/sections/CSpellDef.h b/src/common/resource/sections/CSpellDef.h index 24dd98e89..5c406154f 100644 --- a/src/common/resource/sections/CSpellDef.h +++ b/src/common/resource/sections/CSpellDef.h @@ -78,7 +78,7 @@ class CSpellDef : public CResourceLink * * @brief Check if this Spell has the given flags. * - * @param wFlags The flags. + * @param uiFlags The flags. * * @return true if match, false if not. */ diff --git a/src/common/resource/sections/CWebPageDef.cpp b/src/common/resource/sections/CWebPageDef.cpp index 518f17875..12ee1e8a1 100644 --- a/src/common/resource/sections/CWebPageDef.cpp +++ b/src/common/resource/sections/CWebPageDef.cpp @@ -5,9 +5,11 @@ #include "../../../game/CWorld.h" #include "../../../game/CWorldGameTime.h" #include "../../../network/CClientIterator.h" +#include "../../../network/send.h" #include "../../sphere_library/CSFileList.h" -#include "../../CException.h" -#include "../../CExpression.h" +//#include "../../CException.h" // included in the precompiled header +//#include "../../CExpression.h" // included in the precompiled header +//#include "../../CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../sphereversion.h" #include "../CResourceLock.h" #include "CWebPageDef.h" @@ -163,10 +165,6 @@ bool CWebPageDef::r_LoadVal( CScript & s ) // Load an item Script return SetSourceFile( s.GetArgStr(), nullptr ); case WC_WEBPAGEUPDATE: // (seconds) m_iUpdatePeriod = s.GetArgVal(); - if ( m_iUpdatePeriod && (m_type == WEBPAGE_TEXT) ) - { - m_type = WEBPAGE_TEMPLATE; - } break; default: return CScriptObj::r_LoadVal( s ); @@ -219,7 +217,14 @@ bool CWebPageDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on if ( pszArgs[0] == '\0' ) pszArgs = "%NAME%%REGION.NAME%\n"; Str_CopyLimitNull( pszTmp2, pszArgs, Str_TempLength() ); - pChar->ParseScriptText( Str_MakeFiltered( pszTmp2 ), &g_Serv, 1 ); + + CScriptExprContext scpContext{._pScriptObjI = pChar}; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + CExpression::GetExprParser().ParseScriptText( + Str_MakeFiltered(pszTmp2), + scpContext, pScriptArgs, + &g_Serv, 1 ); + pSrc->SysMessage( pszTmp2 ); } } @@ -231,7 +236,7 @@ bool CWebPageDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on if ( !s.HasArgs() ) return false; - IT_TYPE needtype = ( iHeadKey == WV_GUILDLIST ) ? IT_STONE_GUILD : IT_STONE_TOWN; + const IT_TYPE needtype = ( iHeadKey == WV_GUILDLIST ) ? IT_STONE_GUILD : IT_STONE_TOWN; for ( size_t i = 0; i < g_World.m_Stones.size(); ++i ) { @@ -240,10 +245,16 @@ bool CWebPageDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on continue; ++sm_iListIndex; - Str_CopyLimitNull(pszTmp2, s.GetArgStr(), Str_TempLength()); - pStone->ParseScriptText(Str_MakeFiltered(pszTmp2), &g_Serv, 1); - pSrc->SysMessage(pszTmp2); + + CScriptExprContext scpContext{._pScriptObjI = pStone}; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + CExpression::GetExprParser().ParseScriptText( + Str_MakeFiltered(pszTmp2), + scpContext, pScriptArgs, + &g_Serv, 1); + + pSrc->SysMessage(pszTmp2); } } break; @@ -257,7 +268,14 @@ bool CWebPageDef::r_Verb( CScript & s, CTextConsole * pSrc ) // some command on CGMPage* pPage = sptrPage.get(); ++sm_iListIndex; Str_CopyLimitNull( pszTmp2, s.GetArgStr(), Str_TempLength()); - pPage->ParseScriptText( Str_MakeFiltered( pszTmp2 ), &g_Serv, 1 ); + + CScriptExprContext scpContext{._pScriptObjI = pPage}; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + CExpression::GetExprParser().ParseScriptText( + Str_MakeFiltered(pszTmp2), + scpContext, pScriptArgs, + &g_Serv, 1 ); + pSrc->SysMessage( pszTmp2 ); } } @@ -299,9 +317,7 @@ bool CWebPageDef::WebPageUpdate( bool fNow, lpctstr pszDstName, CTextConsole * p if ( pszDstName == nullptr ) pszDstName = m_sDstFilePath; - if ( m_type != WEBPAGE_TEMPLATE || - *pszDstName == '\0' || - m_sSrcFilePath.IsEmpty()) + if (*pszDstName == '\0' || m_sSrcFilePath.IsEmpty()) return false; CScript FileRead; @@ -317,8 +333,8 @@ bool CWebPageDef::WebPageUpdate( bool fNow, lpctstr pszDstName, CTextConsole * p return false; } + CExpression& expr_parser = CExpression::GetExprParser(); bool fScriptMode = false; - while ( FileRead.ReadTextLine( false )) { tchar *pszTmp = Str_GetTemp(); @@ -329,9 +345,15 @@ bool CWebPageDef::WebPageUpdate( bool fNow, lpctstr pszDstName, CTextConsole * p { // Deal with the stuff preceding the scripts. *pszHead = '\0'; - pszHead += 26; - ParseScriptText( pszTmp, pSrc, 1 ); - FileOut.SysMessage( pszTmp ); + pszHead += 26; + + CScriptExprContext scpContext{._pScriptObjI = this}; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + expr_parser.ParseScriptText( + pszTmp, scpContext, pScriptArgs, + pSrc, 1 ); + + FileOut.SysMessage( pszTmp ); fScriptMode = true; } else @@ -369,7 +391,11 @@ bool CWebPageDef::WebPageUpdate( bool fNow, lpctstr pszDstName, CTextConsole * p } // Look for stuff we can displace here. %STUFF% - ParseScriptText( pszHead, pSrc, 1 ); + CScriptExprContext scpContext{._pScriptObjI = this}; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + expr_parser.ParseScriptText( pszHead, + scpContext, pScriptArgs, + pSrc, 1 ); FileOut.SysMessage( pszHead ); } @@ -381,8 +407,6 @@ void CWebPageDef::WebPageLog() ADDTOCALLSTACK("CWebPageDef::WebPageLog"); if ( ! m_iUpdateLog || ! m_iUpdatePeriod ) return; - if ( m_type != WEBPAGE_TEMPLATE ) - return; CSFileText FileRead; if ( ! FileRead.Open( m_sDstFilePath, OF_READ|OF_TEXT )) @@ -417,6 +441,12 @@ lpctstr const CWebPageDef::sm_szPageExt[] = ".JPG", ".JS", ".TXT", + ".PNG", + ".SVG", + ".WEBP", + ".XML", + ".CSV", + ".JSON", }; bool CWebPageDef::SetSourceFile( lpctstr pszName, CClient * pClient ) @@ -425,13 +455,19 @@ bool CWebPageDef::SetSourceFile( lpctstr pszName, CClient * pClient ) static WEBPAGE_TYPE const sm_szPageExtType[] = { WEBPAGE_BMP, - WEBPAGE_GIF, + WEBPAGE_GIF, WEBPAGE_TEMPLATE, WEBPAGE_TEMPLATE, WEBPAGE_JPG, WEBPAGE_JPG, - WEBPAGE_TEXT, - WEBPAGE_TEXT + WEBPAGE_JS, + WEBPAGE_TEXT, + WEBPAGE_PNG, + WEBPAGE_SVG, + WEBPAGE_WEBP, + WEBPAGE_XML, + WEBPAGE_CSV, + WEBPAGE_JSON, }; // attempt to set this to a source file. @@ -444,7 +480,7 @@ bool CWebPageDef::SetSourceFile( lpctstr pszName, CClient * pClient ) if ( pszExt == nullptr || pszExt[0] == '\0' ) return false; - int iType = FindTableSorted( pszExt, sm_szPageExt, ARRAY_COUNT( sm_szPageExt )); + const int iType = FindTable(pszExt, sm_szPageExt, ARRAY_COUNT(sm_szPageExt)); if ( iType < 0 ) return false; m_type = sm_szPageExtType[iType]; @@ -502,11 +538,18 @@ bool CWebPageDef::IsMatch( lpctstr pszMatch ) const lpctstr const CWebPageDef::sm_szPageType[WEBPAGE_QTY+1] = { - "text/html", // WEBPAGE_TEMPLATE (.htm) - "text/html", // WEBPAGE_TEMPLATE (.html) - "image/x-xbitmap", // WEBPAGE_BMP, + "text/html", // WEBPAGE_TEMPLATE + "text/plain", // WEBPAGE_TEXT + "image/bmp", // WEBPAGE_BMP, "image/gif", // WEBPAGE_GIF, "image/jpeg", // WEBPAGE_JPG, + "text/javascript", // WEBPAGE_JS, + "image/png", // WEBPAGE_PNG, + "image/svg+xml", // WEBPAGE_SVG, + "image/webp", // WEBPAGE_WEBP, + "text/xml", // WEBPAGE_XML, + "application/json", // WEBPAGE_JSON, + "text/csv", // WEBPAGE_CSV, nullptr // WEBPAGE_QTY }; @@ -534,7 +577,9 @@ int CWebPageDef::ServPageRequest( CClient * pClient, lpctstr pszURLArgs, CSTime CResourceLock s; if ( ResourceLock(s)) { - if (CScriptObj::OnTriggerScript( s, sm_szTrigName[WTRIG_Load], pClient, nullptr ) == TRIGRET_RET_TRUE) + // Taking the script args by this way is allowed only because the web page is parsed by the main thread. + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + if (CScriptObj::OnTriggerScript( s, sm_szTrigName[WTRIG_Load], pScriptArgs, pClient ) == TRIGRET_RET_TRUE) return 0; // Block further action. } } @@ -552,7 +597,7 @@ int CWebPageDef::ServPageRequest( CClient * pClient, lpctstr pszURLArgs, CSTime lpctstr pszName; bool fGenerate = false; - if ( m_type == WEBPAGE_TEMPLATE ) // my version of cgi + if (m_type == WEBPAGE_TEMPLATE || m_type == WEBPAGE_TEXT || m_type == WEBPAGE_XML || m_type == WEBPAGE_JSON || m_type == WEBPAGE_CSV) // my version of cgi { pszName = GetDstName(); if ( pszName[0] == '\0' ) @@ -613,7 +658,7 @@ int CWebPageDef::ServPageRequest( CClient * pClient, lpctstr pszURLArgs, CSTime sm_szPageType[m_type] // type of the file. image/gif, image/x-xbitmap, image/jpeg ); - if ( m_type == WEBPAGE_TEMPLATE ) + if (m_type == WEBPAGE_TEMPLATE || m_type == WEBPAGE_TEXT || m_type == WEBPAGE_XML || m_type == WEBPAGE_JSON || m_type == WEBPAGE_CSV) iLen += snprintf(szTmp + iLen, uiWebDataBufSize - iLen, "Expires: 0\r\n"); else iLen += snprintf(szTmp + iLen, uiWebDataBufSize - iLen, "Last-Modified: %s\r\n", CSTime(dateChange).FormatGmt(nullptr)); @@ -714,7 +759,9 @@ bool CWebPageDef::ServPagePost( CClient * pClient, lpctstr pszURLArgs, tchar * p // B or BTN or BUTTON = the buttons // C or CHK or CHECK = the check boxes - CDialogResponseArgs resp; + // TODO: just split CDialogResponseArgs into two objects (CScriptTriggerArgs and a struct for other data?) + // Or just make CScriptTriggerArgs a member of CDialogResponseArgs... Favor composition over inheritance! + auto resp = std::make_shared(); dword dwButtonID = UINT32_MAX; for ( int i = 0; i < iArgs; ++i ) { @@ -742,7 +789,7 @@ bool CWebPageDef::ServPagePost( CClient * pClient, lpctstr pszURLArgs, tchar * p continue; if ( atoi(pszNum) ) { - resp.m_CheckArray.push_back(iNum); + resp->m_CheckArray.push_back(iNum); } break; case 'T': @@ -750,7 +797,7 @@ bool CWebPageDef::ServPagePost( CClient * pClient, lpctstr pszURLArgs, tchar * p { tchar *pszData = Str_GetTemp(); HtmlDeCode( pszData, pszNum ); - resp.AddText((word)(iNum), pszData); + resp->AddText((word)(iNum), pszData); } break; } @@ -766,7 +813,7 @@ bool CWebPageDef::ServPagePost( CClient * pClient, lpctstr pszURLArgs, tchar * p { if ( !s.IsKeyHead("ON", 2) || ( (dword)s.GetArgVal() != dwButtonID )) continue; - OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, pClient, &resp); + OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, resp, pClient); return true; } diff --git a/src/common/resource/sections/CWebPageDef.h b/src/common/resource/sections/CWebPageDef.h index bd4040bbb..4bacb28c4 100644 --- a/src/common/resource/sections/CWebPageDef.h +++ b/src/common/resource/sections/CWebPageDef.h @@ -27,6 +27,13 @@ enum WEBPAGE_TYPE WEBPAGE_BMP, WEBPAGE_GIF, WEBPAGE_JPG, + WEBPAGE_JS, + WEBPAGE_PNG, + WEBPAGE_SVG, + WEBPAGE_WEBP, + WEBPAGE_XML, + WEBPAGE_CSV, + WEBPAGE_JSON, WEBPAGE_QTY }; @@ -117,7 +124,7 @@ class CWebPageDef : public CResourceLink * @param [in,out] pClient If non-null, the client. * @param pszURLArgs The URL arguments. * @param [in,out] pPostData If non-null, information describing the post. - * @param stContentLength Length of the content. + * @param uiContentLength Length of the content. * * @return true if it succeeds, false if it fails. */ @@ -148,7 +155,7 @@ class CWebPageDef : public CResourceLink * * @param [in,out] pClient If non-null, the client. * @param [in,out] pszPage If non-null, the page. - * @param [in,out] pdateLastMod If non-null, the pdate last modifier. + * @param [in,out] pDateLastMod If non-null, the pdate last modifier. */ static void ServPage(CClient * pClient, tchar * pszPage, CSTime * pDateLastMod); @@ -156,9 +163,8 @@ class CWebPageDef : public CResourceLink explicit CWebPageDef(CResourceID id); virtual ~CWebPageDef() = default; -private: - CWebPageDef(const CWebPageDef& copy); - CWebPageDef& operator=(const CWebPageDef& other); + CWebPageDef(const CWebPageDef& copy) = delete; + CWebPageDef& operator=(const CWebPageDef& other) = delete; }; diff --git a/src/common/sphere_library/CSAssoc.cpp b/src/common/sphere_library/CSAssoc.cpp index 3831d757f..d2e0722a4 100644 --- a/src/common/sphere_library/CSAssoc.cpp +++ b/src/common/sphere_library/CSAssoc.cpp @@ -1,6 +1,6 @@ #include "CSAssoc.h" -#include "../CExpression.h" +#include "../CExpression.h" // included in the precompiled header #include "../common.h" diff --git a/src/common/sphere_library/CSFile.cpp b/src/common/sphere_library/CSFile.cpp index b652df0ec..cdc994d35 100644 --- a/src/common/sphere_library/CSFile.cpp +++ b/src/common/sphere_library/CSFile.cpp @@ -9,12 +9,14 @@ #include #endif -// CSFile:: Constructors, Destructor, Asign operator. +// CSFile:: Constructors, Destructor, Assign operator. CSFile::CSFile() { _fileDescriptor = _kInvalidFD; _uiMode = 0; + if (!_fBinaryMode.has_value()) + _fBinaryMode = true; } CSFile::~CSFile() @@ -79,7 +81,7 @@ void CSFile::Close() { ADDTOCALLSTACK("CSFile::Close"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); CSFile::_Close(); } @@ -150,7 +152,7 @@ bool CSFile::_Open( lpctstr ptcFilename, uint uiModeFlags ) bool CSFile::Open( lpctstr ptcFilename, uint uiModeFlags ) { ADDTOCALLSTACK("CSFile::Open"); - MT_UNIQUE_LOCK_RETURN(CSFile::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(this, CSFile::_Open(ptcFilename, uiModeFlags)); } bool CSFile::_IsFileOpen() const @@ -159,7 +161,7 @@ bool CSFile::_IsFileOpen() const } bool CSFile::IsFileOpen() const { - MT_SHARED_LOCK_RETURN(_fileDescriptor != _kInvalidFD); + MT_SHARED_LOCK_RETURN(this, _fileDescriptor != _kInvalidFD); } lpctstr CSFile::_GetFilePath() const @@ -168,7 +170,7 @@ lpctstr CSFile::_GetFilePath() const } lpctstr CSFile::GetFilePath() const { - MT_SHARED_LOCK_RETURN(_strFileName.GetBuffer()); + MT_SHARED_LOCK_RETURN(this, _strFileName.GetBuffer()); } bool CSFile::_SetFilePath( lpctstr pszName ) @@ -191,7 +193,7 @@ bool CSFile::_SetFilePath( lpctstr pszName ) bool CSFile::SetFilePath( lpctstr pszName ) { ADDTOCALLSTACK("CFile::SetFilePath"); - MT_UNIQUE_LOCK_RETURN(CSFile::_SetFilePath(pszName)); + MT_UNIQUE_LOCK_RETURN(this, CSFile::_SetFilePath(pszName)); } @@ -221,7 +223,7 @@ int CSFile::_GetLength() int CSFile::GetLength() { ADDTOCALLSTACK("CSFile::GetLength"); - MT_UNIQUE_LOCK_RETURN(CSFile::_GetLength()); + MT_UNIQUE_LOCK_RETURN(this, CSFile::_GetLength()); } int CSFile::_GetPosition() const @@ -253,13 +255,13 @@ int CSFile::_GetPosition() const int CSFile::GetPosition() const { ADDTOCALLSTACK("CSFile::GetPosition"); - MT_UNIQUE_LOCK_RETURN(CSFile::_GetPosition()); + MT_UNIQUE_LOCK_RETURN(this, CSFile::_GetPosition()); } int CSFile::Read( void * pData, int iLength ) const { ADDTOCALLSTACK("CSFile::Read"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); #ifdef _WIN32 DWORD ret; @@ -309,7 +311,7 @@ int CSFile::_Seek( int iOffset, int iOrigin ) int CSFile::Seek( int iOffset, int iOrigin ) { ADDTOCALLSTACK("CSFile::Seek"); - MT_UNIQUE_LOCK_RETURN(CSFile::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(this, CSFile::_Seek(iOffset, iOrigin)); } void CSFile::_SeekToBegin() @@ -370,14 +372,14 @@ bool CSFile::_Write( const void * pData, int iLength ) bool CSFile::Write(const void* pData, int iLength) { ADDTOCALLSTACK("CSFile::Write"); - MT_UNIQUE_LOCK_RETURN(CSFile::_Write(pData, iLength)); + MT_UNIQUE_LOCK_RETURN(this, CSFile::_Write(pData, iLength)); } // CSFile:: File name operations. lpctstr CSFile::GetFilesTitle( lpctstr pszPath ) // static { - ADDTOCALLSTACK("CSFile::GetFilesTitle"); + //ADDTOCALLSTACK("CSFile::GetFilesTitle"); // strrchr size_t len = strlen(pszPath); while ( len > 0 ) @@ -394,18 +396,18 @@ lpctstr CSFile::GetFilesTitle( lpctstr pszPath ) // static lpctstr CSFile::_GetFileTitle() const { - ADDTOCALLSTACK("CFile::_GetFileTitle"); + //ADDTOCALLSTACK("CFile::_GetFileTitle"); return CSFile::GetFilesTitle(_strFileName.GetBuffer()); } lpctstr CSFile::GetFileTitle() const { - ADDTOCALLSTACK("CFile::GetFileTitle"); + //ADDTOCALLSTACK("CFile::GetFileTitle"); return CSFile::GetFilesTitle( GetFilePath() ); } lpctstr CSFile::GetFilesExt( lpctstr pszName ) // static { - ADDTOCALLSTACK("CSFile::GetFilesExt"); + //ADDTOCALLSTACK("CSFile::GetFilesExt"); // get the EXTension including the . size_t len = strlen( pszName ); while ( len > 0 ) @@ -423,7 +425,7 @@ lpctstr CSFile::GetFilesExt( lpctstr pszName ) // static lpctstr CSFile::GetFileExt() const { - ADDTOCALLSTACK("CSFile::GetFileExt"); + //ADDTOCALLSTACK("CSFile::GetFileExt"); // get the EXTension including the . return GetFilesExt(GetFilePath()); } @@ -464,31 +466,20 @@ CSString CSFile::GetMergedFileName( lpctstr pszBase, lpctstr pszName ) // static // CSFile:: Mode operations. -uint CSFile::_GetFullMode() const -{ - return _uiMode; -} + uint CSFile::GetFullMode() const { - MT_SHARED_LOCK_RETURN(_uiMode); + MT_SHARED_LOCK_RETURN(this, _uiMode); } -uint CSFile::_GetMode() const -{ - return (_uiMode & 0x0FFFFFFF); -} uint CSFile::GetMode() const { - MT_SHARED_LOCK_RETURN(_uiMode & 0x0FFFFFFF); + MT_SHARED_LOCK_RETURN(this, _uiMode & 0x0FFFFFFF); } -bool CSFile::_IsWriteMode() const -{ - return (_uiMode & OF_WRITE); -} bool CSFile::IsWriteMode() const { - MT_SHARED_LOCK_RETURN(_uiMode & OF_WRITE); + MT_SHARED_LOCK_RETURN(this, _uiMode & OF_WRITE); } diff --git a/src/common/sphere_library/CSFile.h b/src/common/sphere_library/CSFile.h index 4929cd1e1..d03a23db2 100644 --- a/src/common/sphere_library/CSFile.h +++ b/src/common/sphere_library/CSFile.h @@ -39,7 +39,7 @@ class CSFile public: static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ CSFile(); @@ -79,8 +79,8 @@ protected: virtual void _Close(); public: virtual void Close(); /** * @brief Open a file in a specified mode. - * @param ptcName file to open. - * @param uiMode open mode. + * @param ptcFilename file to open. + * @param uiModeFlags open mode. * @return true if file is open, false otherwise. */ protected: virtual bool _Open( lpctstr ptcFilename = nullptr, uint uiModeFlags = OF_READ|OF_SHARE_DENY_NONE ); @@ -127,14 +127,14 @@ public: virtual int GetPosition() const; /** * @brief Reads data from the file. * @param pData buffer where store the readed data. - * @param stLength count of bytes to read. + * @param iLength count of bytes to read. * @return count of bytes readed. */ virtual int Read( void * pData, int iLength ) const; /** * @brief Set the position indicator. - * @param stOffset position to set. - * @param stOrigin origin (current position or init of the file). + * @param iOffset position to set. + * @param iOrigin origin (current position or init of the file). * @return position where the position indicator is set on success, -1 on error. */ protected: virtual int _Seek( int iOffset = 0, int iOrigin = SEEK_SET ); @@ -154,7 +154,7 @@ public: int SeekToEnd(); /** * @brief writes supplied data into file. * @param pData data to write. - * @param dwLength lenght of the data to write. + * @param iLength lenght of the data to write. * @return true is success, false otherwise. */ protected: virtual bool _Write(const void* pData, int iLength); @@ -202,27 +202,27 @@ public: lpctstr GetFileTitle() const; * @brief Get open mode (full). * @return full mode flags. */ -protected: uint _GetFullMode() const; -public: uint GetFullMode() const; +protected: inline uint _GetFullMode() const noexcept; +public: uint GetFullMode() const; /** * @brief Get open mode (OF_NONCRIT, OF_TEXT, OF_BINARY, OF_DEFAULTMODE) * * Get rid of OF_NONCRIT type flags * @return mode flags. */ -protected: uint _GetMode() const; -public: uint GetMode() const; +protected: inline uint _GetMode() const noexcept; +public: uint GetMode() const; /** * @brief Check if file es in binary mode. * @return Always true (this method is virtual). */ - virtual bool IsBinaryMode() const { return true; } + inline bool _IsBinaryMode() const; /** * @brief Check if file is open for write. * @return true if file is open for write, false otherwise. */ -protected: bool _IsWriteMode() const; -public: bool IsWriteMode() const; +protected: inline bool _IsWriteMode() const noexcept; +public: bool IsWriteMode() const; ///@} protected: @@ -233,9 +233,10 @@ public: bool IsWriteMode() const; #endif const file_descriptor_t _kInvalidFD = (file_descriptor_t)-1; -protected: CSString _strFileName; // File name (with path). uint _uiMode; // MMSYSTEM may use 32 bit flags. + std::optional _fBinaryMode; + public: file_descriptor_t _fileDescriptor; // File descriptor (POSIX, int) or HANDLE (Windows, size of a pointer). @@ -247,4 +248,27 @@ public: bool IsWriteMode() const; }; +// Inline methods + +uint CSFile::_GetFullMode() const noexcept +{ + return _uiMode; +} + +uint CSFile::_GetMode() const noexcept +{ + return (_uiMode & 0x0FFFFFFF); +} + +bool CSFile::_IsWriteMode() const noexcept +{ + return (_uiMode & OF_WRITE); +} + +bool CSFile::_IsBinaryMode() const +{ + return _fBinaryMode.value_or(true); // It must always have a value actually. +} + + #endif // _INC_CSFILE_H diff --git a/src/common/sphere_library/CSFileList.cpp b/src/common/sphere_library/CSFileList.cpp index 997540b25..f3578fb41 100644 --- a/src/common/sphere_library/CSFileList.cpp +++ b/src/common/sphere_library/CSFileList.cpp @@ -83,7 +83,7 @@ int CSFileList::ReadDir( lpctstr pszFileDir, bool bShowError ) break; } } - + DIR* dirp = opendir(szFileDir); struct dirent * fileinfo = nullptr; diff --git a/src/common/sphere_library/CSFileText.cpp b/src/common/sphere_library/CSFileText.cpp index 87062c863..8b2767d95 100644 --- a/src/common/sphere_library/CSFileText.cpp +++ b/src/common/sphere_library/CSFileText.cpp @@ -5,7 +5,7 @@ #include // for _get_osfhandle (used by STDFUNC_FILENO) #endif -// CSFileText:: Constructors, Destructor, Asign operator. +// CSFileText:: Constructors, Destructor, Assign operator. const char* CSFileText::m_sClassName = "CSFileText"; @@ -16,6 +16,7 @@ CSFileText::CSFileText() #ifdef _WIN32 _fNoBuffer = false; #endif + _fBinaryMode = false; } CSFileText::~CSFileText() @@ -31,7 +32,7 @@ bool CSFileText::_IsFileOpen() const } bool CSFileText::IsFileOpen() const { - MT_SHARED_LOCK_RETURN(_pStream != nullptr); + MT_SHARED_LOCK_RETURN(this, _pStream != nullptr); } bool CSFileText::_Open(lpctstr ptcFilename, uint uiModeFlags) @@ -63,7 +64,7 @@ bool CSFileText::_Open(lpctstr ptcFilename, uint uiModeFlags) bool CSFileText::Open(lpctstr ptcFilename, uint uiModeFlags) { ADDTOCALLSTACK("CSFileText::Open"); - MT_UNIQUE_LOCK_RETURN(CSFileText::_Open(ptcFilename, uiModeFlags)); + MT_UNIQUE_LOCK_RETURN(this, CSFileText::_Open(ptcFilename, uiModeFlags)); } void CSFileText::_Close() @@ -86,7 +87,7 @@ void CSFileText::_Close() void CSFileText::Close() { ADDTOCALLSTACK("CSFileText::Close"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); CSFileText::_Close(); } @@ -122,7 +123,7 @@ int CSFileText::Seek( int iOffset, int iOrigin ) // RETURN: // true = success ADDTOCALLSTACK("CSFileText::Seek"); - MT_UNIQUE_LOCK_RETURN(CSFileText::_Seek(iOffset, iOrigin)); + MT_UNIQUE_LOCK_RETURN(this, CSFileText::_Seek(iOffset, iOrigin)); } void CSFileText::_Flush() const @@ -138,7 +139,7 @@ void CSFileText::_Flush() const void CSFileText::Flush() const { ADDTOCALLSTACK("CSFileText::Flush"); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); CSFileText::_Flush(); } @@ -154,7 +155,7 @@ bool CSFileText::_IsEOF() const bool CSFileText::IsEOF() const { //ADDTOCALLSTACK("CSFileText::IsEOF"); - MT_SHARED_LOCK_RETURN(CSFileText::_IsEOF()); + MT_SHARED_LOCK_RETURN(this, CSFileText::_IsEOF()); } @@ -175,7 +176,7 @@ int CSFileText::Printf( lpctstr pFormat, ... ) ADDTOCALLSTACK("CSFileText::Printf"); ASSERT(pFormat); - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); va_list vargs; va_start( vargs, pFormat ); @@ -195,7 +196,7 @@ int CSFileText::Read( void * pBuffer, int sizemax ) const if ( IsEOF() ) return 0; // LINUX will ASSERT if we read past end. - MT_UNIQUE_LOCK_SET; + MT_UNIQUE_LOCK_SET(this); size_t ret = fread( pBuffer, 1, sizemax, _pStream ); if (ret > INT_MAX) { @@ -220,7 +221,7 @@ tchar * CSFileText::_ReadString( tchar * pBuffer, int sizemax ) tchar * CSFileText::ReadString( tchar * pBuffer, int sizemax ) { ADDTOCALLSTACK("CSFileText::ReadString"); - MT_UNIQUE_LOCK_RETURN(CSFileText::_ReadString(pBuffer, sizemax)); + MT_UNIQUE_LOCK_RETURN(this, CSFileText::_ReadString(pBuffer, sizemax)); } int CSFileText::_VPrintf( lpctstr pFormat, va_list args ) @@ -239,7 +240,7 @@ int CSFileText::VPrintf(lpctstr pFormat, va_list args) ADDTOCALLSTACK("CSFileText::VPrintf"); ASSERT(pFormat); - MT_UNIQUE_LOCK_RETURN(CSFileText::_VPrintf(pFormat, args)); + MT_UNIQUE_LOCK_RETURN(this, CSFileText::_VPrintf(pFormat, args)); } bool CSFileText::_Write( const void * pData, int iLen ) @@ -269,7 +270,7 @@ bool CSFileText::Write(const void* pData, int iLen) { // RETURN: 1 = success else fail. ADDTOCALLSTACK("CSFileText::Write"); - MT_UNIQUE_LOCK_RETURN(CSFileText::_Write(pData, iLen)); + MT_UNIQUE_LOCK_RETURN(this, CSFileText::_Write(pData, iLen)); } bool CSFileText::_WriteString( lpctstr pStr ) @@ -284,7 +285,7 @@ bool CSFileText::_WriteString( lpctstr pStr ) bool CSFileText::WriteString(lpctstr pStr) { ADDTOCALLSTACK("CSFileText::WriteString"); - MT_UNIQUE_LOCK_RETURN(CSFileText::_WriteString(pStr)); + MT_UNIQUE_LOCK_RETURN(this, CSFileText::_WriteString(pStr)); } // CSFileText:: Mode operations. @@ -294,7 +295,7 @@ lpctstr CSFileText::_GetModeStr() const ADDTOCALLSTACK("CSFileText::_GetModeStr"); // end of line translation is crap. ftell and fseek don't work correctly when you use it. // fopen() args - if ( IsBinaryMode()) + if ( _IsBinaryMode()) return ( _IsWriteMode() ? "wb" : "rb" ); if ( _GetMode() & OF_READWRITE ) return "a+b"; diff --git a/src/common/sphere_library/CSFileText.h b/src/common/sphere_library/CSFileText.h index 8727491b4..288a3dbfd 100644 --- a/src/common/sphere_library/CSFileText.h +++ b/src/common/sphere_library/CSFileText.h @@ -15,7 +15,7 @@ class CSFileText : public CSFile public: static const char *m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ public: @@ -43,8 +43,8 @@ protected: virtual bool _IsFileOpen() const override; public: virtual bool IsFileOpen() const override; /** * @brief Open a file in a specified mode. - * @param ptcName file to open. - * @param uiMode open mode. + * @param ptcFilename file to open. + * @param uiModeFlags open mode. * @return true if file is open, false otherwise. */ protected: virtual bool _Open(lpctstr ptcFilename = nullptr, uint uiModeFlags = OF_READ|OF_SHARE_DENY_NONE) override; @@ -139,12 +139,6 @@ public: bool WriteString(lpctstr pStr); * @return string that describes the open mode. */ lpctstr _GetModeStr() const; -public: - /** - * @brief Check if file is open in binary mode. - * @return false always. - */ - virtual bool IsBinaryMode() const override { return false; }; ///@} public: FILE * _pStream; // The current open script type file. diff --git a/src/common/sphere_library/CSMemBlock.cpp b/src/common/sphere_library/CSMemBlock.cpp index 3e5647050..f9ca9adae 100644 --- a/src/common/sphere_library/CSMemBlock.cpp +++ b/src/common/sphere_library/CSMemBlock.cpp @@ -1,7 +1,7 @@ #include #include "CSMemBlock.h" -// CSMemBlock:: Constructors, Destructor, Asign operator. +// CSMemBlock:: Constructors, Destructor, Assign operator. CSMemBlock::CSMemBlock() { @@ -47,7 +47,7 @@ void CSMemBlock::MemLink( byte * pData ) } -// CSMemLenBlock:: Constructors, Destructor, Asign operator. +// CSMemLenBlock:: Constructors, Destructor, Assign operator. CSMemLenBlock::CSMemLenBlock() : m_uiLength(0) diff --git a/src/common/sphere_library/CSMemBlock.h b/src/common/sphere_library/CSMemBlock.h index f38648fa0..e7f3f6484 100644 --- a/src/common/sphere_library/CSMemBlock.h +++ b/src/common/sphere_library/CSMemBlock.h @@ -13,7 +13,7 @@ class CSMemBlock { public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ CSMemBlock(); @@ -34,13 +34,13 @@ class CSMemBlock public: /** * @brief Clear internal data pointer and, if size is valid, alloc mem, updating internal data pointer. - * @param dwSize size to alloc. + * @param uiSize size to alloc. */ void Alloc( size_t uiSize ); protected: /** * @brief Alloc mem (new byte[*] wrapper). Fails if can not alloc or if size is invalid. - * @param dwSize size to alloc. + * @param uiSize size to alloc. * @return pointer to the allocated data. */ static byte * AllocBase( size_t uiSize ); @@ -78,7 +78,7 @@ class CSMemBlock class CSMemLenBlock : public CSMemBlock { public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ CSMemLenBlock(); @@ -99,7 +99,7 @@ class CSMemLenBlock : public CSMemBlock ///@{ /** * @brief Set the size of the buffer and alloc mem. - + @param dwSize new size of the buffer. + + @param uiSize new size of the buffer. */ void Alloc( size_t uiSize ); /** @@ -108,7 +108,7 @@ class CSMemLenBlock : public CSMemBlock void Free(); /** * @brief Resizes the buffer, maintaining the current data. - + @param dwSizeNew new size of the buffer. + + @param uiSizeNew new size of the buffer. */ void Resize( size_t uiSizeNew ); ///@} diff --git a/src/common/sphere_library/CSObjArray.h b/src/common/sphere_library/CSObjArray.h index 4e3f21d26..7bd856ce0 100644 --- a/src/common/sphere_library/CSObjArray.h +++ b/src/common/sphere_library/CSObjArray.h @@ -20,7 +20,7 @@ class CSObjArray : public CSPtrTypeArray public: static const char *m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ public: diff --git a/src/common/sphere_library/CSObjCont.cpp b/src/common/sphere_library/CSObjCont.cpp index 75ae6370d..4985480cb 100644 --- a/src/common/sphere_library/CSObjCont.cpp +++ b/src/common/sphere_library/CSObjCont.cpp @@ -1,5 +1,5 @@ #include "../assertion.h" -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header #include "CSObjCont.h" #include diff --git a/src/common/sphere_library/CSObjCont.h b/src/common/sphere_library/CSObjCont.h index e0174b1ab..8f882b441 100644 --- a/src/common/sphere_library/CSObjCont.h +++ b/src/common/sphere_library/CSObjCont.h @@ -40,7 +40,6 @@ class cont_reverse_iterate }; - /* CSObjCont */ #define BASECONT std::vector @@ -54,7 +53,7 @@ class CSObjCont friend class CSObjContRec; static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ /** @@ -194,7 +193,7 @@ class CSObjCont * Never called directly. Called CSObjContRec::RemoveSelf() * @see CSObjContRec::RemoveSelf(). * - * @param pObRec removed record. + * @param pObjRec removed record. */ virtual void OnRemoveObj( CSObjContRec* pObjRec ); ///@} diff --git a/src/common/sphere_library/CSObjContRec.h b/src/common/sphere_library/CSObjContRec.h index 83bd81c52..f91300567 100644 --- a/src/common/sphere_library/CSObjContRec.h +++ b/src/common/sphere_library/CSObjContRec.h @@ -20,7 +20,7 @@ class CSObjContRec friend class CSObjCont; static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ /** diff --git a/src/common/sphere_library/CSObjList.cpp b/src/common/sphere_library/CSObjList.cpp index a66cad392..b31336c87 100644 --- a/src/common/sphere_library/CSObjList.cpp +++ b/src/common/sphere_library/CSObjList.cpp @@ -1,6 +1,6 @@ #include "CSObjList.h" #include "../assertion.h" -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header // CSObjListRec:: Constructors, Destructor, Assign operator. diff --git a/src/common/sphere_library/CSObjList.h b/src/common/sphere_library/CSObjList.h index dc0401953..0e24390aa 100644 --- a/src/common/sphere_library/CSObjList.h +++ b/src/common/sphere_library/CSObjList.h @@ -15,7 +15,7 @@ class CSObjList friend class CSObjListRec; static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ /** diff --git a/src/common/sphere_library/CSObjListRec.h b/src/common/sphere_library/CSObjListRec.h index 819a07141..7d42b2943 100644 --- a/src/common/sphere_library/CSObjListRec.h +++ b/src/common/sphere_library/CSObjListRec.h @@ -20,7 +20,7 @@ class CSObjListRec friend class CSObjList; static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ /** diff --git a/src/common/sphere_library/CSObjSortArray.h b/src/common/sphere_library/CSObjSortArray.h index 3ed38b3f5..042d6778e 100644 --- a/src/common/sphere_library/CSObjSortArray.h +++ b/src/common/sphere_library/CSObjSortArray.h @@ -14,7 +14,7 @@ template class CSObjSortArray : public CSObjArray { - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ public: diff --git a/src/common/sphere_library/CSPtrTypeArray.h b/src/common/sphere_library/CSPtrTypeArray.h index 95f1181b0..9c7548318 100644 --- a/src/common/sphere_library/CSPtrTypeArray.h +++ b/src/common/sphere_library/CSPtrTypeArray.h @@ -14,7 +14,7 @@ class CSPtrTypeArray : public CSTypedArray public: static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ CSPtrTypeArray() = default; diff --git a/src/common/sphere_library/CSQueue.cpp b/src/common/sphere_library/CSQueue.cpp index 22b76e8d1..18c409aa8 100644 --- a/src/common/sphere_library/CSQueue.cpp +++ b/src/common/sphere_library/CSQueue.cpp @@ -1,11 +1,11 @@ /** -* @file CQueue.cpp +* @file CSQueue.cpp */ #include #include "CSQueue.h" -// CSQueueBytes:: Constructors, Destructor, Asign operator. +// CSQueueBytes:: Constructors, Destructor, Assign operator. CSQueueBytes::CSQueueBytes() noexcept : m_iDataQty(0) diff --git a/src/common/sphere_library/CSQueue.h b/src/common/sphere_library/CSQueue.h index c0d47a9b7..b1b7ff783 100644 --- a/src/common/sphere_library/CSQueue.h +++ b/src/common/sphere_library/CSQueue.h @@ -15,7 +15,7 @@ class CSQueueBytes public: static const char * m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ CSQueueBytes() noexcept; diff --git a/src/common/sphere_library/CSRand.h b/src/common/sphere_library/CSRand.h index e8695dba3..ee3d37116 100644 --- a/src/common/sphere_library/CSRand.h +++ b/src/common/sphere_library/CSRand.h @@ -29,7 +29,7 @@ extern struct CSRand private: // Implementations, using the standard random generator. - + // Integer numbers static int32 genRandInt32(int32 min, int32 max); static int64 genRandInt64(int64 min, int64 max); diff --git a/src/common/sphere_library/CSString.cpp b/src/common/sphere_library/CSString.cpp index 71c875968..a9a604a77 100644 --- a/src/common/sphere_library/CSString.cpp +++ b/src/common/sphere_library/CSString.cpp @@ -429,7 +429,8 @@ void CSString::FormatULLVal(ullong uiVal) } void CSString::FormatSTVal(size_t uiVal) { - static_assert(sizeof(size_t) <= sizeof(ullong), "You can't use StrFromULL with a size_t argument on this architecture. Use the old call to Format instead."); + static_assert(sizeof(size_t) <= sizeof(ullong), + "You can't use FormatSTVal on this architecture (it uses internally Str_FromULL_Fast). Use the old call to Format instead."); //Format("%" PRIuSIZE_T, iVal); FORMATNUM_WRAPPER(Str_FromULL_Fast, uiVal, 10); } diff --git a/src/common/sphere_library/CSString.h b/src/common/sphere_library/CSString.h index b1ebf5404..cca75ed84 100644 --- a/src/common/sphere_library/CSString.h +++ b/src/common/sphere_library/CSString.h @@ -1,5 +1,5 @@ /** -* @file CSString. +* @file CSString.h * @brief Custom string implementation. */ @@ -42,7 +42,7 @@ class CSString void InitDefault(); public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ @@ -74,7 +74,7 @@ class CSString * * @see CopyLen() * @param pStr string to copy. - * #param iLen max number of chars (single-byte) to copy. + * @param iLen max number of chars (single-byte) to copy. */ CSString(lpctstr pStr, int iLen); @@ -89,7 +89,7 @@ class CSString /** * @brief Move constructor. * - * @param pStr string to move the contents from. + * @param s string to move the contents from. */ inline CSString(CSString&& s) noexcept; @@ -151,6 +151,7 @@ class CSString * If the new length is bigger than the current length, alloc memory for the string and copy. * If DEBUG_STRINGS setted, update statistical information (reallocs count, total memory allocated). * @param iLen new length of the string. + * @param fPreciseSize If the length is bigger, should the size be the same? * @return the new length of the CSString. */ int Resize(int iLen, bool fPreciseSize = false); @@ -236,14 +237,14 @@ class CSString /** * @brief Concatenate CSString with a string. - * @param pointer to zero-terminated tchar string to concatenate with. + * @param string to zero-terminated tchar string to concatenate with. * @return The result of concatenate the CSString with string. */ const CSString& operator+=(lpctstr string); /** * @brief Concatenate CSString with a string. - * @param pointer to zero-terminated tchar string to concatenate with. + * @param string to zero-terminated tchar string to concatenate with. * @return The result of concatenate the CSString with string. */ CSString operator+(lpctstr string); @@ -352,7 +353,7 @@ class CSString /** * @brief Print a unsigned char value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatUCVal(uchar uiVal); @@ -366,7 +367,7 @@ class CSString /** * @brief Print a unsigned short value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatUSVal(ushort uiVal); @@ -380,7 +381,7 @@ class CSString /** * @brief Print a unsigned int value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatUVal(uint uiVal); @@ -394,35 +395,35 @@ class CSString /** * @brief Print a ullong value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatULLVal(ullong uiVal); /** * @brief Print a size_t (unsigned) value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatSTVal(size_t uiVal); /** * @brief Print a byte value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatBVal(byte uiVal); /** * @brief Print a word value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatWVal(word uiVal); /** * @brief Print a dword value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatDWVal(dword uiVal); @@ -436,7 +437,7 @@ class CSString /** * @brief Print a unsigned char value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatU8Val(uint8 uiVal); @@ -450,7 +451,7 @@ class CSString /** * @brief Print a unsigned short value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatU16Val(uint16 uiVal); @@ -464,7 +465,7 @@ class CSString /** * @brief Print a unsigned int value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatU32Val(uint32 uiVal); @@ -478,7 +479,7 @@ class CSString /** * @brief Print a ullong value into the string. * @see Format() - * @param iVal value to print. + * @param uiVal value to print. */ void FormatU64Val(uint64 uiVal); diff --git a/src/common/sphere_library/CSTime.h b/src/common/sphere_library/CSTime.h index 4d173aacf..aa02cc8a4 100644 --- a/src/common/sphere_library/CSTime.h +++ b/src/common/sphere_library/CSTime.h @@ -1,5 +1,5 @@ /** -* @file CSTime. +* @file CSTime.h * @brief Custom time management. */ @@ -28,7 +28,7 @@ class CSTime #undef GetCurrentTime // Set once at server startup; used for Windows high-resolution timer - friend class GlobalInitializer; + friend struct GlobalInitializer; static llong _kllTimeProfileFrequency; #endif diff --git a/src/common/sphere_library/CSTypedArray.h b/src/common/sphere_library/CSTypedArray.h index 6e1c54c92..c1653faae 100644 --- a/src/common/sphere_library/CSTypedArray.h +++ b/src/common/sphere_library/CSTypedArray.h @@ -20,7 +20,7 @@ class CSTypedArray : public std::vector public: static const char *m_sClassName; - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ /** @@ -81,7 +81,6 @@ class CSTypedArray : public std::vector }; - /* Template methods (inlined or not) are defined here */ // CSTypedArray:: Constructors, Destructor, Assign operator. diff --git a/src/common/sphere_library/scontainer_ops.h b/src/common/sphere_library/scontainer_ops.h index d3851bee2..a43b07fc8 100644 --- a/src/common/sphere_library/scontainer_ops.h +++ b/src/common/sphere_library/scontainer_ops.h @@ -38,6 +38,13 @@ void AssignInitlistToCSizedArray(outT* dest, const size_t dest_size, std::initia } } +template +[[nodiscard]] constexpr + bool ContainerHas(Tcont const& cont, Telem const& elem) +{ + return cont.end() != std::find(cont.begin(), cont.end(), elem); +} + template [[nodiscard]] constexpr bool ContainerIsSorted(T const& cont) diff --git a/src/common/sphere_library/sfastmath.h b/src/common/sphere_library/sfastmath.h index ed14f1f47..766771249 100644 --- a/src/common/sphere_library/sfastmath.h +++ b/src/common/sphere_library/sfastmath.h @@ -1,21 +1,65 @@ #ifndef _INC_SFASTMATH_H #define _INC_SFASTMATH_H -// Adapted from: https://github.com/ermig1979/Simd/blob/master/src/Simd/SimdMath.h +//#include +//#include +#include +#include -namespace sfmath + +// Most (if not all) of this functions works on the assumption that negative numbers are represented +// using the two's complement. To be fair most common archs use this, so the checks are over, over zealant. + +// Two's complement validation for signed types +template +[[maybe_unused]] +constexpr bool is_twos_complement_signed() +{ + if constexpr (std::is_unsigned_v) + return true; // Unsigned types automatically pass + + // Check 1: All-ones pattern should equal -1 + constexpr bool check_all_ones = static_cast(~T(0)) == T(-1); + + // Check 2: Arithmetic right shift preservation + // Shifting a negative value right by 1 must preserve the sign bit (i.e. yield −1) for arithmetic shift. + constexpr bool check_arith_shift = (T(-1) >> 1) == T(-1); + + return check_all_ones && check_arith_shift; +} +static_assert(is_twos_complement_signed()); + + +namespace sl { +namespace fmath +{ + // Adapted from: https://github.com/ermig1979/Simd/blob/master/src/Simd/SimdMath.h - inline int Average(int a, int b) noexcept + template + inline T Average(T a, T b) noexcept { return (a + b + 1) >> 1; } - inline int Average(int a, int b, int c, int d) noexcept + template + inline T Average(T a, T b, T c, T d) noexcept { return (a + b + c + d + 2) >> 2; } + // Not likely to be needed at all. If so, we have to disable here the 'Wstrict-aliasing' warning for every compiler. + /* + inline float NotF16(float f) noexcept + { + const int i = ~(int&)f; + return (float&)i; + } + */ + + // -- + + /* inline void SortU8(int & a, int & b) noexcept { const int d = a - b; @@ -23,41 +67,217 @@ namespace sfmath b += d & m; a -= d & m; } + */ + + template + inline void sSortPair(T &iMin, T &iMax) noexcept + { + // 1) Compute difference + T d = iMin - iMax; + + // 2) Build mask = ~(d >> (bitwidth-1)): + // - If a>b, d>0 → d>>(W-1) == 0 → mask = ~0 == all-ones + // - If a<=b,d<=0→ d>>(W-1) == -1→ mask = ~(-1) == 0 + + constexpr int W = sizeof(T) * 8 - 1; + const T m = ~(d >> W); + // 3) Conditionally add/subtract the difference + const T t = d & m; + iMax += t; // if a>b: b = b + (a–b) → b = a + iMin -= t; // if a>b: a = a - (a–b) → a = b_old + } + + // Sort two unsigned integers so that a ≤ b, branchlessly + template + inline void uSortPair(T &uiMin, T &uiMax) noexcept + { + // Compute a mask of all-1’s if a < b, else 0 + // (a < b) → 1, negate → T(-1) ; (a < b) → 0, negate → T(0) + const T mask = -T(uiMin < uiMax); + + // XOR-swap trick under mask: + // t = (a ^ b) & mask → t = difference bits if a> 8; return (d & ~m) | (-d & m); } + */ + template + inline T uAbsDiff(T a, T b) noexcept + { + // Use the corresponding signed type. + using SignedT = typename std::make_signed::type; + + // Compute the difference in signed space. + const SignedT diff = static_cast(a) - static_cast(b); + // Compute mask: arithmetic right-shift extracts the sign bit + const int bits = sizeof(SignedT) * 8 - 1; + const SignedT mask = diff >> bits; + + // The standard trick: (diff ^ mask) - mask produces the absolute value. + return static_cast((diff ^ mask) - mask); + } + + template + inline T sAbsDiff(T a, T b) noexcept + { + const T d = a - b; + const T mask = -(d < 0); // 0 if d>=0, -1 if d<0 + return (d ^ mask) - mask; // (d ^ mask) - mask = |d| (absolute value) + } + + /* inline int MaxU8(int a, int b) noexcept { const int d = a - b; const int m = ~(d >> 8); return b + (d & m); } + */ + template + inline T sMax(T a, T b) noexcept + { + const T d = a - b; + const T mask = -(d >= 0); // -1 if a>=b, 0 otherwise + return b + (d & mask); // b + (d & mask) gives max(a,b) + } + /* inline int MinU8(int a, int b) noexcept { const int d = a - b; const int m = ~(d >> 8); return a - (d & m); } + */ + template + inline T sMin(T a, T b) noexcept + { + const T d = a - b; + const T mask = -(d >= 0); // -1 if a>=b, 0 otherwise + return a - (d & mask); // a - (d & mask) gives min(a,b) + } - inline int SaturatedSubtractionU8(int a, int b) noexcept + /* + inline int ZeroSaturatedSubtractionU8(int a, int b) noexcept { const int d = a - b; const int m = ~(d >> 8); return (d & m); } + */ + template + inline T sZeroSatSub(T a, T b) noexcept // clamps negative value to 0 + { + const T d = a - b; + const T mask = -(d >= 0); // -1 if a>=b, 0 otherwise + return d & mask; // (d & mask) clamps negative d to 0 + } - inline float NotF16(float f) noexcept +/* SatSub + Case 1: When a ≥ b (positive difference) + + Example: a = 5, b = 2 → d = 3 + + d = 00000011 (3 in 8-bit) + mask = 00000011 >> 7 = 00000000 (positive→0) + d ^ mask = 00000011 ^ 00000000 = 00000011 + (d ^ mask) - mask = 00000011 - 00000000 = 00000011 (3) + + Case 2: When a < b (negative difference) + + Example: a = 2, b = 5 → d = -3 + + d = 11111101 (-3 in 8-bit two's complement) + mask = 11111101 >> 7 = 11111111 (negative→all 1s) + d ^ mask = 11111101 ^ 11111111 = 00000010 (bit flip) + (d ^ mask) - mask = 00000010 - 11111111 = 00000010 + 1 = 00000011 (3) + + Why it works: When d is negative, XOR with all-1s flips all bits (one's complement), then subtracting -1 adds 1, completing the two's complement negation. +*/ + + // --- + + /* Absolute value */ + + // WARNING: these functions, and even the standard abs(), cannot fully handle the lowest (most negative) value for a signed integer type. + // This is a well-known, inherent limitation of two’s-complement representation. + // Example: int8_t has range –128…127, abs of –128 returns –128 for our signed Abs* (and it's undefined behavior for abs()) and 128 for our Abs*u. + + template + inline T sAbs(T x) noexcept { - const int i = ~(int&)f; - return (float&)i; + // Signed: branchless two’s‐complement abs + // + // mask = x >> (W-1) → 0 for x>=0, -1 for x<0 + // (x ^ mask) - mask → if mask=0: x; if mask=-1: (~x)+1 = -x + constexpr unsigned W = sizeof(T) * 8 - 1; + const T mask = x >> W; + return (x ^ mask) - mask; + } + + /* + inline int8_t sAbs8(int8_t x) noexcept + { + const int8_t mask = x >> 7; + return (x ^ mask) - mask; + } + + inline uint8_t uAbs8(int8_t x) noexcept + { + const uint8_t mask = uint8_t(x) >> 7; + return (uint8_t(x) ^ mask - mask); } + inline int16_t sAbs16(int16_t x) noexcept + { + const int16_t mask = x >> 15; + return (x ^ mask) - mask; + } + + inline uint16_t uAbs16(int16_t x) noexcept + { + const uint16_t mask = uint16_t(x) >> 15; + return (uint16_t(x) ^ mask - mask); + } + + inline int32_t sAbs32(int32_t x) noexcept + { + const int32_t mask = x >> 31;// Arithmetic shift: 0 for x>=0, –1 for x<0 + return (x ^ mask) - mask; // If mask=–1: (~x)–(–1)=–x; else x–0=x + + } + inline uint32_t uAbs32(int32_t x) noexcept + { + const uint32_t mask = uint32_t(x) >> 31; + return (uint32_t(x) ^ mask) - mask; + } + + inline int64_t sAbs64(int64_t x) noexcept + { + const int64_t mask = x >> 63; + return (x ^ mask) - mask; + } + + inline uint64_t uAbs64(int64_t x) noexcept + { + const int64_t mask = x >> 63; + return (uint64_t(x) ^ mask - mask); + } + */ + +} // namespace } // namespace #endif // _INC_SFASTMATH_H diff --git a/src/common/sphere_library/smap.h b/src/common/sphere_library/smap.h index 9ad7ceb51..ef87eddaf 100644 --- a/src/common/sphere_library/smap.h +++ b/src/common/sphere_library/smap.h @@ -8,7 +8,7 @@ #include #include -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header #define _SPHERE_MAP_DEFAULT_SIZE 10 diff --git a/src/common/sphere_library/smutex.cpp b/src/common/sphere_library/smutex.cpp index a6faa28e0..98c951145 100644 --- a/src/common/sphere_library/smutex.cpp +++ b/src/common/sphere_library/smutex.cpp @@ -3,7 +3,7 @@ #endif #include "smutex.h" -// SimpleMutex:: Constructors, Destructor, Asign operator. +// SimpleMutex:: Constructors, Destructor, Assign operator. SimpleMutex::SimpleMutex() noexcept { diff --git a/src/common/sphere_library/smutex.h b/src/common/sphere_library/smutex.h index 29192ab41..4534e21ab 100644 --- a/src/common/sphere_library/smutex.h +++ b/src/common/sphere_library/smutex.h @@ -31,7 +31,7 @@ class SimpleMutex #endif public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ SimpleMutex() noexcept; @@ -98,7 +98,7 @@ class SimpleMutex class SimpleThreadLock { public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ inline explicit SimpleThreadLock(SimpleMutex &mutex) noexcept @@ -140,7 +140,7 @@ class SimpleThreadLock class ManualThreadLock { public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ inline ManualThreadLock() noexcept diff --git a/src/common/sphere_library/sobjpool.h b/src/common/sphere_library/sobjpool.h new file mode 100644 index 000000000..3f44e2b3c --- /dev/null +++ b/src/common/sphere_library/sobjpool.h @@ -0,0 +1,365 @@ +#ifndef _INC_SOBJPOOL_H +#define _INC_SOBJPOOL_H + +#include +#include +#include +#include +#include +#include +#include +#include "sstacks.h" + +namespace sl +{ + +// ObjectPool pre-constructs a fixed number of T objects stored in an internal array. +// It provides two interfaces: acquireUnique() returns a std::unique_ptr and +// acquireShared() returns a std::shared_ptr. When no free objects are available, +// the behavior depends on the AllowFallback template parameter: +// - If AllowFallback is true, a new, dynamically allocated object is returned. +// - Otherwise, a std::runtime_error is thrown. + +template +// tp: template parameter +class ObjectPool +{ +public: + using index_t = uint32_t; // do we really need size_t elements? + static_assert(tp_pool_size <= std::numeric_limits::max()); + + static constexpr index_t sm_pool_size = static_cast(tp_pool_size); + static constexpr bool sm_allow_fallback = tp_allow_fallback; + + // Ensure that PooledObject_t is default constructible and destructible. + static_assert(std::is_default_constructible_v, + "ObjectPool requires the type T to be default constructible"); + static_assert(std::is_destructible_v, + "ObjectPool requires the type T to be destructible"); + + ObjectPool() + : m_fallbackObjsCount(0) + { + // Initialize m_freeIndices: each index corresponds to an available object. + for (index_t i = 0; i < sm_pool_size; ++i) { + m_freeIndices.push(i); + } + } + + // Disable copy and move. + ObjectPool(const ObjectPool&) = delete; + ObjectPool& operator=(const ObjectPool&) = delete; + + // Custom deleter used for both pooled and fallback allocations. + // The optional index indicates whether the object came from the pool. + // When index is engaged, the object is one managed by the pool, and release() is called. + // When not engaged, the deleter deletes the dynamically allocated object. + struct PoolDeleter + { + ObjectPool* m_pool; + std::optional m_index; // Engaged if pooled + + void operator()(PooledObject_t* ptr) const + { + if (m_pool) + { + if (m_index.has_value()) + { + // Object is from the pool; return it. + m_pool->release(m_index.value()); + } + else + { + // Fallback allocation: decrement the fallback counter. + m_pool->fallbackReleased(); + delete ptr; + } + } + else + { + // Should not typically happen if pool pointer is always set. + delete ptr; + } + } + }; + + // Type alias for unique_ptr using PoolDeleter. + using UniquePtr_t = std::unique_ptr; + + // Acquires an object and returns it as a unique_ptr. + // If no pooled objects are available, either returns a fallback allocation or throws. + [[nodiscard]] + UniquePtr_t acquireUnique() + { + if (!m_freeIndices.empty()) [[likely]] + { + const auto idx = m_freeIndices.top(); + m_freeIndices.pop(); + PooledObject_t* ptr = &m_objects[idx]; + return UniquePtr_t(ptr, PoolDeleter {this, idx}); + } + else [[unlikely]] if constexpr (sm_allow_fallback) + { + // Return a fallback (dynamically allocated) object. + PooledObject_t* ptr = new PooledObject_t(); + m_fallbackObjsCount += 1; + return UniquePtr_t(ptr, PoolDeleter {this, std::nullopt}); + } + else + { + throw std::runtime_error("No free object available in pool"); + } + } + + // Type alias for shared_ptr. The deleter goes in the constructor, unlike unique_ptr. + using SharedPtr_t = std::shared_ptr; + + // Acquires an object and returns it as a shared_ptr. + // If no pooled objects are available, either returns a fallback allocation or throws. + [[nodiscard]] + SharedPtr_t acquireShared() + { + if (!m_freeIndices.empty()) [[likely]] + { + const auto idx = m_freeIndices.top(); + m_freeIndices.pop(); + PooledObject_t* ptr = &m_objects[idx]; + return std::shared_ptr(ptr, PoolDeleter {this, idx}); + } + else [[unlikely]] if constexpr (sm_allow_fallback) + { + // Return a fallback (dynamically allocated) object. + PooledObject_t* ptr = new PooledObject_t(); + m_fallbackObjsCount += 1; + return std::shared_ptr(ptr, PoolDeleter {this, std::nullopt}); + } + else + { + throw std::runtime_error("No free object available in pool"); + } + } + + // Returns the current number of outstanding fallback allocations. + [[nodiscard]] + index_t getFallbackCount() const noexcept + { + return m_fallbackObjsCount; + } + + // Static helper method to check whether an object acquired as a unique_ptr was from the pool. + // Hides the internal PoolDeleter implementation details. + [[nodiscard]] + static bool isFromPool(const UniquePtr_t &ptr) noexcept + { + return ptr.get_deleter().m_index.has_value(); + } + + // Static helper method for shared_ptr. + [[nodiscard]] + static bool isFromPool(const std::shared_ptr &ptr) + { + // Does std::get_deleter work only with RTTI enabled? + if (auto deleterPtr = std::get_deleter(ptr)) + return deleterPtr->m_index.has_value(); + return false; + } + +private: + // Called by the custom deleter to return a pooled object to the m_freeIndices stack. + void release(index_t index) + { + m_freeIndices.push(index); + } + + // Called by the custom deleter when a fallback allocation is released. + void fallbackReleased() noexcept + { + m_fallbackObjsCount -= 1; + } + + // Pre-constructed objects stored in an array. + std::array m_objects{}; + + // Stack tracked indices of available objects. + sl::fixed_comptime_stack m_freeIndices; + + // Track outstanding fallback objects. + index_t m_fallbackObjsCount; +}; + + +template +// tp: template parameter +// ts: thread safe +class TSObjectPool +{ +public: + using index_t = uint32_t; // do we really need size_t elements? + static constexpr index_t sm_pool_size = static_cast(tp_pool_size); + static constexpr bool sm_allow_fallback = tp_allow_fallback; + + // Ensure that PooledObject_t is default constructible and destructible. + static_assert(std::is_default_constructible_v, + "ObjectPool requires the type T to be default constructible"); + static_assert(std::is_destructible_v, + "ObjectPool requires the type T to be destructible"); + + TSObjectPool() + : m_fallbackObjsCount(0) + { + // Initialize m_freeIndices: each index corresponds to an available object. + for (index_t i = 0; i < sm_pool_size; ++i) { + m_freeIndices.push(i); + } + } + + // Disable copy and move. + TSObjectPool(const TSObjectPool&) = delete; + TSObjectPool& operator=(const TSObjectPool&) = delete; + + // Custom deleter used for both pooled and fallback allocations. + // The optional index indicates whether the object came from the pool. + // When index is engaged, the object is one managed by the pool, and release() is called. + // When not engaged, the deleter deletes the dynamically allocated object. + struct PoolDeleter + { + TSObjectPool* m_pool; + std::optional m_index; // Engaged if pooled + + void operator()(PooledObject_t* ptr) const + { + if (m_pool) + { + if (m_index.has_value()) + { + // Object is from the pool; return it. + m_pool->release(m_index.value()); + } + else + { + // Fallback allocation: decrement the fallback counter. + m_pool->fallbackReleased(); + delete ptr; + } + } + else + { + // Should not typically happen if pool pointer is always set. + delete ptr; + } + } + }; + + // Type alias for unique_ptr using PoolDeleter. + using UniquePtr_t = std::unique_ptr; + + // Acquires an object and returns it as a unique_ptr. + // If no pooled objects are available, either returns a fallback allocation or throws. + [[nodiscard]] + UniquePtr_t acquireUnique() + { + std::lock_guard lock(m_mtx); + if (!m_freeIndices.empty()) [[likely]] + { + const auto idx = m_freeIndices.top(); + m_freeIndices.pop(); + PooledObject_t* ptr = &m_objects[idx]; + return UniquePtr_t(ptr, PoolDeleter {this, idx}); + } + else [[unlikely]] if constexpr (sm_allow_fallback) + { + // Return a fallback (dynamically allocated) object. + PooledObject_t* ptr = new PooledObject_t(); + m_fallbackObjsCount.fetch_add(1, std::memory_order_relaxed); + return UniquePtr_t(ptr, PoolDeleter {this, std::nullopt}); + } + else + { + throw std::runtime_error("No free object available in pool"); + } + } + + // Type alias for shared_ptr. The deleter goes in the constructor, unlike unique_ptr. + using SharedPtr_t = std::shared_ptr; + + // Acquires an object and returns it as a shared_ptr. + // If no pooled objects are available, either returns a fallback allocation or throws. + [[nodiscard]] + SharedPtr_t acquireShared() + { + std::lock_guard lock(m_mtx); + if (!m_freeIndices.empty()) [[likely]] + { + const auto idx = m_freeIndices.top(); + m_freeIndices.pop(); + PooledObject_t* ptr = &m_objects[idx]; + return std::shared_ptr(ptr, PoolDeleter {this, idx}); + } + else [[unlikely]] if constexpr (sm_allow_fallback) + { + // Return a fallback (dynamically allocated) object. + PooledObject_t* ptr = new PooledObject_t(); + m_fallbackObjsCount.fetch_add(1, std::memory_order_relaxed); + return std::shared_ptr(ptr, PoolDeleter {this, std::nullopt}); + } + else + { + throw std::runtime_error("No free object available in pool"); + } + } + + // Returns the current number of outstanding fallback allocations. + [[nodiscard]] + index_t getFallbackCount() const noexcept + { + return m_fallbackObjsCount.load(std::memory_order_relaxed); + } + + // Static helper method to check whether an object acquired as a unique_ptr was from the pool. + // Hides the internal PoolDeleter implementation details. + [[nodiscard]] + static bool isFromPool(const UniquePtr_t &ptr) noexcept + { + return ptr.get_deleter().m_index.has_value(); + } + + // Static helper method for shared_ptr. + [[nodiscard]] + static bool isFromPool(const std::shared_ptr &ptr) + { + if (auto deleterPtr = std::get_deleter(ptr)) + return deleterPtr->m_index.has_value(); + return false; + } + +private: + // Called by the custom deleter to return a pooled object to the m_freeIndices stack. + void release(index_t index) + { + std::lock_guard lock(m_mtx); + m_freeIndices.push(index); + } + + // Called by the custom deleter when a fallback allocation is released. + void fallbackReleased() noexcept + { + m_fallbackObjsCount.fetch_sub(1, std::memory_order_relaxed); + } + + // Pre-constructed objects stored in an array. + std::array m_objects{}; + + // Stack tracked indices of available objects. + sl::fixed_comptime_stack m_freeIndices; + + // Mutex to ensure thread safety. + mutable std::mutex m_mtx; + + // Atomic counter tracking outstanding fallback objects. + mutable std::atomic m_fallbackObjsCount; +}; + + +} // namespace sl + +#endif // _INC_SOBJPOOL_H diff --git a/src/common/sphere_library/sptr.h b/src/common/sphere_library/sptr.h index ce9b396cc..1319afaf0 100644 --- a/src/common/sphere_library/sptr.h +++ b/src/common/sphere_library/sptr.h @@ -125,7 +125,6 @@ namespace sl } - /* Disable unwanted behavior of object_ptr */ // Construct an object_ptr from a raw pointer @@ -142,7 +141,6 @@ namespace sl }; - /* raw_ptr_view */ // It's a NON-OWNING "smart" pointer. It stores a NON-OWNED pointer. diff --git a/src/common/sphere_library/squeues.h b/src/common/sphere_library/squeues.h index e85ea2f01..43d212991 100644 --- a/src/common/sphere_library/squeues.h +++ b/src/common/sphere_library/squeues.h @@ -7,7 +7,7 @@ #define _INC_SQUEUES_H #include -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header #define _SPHERE_QUEUE_DEFAULT_SIZE 10 diff --git a/src/common/sphere_library/sresetevents.cpp b/src/common/sphere_library/sresetevents.cpp index bc4d875c3..069b3c350 100644 --- a/src/common/sphere_library/sresetevents.cpp +++ b/src/common/sphere_library/sresetevents.cpp @@ -1,179 +1,209 @@ #include "sresetevents.h" +#include -// AutoResetEvent:: Constructors, Destructor, Assign operator. - -AutoResetEvent::AutoResetEvent() -{ #ifdef _WIN32 - m_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr); +#include #else - pthread_mutexattr_init(&m_criticalSectionAttr); -#ifndef __APPLE__ - pthread_mutexattr_settype(&m_criticalSectionAttr, PTHREAD_MUTEX_RECURSIVE_NP); +// Helper: convert “now + ms” to an absolute timespec +static void make_timespec(struct timespec& ts, uint32_t ms) noexcept +{ + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += ms / 1000; + ts.tv_nsec += (ms % 1000) * 1000000L; + if (ts.tv_nsec >= 1000000000L) //static_cast(1.0e9); + { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000L; + } +} #endif - pthread_mutex_init(&m_criticalSection, &m_criticalSectionAttr); - pthread_condattr_init(&m_conditionAttr); - pthread_cond_init(&m_condition, &m_conditionAttr); -#endif -} -AutoResetEvent::~AutoResetEvent() +AutoResetEvent::AutoResetEvent() noexcept { #ifdef _WIN32 - CloseHandle(m_handle); + m_handle = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); // auto-reset #else - pthread_condattr_destroy(&m_conditionAttr); - pthread_cond_destroy(&m_condition); - pthread_mutexattr_destroy(&m_criticalSectionAttr); - pthread_mutex_destroy(&m_criticalSection); + pthread_mutex_init(&m_criticalSection, nullptr); + pthread_cond_init (&m_condition , nullptr); + m_signaled = false; #endif } -// AutoResetEvent:: Interaction. - -void AutoResetEvent::wait(uint timeout) +AutoResetEvent::~AutoResetEvent() noexcept { - if (timeout == 0) - { - // if timeout is 0 then the thread's timeslice may not be given up as with normal - // sleep methods - so we will check for this condition ourselves and use SleepEx - // instead #ifdef _WIN32 - SleepEx(0, TRUE); + ::CloseHandle(m_handle); #else - SLEEP(0); + pthread_cond_destroy (&m_condition); + pthread_mutex_destroy(&m_criticalSection); #endif - return; - } +} +void AutoResetEvent::wait(uint32 timeout) noexcept +{ #ifdef _WIN32 - // it's possible for WaitForSingleObjectEx to exit before the timeout and without - // the signal. due to bAlertable=TRUE other events (e.g. async i/o) can cancel the - // waiting period early. - WaitForSingleObjectEx(m_handle, timeout, TRUE); -#else - pthread_mutex_lock(&m_criticalSection); - - // it's possible for pthread_cond_wait/timedwait to exit before the timeout and - // without a signal, but there's little we can actually do to check it since we - // don't usually care about the condition - if the calling thread does care then - // it needs to implement it's own checks - if (timeout == _kiInfinite) - { - pthread_cond_wait(&m_condition, &m_criticalSection); - } - else - { - // pthread_cond_timedwait expects the timeout to be the actual time - timespec time; - clock_gettime(CLOCK_REALTIME, &time); - time.tv_sec += timeout / 1000; - time.tv_nsec += (timeout % 1000) * 1000000L; - - pthread_cond_timedwait(&m_condition, &m_criticalSection, &time); - } - - pthread_mutex_unlock(&m_criticalSection); + if (timeout == 0) + { + // If timeout is 0 then the thread's timeslice may not be given up as with normal + // sleep methods - so we will check for this condition ourselves and use SleepEx + // instead + ::SleepEx(0, TRUE); + + // or, if we want a non-blocking probe + //::WaitForSingleObjectEx(m_handle, 0, FALSE); + return; + } + + const DWORD start = ::GetTickCount(); + DWORD remaining = timeout; + + while (true) + { + DWORD rc = ::WaitForSingleObjectEx(m_handle, remaining, TRUE); + if (rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT) + return; // signalled or timed out + + // WAIT_IO_COMPLETION – recalc remaining time + DWORD now = ::GetTickCount(); + if (now - start >= timeout) + return; + remaining = timeout - (now - start); + } +#else // POSIX + pthread_mutex_lock(&m_criticalSection); + + if (timeout == 0 && !m_signaled) + { // immediate return + pthread_mutex_unlock(&m_criticalSection); + return; + } + + struct timespec ts; + if (timeout != _kiInfinite) + make_timespec(ts, timeout); + + // Check m_signaled to protect outselves from spurious wakeups, like above, but leverage POSIX threads + int res = 0; + while (!m_signaled && res == 0) + { + if (timeout == _kiInfinite) + res = pthread_cond_wait(&m_condition, &m_criticalSection); + else + res = pthread_cond_timedwait(&m_condition, &m_criticalSection, &ts); + } + + if (m_signaled) + m_signaled = false; // auto-reset + + pthread_mutex_unlock(&m_criticalSection); #endif } -void AutoResetEvent::signal() +void AutoResetEvent::signal() noexcept { #ifdef _WIN32 - SetEvent(m_handle); + ::SetEvent(m_handle); // kernel handles auto-reset #else - pthread_mutex_lock(&m_criticalSection); - pthread_cond_signal(&m_condition); - pthread_mutex_unlock(&m_criticalSection); + pthread_mutex_lock(&m_criticalSection); + m_signaled = true; + pthread_cond_signal(&m_condition); // wake exactly one waiter + pthread_mutex_unlock(&m_criticalSection); #endif } -// ManualResetEvent:: Constructors, Destructor, Asign operator. +/*──────────────────────────── Manual-reset event ─────────────────────────*/ -ManualResetEvent::ManualResetEvent() +ManualResetEvent::ManualResetEvent() noexcept { #ifdef _WIN32 - m_handle = CreateEvent(nullptr, TRUE, FALSE, nullptr); + m_handle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); // manual-reset #else - m_value = false; - pthread_mutexattr_init(&m_criticalSectionAttr); -#ifndef __APPLE__ - pthread_mutexattr_settype(&m_criticalSectionAttr, PTHREAD_MUTEX_RECURSIVE_NP); -#endif - pthread_mutex_init(&m_criticalSection, &m_criticalSectionAttr); - - pthread_condattr_init(&m_conditionAttr); - pthread_cond_init(&m_condition, &m_conditionAttr); + pthread_mutex_init(&m_criticalSection, nullptr); + pthread_cond_init (&m_condition , nullptr); + m_signaled = false; #endif } -ManualResetEvent::~ManualResetEvent() +ManualResetEvent::~ManualResetEvent() noexcept { #ifdef _WIN32 - CloseHandle(m_handle); + ::CloseHandle(m_handle); #else - pthread_condattr_destroy(&m_conditionAttr); - pthread_cond_destroy(&m_condition); - pthread_mutexattr_destroy(&m_criticalSectionAttr); - pthread_mutex_destroy(&m_criticalSection); + pthread_cond_destroy(&m_condition); + pthread_mutex_destroy(&m_criticalSection); #endif } -// ManualResetEvent:: Interaction. - -void ManualResetEvent::wait(uint timeout) +void ManualResetEvent::wait(uint32 timeout) noexcept { #ifdef _WIN32 - WaitForSingleObjectEx(m_handle, timeout, FALSE); -#else - pthread_mutex_lock(&m_criticalSection); - - // pthread_cond_timedwait expects the timeout to be the actual time, calculate once here - // rather every time inside the loop - timespec time; - if (timeout != _kiInfinite) - { - clock_gettime(CLOCK_REALTIME, &time); - time.tv_sec += timeout / 1000; - time.tv_nsec += (timeout % 1000) * 1000000L; - } - - // it's possible for pthread_cond_wait/timedwait to exit before the timeout and - // without a signal - int result = 0; - while (result == 0 && m_value == false) - { - if (timeout == _kiInfinite) - result = pthread_cond_wait(&m_condition, &m_criticalSection); - else - result = pthread_cond_timedwait(&m_condition, &m_criticalSection, &time); - } - - pthread_mutex_unlock(&m_criticalSection); + if (timeout == 0) + { + ::WaitForSingleObjectEx(m_handle, 0, FALSE); + return; + } + + const DWORD start = ::GetTickCount(); + DWORD remaining = timeout; + + while (true) + { + DWORD rc = ::WaitForSingleObjectEx(m_handle, remaining, TRUE); + if (rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT) + return; + + DWORD now = ::GetTickCount(); + if (now - start >= timeout) + return; + remaining = timeout - (now - start); + } + +#else // POSIX + pthread_mutex_lock(&m_criticalSection); + + if (timeout == 0 && !m_signaled) + { + pthread_mutex_unlock(&m_criticalSection); + return; + } + + struct timespec ts; + if (timeout != _kiInfinite) + make_timespec(ts, timeout); + + int res = 0; + while (!m_signaled && res == 0) + { + if (timeout == _kiInfinite) + res = pthread_cond_wait(&m_condition, &m_criticalSection); + else + res = pthread_cond_timedwait(&m_condition, &m_criticalSection, &ts); + } + + pthread_mutex_unlock(&m_criticalSection); #endif } -void ManualResetEvent::set() +void ManualResetEvent::set() noexcept { #ifdef _WIN32 - SetEvent(m_handle); + ::SetEvent(m_handle); #else - pthread_mutex_lock(&m_criticalSection); - m_value = true; - pthread_cond_signal(&m_condition); - pthread_mutex_unlock(&m_criticalSection); + pthread_mutex_lock(&m_criticalSection); + m_signaled = true; + pthread_cond_broadcast(&m_condition); // wake *all* waiters + pthread_mutex_unlock(&m_criticalSection); #endif } -void ManualResetEvent::reset() +void ManualResetEvent::reset() noexcept { #ifdef _WIN32 - ResetEvent(m_handle); + ::ResetEvent(m_handle); #else - pthread_mutex_lock(&m_criticalSection); - m_value = false; - pthread_cond_signal(&m_condition); - pthread_mutex_unlock(&m_criticalSection); + pthread_mutex_lock(&m_criticalSection); + m_signaled = false; + pthread_mutex_unlock(&m_criticalSection); #endif } diff --git a/src/common/sphere_library/sresetevents.h b/src/common/sphere_library/sresetevents.h index 98a25a4d8..d85cf473a 100644 --- a/src/common/sphere_library/sresetevents.h +++ b/src/common/sphere_library/sresetevents.h @@ -24,14 +24,14 @@ class AutoResetEvent static constexpr uint _kiInfinite = 0xffffffff; // Default wait time. #endif - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ - AutoResetEvent(); - ~AutoResetEvent(); -private: - AutoResetEvent(const AutoResetEvent& copy); - AutoResetEvent& operator=(const AutoResetEvent& other); + AutoResetEvent() noexcept; + ~AutoResetEvent() noexcept; + + AutoResetEvent(const AutoResetEvent& copy) = delete; + AutoResetEvent& operator=(const AutoResetEvent& other) = delete; ///@} public: @@ -42,21 +42,20 @@ class AutoResetEvent * @brief Wait until the timeout interval elapses or this object is signaled. * @param timeout max elapsed time to wait. */ - void wait(uint timeout = _kiInfinite); + void wait(uint timeout = _kiInfinite) noexcept; /** * @brief Signal this object to stop the wait. */ - void signal(); + void signal() noexcept; ///@} private: #ifdef _WIN32 - HANDLE m_handle; // Windows API Handler to handle the event. + HANDLE m_handle; // Windows API Handler to handle the event. #else - pthread_mutex_t m_criticalSection; // Unix API mutex. - pthread_mutexattr_t m_criticalSectionAttr; // Unix API mutex attr. - pthread_condattr_t m_conditionAttr; // Unix API condition attr. - pthread_cond_t m_condition; // Unix API condition. + pthread_mutex_t m_criticalSection; // Unix API mutex. + pthread_cond_t m_condition; // Unix API condition. + bool m_signaled; #endif }; @@ -73,44 +72,42 @@ class ManualResetEvent static constexpr uint _kiInfinite = 0xffffffff; // Default wait time. #endif - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ - ManualResetEvent(); - ~ManualResetEvent(); -private: - ManualResetEvent(const ManualResetEvent& copy); - ManualResetEvent& operator=(const ManualResetEvent& other); + ManualResetEvent() noexcept; + ~ManualResetEvent() noexcept; + + ManualResetEvent(const ManualResetEvent& copy) = delete; + ManualResetEvent& operator=(const ManualResetEvent& other) = delete; ///@} public: - /** @name Interaction: + /** @name Interaction: */ ///@{ /** * @brief Waits until event is set, checking it each time interval. * @param timeout time interval to check. */ - void wait(uint timeout = _kiInfinite); + void wait(uint timeout = _kiInfinite) noexcept; /** * @brief resets the event. */ - void reset(); + void reset() noexcept; /** * @brief sets the event. */ - void set(); + void set() noexcept; ///@} private: #ifdef _WIN32 - HANDLE m_handle; // Windows API Handler to handle the event. + HANDLE m_handle; // Windows API Handler to handle the event. #else - bool m_value; - pthread_mutex_t m_criticalSection; // Unix API mutex. - pthread_mutexattr_t m_criticalSectionAttr; // Unix API mutex attr. - pthread_condattr_t m_conditionAttr; // Unix API condition attr. - pthread_cond_t m_condition; // Unix API condition. + pthread_mutex_t m_criticalSection; // Unix API mutex. + pthread_cond_t m_condition; // Unix API condition. + bool m_signaled; #endif }; diff --git a/src/common/sphere_library/ssorted_vector.h b/src/common/sphere_library/ssorted_vector.h index ebc54d672..731d0683e 100644 --- a/src/common/sphere_library/ssorted_vector.h +++ b/src/common/sphere_library/ssorted_vector.h @@ -8,11 +8,12 @@ // Sphere library namespace sl { - consteval - size_t scont_bad_index() noexcept { + consteval size_t scont_bad_index() noexcept { return size_t(-1); } - + + // -- + template > class sorted_vector : public std::vector<_Type> { @@ -49,14 +50,14 @@ namespace sl void push_back(const bool& value) = delete; template - iterator emplace(const_iterator itWhere, _ValType&&... value) = delete; + constexpr iterator emplace(const_iterator itWhere, _ValType&&... value) = delete; - iterator insert(const_iterator itWhere, _Type const& value) = delete; - iterator insert(const_iterator itWhere, _Type&& value) = delete; - iterator insert(const_iterator itWhere, const size_t _Count, _Type const& value) = delete; + constexpr iterator insert(const_iterator itWhere, _Type const& value) = delete; + constexpr iterator insert(const_iterator itWhere, _Type&& value) = delete; + constexpr iterator insert(const_iterator itWhere, const size_t _Count, _Type const& value) = delete; template - iterator emplace(_ValType&&... value) + constexpr iterator emplace(_ValType&&... value) { //_Type * _obj = new _Type(std::forward<_ValType>(value)...); _Type _obj(std::forward<_ValType>(value)...); @@ -65,16 +66,31 @@ namespace sl return base_type::emplace(base_type::begin() + _pos, std::move(_obj)); } - inline iterator insert(_Type const& value) { + template + constexpr _Type& emplace_unsorted(_ValType&&... value) + { + //_Type * _obj = new _Type(std::forward<_ValType>(value)...); + _Type _obj(std::forward<_ValType>(value)...); + return base_type::emplace_back(std::move(_obj)); + } + + constexpr inline iterator insert(_Type const& value) { return this->emplace(value); } - inline iterator insert(_Type&& value) { + constexpr inline iterator insert(_Type&& value) { return this->emplace(std::move(value)); } - //iterator insert(const size_Typepe _Count, const _Typepe& value) = delete; + //constexpr iterator insert(const size_Typepe _Count, const _Typepe& value) = delete; - inline void remove_index(const size_t index) { - base_type::erase(base_type::begin() + index); + constexpr inline _Type& insert_unsorted(_Type const& value) { + return this->emplace_unsorted(value); + } + constexpr inline _Type& insert_unsorted(_Type&& value) { + return this->emplace_unsorted(std::move(value)); + } + + constexpr inline void remove_index(const size_t index) { + base_type::erase(base_type::cbegin() + index); } @@ -165,11 +181,10 @@ namespace sl size_t sorted_vector<_Type, _Comp>::find(_Type const& value) const noexcept { const size_t _mySize = base_type::size(); - if (_mySize == 0) { - return sl::scont_bad_index(); - } const _Type* const _dataptr = base_type::data(); - size_t _idx = this->lower_element(_mySize, _dataptr, value); + if ((_mySize == 0) || !_dataptr) + return sl::scont_bad_index(); + const size_t _idx = this->lower_element(_mySize, _dataptr, value); if (_idx == _mySize) return sl::scont_bad_index(); return (!this->_comparatorObj(value, _dataptr[_idx])) ? _idx : sl::scont_bad_index(); @@ -180,14 +195,12 @@ namespace sl size_t sorted_vector<_Type, _Comp>::find_predicate(_ValType const& value, _Pred && predicate) const noexcept { const size_t _mySize = base_type::size(); - if (_mySize == 0) { + if ((_mySize == 0) || !base_type::data()) return sl::scont_bad_index(); - } return this->binary_search_predicate(_mySize, value, predicate); } } - #endif // _INC_SORTED_VECTOR_H diff --git a/src/common/sphere_library/sstacks.h b/src/common/sphere_library/sstacks.h index 3617ed073..587f5157b 100644 --- a/src/common/sphere_library/sstacks.h +++ b/src/common/sphere_library/sstacks.h @@ -7,18 +7,45 @@ #define _INC_CSTACK_H #include -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header -#define _SPHERE_STACK_DEFAULT_SIZE 10 +namespace sl +{ + +static constexpr size_t _SPHERE_STACK_DEFAULT_SIZE = 10; + +template +class fixed_comptime_stack +{ +public: + constexpr fixed_comptime_stack(); + + constexpr bool empty() const; + constexpr bool full() const; + constexpr size_t size() const; + constexpr size_t capacity() const; + + void push(const T& value); + void pop(); + T& top(); + + const T& top() const; + const T* data() const; + //T* data(); + +private: + T m_data[N]; + size_t m_top; +}; template -class fixedstack { +class fixed_runtime_stack { public: - fixedstack(size_t size=_SPHERE_STACK_DEFAULT_SIZE); - fixedstack(const fixedstack & o); - ~fixedstack(); + fixed_runtime_stack(size_t size=_SPHERE_STACK_DEFAULT_SIZE); + fixed_runtime_stack(const fixed_runtime_stack & o); + ~fixed_runtime_stack(); - fixedstack & operator=(const fixedstack & o); + fixed_runtime_stack & operator=(const fixed_runtime_stack & o); void push(T t); void pop(); @@ -34,13 +61,13 @@ class fixedstack { }; template -class fixedgrowingstack { +class fixed_growing_stack { public: - fixedgrowingstack(size_t size=_SPHERE_STACK_DEFAULT_SIZE); - fixedgrowingstack(const fixedgrowingstack & o); - ~fixedgrowingstack(); + fixed_growing_stack(size_t size=_SPHERE_STACK_DEFAULT_SIZE); + fixed_growing_stack(const fixed_growing_stack & o); + ~fixed_growing_stack(); - fixedgrowingstack & operator=(const fixedgrowingstack & o); + fixed_growing_stack & operator=(const fixed_growing_stack & o); void push(T t); void pop(); @@ -55,15 +82,15 @@ class fixedgrowingstack { }; template -class dynamicstack { +class dynamic_list_stack { public: - dynamicstack(); + dynamic_list_stack(); // To allow thread secure wrapper constructor. - dynamicstack(size_t _); - dynamicstack(const dynamicstack & o); - ~dynamicstack(); + dynamic_list_stack(size_t _); + dynamic_list_stack(const dynamic_list_stack & o); + ~dynamic_list_stack(); - dynamicstack & operator=(const dynamicstack & o); + dynamic_list_stack & operator=(const dynamic_list_stack & o); void push(T t); void pop(); @@ -74,13 +101,13 @@ class dynamicstack { size_t size() const; private: - struct _dynamicstackitem { + struct _dynamicliststackitem { public: - _dynamicstackitem(T item, _dynamicstackitem * next); + _dynamicliststackitem(T item, _dynamicliststackitem * next); T _item; - _dynamicstackitem * _next; + _dynamicliststackitem * _next; }; - _dynamicstackitem * _top; + _dynamicliststackitem * _top; size_t _size; }; @@ -104,28 +131,85 @@ class tsstack { S _s; }; +template +using ts_fixed_comptime_stack = tsstack>; + template -using tsfixedstack = tsstack>; +using ts_fixed_runtime_stack = tsstack>; template -using tsfixedgrowingstack = tsstack>; +using ts_fixed_growing_stack = tsstack>; template -using tsdynamicstack = tsstack>; +using ts_dynamic_list_stack = tsstack>; /**** * Implementations. */ +template +constexpr fixed_comptime_stack::fixed_comptime_stack() : m_top(0) {} + +template +constexpr bool fixed_comptime_stack::empty() const { return m_top == 0; } + +template + +constexpr bool fixed_comptime_stack::full() const { return m_top == N; } + +template +constexpr size_t fixed_comptime_stack::size() const { return m_top; } + +template +constexpr size_t fixed_comptime_stack::capacity() const { return N; } + +template +void fixed_comptime_stack::push(const T& value) { + if (full()) [[unlikely]] + throw std::overflow_error("Stack full"); + m_data[m_top++] = value; +} + +template +void fixed_comptime_stack::pop() { + if (empty()) [[unlikely]] + throw std::underflow_error("Stack empty"); + --m_top; +} + +template +T& fixed_comptime_stack::top() { + if (empty()) [[unlikely]] + throw std::underflow_error("Stack empty"); + return m_data[m_top - 1]; +} + +template +const T& fixed_comptime_stack::top() const { + if (empty()) [[unlikely]] + throw std::underflow_error("Stack empty"); + return m_data[m_top - 1]; +} + +template +const T* fixed_comptime_stack::data() const { + return m_data; +} +/* +template +T* fixed_comptime_stack::data() { + return m_data; +} +*/ template -fixedstack::fixedstack(size_t size) { +fixed_runtime_stack::fixed_runtime_stack(size_t size) { _stack = new T[size]; _top = 0; _size = size; } template -fixedstack::fixedstack(const fixedstack & o) { +fixed_runtime_stack::fixed_runtime_stack(const fixed_runtime_stack & o) { _stack = new T[o._size]; _top = o._top; _size = o._size; @@ -133,12 +217,12 @@ fixedstack::fixedstack(const fixedstack & o) { } template -fixedstack::~fixedstack() { +fixed_runtime_stack::~fixed_runtime_stack() { delete[] _stack; } template -fixedstack & fixedstack::operator=(const fixedstack & o) { +fixed_runtime_stack & fixed_runtime_stack::operator=(const fixed_runtime_stack & o) { delete[] _stack; _stack = new T[o._size]; _top = o._top; @@ -148,7 +232,7 @@ fixedstack & fixedstack::operator=(const fixedstack & o) { } template -void fixedstack::push(T t) { +void fixed_runtime_stack::push(T t) { if (_top == _size) { throw CSError(LOGL_FATAL, 0, "stack is full."); } @@ -156,7 +240,7 @@ void fixedstack::push(T t) { } template -void fixedstack::pop() { +void fixed_runtime_stack::pop() { if (!_top) { throw CSError(LOGL_FATAL, 0, "stack is empty."); } @@ -164,7 +248,7 @@ void fixedstack::pop() { } template -T fixedstack::top() const { +T fixed_runtime_stack::top() const { if (!_top) { throw CSError(LOGL_FATAL, 0, "stack is empty."); } @@ -172,31 +256,31 @@ T fixedstack::top() const { } template -void fixedstack::clear() { +void fixed_runtime_stack::clear() { while(!empty()) { pop(); } } template -bool fixedstack::empty() const { +bool fixed_runtime_stack::empty() const { return _top == 0; } template -size_t fixedstack::size() const { +size_t fixed_runtime_stack::size() const { return _top; } template -fixedgrowingstack::fixedgrowingstack(size_t size) { -_stack = new T[size]; -_top = 0; -_size = size; +fixed_growing_stack::fixed_growing_stack(size_t size) { + _stack = new T[size]; + _top = 0; + _size = size; } template -fixedgrowingstack::fixedgrowingstack(const fixedgrowingstack & o) { +fixed_growing_stack::fixed_growing_stack(const fixed_growing_stack & o) { _stack = new T[o._size]; _top = o._top; _size = o._size; @@ -204,12 +288,12 @@ fixedgrowingstack::fixedgrowingstack(const fixedgrowingstack & o) { } template -fixedgrowingstack::~fixedgrowingstack() { +fixed_growing_stack::~fixed_growing_stack() { delete[] _stack; } template -fixedgrowingstack & fixedgrowingstack::operator=(const fixedgrowingstack & o) { +fixed_growing_stack & fixed_growing_stack::operator=(const fixed_growing_stack & o) { _stack = new T[o._size]; _top = o._top; _size = o._size; @@ -218,7 +302,7 @@ fixedgrowingstack & fixedgrowingstack::operator=(const fixedgrowingstack -void fixedgrowingstack::push(T t) { +void fixed_growing_stack::push(T t) { if (_top == _size) { T * nstack = new T[_size + _SPHERE_STACK_DEFAULT_SIZE]; for(size_t i = 0; i < _top; ++i) nstack[i] = _stack[i]; @@ -230,7 +314,7 @@ void fixedgrowingstack::push(T t) { } template -void fixedgrowingstack::pop() { +void fixed_growing_stack::pop() { if (!_top) { throw CSError(LOGL_FATAL, 0, "stack is empty."); } @@ -238,7 +322,7 @@ void fixedgrowingstack::pop() { } template -T fixedgrowingstack::top() const { +T fixed_growing_stack::top() const { if (!_top) { throw CSError(LOGL_FATAL, 0, "stack is empty."); } @@ -246,42 +330,42 @@ T fixedgrowingstack::top() const { } template -void fixedgrowingstack::clear() { +void fixed_growing_stack::clear() { while(!empty()) { pop(); } } template -bool fixedgrowingstack::empty() const { +bool fixed_growing_stack::empty() const { return _top == 0; } template -size_t fixedgrowingstack::size() const { +size_t fixed_growing_stack::size() const { return _top; } template -dynamicstack::dynamicstack() { +dynamic_list_stack::dynamic_list_stack() { _top = nullptr; _size = 0; } template -dynamicstack::dynamicstack(size_t _) : dynamicstack() { UnreferencedParameter(_); } +dynamic_list_stack::dynamic_list_stack(size_t _) : dynamic_list_stack() { UnreferencedParameter(_); } template -dynamicstack::dynamicstack(const dynamicstack & o) { +dynamic_list_stack::dynamic_list_stack(const dynamic_list_stack & o) { _size = o._size; _top = nullptr; - _dynamicstackitem * next = o._top, * prev; + _dynamicliststackitem * next = o._top, * prev; if (next) { - _top = new _dynamicstackitem(next->_item, nullptr); + _top = new _dynamicliststackitem(next->_item, nullptr); prev = _top; next = next->_next; while (next) { - prev->_next = new _dynamicstackitem(next->_item, nullptr); + prev->_next = new _dynamicliststackitem(next->_item, nullptr); prev = prev->_next; next = next->_next; } @@ -289,21 +373,21 @@ dynamicstack::dynamicstack(const dynamicstack & o) { } template -dynamicstack::~dynamicstack() { +dynamic_list_stack::~dynamic_list_stack() { clear(); }; template -dynamicstack & dynamicstack::operator=(const dynamicstack & o) { +dynamic_list_stack & dynamic_list_stack::operator=(const dynamic_list_stack & o) { _size = o._size; _top = nullptr; - _dynamicstackitem * next = o._top, * prev; + _dynamicliststackitem * next = o._top, * prev; if (next) { - _top = new _dynamicstackitem(next->_item, nullptr); + _top = new _dynamicliststackitem(next->_item, nullptr); prev = _top; next = next->_next; while (next) { - prev->_next = new _dynamicstackitem(next->_item, nullptr); + prev->_next = new _dynamicliststackitem(next->_item, nullptr); prev = prev->_next; next = next->_next; } @@ -312,25 +396,25 @@ dynamicstack & dynamicstack::operator=(const dynamicstack & o) { } template -void dynamicstack::push(T t) { - _dynamicstackitem * ntop = new _dynamicstackitem(t, _top); +void dynamic_list_stack::push(T t) { + _dynamicliststackitem * ntop = new _dynamicliststackitem(t, _top); _top = ntop; _size++; } template -void dynamicstack::pop() { +void dynamic_list_stack::pop() { if (_top == nullptr) { throw CSError(LOGL_FATAL, 0, "stack is empty."); } - _dynamicstackitem * oldtop = _top; + _dynamicliststackitem * oldtop = _top; _top = oldtop->_next; delete oldtop; _size--; } template -T dynamicstack::top() const { +T dynamic_list_stack::top() const { if (_top == nullptr) { throw CSError(LOGL_FATAL, 0, "stack is empty."); } @@ -338,24 +422,24 @@ T dynamicstack::top() const { } template -void dynamicstack::clear() { +void dynamic_list_stack::clear() { while(!empty()) { pop(); } } template -bool dynamicstack::empty() const { +bool dynamic_list_stack::empty() const { return _top == nullptr; } template -size_t dynamicstack::size() const { +size_t dynamic_list_stack::size() const { return _size; } template -dynamicstack::_dynamicstackitem::_dynamicstackitem(T item, _dynamicstackitem * next) { +dynamic_list_stack::_dynamicliststackitem::_dynamicliststackitem(T item, _dynamicliststackitem * next) { _item = item; _next = next; } @@ -424,4 +508,6 @@ size_t tsstack::size() const { return s; } +} + #endif //_INC_CSTACK_H diff --git a/src/common/sphere_library/sstring.cpp b/src/common/sphere_library/sstring.cpp index 8674cb196..cce8d53f9 100644 --- a/src/common/sphere_library/sstring.cpp +++ b/src/common/sphere_library/sstring.cpp @@ -1,7 +1,8 @@ #include "sstring.h" //#include "../../common/CLog.h" #include "../../sphere/ProfileTask.h" -#include "../CExpression.h" +#include "../CExpression.h" // included in the precompiled header +#include // for std::countl_zero #ifdef MSVC_COMPILER @@ -57,21 +58,21 @@ bool cstr_to_num( const char * RESTRICT str, _IntType * const out, uint base = 0, + size_t stop_at_len = 0, bool const ignore_trailing_extra_chars = false ) noexcept { static_assert(std::is_integral_v<_IntType>, "Only integers supported"); - if (!str || !out || base == 1 || base > 16) + if (!str || !out || base == 1 || base > 16) [[unlikely]] return false; - // 1) Skip leading spaces - while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') { // Instead of using ISWHITESPACE + // Skip leading whitespace + while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') ++str; - } if (*str == '\0') return false; - // 2) Optional sign + // Optional sign (only for signed types) bool neg = false; if constexpr (std::is_signed_v<_IntType>) { @@ -89,18 +90,21 @@ bool cstr_to_num( } } - // 3) Hex‐prefix via leading '0' + // Auto-detect base or handle explicit hex prefix bool hex = false; if (base == 0) { - // Auto-detect. + // Auto-detect: '0' followed by hex digit → base 16; else base 10 if (*str == '0' && str[1] != '\0' && str[1] != '.') { - if (IsHexNumDigit(str[1])) + const char next = str[1]; + if ((next >= '0' && next <= '9') || + (next >= 'A' && next <= 'F') || + (next >= 'a' && next <= 'f')) { hex = true; base = 16; - ++str; // skip the '0' prefix + ++str; // skip '0' prefix } else { @@ -112,164 +116,277 @@ bool cstr_to_num( base = 10; } } - if (base == 16 && str[0] == '0' && str[1] != '\0' && str[1] != '.') + else if (base == 16 && *str == '0' && str[1] != '\0' && str[1] != '.') { hex = true; - ++str; + ++str; // consume '0' prefix } using _UIntType = std::make_unsigned_t<_IntType>; - const _UIntType base_casted = uint8_t(base); - const _UIntType maxDiv = std::numeric_limits<_UIntType>::max() / _UIntType(base_casted); - const _UIntType maxRem = std::numeric_limits<_UIntType>::max() % _UIntType(base_casted); - _UIntType acc = 0; // accumulator - // 4) Parse digits - ushort ndigits = 0; - const char* startDigits = str; - for (; *str; ++str) + // Compute overflow limits based on target type and sign + _UIntType limit; + if constexpr (std::is_signed_v<_IntType>) { - const char c = *str; - _UIntType digit; - if (c >= '0' && c <= '9') - digit = c - '0'; - else if (hex && c >= 'A' && c <= 'F') - digit = c - 'A' + 10; - else if (hex && c >= 'a' && c <= 'f') - digit = c - 'a' + 10; - else if (!hex && c == '.') - continue; // Skip decimal point - else - break; - if (acc > maxDiv || (acc == maxDiv && digit > maxRem)) - return false; // overflow - acc = acc * base_casted + digit; - ++ndigits; + // For negative: can go one past max (to represent min value in two's complement) + // For positive: limited to max + limit = neg ? (_UIntType(std::numeric_limits<_IntType>::max()) + 1u) + : _UIntType(std::numeric_limits<_IntType>::max()); + } + else + { + limit = std::numeric_limits<_UIntType>::max(); } - if (str == startDigits) - return false; // no digits consumed - // 5) Trailing‐space or end‐of‐string - if (!ignore_trailing_extra_chars) + _UIntType acc = 0; + ushort ndigits = 0; + const char* startDigits = str; + + switch (base) { - // Some old code expects string to num conversion to tolerate trailing whitespaces or even extra chars - // (like the atoi C function). - constexpr bool tolerate_trailing_whitespaces = true; - if constexpr (tolerate_trailing_whitespaces) + // Fast path: base 10 (most common) + case 10: { - while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') + const _UIntType maxDiv = limit / 10u; + const _UIntType maxRem = limit % 10u; + + while (*str) + { + if (stop_at_len && (size_t(str - startDigits) >= stop_at_len)) + break; + + const char c = *str; + + // Skip dots (Sphere convention: no true floats) + if (c == '.') + { + ++str; + continue; + } + + if (c < '0' || c > '9') + break; + + const _UIntType digit = c - '0'; + + // Check overflow before multiplying + if (acc > maxDiv || (acc == maxDiv && digit > maxRem)) + return false; + + acc = acc * 10u + digit; + ++ndigits; ++str; + } } + // Fast path: base 16 (second most common) + case 16: + { + const _UIntType maxDiv = limit / 16u; + const _UIntType maxRem = limit % 16u; + + // Skip other leading zeroes. + while (*str == '0') + ++str; + startDigits = str; + + while (*str) + { + if (stop_at_len && (size_t(str - startDigits) >= stop_at_len)) + break; + + const char c = *str; + _UIntType digit; + + if (c >= '0' && c <= '9') + digit = c - '0'; + else if (c >= 'A' && c <= 'F') + digit = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + digit = c - 'a' + 10; + else + break; + + // Check overflow + if (acc > maxDiv || (acc == maxDiv && digit > maxRem)) + return false; + + acc = acc * 16u + digit; + ++ndigits; + ++str; + } + } + // Generic path: other bases (2-15, cold path) + default: + { + const _UIntType base_casted = uint8_t(base); + const _UIntType maxDiv = limit / base_casted; + const _UIntType maxRem = limit % base_casted; + + while (*str) + { + if (stop_at_len && (size_t(str - startDigits) >= stop_at_len)) + break; + + const char c = *str; + _UIntType digit; + + if (c >= '0' && c <= '9') + digit = c - '0'; + else if (c >= 'A' && c <= 'F') + digit = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + digit = c - 'a' + 10; + else + break; + + // Validate digit is within base + if (digit >= base_casted) + return false; + + // Check overflow + if (acc > maxDiv || (acc == maxDiv && digit > maxRem)) + return false; + + acc = acc * base_casted + digit; + ++ndigits; + ++str; + } + } + } // switch + + if (ndigits == 0) + return false; // no valid digits consumed + + // Check trailing characters + if (!ignore_trailing_extra_chars) + { + // Skip trailing whitespace + while (*str == ' ' || *str == '\t' || *str == '\r' || *str == '\n') + ++str; + if (*str != '\0') - return false; + return false; // unexpected trailing characters } - // 6) Make short (< UINT32_MAX, or <= 8 hex digits) hex numbers behave like 32 bits numbers. - /* - * Why Sphere expects this and works like this is unknown to me (maybe TUS/Grayworld or - * prehistoric Sphere versions worked with 32 bit numbers instead of 64 bit). - * - */ - if (hex && ndigits<=8 && std::is_signed_v<_IntType>) + // Sphere hex convention: ≤8 hex digits → interpret as signed 32-bit, then extend + if (hex && ndigits <= 8) { // Reinterpret the bits as a signed 32 bit number, then expand that value to a 32 bit number. // if ndigits <= 8 (so number is always <= 0xFFFF FFFF), it will always be < UINT32_MAX. - int32_t v32 = int32_t(uint32_t(acc)); - *out = _IntType(v32); + + // ..Why Sphere expects this and works like this is unknown to me (maybe TUS/Grayworld or + // prehistoric Sphere versions worked with 32 bit numbers instead of 64 bit). + + const int32_t v32 = static_cast(static_cast(acc)); + + // Apply the leading minus if present + const int64_t v = neg ? -static_cast(v32) : static_cast(v32); + // branchless: + //uint64_t m = 0 - static_cast(neg); // 0 or 0xFFFF..FFFF, defined modulo 2^64 + //int64_t v = static_cast((static_cast(v64) ^ m) - m); // relies only on defined unsigned wraparound + + // Verify it fits in the target type (important for int8_t, int16_t) + if constexpr (sizeof(_IntType) < sizeof(int32_t)) + { + if (v32 < std::numeric_limits<_IntType>::min() || + v32 > std::numeric_limits<_IntType>::max()) + return false; + } + + *out = static_cast<_IntType>(v); return true; } - // 7) Store result + // Non-hex path. Store final result if constexpr (std::is_signed_v<_IntType>) { if (neg) { - if (acc > _UIntType(std::numeric_limits<_IntType>::max()) + 1u) - return false; // too negative - *out = _IntType(0) - _IntType(acc); + // Negate using well-defined unsigned arithmetic, then cast + *out = static_cast<_IntType>(_UIntType(0) - acc); } else { - if (acc > _UIntType(std::numeric_limits<_IntType>::max())) - return false; // too large positive - *out = _IntType(acc); + *out = static_cast<_IntType>(acc); } } else { - *out = _IntType(acc); + *out = static_cast<_IntType>(acc); } + return true; } +// Wrapper functions -std::optional Str_ToI8 (const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToI8 (const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { char val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToU8 (const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToU8 (const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { uchar val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToI16 (const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToI16 (const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { short val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToU16 (const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToU16 (const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { ushort val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToI (const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToI (const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { int val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToU(const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToU(const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { uint val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToLL(const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToLL(const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { llong val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; } -std::optional Str_ToULL(const tchar * ptcStr, uint base, bool fIgnoreExcessChars) noexcept +std::optional Str_ToULL(const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { ullong val = 0; - const bool fSuccess = cstr_to_num(ptcStr, &val, base, fIgnoreExcessChars); + const bool fSuccess = cstr_to_num(ptcStr, &val, base, uiStopAtLen, fIgnoreExcessChars); if (!fSuccess) return std::nullopt; return val; @@ -279,98 +396,316 @@ std::optional Str_ToULL(const tchar * ptcStr, uint base, bool fIgnoreExc // -- // Number to String -static constexpr tchar DIGITS[] = "0123456789abcdef"; +/* +Hex width and two’s-complement: +After the hex marker '0', leading zeros are ignored for width selection. +Count significant hex digits starting at the first non-zero nibble: + ≤ 8 significant digits → interpret the bit pattern as signed 32-bit two’s-complement (then widen to 64-bit to return). + 9..16 significant digits → interpret as signed 64-bit two’s-complement. + 16 significant digits → warn about 64-bit overflow, return −1, and consume the entire hex token. +*/ + +namespace str2int_detail +{ + +// Uppercase hex digit table +static constexpr char DIGITS_UPPER[16] = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' +}; + +// Map a nibble [0,15] to its uppercase hex character +static inline char hexdig_upper(uint32_t v) noexcept +{ + return DIGITS_UPPER[v & 0xF]; +} + +// Compute hex digits to emit within a fixed width (8 or 16 nibbles) after +// trimming leading zero nibbles within that width. +// Special-case zero as exactly one hex digit ("0"), so final hex "00". +static inline int hex_digits_from_width(uint64_t u, int iWidthNibbles) noexcept +{ + if (iWidthNibbles == 8) + { + uint32 v = static_cast(u); + if (v == 0) + return 1; // represent 0 as "0" (the caller will prefix a single '0' for "00") + // Count leading zero bits in 32-bit domain, then convert to nibbles. + // countl_zero(0) would be 32, but we have v != 0 here. + int lz = std::countl_zero(v); + return 8 - (lz / 4); + } + else + { + if (u == 0) + return 1; + int lz = std::countl_zero(u); // counts in 64-bit domain + return 16 - (lz / 4); + } +} +// Base-10 two-digit lookup table (LUT): "00", "01", ..., "99" laid out consecutively. +// Emission logic divides by 100, uses remainder r in [0,99], and copies DEC_00_99[2*r+0..1]. +// This halves the number of division/mod operations versus single-digit steps. +static constexpr std::array DEC_00_99 = []{ + std::array a{}; + for (int i = 0; i < 100; ++i) { + const int tens = i / 10; // exact for 0..99 + const int ones = i - tens * 10; // exact for 0..99 + a[2*i + 0] = char('0' + tens); + a[2*i + 1] = char('0' + ones); + } + return a; +}(); + +} // namespace + +// Template entry point: returns ptcOutBuf on success, nullptr on failure. +// uiBase must be in [2,16]. Hex path (uiBase 16) emits uppercase and starts with '0'. +// Decimal path (uiBase 10) uses a two-digit LUT for throughput. +// Other bases use a generic fallback (reverse into tmp, then write forward). template -tchar* Str_FromInt_Fast(_IntType val, lptstr_restrict out_buf, size_t buf_length, uint base) noexcept +tchar* Str_FromInt_Fast(_IntType val, tchar* ptcOutBuf, size_t uiBufLength, uint32 uiBase) noexcept { - if (!out_buf || buf_length == 0 || base == 0 || base > 16) + static_assert(std::is_integral_v<_IntType>, "Str_FromInt_Fast requires an integral type"); + static_assert(sizeof(_IntType) <= 8, "Only up to 64-bit integers are supported"); + + if (!ptcOutBuf || uiBufLength == 0) + { +#ifdef _DEBUG + g_Log.EventError("Str_FromInt_Fast: null buffer or zero length.\n"); +#endif + return nullptr; + } + if (uiBase < 2 || uiBase > 16) + { +#ifdef _DEBUG + g_Log.EventError("Str_FromInt_Fast: invalid base %u (supported 2..16).\n", uiBase); +#endif return nullptr; + } - const bool fHex = (base == 16); - // Handle zero or base==0 case + // Zero fast path matches existing semantics exactly. if (val == 0) { - if (fHex) + if (uiBase == 16) { - if (buf_length < 3) + // Hex zero as "00" + if (uiBufLength < 3) + { +#ifdef _DEBUG + g_Log.EventError("Str_FromInt_Fast[hex]: insufficient buffer (need 3 for \"00\\0\").\n"); +#endif return nullptr; - out_buf[0] = '0'; out_buf[1] = '0'; out_buf[2] = '\0'; + } + ptcOutBuf[0] = '0'; + ptcOutBuf[1] = '0'; + ptcOutBuf[2] = '\0'; + return ptcOutBuf; } else { - if (buf_length < 2) + if (uiBufLength < 2) + { +#ifdef _DEBUG + g_Log.EventError("Str_FromInt_Fast[base=%u]: insufficient buffer (need 2 for \"0\\0\").\n", uiBase); +#endif return nullptr; - out_buf[0] = '0'; out_buf[1] = '\0'; + } + ptcOutBuf[0] = '0'; + ptcOutBuf[1] = '\0'; + return ptcOutBuf; } - return out_buf; } - using _UIntType = std::make_unsigned_t<_IntType>; - _UIntType uval; - bool fIsNegative = false; - - if constexpr (std::is_signed_v<_IntType>) + // Specialize only 16 and 10; generic fallback for others. + switch (uiBase) { - fIsNegative = (val < 0); - // two's-complement bit-pattern reinterpret - const auto utmp = static_cast<_UIntType>(val); - if (fHex) + // Sphere hex formatting: + // - Always consider an input hex number in scripts as the representation of an unsigned number, but keep in mind that internally + // everything is converted to a signed number, so the upper numeric limit is INT64_MAX, not UINT64_MAX. + // - The string is written as a 32 bit number if number < UINT32_MAX. Using INT32_MAX as upper limit is wrong, + // since we said that we consider the input string to be the representation of an unsigned number. + // So, if value fits in int32 (<= 0xFFFFFFFF) → 32-bit t.c.; else 64-bit t.c. + // - Uppercase, prefix '0' + // - Variable width after trimming within chosen width: + case 16: { - uval = utmp; + // Sphere hex formatting (uppercase, prefix '0', trimmed within 32/64-bit width). + uint64_t u = 0; + int width_nibbles = 0; + + // If type is <=32-bit or runtime value fits 32 bit integer, use 32-bit two's-complement view; else 64-bit. + if (sizeof(_IntType) <= 4 + || static_cast(val) <= static_cast(UINT32_MAX)) + { + // Casting through signed then to unsigned preserves the bit pattern modulo 2^32. + const uint32_t u32 = static_cast(static_cast(val)); + u = static_cast(u32); + width_nibbles = 8; + } + else + { + // View through 64-bit signed then to unsigned to preserve bit pattern. + u = static_cast(static_cast(val)); + width_nibbles = 16; + } + + // Compute number of significant hex digits using count-leading-zero in the selected width. + const int iDigits = str2int_detail::hex_digits_from_width(u, width_nibbles); + + // Required: '0' prefix + digits + NUL. + const size_t need = static_cast(iDigits) + 2; + if (uiBufLength < need) + { +#ifdef _DEBUG + g_Log.EventWarn("Str_FromInt_Fast[hex]: insufficient buffer (need %" PRIuSIZE_T ", have %" PRIuSIZE_T ").\n", need, uiBufLength); +#endif + return nullptr; + } + + // Emit: '0' + exactly 'digits' hex characters (no re-emitted trimmed zeros). + // Right-shift on unsigned is well-defined and zero-filling on the left. + ptcOutBuf[0] = '0'; + size_t uiPos = 1; + int iShift = (iDigits - 1) * 4; // start at the most significant non-zero nibble + for (; iShift >= 0; iShift -= 4) + ptcOutBuf[uiPos++] = str2int_detail::hexdig_upper(static_cast((u >> iShift) & 0xFull)); + ptcOutBuf[uiPos] = '\0'; + return ptcOutBuf; } - else + + case 10: { - // Unsigned subtraction is always defined behaviour, it wraps around. - uval = fIsNegative ? (_UIntType(0) - utmp) : utmp; - } - } - else - { - uval = static_cast<_UIntType>(val); - } + // In Sphere scripting decimal numbers are always signed. There's no concept of unsigned number. + // Base-10 fast path using two-digit LUT: + // Strategy: + // 1) Convert to unsigned magnitude |val| in a way that is correct even for INT_MIN. + // 2) While magnitude >= 100: divide by 100 => quotient q and remainder r in [0,99]. + // 3) Use r to copy two chars from DEC_00_99 to a small reverse buffer. + // 4) Emit the final one or two digits. + // 5) Copy forward to out_buf (prepend '-' if original value was negative). + using _UInt = std::make_unsigned_t<_IntType>; + + _UInt uiMagnitude; + bool fNeg = false; + + if constexpr (std::is_signed_v<_IntType>) + { + // Two's-complement safe: interpret val as unsigned (no UB), then negate if negative. + _UInt utwos = static_cast<_UInt>(val); + fNeg = (val < 0); + uiMagnitude = fNeg ? (_UInt(0) - utwos) : utwos; + } + else + { + uiMagnitude = static_cast<_UInt>(val); + } - // Write digits from back of buffer -#define WRITE_OUT(ch) \ - do { \ - if (idx == 0) \ - return nullptr; \ - out_buf[--idx] = (ch); \ - } while (0) + // Reverse buffer; 32 bytes sufficient for 64-bit decimal (max 20 digits). + tchar ptcTemp[32]; + size_t uiPos = 0; - size_t idx = buf_length; - out_buf[--idx] = '\0'; + // Emit two digits per iteration using /100, significantly reducing division/mod count. + while (uiMagnitude >= 100) + { + const _UInt q = uiMagnitude / 100; // quotient + const uint32_t r = static_cast(uiMagnitude - q * 100); // remainder in [0,99] + // Store in reverse order: ones then tens, since we're building least significant first. + ptcTemp[uiPos + 0] = str2int_detail::DEC_00_99[2 * r + 1]; + ptcTemp[uiPos + 1] = str2int_detail::DEC_00_99[2 * r + 0]; + uiPos += 2; + uiMagnitude = q; + } - if (fHex) - { - // Hex path: mask & shift by 4 bits (it's the same as dividing by 16). - do - { - WRITE_OUT(DIGITS[uval & 0xFu]); - uval >>= 4; - } while (uval); - WRITE_OUT('0'); // leading 0 hex prefix - } - else - { - // General path: division + multiply to get remainder - do - { - const _UIntType q = uval / base; - const _UIntType d = uval - q * base; - WRITE_OUT(DIGITS[d]); - uval = q; - } while (uval); - - if (fIsNegative) { - WRITE_OUT('-'); + // Handle the last one or two digits without branching on zero. + if (uiMagnitude < 10) + { + ptcTemp[uiPos++] = static_cast('0' + static_cast(uiMagnitude)); + } + else + { + const uint32_t r = static_cast(uiMagnitude); // 10..99 + ptcTemp[uiPos + 0] = str2int_detail::DEC_00_99[2 * r + 1]; + ptcTemp[uiPos + 1] = str2int_detail::DEC_00_99[2 * r + 0]; + uiPos += 2; + } + + // Buffer need: optional '-' + digits + NUL. + const size_t uiNeed = (fNeg ? 1 : 0) + uiPos + 1; + if (uiBufLength < uiNeed) + { +#ifdef _DEBUG + g_Log.EventWarn("Str_FromInt_Fast[base=10]: insufficient buffer (need %" PRIuSIZE_T ", have %" PRIuSIZE_T ").\n", uiNeed, uiBufLength); +#endif + return nullptr; + } + + // Write sign and digits forward. + size_t n = uiPos; // save digit count before resetting + size_t pos = 0; + if (fNeg) + ptcOutBuf[pos++] = '-'; + while (n) + ptcOutBuf[pos++] = ptcTemp[--n]; + ptcOutBuf[pos] = '\0'; } - } -#undef WRITE_OUT - return &out_buf[idx]; + default: + { + // Generic fallback for other bases in [2,16], reverse-then-forward. + using _UInt = std::make_unsigned_t<_IntType>; + + _UInt uiMagnitude; + bool fNeg = false; + + if constexpr (std::is_signed_v<_IntType>) + { + const _UInt utwos = static_cast<_UInt>(val); + fNeg = (val < 0); + uiMagnitude = fNeg ? (_UInt(0) - utwos) : utwos; + } + else + { + uiMagnitude = static_cast<_UInt>(val); + } + + // 65 bytes: 64 bits in base 2 = 64 digits max, plus room for sign handling if needed + tchar ptcTemp[65]; + size_t n = 0; + const _UInt B = static_cast<_UInt>(uiBase); + + // Repeated division/modulo; acceptable since this path is cold in your workload. + do + { + const _UInt q = uiMagnitude / B; + const uint32_t r = static_cast(uiMagnitude - q * B); + uiMagnitude = q; + ptcTemp[n++] = str2int_detail::DIGITS_UPPER[r]; + } + while (uiMagnitude); + + const size_t uiNeed = (fNeg ? 1 : 0) + n + 1; + if (uiBufLength < uiNeed) + { +#ifdef _DEBUG + g_Log.EventWarn("Str_FromInt_Fast[base=%u]: insufficient buffer (need %" PRIuSIZE_T ", have %" PRIuSIZE_T ").\n", uiBase, uiNeed, uiBufLength); +#endif + return nullptr; + } + + size_t uiPos = 0; + if (fNeg) + ptcOutBuf[uiPos++] = '-'; + while (n) + ptcOutBuf[uiPos++] = ptcTemp[--n]; + ptcOutBuf[uiPos] = '\0'; + return ptcOutBuf; + } + } // switch } +// Typed front-writing wrappers: they return buf on success, nullptr on failure. +// For now keep _Fast and standard variants. They were there for historical purposes (previous implementation was back-writing). tchar* Str_FromI_Fast(int val, tchar* buf, size_t buf_length, uint base) noexcept { return Str_FromInt_Fast(val, buf, buf_length, base); @@ -393,38 +728,22 @@ tchar* Str_FromULL_Fast (ullong val, tchar* buf, size_t buf_length, uint base) n void Str_FromI(int val, tchar* buf, size_t buf_length, uint base) noexcept { - tchar* modified_buf = Str_FromI_Fast(val, buf, buf_length, base); - const size_t offset = size_t(modified_buf - buf); - if (offset > 0) { - memmove(buf, modified_buf, buf_length - offset); - } + (void) Str_FromI_Fast(val, buf, buf_length, base); } void Str_FromUI(uint val, tchar* buf, size_t buf_length, uint base) noexcept { - tchar* modified_buf = Str_FromUI_Fast(val, buf, buf_length, base); - const size_t offset = size_t(modified_buf - buf); - if (offset > 0) { - memmove(buf, modified_buf, buf_length - offset); - } + (void) Str_FromUI_Fast(val, buf, buf_length, base); } void Str_FromLL(llong val, tchar* buf, size_t buf_length, uint base) noexcept { - tchar* modified_buf = Str_FromLL_Fast(val, buf, buf_length, base); - const size_t offset = size_t(modified_buf - buf); - if (offset > 0) { - memmove(buf, modified_buf, buf_length - offset); - } + (void) Str_FromLL_Fast(val, buf, buf_length, base); } void Str_FromULL(ullong val, tchar* buf, size_t buf_length, uint base) noexcept { - tchar* modified_buf = Str_FromULL_Fast(val, buf, buf_length, base); - const size_t offset = size_t(modified_buf - buf); - if (offset > 0) { - memmove(buf, modified_buf, buf_length - offset); - } + (void) Str_FromULL_Fast(val, buf, buf_length, base); } @@ -524,7 +843,7 @@ int StrncpyCharBytesWritten(int iBytesToWrite, size_t uiBufSize, bool fPrintErro return (uiBufSize > 1) ? int(uiBufSize - 1) : 0; // Bytes written, excluding the string terminator. } -bool IsStrEmpty( const tchar * pszTest ) +bool IsStrEmpty( const tchar * pszTest ) noexcept { if ( !pszTest || !*pszTest ) return true; @@ -538,7 +857,7 @@ bool IsStrEmpty( const tchar * pszTest ) return true; } -bool IsStrNumericDec( const tchar * pszTest ) +bool IsStrNumericDec( const tchar * pszTest ) noexcept { if ( !pszTest || !*pszTest ) return false; @@ -554,7 +873,7 @@ bool IsStrNumericDec( const tchar * pszTest ) } -bool IsStrNumeric( const tchar * pszTest ) +bool IsStrNumeric( const tchar * pszTest ) noexcept { if ( !pszTest || !*pszTest ) return false; @@ -575,7 +894,7 @@ bool IsStrNumeric( const tchar * pszTest ) return true; } -bool IsSimpleNumberString( lpctstr_restrict pszTest ) +bool IsSimpleNumberString( lpctstr_restrict pszTest ) noexcept { // is this a string or a simple numeric expression ? // string = 1 2 3, sdf, sdf sdf sdf, 123d, 123 d, @@ -638,10 +957,10 @@ bool IsSimpleNumberString( lpctstr_restrict pszTest ) // strncpy doesn't null-terminate if it truncates the copy, and if uiMaxlen is > than the source string length, the remaining space is filled with '\0' size_t Str_CopyLimit(lptstr_restrict pDst, lpctstr_restrict pSrc, const size_t uiMaxSize) noexcept { - if (uiMaxSize == 0) + if (uiMaxSize == 0) [[unlikely]] return 0; - if (pSrc[0] == '\0') + if (pSrc[0] == '\0') [[unlikely]] { pDst[0] = '\0'; @@ -660,11 +979,11 @@ size_t Str_CopyLimit(lptstr_restrict pDst, lpctstr_restrict pSrc, const size_t u size_t Str_CopyLimitNull(lptstr_restrict pDst, lpctstr_restrict pSrc, size_t uiMaxSize) noexcept { - if (uiMaxSize == 0) + if (uiMaxSize == 0) [[unlikely]] { return 0; } - if (pSrc[0] == '\0') + if (pSrc[0] == '\0') [[unlikely]] { pDst[0] = '\0'; return 0; @@ -686,6 +1005,70 @@ size_t Str_CopyLimitNull(lptstr_restrict pDst, lpctstr_restrict pSrc, size_t uiM return len; // bytes copied in pDst string (not counting the string terminator) } +/* +// Copy up to max_len-1 chars from src to dst, NUL-terminate. +// Returns number of chars written (excluding the terminator). +size_t Str_CopyLimitNull_ShortStr(char* dst, const char* src, size_t max_len) +{ + if (max_len == 0) [[unlikely]] + return 0; + + char* const start = dst; + size_t remaining = max_len - 1; + + // Use 64-bit words on modern 64-bit targets + using word_t = std::conditional_t= 8, uint64_t, uint32_t>; + constexpr ushort W = static_cast(sizeof(word_t)); + + // Magic constants for zero detection + // lomagic (0x0101010101010101ULL) subtracts 1 from each byte. + // himagic (0x8080808080808080ULL) is used to mask out the high bit of each byte. + // The expression ((w - lomagic) & ~w & himagic) evaluates to non-zero if any byte in the word was zero. + constexpr word_t lomagic = (W == 8 ? 0x0101010101010101ULL : 0x01010101U); + constexpr word_t himagic = (W == 8 ? 0x8080808080808080ULL : 0x80808080U); + + // Word-wise loop (assumes unaligned loads/stores are fast) + while (remaining >= W) + { + // Direct unaligned load/store, acceptable for short strings + word_t w = *reinterpret_cast(src); + *reinterpret_cast(dst) = w; + + // Detect any zero byte + if (((w - lomagic) & ~w & himagic) != 0) + { + // Scan this word byte-by-byte for the exact NUL + for (size_t i = 0; i < W; ++i) + { + char c = src[i]; + dst[i] = c; + if (c == '\0') + return (dst + i) - start; + } + // Should never get here + ASSERT(false); + } + src += W; + dst += W; + remaining -= W; + } + + // Tail bytes + while (remaining-- > 0) + { + const char c = *src++; + *dst++ = c; + if (c == '\0') + return dst - start - 1; + } + + // Buffer full – append NUL + *dst = '\0'; + return dst - start; +} +*/ + +// Acceptable overhead for out use cases (non-performance critical). size_t Str_CopyLen(lptstr_restrict pDst, lpctstr_restrict pSrc) noexcept { strcpy(pDst, pSrc); @@ -960,7 +1343,7 @@ void Str_MakeUnQuoted(tchar* pStr) noexcept tchar* endPtr = src + std::strlen(src); - // If quoted, locate closing quote and adjust endPtr + // If quoted, locate servClosing quote and adjust endPtr if (fQuoted) { tchar* p = endPtr; @@ -1016,7 +1399,8 @@ tchar * Str_GetUnQuoted(lptstr_restrict pStr) noexcept int Str_TrimEndWhitespace(tchar * pStr, int len) noexcept { - ASSERT(len >= 0); + if (!pStr) [[unlikely]] + return -1; while (len > 0 && IsWhitespace(pStr[len - 1])) { --len; } @@ -1026,6 +1410,8 @@ int Str_TrimEndWhitespace(tchar * pStr, int len) noexcept tchar * Str_TrimWhitespace(tchar * pStr) noexcept { + if (!pStr) [[unlikely]] + return nullptr; GETNONWHITESPACE(pStr); Str_TrimEndWhitespace(pStr, (int)strlen(pStr)); return pStr; @@ -1033,7 +1419,7 @@ tchar * Str_TrimWhitespace(tchar * pStr) noexcept void Str_EatEndWhitespace(const tchar* const pStrBegin, tchar*& pStrEnd) noexcept { - if (pStrBegin == pStrEnd) + if (pStrBegin == pStrEnd) [[unlikely]] return; tchar* ptcPrev = pStrEnd - 1; @@ -1047,83 +1433,6 @@ void Str_EatEndWhitespace(const tchar* const pStrBegin, tchar*& pStrEnd) noexcep } } -/* -void Str_SkipEnclosedAngularBrackets(tchar*& ptcLine) noexcept -{ - // Move past a < > statement. It can have ( ) inside, if it happens, ignore < > characters inside (). - bool fOpenedOneAngular = false; - int iOpenAngular = 0, iOpenCurly = 0; - tchar* ptcTest = ptcLine; - while (const tchar ch = *ptcTest) - { - if (IsWhitespace(ch)) - ; - else if (ch == '(') - ++iOpenCurly; - else if (ch == ')') - --iOpenCurly; - else if (iOpenCurly == 0) - { - if (ch == '<') - { - bool fOperator = false; - if ((ptcTest[1] == '<') && (ptcTest[2] != '\0') && IsWhitespace(ptcTest[2])) - { - // I want a whitespace after the operator and some text after it. - const tchar * ptcOpTest = &(ptcTest[3]); - if (*ptcOpTest != '\0') - { - GETNONWHITESPACE(ptcOpTest); - if (*ptcOpTest != '\0') // There's more text to parse - { - // I guess i have sufficient proof: skip, it's a << operator - fOperator = true; - ptcTest += 2; // Skip the second > and the following whitespace - } - } - } - if (!fOperator) - { - fOpenedOneAngular = true; - ++iOpenAngular; - } - } - else if (ch == '>') - { - bool fOperator = false; - if ((ptcTest[1] == '>') && (ptcTest[2] != '\0') && IsWhitespace(ptcTest[2])) - { - if ((ptcLine == ptcTest) || ((iOpenAngular > 0) && IsWhitespace(*(ptcTest - 1)))) - { - const tchar * ptcOpTest = &(ptcTest[3]); - if (*ptcOpTest != '\0') - { - GETNONWHITESPACE(ptcOpTest); - if (*ptcOpTest != '\0') // There's more text to parse - { - // I guess i have sufficient proof: skip, it's a >> operator - fOperator = true; - ptcTest += 2; // Skip the second > and the following whitespace - } - } - } - } - if (!fOperator) - { - --iOpenAngular; - if (fOpenedOneAngular && !iOpenAngular) - { - ptcLine = ptcTest + 1; - return; - } - } - } - } - ++ptcTest; - } -} -*/ - void Str_SkipEnclosedAngularBrackets(tchar*& ptcLine) noexcept { // Move past a < > statement. It can have ( ) inside, if it happens, ignore < > characters inside (). @@ -1316,27 +1625,30 @@ int FindCAssocRegTableHeadSorted(const tchar * pszFind, const tchar * const* pps return -1; } -bool Str_Check(const tchar * pszIn) noexcept +bool Str_Untrusted_InvalidTermination(const tchar * pszIn, size_t uiMaxAcceptableSize) noexcept { if (pszIn == nullptr) return true; const tchar * p = pszIn; - while (*p != '\0' && (*p != 0x0A) && (*p != 0x0D)) + while ((*p != '\0') && (*p != 0x0A /* '\n' */) && (*p != 0x0D /* '\r' */) + && ((p - pszIn) < ptrdiff_t(uiMaxAcceptableSize))) + { ++p; + } return (*p != '\0'); } -bool Str_CheckName(const tchar * pszIn) noexcept +bool Str_Untrusted_InvalidName(const tchar * pszIn, size_t uiMaxAcceptableSize) noexcept { if (pszIn == nullptr) return true; const tchar * p = pszIn; - while (*p != '\0' && - ( - ((*p >= 'A') && (*p <= 'Z')) || + while (*p != '\0' && ((p - pszIn) < ptrdiff_t(uiMaxAcceptableSize)) + && ( + ((*p >= 'A') && (*p <= 'Z')) || ((*p >= 'a') && (*p <= 'z')) || ((*p >= '0') && (*p <= '9')) || ((*p == ' ') || (*p == '\'') || (*p == '-') || (*p == '.')) @@ -1468,7 +1780,7 @@ MATCH_TYPE Str_Match(const tchar * pPattern, const tchar * pText) noexcept fInvert = true; ++pPattern; } - // if closing bracket here or at range start then we have a + // if servClosing bracket here or at range start then we have a // malformed pattern if (*pPattern == ']') return MATCH_PATTERN; diff --git a/src/common/sphere_library/sstring.h b/src/common/sphere_library/sstring.h index ed96c179b..8417dfccd 100644 --- a/src/common/sphere_library/sstring.h +++ b/src/common/sphere_library/sstring.h @@ -65,17 +65,18 @@ struct KeyTableDesc_s /** @name String utilities: Modifiers */ -// If you want to use base = 16 to convert an hexadecimal string, it has to be in the format: 0x*** -[[nodiscard]] std::optional Str_ToI8 (const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToU8 (const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToI16(const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToU16(const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToI (const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToU (const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToLL (const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; -[[nodiscard]] std::optional Str_ToULL(const tchar * ptcStr, uint base = 0, bool fIgnoreExcessChars = true) noexcept; +// If you want to use base = 16 to convert an hexadecimal string, it has to be in the format: 0*** +// uiStopAtLen param: stop parsing at char at the given index. If 0, just parse until the end. +[[nodiscard]] std::optional Str_ToI8 (const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToU8 (const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToI16(const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToU16(const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToI (const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToU (const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToLL (const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; +[[nodiscard]] std::optional Str_ToULL(const tchar * ptcStr, uint base = 0, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; [[nodiscard]] inline -std::optional Str_ToST(const tchar * ptcStr, uint base = 10) noexcept; +std::optional Str_ToST(const tchar * ptcStr, uint base = 10, size_t uiStopAtLen = 0, bool fIgnoreExcessChars = true) noexcept; // The _Fast variants write from the end of the given buffer and return a pointer to the new start of the string, which in most // cases is different from the pointer passed as argument! @@ -256,10 +257,10 @@ void Str_SkipEnclosedAngularBrackets(tchar*& ptcLine) noexcept; ///@{ //TODOC -bool IsSimpleNumberString( const tchar * pszTest ); -bool IsStrNumericDec( const tchar * pszTest ); -bool IsStrNumeric( const tchar * pszTest ); -bool IsStrEmpty( const tchar * pszTest ); +bool IsSimpleNumberString( const tchar * pszTest ) noexcept; +bool IsStrNumericDec( const tchar * pszTest ) noexcept; +bool IsStrNumeric( const tchar * pszTest ) noexcept; +bool IsStrEmpty( const tchar * pszTest ) noexcept; // strncpy does not always return the actual amount of bytes written. this doesn't count the string terminator. int StrncpyCharBytesWritten(int iBytesToWrite, size_t uiBufSize, bool fPrintError = true); @@ -270,7 +271,6 @@ int StrncpyCharBytesWritten(int iBytesToWrite, size_t uiBufSize, bool fPrintErro * @param pFind string we are looking for. * @param ppTable table where we are looking for the string. * @param iCount max iterations. -* @param uiElemSize size of elements of the table. * @return the index of string if success, -1 otherwise. */ int FindTable(const tchar * pFind, const tchar * const * ppTable, int iCount) noexcept; @@ -280,7 +280,6 @@ int FindTable(const tchar * pFind, const tchar * const * ppTable, int iCount) no * @param pFind string we are looking for. * @param ppTable table where we are looking for the string. * @param iCount max iterations. -* @param uiElemSize size of elements of the table. * @return the index of string if success, -1 otherwise. */ int FindTableSorted(const tchar * pFind, const tchar * const * ppTable, int iCount) noexcept; @@ -300,7 +299,6 @@ int FindCAssocRegTableHeadSorted(const tchar * pFind, const tchar * const* ppTab * @param pFind string we are looking for. * @param ppTable table where we are looking for the string. * @param iCount max iterations. -* @param uiElemSize size of elements of the table. * @return the index of string if success, -1 otherwise. */ int FindTableHead(const tchar * pFind, const tchar * const * ppTable, int iCount) noexcept; @@ -310,22 +308,22 @@ int FindTableHead(const tchar * pFind, const tchar * const * ppTable, int iCount * @param pFind string we are looking for. * @param ppTable table where we are looking for the string. * @param iCount max iterations. -* @param uiElemSize size of elements of the table. * @return the index of string if success, -1 otherwise. */ int FindTableHeadSorted(const tchar * pFind, const tchar * const * ppTable, int iCount) noexcept; /** * @param pszIn string to check. +* @param uiMaxAcceptableSize a string greater than this is invalid. * @return true if string is empty or has '\c' or '\n' characters, false otherwise. */ -bool Str_Check(const tchar * pszIn) noexcept; +bool Str_Untrusted_InvalidTermination(const tchar * pszIn, size_t uiMaxAcceptableSize = SCRIPT_MAX_LINE_LEN) noexcept; /** * @param pszIn string to check. * @return false if string match "[a-zA-Z0-9_- \'\.]+", true otherwise. */ -bool Str_CheckName(const tchar * pszIn) noexcept; +bool Str_Untrusted_InvalidName(const tchar * pszIn, size_t uiMaxAcceptableSize = 100 /* arbitrary */) noexcept; /** * @brief find a substring in a string from an offset. @@ -409,20 +407,20 @@ inline ssize_t sGetLine_StaticBuf(const char *data, const size_t datasize) noexc //--- Inline methods -std::optional Str_ToST(const tchar * ptcStr, uint base) noexcept +std::optional Str_ToST(const tchar * ptcStr, uint base, size_t uiStopAtLen, bool fIgnoreExcessChars) noexcept { if constexpr (sizeof(size_t) == 4) - return Str_ToU(ptcStr, base); + return Str_ToU(ptcStr, base, uiStopAtLen, fIgnoreExcessChars); else - return Str_ToULL(ptcStr, base); + return Str_ToULL(ptcStr, base, uiStopAtLen, fIgnoreExcessChars); } bool IsHexNumDigit(int c) noexcept { return - (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f') || - (c >= '0' && c <= '9'); + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f') || + (c >= '0' && c <= '9'); } diff --git a/src/common/sphere_library/sstringobjs.cpp b/src/common/sphere_library/sstringobjs.cpp index 1f31f68b2..c7d9e2dfc 100644 --- a/src/common/sphere_library/sstringobjs.cpp +++ b/src/common/sphere_library/sstringobjs.cpp @@ -1,5 +1,5 @@ #include "../../sphere/threads.h" -#include "../CException.h" +//#include "../CException.h" // included in the precompiled header #include "sstringobjs.h" /* @@ -54,6 +54,24 @@ tchar* Str_GetTemp() noexcept return AbstractSphereThread::Strings::allocateBuffer(); } +[[nodiscard]] +tchar* Str_CopyToTemp(lpctstr pSrc) noexcept +{ + lptstr pDest = Str_GetTemp(); + Str_CopyLimitNull(pDest, pSrc, Str_TempLength()); + return pDest; +} + +[[nodiscard]] +lpctstr Str_mtEngineGetSafeTemp(lpctstr pSrc) noexcept +{ +#if MT_ENGINES + return Str_CopyToTemp(pSrc); +#else + return pSrc; +#endif +} + /* * AbstractString diff --git a/src/common/sphere_library/sstringobjs.h b/src/common/sphere_library/sstringobjs.h index 86e591715..3a01fc718 100644 --- a/src/common/sphere_library/sstringobjs.h +++ b/src/common/sphere_library/sstringobjs.h @@ -23,6 +23,13 @@ size_t Str_TempLength() noexcept [[nodiscard]] tchar* Str_GetTemp() noexcept; +[[nodiscard]] +tchar* Str_CopyToTemp(lpctstr pSrc) noexcept; + +[[nodiscard]] +lpctstr Str_mtEngineGetSafeTemp(lpctstr pSrc) noexcept; + + //-- // Base abstract class for strings, provides basic information of what should be available @@ -116,7 +123,6 @@ class HeapString : public AbstractString */ - // Temporary string implementation. Works with thread-safe string // To create such string: // TemporaryString str; @@ -143,7 +149,7 @@ class TemporaryString : public AbstractString * @brief "Copy" constructor. * * @param pStr string to copy. - * #param iLen max number of chars (single-byte) to copy. + * @param uiLen max number of chars (single-byte) to copy. */ TemporaryString(lpctstr pStr, size_t uiLen); diff --git a/src/common/sphere_library/stypecast.cpp b/src/common/sphere_library/stypecast.cpp index f0e0c3de4..ed118100c 100644 --- a/src/common/sphere_library/stypecast.cpp +++ b/src/common/sphere_library/stypecast.cpp @@ -9,3 +9,127 @@ void LogEventWarnWrapper(const char* warn_str) } } + +[[nodiscard]] +uint32 usize_narrow_u32_checked(const size_t source_val, bool should_assert) +{ + if (should_assert) { + ASSERT(source_val <= std::numeric_limits::max()); + } + else if (source_val > std::numeric_limits::max()) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from size_t to 32 bits unsigned integer will overflow.\n"); + } + return usize_narrow_u32(source_val); +} + +[[nodiscard]] +int8 i8_from_u8_checked(const uint8 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint8)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 8 bits unsigned integer to 8 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int16 i8_from_u16_checked(const uint16 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint16)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 16 bits unsigned integer to 8 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int16 i16_from_u16_checked(const uint16 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint16)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 16 bits unsigned integer to 16 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int16 i16_from_u32_checked(const uint32 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint32)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 32 bits unsigned integer to 16 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int16 i16_from_u64_checked(const uint64 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint64)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 64 bits unsigned integer to 16 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int32 i32_from_u32_checked(const uint32 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint32)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 32 bits unsigned integer to 32 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int32 i32_from_u64_checked(const uint64 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint64)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 64 bits unsigned integer to 32 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} + +[[nodiscard]] +int64 i64_from_u64_checked(const uint64 source_val, bool should_assert) // not clamping/capping +{ + const bool would_overflow = (source_val > (uint64)std::numeric_limits::max()); + if (should_assert) { + ASSERT(!would_overflow); + } + else if (would_overflow) { + detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 64 bits unsigned integer to 64 bits signed integer will overflow.\n"); + } + + return static_cast(source_val); +} diff --git a/src/common/sphere_library/stypecast.h b/src/common/sphere_library/stypecast.h index d4143ec2a..2de4c06f6 100644 --- a/src/common/sphere_library/stypecast.h +++ b/src/common/sphere_library/stypecast.h @@ -7,13 +7,8 @@ namespace detail_stypecast { -void LogEventWarnWrapper(const char* warn_str); -} - -// Helper macros -//#define n64_narrow_n32_assert(source_val) (n64_narrow_n32_checked(source_val, true)) -//#define n64_narrow_n32_warn(source_val) (n64_narrow_n32_checked(source_val, false)) +void LogEventWarnWrapper(const char* warn_str); // Helper template to work on enum types. @@ -35,6 +30,12 @@ struct underlying_or_self>> template using underlying_or_self_t = typename underlying_or_self::type; +} + +// Helper macros +//#define n64_narrow_n32_assert(source_val) (n64_narrow_n32_checked(source_val, true)) +//#define n64_narrow_n32_warn(source_val) (n64_narrow_n32_checked(source_val, false)) + // Use this as a double check, to be sure at compile time that the two variables have the same size and sign. template @@ -58,8 +59,12 @@ constexpr Tout enum_alias_cast(const Tin source_val) noexcept static_assert(std::is_arithmetic_v || std::is_enum_v, "Output variable is not a numeric type."); static_assert(sizeof(Tin) == sizeof(Tout), "Input and output types do not have the same size."); - static_assert(!(std::is_signed_v> && std::is_unsigned_v>), "Casting signed to unsigned."); - static_assert(!(std::is_signed_v> && std::is_unsigned_v> ), "Casting unsigned to signed."); + static_assert(!(std::is_signed_v > && + std::is_unsigned_v >), + "Casting signed to unsigned."); + static_assert(!(std::is_signed_v > && + std::is_unsigned_v > ), + "Casting unsigned to signed."); /* static_assert(std::is_enum_v || @@ -167,8 +172,8 @@ constexpr auto n64_narrow_n32(const T source_val) noexcept // ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow, otherwise // otherwise just print an error if it overflows (wise to do this if the values depends from user input/scripts). template -[[nodiscard]] inline - auto n64_narrow_n32_checked(const T source_val, bool should_assert) +[[nodiscard]] + auto n64_narrow_n32_checked(const T source_val, bool should_assert = false) { if (should_assert) { @@ -195,7 +200,7 @@ template template [[nodiscard]] -constexpr auto enum64_narrow_n32_checked(const T source_val, bool should_assert) noexcept +constexpr auto enum64_narrow_n32_checked(const T source_val, bool should_assert = false) noexcept { static_assert(std::is_enum_v, "Input variable is not an enum type."); return n64_narrow_n32_checked(static_cast>(source_val, should_assert)); @@ -226,8 +231,8 @@ constexpr auto n64_narrow_n16(const T source_val) noexcept // ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow, otherwise // otherwise just print an error if it overflows (wise to do this if the values depends from user input/scripts). template -[[nodiscard]] inline - auto n64_narrow_n16_checked(const T source_val, bool should_assert) +[[nodiscard]] + auto n64_narrow_n16_checked(const T source_val, bool should_assert = false) { if (should_assert) { @@ -255,7 +260,7 @@ template template [[nodiscard]] -constexpr auto enum64_narrow_n16_checked(const T source_val, bool should_assert) noexcept +constexpr auto enum64_narrow_n16_checked(const T source_val, bool should_assert = false) noexcept { static_assert(std::is_enum_v, "Input variable is not an enum type."); return n64_narrow_n16_checked(static_cast>(source_val, should_assert)); @@ -283,8 +288,8 @@ constexpr auto n64_narrow_n8(const T source_val) noexcept // ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow, otherwise // otherwise just print an error if it overflows (wise to do this if the values depends from user input/scripts). template -[[nodiscard]] inline - auto n64_narrow_n8_checked(const T source_val, bool should_assert) +[[nodiscard]] + auto n64_narrow_n8_checked(const T source_val, bool should_assert = false) { if (should_assert) { @@ -311,7 +316,7 @@ template template [[nodiscard]] -constexpr auto enum64_narrow_n8_checked(const T source_val, bool should_assert) noexcept +constexpr auto enum64_narrow_n8_checked(const T source_val, bool should_assert = false) noexcept { static_assert(std::is_enum_v, "Input variable is not an enum type."); return n64_narrow_n8_checked(static_cast>(source_val, should_assert)); @@ -342,8 +347,8 @@ constexpr auto n32_narrow_n16(const T source_val) noexcept // ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow, otherwise // otherwise just print an error if it overflows (wise to do this if the values depends from user input/scripts). template -[[nodiscard]] inline - auto n32_narrow_n16_checked(const T source_val, bool should_assert) +[[nodiscard]] + auto n32_narrow_n16_checked(const T source_val, bool should_assert = false) { if (should_assert) { @@ -370,7 +375,7 @@ template template [[nodiscard]] -constexpr auto enum32_narrow_n16_checked(const T source_val, bool should_assert) noexcept +constexpr auto enum32_narrow_n16_checked(const T source_val, bool should_assert = false) noexcept { static_assert(std::is_enum_v, "Input variable is not an enum type."); return n32_narrow_n16_checked(static_cast>(source_val, should_assert)); @@ -396,8 +401,8 @@ constexpr auto n32_narrow_n8(const T source_val) noexcept // ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow, otherwise // otherwise just print an error if it overflows (wise to do this if the values depends from user input/scripts). template -[[nodiscard]] inline - auto n32_narrow_n8_checked(const T source_val, bool should_assert) +[[nodiscard]] + auto n32_narrow_n8_checked(const T source_val, bool should_assert = false) { if (should_assert) { @@ -424,7 +429,7 @@ template template [[nodiscard]] -constexpr auto enum32_narrow_n8_checked(const T source_val, bool should_assert) noexcept +constexpr auto enum32_narrow_n8_checked(const T source_val, bool should_assert = false) noexcept { static_assert(std::is_enum_v, "Input variable is not an enum type."); return n32_narrow_n8_checked(static_cast>(source_val, should_assert)); @@ -451,8 +456,8 @@ constexpr auto n16_narrow_n8(const T source_val) noexcept // ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow, otherwise // otherwise just print an error if it overflows (wise to do this if the values depends from user input/scripts). template -[[nodiscard]] inline - auto n16_narrow_n8_checked(const T source_val, bool should_assert) +[[nodiscard]] + auto n16_narrow_n8_checked(const T source_val, bool should_assert = false) { if (should_assert) { @@ -479,7 +484,7 @@ template template [[nodiscard]] -constexpr auto enum16_narrow_n8_checked(const T source_val, bool should_assert) noexcept +constexpr auto enum16_narrow_n8_checked(const T source_val, bool should_assert= false) noexcept { static_assert(std::is_enum_v, "Input variable is not an enum type."); return n16_narrow_n8_checked(static_cast>(source_val, should_assert)); @@ -496,9 +501,9 @@ constexpr uint32 usize_narrow_u32(const size_t source_val) noexcept else return a; */ -#if SIZE_MAX == UINT64_MAX +#if (__SIZEOF_POINTER__ == 8) //SIZE_MAX == UINT64_MAX return n64_narrow_n32(source_val); -#elif SIZE_MAX == UINT32_MAX +#elif (__SIZEOF_POINTER__ == 4) //SIZE_MAX == UINT32_MAX return source_val; #else # error "size_t is neither 8 nor 4 bytes?" @@ -506,17 +511,8 @@ constexpr uint32 usize_narrow_u32(const size_t source_val) noexcept } // If size_t is bigger than a 32 bits number, narrow it to a 32 bits number and ASSERT (because you're reasonably sure but not absolutely certain) that it won't overflow. If size_t has 32 bits size, plain return the same value. -[[nodiscard]] inline -uint32 usize_narrow_u32_checked(const size_t source_val, bool should_assert) -{ - if (should_assert) { - ASSERT(source_val <= std::numeric_limits::max()); - } - else if (source_val > std::numeric_limits::max()) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from size_t to 32 bits unsigned integer will overflow.\n"); - } - return usize_narrow_u32(source_val); -} +[[nodiscard]] +uint32 usize_narrow_u32_checked(const size_t source_val, bool should_assert = false); /* Unsigned (and size_t) to signed, clamping. */ @@ -569,136 +565,49 @@ uint32 usize_narrow_u32_checked(const size_t source_val, bool should_assert) /* Unsigned (and size_t) to signed, checked for overflows. */ -[[nodiscard]] inline - int8 i8_from_u8_checked(const uint8 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint8)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 8 bits unsigned integer to 8 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int8 i8_from_u8_checked(const uint8 source_val, bool should_assert = false); // not clamping/capping template int8 i8_from_u8_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int16 i8_from_u16_checked(const uint16 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint16)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 16 bits unsigned integer to 8 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int16 i8_from_u16_checked(const uint16 source_val, bool should_assert = false); // not clamping/capping template int16 i8_from_u16_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int16 i16_from_u16_checked(const uint16 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint16)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 16 bits unsigned integer to 16 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int16 i16_from_u16_checked(const uint16 source_val, bool should_assert = false); // not clamping/capping template int16 i16_from_u16_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int16 i16_from_u32_checked(const uint32 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint32)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 32 bits unsigned integer to 16 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int16 i16_from_u32_checked(const uint32 source_val, bool should_assert = false); // not clamping/capping template int16 i16_from_u32_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int16 i16_from_u64_checked(const uint64 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint64)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 64 bits unsigned integer to 16 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int16 i16_from_u64_checked(const uint64 source_val, bool should_assert = false); // not clamping/capping template int16 i16_from_u64_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument // Convert a 32 bits unsigned value to signed and ASSERT (because you're reasonably sure but not absolutely certain) that it will fit into its signed datatype counterpart (unsigned variables can store greater values than signed ones). -[[nodiscard]] inline - int32 i32_from_u32_checked(const uint32 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint32)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 32 bits unsigned integer to 32 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int32 i32_from_u32_checked(const uint32 source_val, bool should_assert = false); // not clamping/capping template int32 i32_from_u32_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int32 i32_from_u64_checked(const uint64 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint64)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 64 bits unsigned integer to 32 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int32 i32_from_u64_checked(const uint64 source_val, bool should_assert = false); // not clamping/capping template int32 i32_from_u64_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument // Convert a 64 bits unsigned value to signed and ASSERT (because you're reasonably sure but not absolutely certain) that it will fit into its signed datatype counterpart (unsigned variables can store greater values than signed ones). -[[nodiscard]] inline - int64 i64_from_u64_checked(const uint64 source_val, bool should_assert) // not clamping/capping -{ - const bool would_overflow = (source_val > (uint64)std::numeric_limits::max()); - if (should_assert) { - ASSERT(!would_overflow); - } - else if (would_overflow) { - detail_stypecast::LogEventWarnWrapper("[Internal] Narrowing conversion from 64 bits unsigned integer to 64 bits signed integer will overflow.\n"); - } - - return static_cast(source_val); -} +[[nodiscard]] + int64 i64_from_u64_checked(const uint64 source_val, bool should_assert = false); // not clamping/capping template int64 i64_from_u64_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument // size_t conversions -[[nodiscard]] inline - int8 i8_from_usize_checked(const size_t source_val, bool should_assert) // not clamping/capping +[[nodiscard]] +inline + int8 i8_from_usize_checked(const size_t source_val, bool should_assert = false) // not clamping/capping { -#if SIZE_MAX == UINT64_MAX +#if (__SIZEOF_POINTER__ == 8) //SIZE_MAX == UINT64_MAX return n64_narrow_n8_checked(source_val, should_assert); -#elif SIZE_MAX == UINT32_MAX +#elif (__SIZEOF_POINTER__ == 4) // SIZE_MAX == UINT32_MAX return n32_narrow_n8_checked(source_val, should_assert); #else # error "size_t is neither 8 nor 4 bytes?" @@ -707,12 +616,13 @@ template int64 i64_from_u64_checked(T, bool) = delete; // disable i template int8 i8_from_usize_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int16 i16_from_usize_checked(const size_t source_val, bool should_assert) // not clamping/capping +[[nodiscard]] +inline + int16 i16_from_usize_checked(const size_t source_val, bool should_assert = false) // not clamping/capping { -#if SIZE_MAX == UINT64_MAX +#if (__SIZEOF_POINTER__ == 8) //SIZE_MAX == UINT64_MAX return n64_narrow_n16_checked(source_val, should_assert); -#elif SIZE_MAX == UINT32_MAX +#elif (__SIZEOF_POINTER__ == 4) // SIZE_MAX == UINT32_MAX return n32_narrow_n16_checked(source_val, should_assert); #else # error "size_t is neither 8 nor 4 bytes?" @@ -720,12 +630,13 @@ template int8 i8_from_usize_checked(T, bool) = delete; // disable i } template int16 i16_from_usize_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int32 i32_from_usize_checked(const size_t source_val, bool should_assert) // not clamping/capping +[[nodiscard]] +inline + int32 i32_from_usize_checked(const size_t source_val, bool should_assert = false) // not clamping/capping { -#if SIZE_MAX == UINT64_MAX +#if (__SIZEOF_POINTER__ == 8) //SIZE_MAX == UINT64_MAX return i32_from_u32_clamping(n64_narrow_n32_checked(source_val, should_assert)); -#elif SIZE_MAX == UINT32_MAX +#elif (__SIZEOF_POINTER__ == 4) // SIZE_MAX == UINT32_MAX return i32_from_u32_checked(n_alias_cast(source_val), should_assert); #else # error "size_t is neither 8 nor 4 bytes?" @@ -733,12 +644,13 @@ template int16 i16_from_usize_checked(T, bool) = delete; // disable } template int32 i32_from_usize_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument -[[nodiscard]] inline - int64 i64_from_usize_checked(const size_t source_val, bool should_assert) // not clamping/capping +[[nodiscard]] +inline + int64 i64_from_usize_checked(const size_t source_val, bool should_assert = false) // not clamping/capping { -#if SIZE_MAX == UINT64_MAX +#if (__SIZEOF_POINTER__ == 8) //SIZE_MAX == UINT64_MAX return i64_from_u64_checked(n_alias_cast(source_val), should_assert); -#elif SIZE_MAX == UINT32_MAX +#elif (__SIZEOF_POINTER__ == 4) // SIZE_MAX == UINT32_MAX (void)should_assert; return static_cast(source_val); // For sure it will fit #else @@ -748,6 +660,4 @@ template int32 i32_from_usize_checked(T, bool) = delete; // disable template int64 i64_from_usize_checked(T, bool) = delete; // disable implicit type conversion for the inpuT source_valrgument - - #endif // _INC_STYPECAST_H diff --git a/src/common/sqlite/SQLite.cpp b/src/common/sqlite/SQLite.cpp index 7e2595107..e4d7d93ab 100644 --- a/src/common/sqlite/SQLite.cpp +++ b/src/common/sqlite/SQLite.cpp @@ -1,7 +1,7 @@ #include "../../common/CLog.h" #include "../../sphere/threads.h" -#include "../CExpression.h" -#include "../CException.h" +#include "../CExpression.h" // included in the precompiled header +//#include "../CException.h" // included in the precompiled header #include "../CScript.h" #include "SQLite.h" #include @@ -240,7 +240,7 @@ int CSQLite::ImportDB(lpctstr strInFileName) iErr = sqlite3_exec( in_db, UTF8MBSTR(pcQuery), nullptr, nullptr, &pcErrMsg ); if (iErr != SQLITE_OK) goto clean_and_ret; - + // Copy the table schema: how? // End @@ -468,13 +468,13 @@ bool CSQLite::r_Verb(CScript & s, CTextConsole * pSrc) { case LDBOV_CLOSE: if ( _fInMemory ) - return false; + return false; Close(); break; case LDBOV_CONNECT: if ( _fInMemory ) - return false; + return false; Open(s.GetArgStr()); break; diff --git a/src/common/target_info.h b/src/common/target_info.h index dfd9f643f..ea12a1bc6 100644 --- a/src/common/target_info.h +++ b/src/common/target_info.h @@ -1,7 +1,6 @@ #ifndef _INC_TARGET_INFO_H #define _INC_TARGET_INFO_H - [[maybe_unused, nodiscard]] constexpr const char* get_target_os_str() { @@ -39,7 +38,7 @@ constexpr const char* get_target_arch_str() #elif defined(__ARM_ARCH_4T__) || defined(__TARGET_ARM_4T) return "ARMv4T"; #elif defined(__ARM_ARCH_5_) || defined(__ARM_ARCH_5E_) - return "ARMv5" + return "ARMv5"; #elif defined(__ARM_ARCH_6T2_) || defined(__ARM_ARCH_6T2_) return "ARMv6T2"; #elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) @@ -57,7 +56,7 @@ constexpr const char* get_target_arch_str() #elif defined(__aarch64__) || defined(_M_ARM64) return "ARMv64"; #elif defined(__riscv) - return "RISC-V" + return "RISC-V"; #elif defined(mips) || defined(__mips__) || defined(__mips) return "MIPS"; #elif defined(__sh__) @@ -75,5 +74,4 @@ constexpr const char* get_target_arch_str() #endif } - #endif // _INC_TARGET_INFO_H diff --git a/src/doxygen.cfg b/src/doxygen.cfg index 335b26a59..19edfb0cc 100644 --- a/src/doxygen.cfg +++ b/src/doxygen.cfg @@ -1,7 +1,7 @@ -# Doxyfile 1.8.6 +# Doxyfile 1.14.0 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. +# Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. @@ -12,16 +12,26 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -32,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "SphereServer" +PROJECT_NAME = "SphereServer X" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -41,58 +51,84 @@ PROJECT_NAME = "SphereServer" PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "Ultima Online game server, developed in C++." -# With the PROJECT_LOGO tag one can specify an logo or icon that is included in -# the documentation. The maximum height of the logo should not exceed 55 pixels -# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo -# to the output directory. +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. PROJECT_LOGO = +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If +# entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = doc +OUTPUT_DIRECTORY = ../docs/doxygen -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise cause +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = YES +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + # The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this +# documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the @@ -113,13 +149,13 @@ REPEAT_BRIEF = YES ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief +# Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. @@ -127,7 +163,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -137,11 +173,11 @@ FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to +# If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. +# will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @@ -155,31 +191,42 @@ STRIP_FROM_PATH = STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot, question mark or exclamation mark) of a +# Javadoc-style comment as the brief description. If set to NO, the Javadoc- +# style will behave just like regular Qt-style comments (thus requiring an +# explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by Doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot, question mark or exclamation mark) of a Qt-style +# comment as the brief description. If set to NO, the Qt-style will behave just +# like regular Qt-style comments (thus requiring an explicit \brief command for +# a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this @@ -191,15 +238,23 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a -# new page for each member. If set to NO, the documentation of a member will be -# part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO @@ -214,20 +269,19 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -256,46 +310,90 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make Doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # -# Note For files without extension you can use no_extension as a placeholder. +# Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES -# When enabled doxygen tries to link words that correspond to documented +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 6 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by by putting a % sign in front of the word -# or globally by setting AUTOLINK_SUPPORT to NO. +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. Words listed in the +# AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# +# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and +# tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES @@ -307,16 +405,16 @@ BUILTIN_STL_SUPPORT = YES CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. +# Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. @@ -325,13 +423,20 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first +# tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent @@ -376,21 +481,42 @@ TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest +# symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -400,35 +526,41 @@ LOOKUP_CACHE_SIZE = 0 EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = YES -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local methods, +# This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO only methods in the interface are +# included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. @@ -443,7 +575,14 @@ EXTRACT_LOCAL_METHODS = YES EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. @@ -451,23 +590,32 @@ EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set -# to NO these classes will be included in the various overviews. This option has -# no effect if EXTRACT_ALL is enabled. +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO these declarations will be -# included in the documentation. +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO these +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -480,23 +628,44 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES the +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -509,7 +678,7 @@ SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = NO -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. @@ -521,22 +690,22 @@ FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. +# name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. Note that +# name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. @@ -548,7 +717,7 @@ SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. @@ -565,37 +734,35 @@ SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO -# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the -# todo list. This list is created by putting \todo commands in the -# documentation. +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the -# test list. This list is created by putting \test commands in the -# documentation. +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. @@ -620,8 +787,8 @@ ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES the list -# will mention the files that were used to generate the documentation. +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES @@ -641,24 +808,25 @@ SHOW_FILES = YES SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from +# Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file +# by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated +# by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can +# that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = @@ -666,27 +834,42 @@ LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. Do not use file names with spaces, bibtex cannot handle them. See -# also \cite for info how to create references. +# search path. See also \cite for info how to create references. CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the +# standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -694,42 +877,97 @@ QUIET = NO WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO doxygen will only warn about wrong or incomplete parameter -# documentation, but not about the absence of documentation. +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO -# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = @@ -740,28 +978,48 @@ WARN_LOGFILE = # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with -# spaces. +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = @@ -775,7 +1033,7 @@ RECURSIVE = YES # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # -# Note that relative paths are relative to the directory from which doxygen is +# Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = @@ -800,10 +1058,7 @@ EXCLUDE_PATTERNS = # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -833,7 +1088,7 @@ EXAMPLE_RECURSIVE = NO IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should +# The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # @@ -847,6 +1102,15 @@ IMAGE_PATH = # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. +# +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. INPUT_FILTER = @@ -856,11 +1120,15 @@ INPUT_FILTER = # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER ) will also be used to filter the input files that are used for +# INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. @@ -877,10 +1145,28 @@ FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. +# and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -895,12 +1181,13 @@ USE_MDFILE_AS_MAINPAGE = SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. @@ -908,7 +1195,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -920,7 +1207,7 @@ REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. @@ -938,28 +1225,28 @@ REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # -# The result: instead of the source browser generated by doxygen, the links to +# The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. @@ -967,6 +1254,46 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -978,17 +1305,11 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -997,7 +1318,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1018,40 +1339,40 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a +# each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. +# that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally +# for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description +# default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard +# generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. +# that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. +# the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. +# sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. @@ -1059,13 +1380,20 @@ HTML_FOOTER = HTML_STYLESHEET = -# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- -# defined cascading style sheet that is included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. -# Doxygen will copy the style sheet file to the output directory. For an example -# see the documentation. +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1080,10 +1408,23 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the stylesheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1092,7 +1433,7 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1110,13 +1451,16 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_TIMESTAMP = YES +HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the @@ -1126,6 +1470,33 @@ HTML_TIMESTAMP = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1141,13 +1512,14 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1161,6 +1533,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1183,14 +1562,18 @@ DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline (the HTML help workshop was already many +# years in maintenance mode). You can download the HTML help workshop from the +# web archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for @@ -1209,28 +1592,29 @@ GENERATE_HTMLHELP = NO CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler ( hhc.exe). If non-empty -# doxygen will try to run the HTML help compiler on the generated index.hhp. +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = -# The GENERATE_CHI flag controls if a separate .chi index file is generated ( -# YES) or that it should be included in the master .chm file ( NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = -# The BINARY_TOC flag controls whether a binary table of contents is generated ( -# YES) or a normal table of contents ( NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1243,6 +1627,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1261,7 +1655,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1269,8 +1664,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1278,30 +1673,30 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1343,19 +1738,40 @@ DISABLE_INDEX = NO # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has more details information than the tab index, you +# could consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO +# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines +# if an additional navigation panel is shown at the right hand side of the +# screen, displaying an outline of the contents of the main page, similar to +# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to +# NO, this option has no effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +PAGE_OUTLINE_PANEL = YES + +# When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if +# the side bar is limited to only the treeview area (value NO) or if it should +# extend to the full height of the window (value YES). Setting this to YES gives +# a layout similar to e.g. https://docs.readthedocs.io with more room for +# contents, but less room for the project logo, title, and description. If +# GENERATE_TREEVIEW is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = YES + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. +# Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. @@ -1364,6 +1780,12 @@ GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -1371,36 +1793,49 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML +# Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. -FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. @@ -1409,11 +1844,29 @@ FORMULA_TRANSPARENT = YES USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1426,33 +1879,40 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then +# For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically @@ -1469,26 +1929,27 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There -# are two flavours of web server based searching depending on the -# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for -# searching and an index file used by the script. When EXTERNAL_SEARCH is -# enabled the indexing and searching needs to be provided by external tools. See -# the section "External Indexing and Searching" for details. +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, Doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. # The default value is: NO. # This tag requires that the tag SEARCHENGINE is set to YES. SERVER_BASED_SEARCH = NO -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# When EXTERNAL_SEARCH tag is enabled Doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain the # search results. # -# Doxygen ships with an example indexer ( doxyindexer) and search engine +# Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1499,10 +1960,11 @@ EXTERNAL_SEARCH = NO # The SEARCHENGINE_URL should point to a search engine hosted by a web server # which will return the search results when EXTERNAL_SEARCH is enabled. # -# Doxygen ships with an example indexer ( doxyindexer) and search engine +# Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1523,7 +1985,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through Doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of @@ -1537,7 +1999,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = NO @@ -1553,22 +2015,36 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, Doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1586,39 +2062,57 @@ COMPACT_LATEX = NO PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. To get the times font for -# instance you can specify -# EXTRA_PACKAGES=times +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank Doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that Doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will -# replace them by respectively the title of the page, the current date and time, -# only the current date, the version number of doxygen, the project name (see -# PROJECT_NAME), or the project number (see PROJECT_NUMBER). +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank Doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that Doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by Doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output # directory. Note that the files will be copied as-is; there are no commands or @@ -1636,53 +2130,59 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, Doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# If the LATEX_HIDE_INDICES tag is set to YES then Doxygen will not include the # index chapters (such as File Index, Compound Index, etc.) in the output. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plainnat. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, Doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -1697,7 +2197,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, Doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1717,28 +2217,36 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to Doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. +# default style sheet that Doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to Doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTRA_FILES = + #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, Doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -1762,7 +2270,14 @@ MAN_OUTPUT = man MAN_EXTENSION = .3 -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without # them the man command would be unable to find the correct page. @@ -1775,7 +2290,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, Doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -1789,19 +2304,7 @@ GENERATE_XML = NO XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify a XML DTD, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, Doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -1810,11 +2313,18 @@ XML_DTD = XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, Doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, Doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -1832,19 +2342,45 @@ DOCBOOK_OUTPUT = docbook # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen -# Definitions (see http://autogen.sf.net) file that captures the structure of -# the code including all documentation. Note that this feature is still -# experimental and incomplete at the moment. +# If the GENERATE_AUTOGEN_DEF tag is set to YES, Doxygen will generate an +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES Doxygen will generate a Sqlite3 +# database with symbols found by Doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each Doxygen run. If set to NO, Doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, Doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -1852,7 +2388,7 @@ GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, Doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -1860,9 +2396,9 @@ GENERATE_PERLMOD = NO PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely # formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO the +# understand what is going on. On the other hand, if this tag is set to NO, the # size of the Perl module output will be much smaller and Perl will parse it # just the same. # The default value is: YES. @@ -1882,14 +2418,14 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, Doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names -# in the source code. If set to NO only conditional compilation will be +# If the MACRO_EXPANSION tag is set to YES, Doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. # The default value is: NO. @@ -1905,7 +2441,7 @@ MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO -# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. @@ -1914,7 +2450,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -1946,10 +2483,10 @@ PREDEFINED = EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all refrences to function-like macros that are alone on a line, have an -# all uppercase name, and do not end with a semicolon. Such function macros are -# typically used for boiler-plate code, and will confuse the parser if not +# If the SKIP_FUNCTION_MACROS tag is set to YES then Doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not # removed. # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. @@ -1969,90 +2506,60 @@ SKIP_FUNCTION_MACROS = YES # where loc1 and loc2 can be relative or absolute paths or URLs. See the # section "Linking to external documentation" for more information about the use # of tag files. -# Note: Each tag file must have an unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which Doxygen is # run, you must also specify the path to the tagfile here. TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external class will be listed in the -# class index. If set to NO only the inherited external classes will be listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in -# the modules index. If set to NO, only the current project's groups will be +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. EXTERNAL_GROUPS = YES -# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in # the related pages index. If set to NO, only the current project's pages will # be listed. # The default value is: YES. EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide inheritance +# If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# If you set the HAVE_DOT tag to YES then Doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. HAVE_DOT = YES -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of +# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed +# to run in parallel. When set to 0 Doxygen will base this on the number of # processors available in the system. You can set it explicitly to a value # larger than 0 to get control over the balance between CPU load and processing # speed. @@ -2061,55 +2568,83 @@ HAVE_DOT = YES DOT_NUM_THREADS = 6 -# When you want a differently looking font n the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# Doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2126,10 +2661,41 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the UML_LOOK tag is enabled, field labels are shown along the edge between +# two class nodes. If there are many fields and many nodes the graph may become +# too cluttered. The UML_MAX_EDGE_LABELS threshold limits the number of items to +# make the size more manageable. Set this to 0 for no limit. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_MAX_EDGE_LABELS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, Doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2139,79 +2705,105 @@ UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the +# YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing +# set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. CALL_GRAPH = NO -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). -# Possible values are: png, jpg, gif and svg. +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# https://www.graphviz.org/)). +# +# Note the formats svg:cairo and svg:cairo:cairo cannot be used in combination +# with INTERACTIVE_SVG (the INTERACTIVE_SVG will be set to NO). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus, +# png:gdiplus:gdiplus, svg:cairo, svg:cairo:cairo, svg:svg, svg:svg:core, +# gif:cairo, gif:cairo:gd, gif:cairo:gdiplus, gif:gdiplus, gif:gdiplus:gdiplus, +# gif:gd, gif:gd:gd, jpg:cairo, jpg:cairo:gd, jpg:cairo:gdiplus, jpg:gd, +# jpg:gd:gd, jpg:gdiplus and jpg:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. +# If DOT_IMAGE_FORMAT is set to svg or svg:svg or svg:svg:core, then this option +# can be set to YES to enable generation of interactive SVG images that allow +# zooming and panning. # # Note that this requires a modern browser other than Internet Explorer. Tested # and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. +# +# Note This option will be automatically disabled when DOT_IMAGE_FORMAT is set +# to svg:cairo or svg:cairo:cairo. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2230,11 +2822,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in Doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2242,10 +2835,34 @@ MSCFILE_DIRS = DIAFILE_DIRS = +# When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for PlantUML. + +PLANTUML_CFG_FILE = + +# When using PlantUML, the specified paths are searched for files specified by +# the !include statement in a PlantUML block. + +PLANTUML_INCLUDE_PATH = + +# The PLANTUMLFILE_DIRS tag can be used to specify one or more directories that +# contain PlantUml files that are included in the documentation (see the +# \plantumlfile command). + +PLANTUMLFILE_DIRS = + # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct +# larger than this value, Doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that # the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. @@ -2266,19 +2883,7 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support # this, this feature is disabled by default. @@ -2287,17 +2892,37 @@ DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = YES -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES + +# You can define message sequence charts within Doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, Doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/src/game/CBase.cpp b/src/game/CBase.cpp index 15ce568bf..a078541a8 100644 --- a/src/game/CBase.cpp +++ b/src/game/CBase.cpp @@ -1,6 +1,6 @@ -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/common.h" #include "CServerConfig.h" #include "CBase.h" diff --git a/src/game/CBase.h b/src/game/CBase.h index 2093e72e6..ee92b929d 100644 --- a/src/game/CBase.h +++ b/src/game/CBase.h @@ -179,6 +179,7 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps * @param ptcKey The key. * @param iVal Zero-based index of the value. * @param fDeleteZero true to zero. + * @param fWarnOverwrite Log warning to console. */ void SetDefNum(lpctstr ptcKey, int64 iVal, bool fDeleteZero = true, bool fWarnOverwrite = true) { @@ -191,6 +192,7 @@ struct CBaseBaseDef : public CResourceLink, public CEntityProps * @param pszVal The value. * @param fQuoted true if quoted. * @param fDeleteZero true to zero. + * @param fWarnOverwrite Log warning to console. */ void SetDefStr(lpctstr ptcKey, lpctstr pszVal, bool fQuoted = false, bool fDeleteZero = true, bool fWarnOverwrite = true) { diff --git a/src/game/CContainer.cpp b/src/game/CContainer.cpp index 1fc04c21f..1e45f898e 100644 --- a/src/game/CContainer.cpp +++ b/src/game/CContainer.cpp @@ -1,7 +1,7 @@ #include "../common/sphere_library/CSRand.h" -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CUID.h" #include "../network/send.h" #include "chars/CChar.h" @@ -35,7 +35,7 @@ void CContainer::_GoSleep() { CItem* pItem = static_cast(pObjRec); //std::unique_lock lock(pItem->MT_CMUTEX); - if (!pItem->CanTick()) + if (!pItem->TickableState()) { pItem->GoSleep(); } @@ -182,13 +182,13 @@ void CContainer::ContentAddPrivate( CItem *pItem ) } } -void CContainer::OnRemoveObj( CSObjContRec *pObRec ) // Override this = called when removed from list. +void CContainer::OnRemoveObj(CSObjContRec *pObjRec ) // Override this = called when removed from list. { ADDTOCALLSTACK("CContainer::OnRemoveObj"); // remove this object from the container list. // Overload the RemoveAt for general lists to come here. - DEBUG_ASSERT(dynamic_cast(pObRec)); - CItem *pItem = static_cast(pObRec); + DEBUG_ASSERT(dynamic_cast(pObjRec)); + CItem *pItem = static_cast(pObjRec); ASSERT(pItem); CSObjCont::OnRemoveObj(pItem); @@ -213,7 +213,7 @@ void CContainer::r_WriteContent( CScript &s ) const } } -CItem *CContainer::ContentFind( CResourceID const& rid, dword dwArg, int iDecendLevels ) const +CItem *CContainer::ContentFind(CResourceID const& rid, dword dwArg, int iDescendLevels ) const { ADDTOCALLSTACK("CContainer::ContentFind"); // send all the items in the container. @@ -227,7 +227,7 @@ CItem *CContainer::ContentFind( CResourceID const& rid, dword dwArg, int iDecend if ( pItem->IsResourceMatch(rid, dwArg) ) return pItem; - if ( iDecendLevels <= 0 ) + if ( iDescendLevels <= 0 ) continue; CItemContainer *pCont = dynamic_cast(pItem); @@ -235,7 +235,7 @@ CItem *CContainer::ContentFind( CResourceID const& rid, dword dwArg, int iDecend { if ( !pCont->IsSearchable() ) continue; - CItem *pItemInCont = pCont->ContentFind(rid, dwArg, iDecendLevels - 1); + CItem *pItemInCont = pCont->ContentFind(rid, dwArg, iDescendLevels - 1); if ( pItemInCont ) return pItemInCont; } @@ -243,10 +243,12 @@ CItem *CContainer::ContentFind( CResourceID const& rid, dword dwArg, int iDecend return nullptr; } -TRIGRET_TYPE CContainer::OnContTriggerForLoop(CScript &s, CTextConsole *pSrc, CScriptTriggerArgs *pArgs, - CSString *pResult, CScriptLineContext &StartContext, CScriptLineContext &EndContext, const CResourceID &rid, dword dwArg, int iDecendLevels ) +TRIGRET_TYPE CContainer::OnContTriggerForLoop( + CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole *pSrc, + CSString *pResult, CScriptLineContext &StartContext, CScriptLineContext &EndContext, + const CResourceID &rid, dword dwArg, int iDescendLevels ) { - ADDTOCALLSTACK("CContainer::OnContTriggerForLoop"); + ADDTOCALLSTACK("CContainer::OnContTriggerForLoop"); if ( rid.GetResIndex() != 0 ) { for (CSObjContRec *pObjRec : GetIterationSafeContReverse()) @@ -255,7 +257,7 @@ TRIGRET_TYPE CContainer::OnContTriggerForLoop(CScript &s, CTextConsole *pSrc, CS if ( pItem->IsResourceMatch(rid, dwArg) ) { s.SeekContext(StartContext); - TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK || iRet == TRIGRET_RET_ABORTED) { EndContext = StartContext; @@ -268,7 +270,7 @@ TRIGRET_TYPE CContainer::OnContTriggerForLoop(CScript &s, CTextConsole *pSrc, CS else EndContext = s.GetContext(); } - if ( iDecendLevels <= 0 ) + if ( iDescendLevels <= 0 ) continue; CItemContainer *pCont = dynamic_cast(pItem); @@ -277,7 +279,7 @@ TRIGRET_TYPE CContainer::OnContTriggerForLoop(CScript &s, CTextConsole *pSrc, CS if ( pCont->IsSearchable() ) { CContainer *pContBase = dynamic_cast(pCont); - TRIGRET_TYPE iRet = pContBase->OnContTriggerForLoop(s, pSrc, pArgs, pResult, StartContext, EndContext, rid, dwArg, iDecendLevels - 1); + TRIGRET_TYPE iRet = pContBase->OnContTriggerForLoop(s, pScriptArgs, pSrc, pResult, StartContext, EndContext, rid, dwArg, iDescendLevels - 1); if ( iRet != TRIGRET_ENDIF ) return iRet; @@ -291,7 +293,7 @@ TRIGRET_TYPE CContainer::OnContTriggerForLoop(CScript &s, CTextConsole *pSrc, CS if ( EndContext.m_iOffset <= StartContext.m_iOffset ) { CScriptObj *pScript = dynamic_cast(this); - TRIGRET_TYPE iRet = pScript->OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pScript->OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); if ( iRet != TRIGRET_ENDIF ) return iRet; } @@ -303,7 +305,7 @@ TRIGRET_TYPE CContainer::OnContTriggerForLoop(CScript &s, CTextConsole *pSrc, CS } TRIGRET_TYPE CContainer::OnGenericContTriggerForLoop( - CScript &s, CTextConsole *pSrc, CScriptTriggerArgs *pArgs, + CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole *pSrc, CSString *pResult, CScriptLineContext &StartContext, CScriptLineContext &EndContext, int iDecendLevels ) { ADDTOCALLSTACK("CContainer::OnGenericContTriggerForLoop"); @@ -311,7 +313,7 @@ TRIGRET_TYPE CContainer::OnGenericContTriggerForLoop( { CItem* pItem = static_cast(pObjRec); s.SeekContext(StartContext); - TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK || iRet == TRIGRET_RET_ABORTED) { EndContext = StartContext; @@ -330,7 +332,7 @@ TRIGRET_TYPE CContainer::OnGenericContTriggerForLoop( if ( pCont && pCont->IsSearchable() ) { CContainer *pContBase = dynamic_cast(pCont); - iRet = pContBase->OnGenericContTriggerForLoop(s, pSrc, pArgs, pResult, StartContext, EndContext, iDecendLevels - 1); + iRet = pContBase->OnGenericContTriggerForLoop(s, pScriptArgs, pSrc, pResult, StartContext, EndContext, iDecendLevels - 1); if ( iRet != TRIGRET_ENDIF ) return iRet; @@ -342,7 +344,7 @@ TRIGRET_TYPE CContainer::OnGenericContTriggerForLoop( if ( EndContext.m_iOffset <= StartContext.m_iOffset ) { CScriptObj *pScript = dynamic_cast(this); - TRIGRET_TYPE iRet = pScript->OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pScript->OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); if ( iRet != TRIGRET_ENDIF ) return iRet; } @@ -368,7 +370,7 @@ CItem *CContainer::ContentFindRandom() const return static_cast(GetContentIndex(g_Rand.GetVal((int32)GetContentCount()))); } -int CContainer::ContentConsumeTest( const CResourceID& rid, int amount, dword dwArg ) const +int CContainer::ContentConsumeTest( const CResourceID& rid, int iAmount, dword dwArg ) const { ADDTOCALLSTACK("CContainer::ContentConsumeTest"); // ARGS: @@ -378,7 +380,7 @@ int CContainer::ContentConsumeTest( const CResourceID& rid, int amount, dword dw // # = number left to be consumed. (still required) if ( rid.GetResIndex() == 0 ) - return amount; // from skills menus. + return iAmount; // from skills menus. for (const CSObjContRec* pObjRec : *this) { @@ -386,9 +388,9 @@ int CContainer::ContentConsumeTest( const CResourceID& rid, int amount, dword dw if ( pItem->IsResourceMatch(rid, dwArg) ) { const word wAmountMax = pItem->GetAmount(); - const word wAmountToConsume = (word)minimum(amount,UINT16_MAX); - amount -= (wAmountMax > wAmountToConsume ) ? wAmountToConsume : wAmountMax; - if ( amount <= 0 ) + const word wAmountToConsume = (word)minimum(iAmount,UINT16_MAX); + iAmount -= (wAmountMax > wAmountToConsume ) ? wAmountToConsume : wAmountMax; + if ( iAmount <= 0 ) break; } @@ -405,12 +407,12 @@ int CContainer::ContentConsumeTest( const CResourceID& rid, int amount, dword dw if ( !pCont->IsSearchable() ) continue; } - amount = pCont->ContentConsumeTest(rid, amount, dwArg); - if ( amount <= 0 ) + iAmount = pCont->ContentConsumeTest(rid, iAmount, dwArg); + if ( iAmount <= 0 ) break; } } - return amount; + return iAmount; } int CContainer::ContentConsume( const CResourceID& rid, int amount, dword dwArg ) diff --git a/src/game/CContainer.h b/src/game/CContainer.h index 239b21433..8a88500ed 100644 --- a/src/game/CContainer.h +++ b/src/game/CContainer.h @@ -44,11 +44,11 @@ class CContainer : public CSObjCont // This class contains a list of items but m void ContentDelete(bool fForce); /** - * @fn virtual void CContainer::OnRemoveObj( CSObjListRec* pObRec ); + * @fn virtual void CContainer::OnRemoveObj( CSObjListRec* pObjRec ); * @brief Override this = called when removed from list. - * @param [in,out] pObRec If non-null, the ob record. + * @param [in,out] pObjRec If non-null, the ob record. */ - virtual void OnRemoveObj(CSObjContRec* pObRec) override; + virtual void OnRemoveObj(CSObjContRec* pObjRec) override; /** * @fn void CContainer::ContentAddPrivate( CItem * pItem ); @@ -94,10 +94,10 @@ class CContainer : public CSObjCont // This class contains a list of items but m CItem * ContentFindRandom() const; /** - * @fn void CContainer::ContentsDump( const CPointMap & pt, uint64 iAttr = 0 ); + * @fn void CContainer::ContentsDump( const CPointMap & pt, uint64 uiAttr = 0 ); * @brief Contents dump. * @param pt The point. - * @param dwAttr The attribute. + * @param uiAttr The attribute. */ void ContentsDump( const CPointMap & pt, uint64 uiAttr = 0 ); @@ -110,9 +110,9 @@ class CContainer : public CSObjCont // This class contains a list of items but m void ContentsTransfer( CItemContainer * pCont, bool fNoNewbie ); /** - * @fn void CContainer::ContentAttrMod( uint64 iAttr, bool fSet ); + * @fn void CContainer::ContentAttrMod( uint64 uiAttr, bool fSet ); * @brief Content attribute modifier. - * @param dwAttr The attribute. + * @param uiAttr The attribute. * @param fSet true to set. */ void ContentAttrMod( uint64 uiAttr, bool fSet ); @@ -126,17 +126,17 @@ class CContainer : public CSObjCont // This class contains a list of items but m // For resource usage and gold. /** - * @fn CItem * CContainer::ContentFind( RESOURCE_ID_BASE rid, dword dwArg = 0, int iDecendLevels = 255 ) const; + * @fn CItem * CContainer::ContentFind( RESOURCE_ID_BASE rid, dword dwArg = 0, int iDescendLevels = 255 ) const; * @brief Content find. * @param rid The rid. * @param dwArg The argument. - * @param iDecendLevels Zero-based index of the decend levels. + * @param iDescendLevels Zero-based index of the decend levels. * @return null if it fails, else a pointer to a CItem. */ - CItem * ContentFind( CResourceID const& rid, dword dwArg = 0, int iDecendLevels = 255 ) const; + CItem * ContentFind( CResourceID const& rid, dword dwArg = 0, int iDescendLevels = 255 ) const; /** - * @fn TRIGRET_TYPE CContainer::OnContTriggerForLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, RESOURCE_ID_BASE rid, dword dwArg = 0, int iDecendLevels = 255 ); + * @fn TRIGRET_TYPE CContainer::OnContTriggerForLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgsPtr const& pScriptArgs, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, RESOURCE_ID_BASE rid, dword dwArg = 0, int iDescendLevels = 255 ); * @brief Executes the container trigger for loop action. * @param [in,out] s The CScript to process. * @param [in,out] pSrc If non-null, source for the. @@ -146,14 +146,14 @@ class CContainer : public CSObjCont // This class contains a list of items but m * @param [in,out] EndContext Context for the end. * @param rid The rid. * @param dwArg The argument. - * @param iDecendLevels Zero-based index of the decend levels. + * @param iDescendLevels Zero-based index of the decend levels. * * @return A TRIGRET_TYPE. */ - TRIGRET_TYPE OnContTriggerForLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, CResourceID const& rid, dword dwArg = 0, int iDecendLevels = 255 ); + TRIGRET_TYPE OnContTriggerForLoop(CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, CResourceID const& rid, dword dwArg = 0, int iDescendLevels = 255 ); /** - * @fn TRIGRET_TYPE CContainer::OnGenericContTriggerForLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, int iDecendLevels = 255 ); + * @fn TRIGRET_TYPE CContainer::OnGenericContTriggerForLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgsPtr const& pScriptArgs, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, int iDecendLevels = 255 ); * @brief Executes the generic container trigger for loop action. * @param [in,out] s The CScript to process. * @param [in,out] pSrc If non-null, source for the. @@ -165,7 +165,7 @@ class CContainer : public CSObjCont // This class contains a list of items but m * * @return A TRIGRET_TYPE. */ - TRIGRET_TYPE OnGenericContTriggerForLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, int iDecendLevels = 255 ); + TRIGRET_TYPE OnGenericContTriggerForLoop(CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult, CScriptLineContext & StartContext, CScriptLineContext & EndContext, int iDecendLevels = 255 ); /** * @fn int CContainer::ContentCount( RESOURCE_ID_BASE rid, dword dwArg = 0 ); @@ -195,15 +195,15 @@ class CContainer : public CSObjCont // This class contains a list of items but m int ContentConsume( const CResourceID& rid, int iQty = 1, dword dwArg = 0 ); /** - * @fn int CContainer::ContentConsumeTest( RESOURCE_ID_BASE rid, int iQty = 1, dword dwArg = 0 ); + * @fn int CContainer::ContentConsumeTest( RESOURCE_ID_BASE rid, int iAmount = 1, dword dwArg = 0 ); * @brief Content consume. * @param rid The rid. - * @param iQty Zero-based index of the qty. + * @param iAmount Zero-based index of the qty. * @param dwArg The argument. * * @return 0 = all consumed, # = number left to be consumed. */ - int ContentConsumeTest( const CResourceID& rid, int amount, dword dwArg = 0) const; + int ContentConsumeTest(const CResourceID& rid, int iAmount, dword dwArg = 0) const; /** * @fn int CContainer::ResourceConsume( const CResourceQtyArray * pResources, int iReplicationQty, bool fTest = false, dword dwArg = 0 ); @@ -240,9 +240,9 @@ class CContainer : public CSObjCont // This class contains a list of items but m * @fn virtual void CContainer::ContentAdd( CItem * pItem ) = 0; * @brief Content add. * @param [in,out] pItem If non-null, the item. - * @param [in,out] bForceNoStack Do not stack on other identical items, even if it's a stackable type. + * @param [in,out] fForceNoStack Do not stack on other identical items, even if it's a stackable type. */ - virtual void ContentAdd( CItem * pItem, bool bForceNoStack = false ) = 0; + virtual void ContentAdd( CItem * pItem, bool fForceNoStack = false ) = 0; }; diff --git a/src/game/CEntity.cpp b/src/game/CEntity.cpp index 923bbf137..6db19a075 100644 --- a/src/game/CEntity.cpp +++ b/src/game/CEntity.cpp @@ -1,5 +1,5 @@ #include "../sphere/threads.h" -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../common/CLog.h" #include "../common/CScript.h" #include "CEntity.h" diff --git a/src/game/CEntity.h b/src/game/CEntity.h index 2e83551c6..560db6c9f 100644 --- a/src/game/CEntity.h +++ b/src/game/CEntity.h @@ -23,7 +23,7 @@ class CEntity /** * @brief Calls Delete on contained CComponents. * - * @param fForce. + * @param fForce */ void Delete(bool fForce = false); @@ -80,7 +80,6 @@ class CEntity * Will save important data on worldsave files. * * @param s the save file. - * @param pRef a pointer to the object found. */ void r_Write(CScript & s); /** @@ -110,6 +109,7 @@ class CEntity * Executes a command (eg: dupe, bounce...) * * @param s the container with the keys and values to execute. + * @param pSrc Source of the command. * @return true if there was a key which could be executed. */ bool r_Verb(CScript & s, CTextConsole * pSrc); ///< Execute command from script. diff --git a/src/game/CEntityProps.h b/src/game/CEntityProps.h index 1735aac7b..82d56d721 100644 --- a/src/game/CEntityProps.h +++ b/src/game/CEntityProps.h @@ -69,7 +69,7 @@ class CEntityProps /** * @brief Unsubscribes a CComponentProps. * - * @param pComponent the CComponentProps to unsuscribe. + * @param pCCProp the CComponentProps to unsuscribe. */ void UnsubscribeComponentProps(CComponentProps *pCCProp); @@ -99,7 +99,6 @@ class CEntityProps * Will save important data on worldsave files. * * @param s the save file. - * @param pRef a pointer to the object found. */ void r_Write(CScript & s); @@ -177,7 +176,7 @@ _ComponentType* CEntityProps::TrySubscribeAllowedComponentProps(const _Subscribe { if (!_ComponentType::CanSubscribe(pSubscriber)) return nullptr; - + return TrySubscribeComponentProps<_ComponentType, _ArgsType...>(std::forward<_ArgsType>(args)...); } diff --git a/src/game/CObjBase.cpp b/src/game/CObjBase.cpp index 7b4eba652..a97cdd9f6 100644 --- a/src/game/CObjBase.cpp +++ b/src/game/CObjBase.cpp @@ -1,9 +1,10 @@ #include "../common/sphere_library/CSRand.h" #include "../common/resource/CResourceLock.h" -#include "../common/CException.h" -#include "../common/CExpression.h" -#include "../common/sphereversion.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../common/CLog.h" +#include "../common/sphereversion.h" #include "../network/CClientIterator.h" #include "../network/send.h" #include "../sphere/ProfileTask.h" @@ -120,7 +121,7 @@ CObjBase::CObjBase( bool fItem ) : m_PropertyHash = 0; m_PropertyRevision = 0; - if ( g_Serv.IsLoading()) + if ( g_Serv.IsLoadingGeneric()) { // Don't do this yet if we are loading. UID will be set later. // Just say if this is an item or not. @@ -206,9 +207,7 @@ void CObjBase::DeletePrepare() CObjBase::_GoSleep(); // virtual, but superclass methods are called in their ::DeletePrepare methods - const SERVMODE_TYPE servMode = g_Serv.GetServerMode(); - const bool fDestroyingWorld = (servMode == SERVMODE_Exiting || servMode == SERVMODE_Loading); - if (!fDestroyingWorld) + if (!g_Serv.IsDestroyingWorld()) { RemoveFromView(); } @@ -248,9 +247,7 @@ bool CObjBase::Delete(bool fForce) EXC_TRY("Cleanup in Delete method"); bool fScheduleDeletion = true; - const SERVMODE_TYPE servMode = g_Serv.GetServerMode(); - const bool fDestroyingWorld = (servMode == SERVMODE_Exiting || servMode == SERVMODE_Loading); - if (fDestroyingWorld) + if (g_Serv.IsDestroyingWorld()) { // Why resort to _uiInternalStateFlags and not simply check if GetParent() is a CSectorObjCont* ? // Because at this point CSObjContRec::RemoveSelf might have been called (it depends on how CObjBase::Delete was called, @@ -327,7 +324,7 @@ void CObjBase::SetHueQuick(HUE_TYPE wHue) void CObjBase::SetHue( HUE_TYPE wHue, bool fAvoidTrigger, CTextConsole *pSrc, CObjBase *pSourceObj, llong iSound) { ADDTOCALLSTACK("CObjBase::SetHue"); - if (g_Serv.IsLoading()) //We do not want tons of @Dye being called during world load, just set the hue then continue... + if (g_Serv.IsLoadingGeneric()) //We do not want tons of @Dye being called during world load, just set the hue then continue... { m_wHue = wHue; return; @@ -340,18 +337,19 @@ void CObjBase::SetHue( HUE_TYPE wHue, bool fAvoidTrigger, CTextConsole *pSrc, CO lpctstr ptcTrig = (IsChar() ? CChar::sm_szTrigName[CTRIG_DYE] : CItem::sm_szTrigName[ITRIG_DYE]); if (IsTrigUsed(ptcTrig)) { - CScriptTriggerArgs args(wHue, iSound, pSourceObj); - TRIGRET_TYPE iRet = OnTrigger(ptcTrig, pSrc, &args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(wHue, iSound, 0, pSourceObj); + TRIGRET_TYPE iRet = OnTrigger(ptcTrig, pScriptArgs, pSrc); if (iRet == TRIGRET_RET_TRUE) return; - if (args.m_iN2 > 0) // No sound? No checks for who can hear, packets... + if (pScriptArgs->m_iN2 > 0) // No sound? No checks for who can hear, packets... { - Sound((SOUND_TYPE)(args.m_iN2)); + Sound((SOUND_TYPE)(pScriptArgs->m_iN2)); } - m_wHue = (HUE_TYPE)(args.m_iN1); + m_wHue = (HUE_TYPE)(pScriptArgs->m_iN1); return; } } @@ -378,7 +376,7 @@ int CObjBase::IsWeird() const { return( iResultCode ); } - if ( ! g_Serv.IsLoading()) + if ( ! g_Serv.IsLoadingGeneric()) { if ( GetUID().ObjFind() != this ) // make sure it's linked both ways correctly. { @@ -804,7 +802,9 @@ bool CObjBase::MoveNear(CPointMap pt, ushort iSteps ) return MoveTo(pt); } -void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, bool fUnicode ) const +void CObjBase::UpdateObjMessage( + lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, + HUE_TYPE wHue, TALKMODE_TYPE iMode, FONT_TYPE iFont, bool fUnicode ) const { ADDTOCALLSTACK("CObjBase::UpdateObjMessage"); // Show everyone a msg coming from this object. @@ -818,9 +818,9 @@ void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * continue; if (( pClient->GetChar() == this ) && pTextYou != nullptr ) - pClient->addBarkParse(pTextYou, this, wHue, mode, font, fUnicode ); + pClient->addBarkParse(pTextYou, this, wHue, iMode, iFont, fUnicode ); else if (( pClient->GetChar() != this ) && pTextThem != nullptr ) - pClient->addBarkParse(pTextThem, this, wHue, mode, font, fUnicode ); + pClient->addBarkParse(pTextThem, this, wHue, iMode, iFont, fUnicode ); //pClient->addBarkParse(( pClient->GetChar() == this )? pTextYou : pTextThem, this, wHue, mode, font, bUnicode ); } @@ -843,7 +843,7 @@ void CObjBase::UpdateCanSee(PacketSend *packet, CClient *exclude) const delete packet; } -TRIGRET_TYPE CObjBase::OnHearTrigger( CResourceLock & s, lpctstr pszCmd, CChar * pSrc, TALKMODE_TYPE & mode, HUE_TYPE wHue) +TRIGRET_TYPE CObjBase::OnHearTrigger( CResourceLock & s, lpctstr pszCmd, CChar * pSrc, TALKMODE_TYPE & iModeRef, HUE_TYPE wHue) { ADDTOCALLSTACK("CObjBase::OnHearTrigger"); // Check all the keys in this script section. @@ -851,15 +851,15 @@ TRIGRET_TYPE CObjBase::OnHearTrigger( CResourceLock & s, lpctstr pszCmd, CChar * // RETURN: // TRIGRET_ENDIF = no match. // TRIGRET_DEFAULT = found match but it had no RETURN - std::unique_ptr Args; bool fMatch = false; + CScriptTriggerArgsPtr pScriptArgs; while ( s.ReadKeyParse()) { if ( s.IsKeyHead("ON",2)) { // Look for some key word. - tchar* ptcOn = s.GetArgStr(); + tchar* ptcOn = s.GetArgStr(); //_strupr(ptcOn); // Str_Match is already case insensitive if ( Str_Match( ptcOn, pszCmd ) == MATCH_VALID ) fMatch = true; @@ -869,22 +869,20 @@ TRIGRET_TYPE CObjBase::OnHearTrigger( CResourceLock & s, lpctstr pszCmd, CChar * if ( ! fMatch ) continue; // look for the next "ON" section. - if (!Args) - { - // Allocate when needed - Args = std::make_unique(pszCmd); - Args->m_iN1 = mode; - Args->m_iN2 = wHue; - } - TRIGRET_TYPE iRet = CObjBase::OnTriggerRunVal( s, TRIGRUN_SECTION_EXEC, pSrc, Args.get() ); + pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pszCmd); + pScriptArgs->m_iN1 = iModeRef; + pScriptArgs->m_iN2 = wHue; + TRIGRET_TYPE iRet = CObjBase::OnTriggerRunVal( s, TRIGRUN_SECTION_EXEC, pScriptArgs, pSrc ); + if ( iRet != TRIGRET_RET_FALSE ) return iRet; fMatch = false; } - if (Args) - mode = TALKMODE_TYPE(Args->m_iN1); + if (pScriptArgs) + iModeRef = TALKMODE_TYPE(pScriptArgs->m_iN1); return TRIGRET_ENDIF; // continue looking. } @@ -985,8 +983,9 @@ bool CObjBase::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc, SKIP_SEPARATORS(pszArgs); } - CScriptTriggerArgs Args( pszArgs != nullptr ? pszArgs : "" ); - if (r_Call(uiFunctionIndex, pSrc, &Args, &sVal)) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pszArgs != nullptr ? pszArgs : ""); + if (r_Call(uiFunctionIndex, pScriptArgs, pSrc, &sVal)) { return true; } @@ -1923,7 +1922,7 @@ bool CObjBase::r_LoadVal( CScript & s ) } const HUE_TYPE hue = (HUE_TYPE)s.GetArgVal(); SetHue(hue, false, &g_Serv); //@Dye is called from @Create/.xcolor/script command here // since we can not receive pSrc on this r_LoadVal function ARGO/SRC will be null - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) Update(); } break; @@ -1963,7 +1962,9 @@ bool CObjBase::r_LoadVal( CScript & s ) SetName( s.GetArgStr()); fResendTooltip = true; break; - case OC_P: // Must set the point via the CItem or CChar methods. + case OC_P: + // Need to use it in r_LoadVal only for server load stage. + // Must set the point/position via the CItem or CChar methods. return false; case OC_SPEED: { @@ -1977,7 +1978,7 @@ bool CObjBase::r_LoadVal( CScript & s ) case OC_TIMER: { int64 iTimeout = s.GetArg64Val(); - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) { const int iPrevBuild = g_World.m_iPrevBuild; /* @@ -2009,7 +2010,7 @@ bool CObjBase::r_LoadVal( CScript & s ) SetTimeStampS(s.GetArgLLVal()); break; case OC_SPAWNITEM: - if ( !g_Serv.IsLoading() ) // SPAWNITEM is read-only + if ( !g_Serv.IsLoadingGeneric() ) // SPAWNITEM is read-only return false; _uidSpawn.SetObjUID(s.GetArgDWVal()); break; @@ -2104,8 +2105,9 @@ bool CObjBase::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command fro { // RES_FUNCTION call CSString sVal; - CScriptTriggerArgs Args( s.GetArgRaw() ); - if ( r_Call( uiFunctionIndex, pSrc, &Args, &sVal ) ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init( s.GetArgRaw() ); + if ( r_Call( uiFunctionIndex, pScriptArgs, pSrc, &sVal ) ) return true; } @@ -2962,6 +2964,7 @@ bool CObjBase::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command fro EXC_SET_BLOCK("DCLICK"); if (!pCharSrc) return false; + if (s.HasArgs()) { if (!IsChar()) @@ -2986,12 +2989,11 @@ bool CObjBase::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command fro if (!IsChar()) return false; - CObjBase* pObj = CUID::ObjFindFromUID(s.GetArgDWVal()); - if (!pObj) + CChar* pChar = CUID::CharFindFromUID(s.GetArgDWVal()); + if (!pChar) return false; - CChar *pChar = static_cast (this); - return pChar->Use_Obj( pObj, false, true ); + return pChar->Use_Obj( pChar, false, true ); } else return pCharSrc->Use_Obj( this, false, true ); @@ -3157,7 +3159,7 @@ dword CObjBase::UpdatePropertyRevision(dword hash) void CObjBase::UpdatePropertyFlag() { ADDTOCALLSTACK("CObjBase::UpdatePropertyFlag"); - if (!(g_Cfg.m_iFeatureAOS & FEATURE_AOS_UPDATE_B) || g_Serv.IsLoading()) + if (!(g_Cfg.m_iFeatureAOS & FEATURE_AOS_UPDATE_B) || g_Serv.IsLoadingGeneric()) return; m_fStatusUpdate |= SU_UPDATE_TOOLTIP; @@ -3271,36 +3273,53 @@ void CObjBase::_GoSleep() */ } -bool CObjBase::_CanTick() const +bool CObjBase::_TickableState() const { //ADDTOCALLSTACK_DEBUG("CObjBase::_CanTick"); // Called very frequently. // This doesn't check the sector sleeping status, it's only about this object. - EXC_TRY("Can tick?"); + //EXC_TRY("Able to tick?"); // Directly call the method specifying the belonging class, to avoid the overhead of vtable lookup under the hood. - bool fCanTick = !CTimedObject::_IsSleeping(); - if (!fCanTick) + return !CTimedObject::_IsSleeping(); + + //EXC_CATCH; + return false; +} + +std::optional CObjBase::_TickableStateOverride() const +{ + if (Can(CAN_O_NOSLEEP)) { - // Try to call the Can method the less often possible. - // - // This should happen only if the item was manually put to sleep. - // CAN_O_NOSLEEP items should not be put to sleep by the source. - fCanTick = Can(CAN_O_NOSLEEP); + // CAN_O_NOSLEEP items should not be put to sleep by the source. + // SECF_NoSleep is a property of the sector, not of the item, so it's managed in the sector code. + return true; // Override: i should never sleep. } + // No override. Do the default thing. + return std::nullopt; +} + +bool CObjBase::_CanTick(bool fParentGoingToSleep) const +{ + EXC_TRY("Can tick?"); - return fCanTick; + const bool fTickable = _TickableState(); + const std::optional fOverriding = _TickableStateOverride(); + if (fParentGoingToSleep && (!fTickable || !fOverriding.value_or(false))) + return false; - EXC_CATCH; + return fTickable; + EXC_CATCH; return false; } + void CObjBase::ResendTooltip(bool fSendFull, bool fUseCache) { ADDTOCALLSTACK("CObjBase::ResendTooltip"); // Send tooltip packet to all nearby clients - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) return; else if ( IsAosFlagEnabled(FEATURE_AOS_UPDATE_B) == false ) return; // tooltips are disabled. @@ -3479,6 +3498,7 @@ void CObjBase::ModPropNum( COMPPROPS_TYPE iCompPropsType, CComponentProps::Prope { const CBaseBaseDef* pBase = Base_GetDef(); const CComponentProps* pBaseCompProps = pBase->GetComponentProps(iCompPropsType); + ASSERT(pBaseCompProps); pBaseCompProps->GetPropertyNumPtr(iPropIndex, &iVal); } if (!iVal && !iMod) @@ -3625,7 +3645,7 @@ void CObjBase::DupeCopy( const CObjBase * pObj ) CEntityProps::Copy(pObj); } -TRIGRET_TYPE CObjBase::Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CChar * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CObjBase::Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CScriptTriggerArgsPtr const& pScriptArgs, CChar * pSrc ) { ADDTOCALLSTACK("CObjBase::Spell_OnTrigger"); CSpellDef * pSpellDef = g_Cfg.GetSpellDef( spell ); @@ -3638,7 +3658,7 @@ TRIGRET_TYPE CObjBase::Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CCh CResourceLock s; if ( pSpellDef->ResourceLock( s )) { - return CScriptObj::OnTriggerScript( s, CSpellDef::sm_szTrigName[stage], pSrc, pArgs ); + return CScriptObj::OnTriggerScript( s, CSpellDef::sm_szTrigName[stage], pScriptArgs, pSrc ); } } return TRIGRET_RET_DEFAULT; @@ -3658,7 +3678,7 @@ bool CObjBase::CallPersonalTrigger(tchar * pArgs, CTextConsole * pSrc, TRIGRET_T if ( iResultArgs > 0 ) { lpctstr callTrigger = ppCmdTrigger[0]; - CScriptTriggerArgs csTriggerArgs; + CScriptTriggerArgsPtr pTriggerArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr();; if ( iResultArgs == 3 ) { @@ -3670,25 +3690,25 @@ bool CObjBase::CallPersonalTrigger(tchar * pArgs, CTextConsole * pSrc, TRIGRET_T iResultArgs = Str_ParseCmds(ppCmdTrigger[2], Arg_piCmd, ARRAY_COUNT(Arg_piCmd), ","); if ( iResultArgs == 3 ) - csTriggerArgs.m_iN3 = Arg_piCmd[2]; + pTriggerArgs->m_iN3 = Arg_piCmd[2]; if ( iResultArgs >= 2 ) - csTriggerArgs.m_iN2 = Arg_piCmd[1]; + pTriggerArgs->m_iN2 = Arg_piCmd[1]; if ( iResultArgs >= 1 ) - csTriggerArgs.m_iN1 = Arg_piCmd[0]; + pTriggerArgs->m_iN1 = Arg_piCmd[0]; } else if ( iTriggerArgType == 2 ) // ARGS { - csTriggerArgs.m_s1 = ppCmdTrigger[2]; - csTriggerArgs.m_s1_buf_vec = ppCmdTrigger[2]; + pTriggerArgs->m_s1 = ppCmdTrigger[2]; + pTriggerArgs->m_s1_buf_vec = ppCmdTrigger[2]; } else if ( iTriggerArgType == 3 ) // ARGO { CUID guTriggerArg(Exp_GetVal(ppCmdTrigger[2])); CObjBase * pTriggerArgObj = guTriggerArg.ObjFind(); if ( pTriggerArgObj ) - csTriggerArgs.m_pO1 = pTriggerArgObj; + pTriggerArgs->m_pO1 = pTriggerArgObj; } else if ( iTriggerArgType == 4 ) // FULLTRIGGER { @@ -3698,27 +3718,27 @@ bool CObjBase::CallPersonalTrigger(tchar * pArgs, CTextConsole * pSrc, TRIGRET_T // ARGS if ( iResultArgs == 5 ) { - csTriggerArgs.m_s1 = Arg_ppCmd[4]; - csTriggerArgs.m_s1_buf_vec = Arg_ppCmd[4]; + pTriggerArgs->m_s1 = Arg_ppCmd[4]; + pTriggerArgs->m_s1_buf_vec = Arg_ppCmd[4]; } // ARGNs if ( iResultArgs >= 4 ) - csTriggerArgs.m_iN3 = Exp_GetVal(Arg_ppCmd[3]); + pTriggerArgs->m_iN3 = Exp_GetVal(Arg_ppCmd[3]); if ( iResultArgs >= 3 ) - csTriggerArgs.m_iN2 = Exp_GetVal(Arg_ppCmd[2]); + pTriggerArgs->m_iN2 = Exp_GetVal(Arg_ppCmd[2]); if ( iResultArgs >= 2 ) - csTriggerArgs.m_iN1 = Exp_GetVal(Arg_ppCmd[1]); + pTriggerArgs->m_iN1 = Exp_GetVal(Arg_ppCmd[1]); // ARGO if ( iResultArgs >= 1 ) { CObjBase * pTriggerArgObj = CUID::ObjFindFromUID(Exp_GetVal(Arg_ppCmd[0])); if ( pTriggerArgObj ) - csTriggerArgs.m_pO1 = pTriggerArgObj; + pTriggerArgs->m_pO1 = pTriggerArgObj; } } } - trResult = OnTrigger(callTrigger, pSrc, &csTriggerArgs); + trResult = OnTrigger(callTrigger, pTriggerArgs, pSrc); return true; } diff --git a/src/game/CObjBase.h b/src/game/CObjBase.h index 082a21daa..b11546656 100644 --- a/src/game/CObjBase.h +++ b/src/game/CObjBase.h @@ -125,7 +125,7 @@ public: virtual bool IsDeleted() const override; /** * @brief Deletes this CObjBase from game (doesn't delete the raw class instance). - * @param bForce Force deletion. + * @param fForce Force deletion. * @return Was deleted. */ virtual bool Delete(bool fForce = false); @@ -195,7 +195,7 @@ public: virtual bool IsDeleted() const override; /** * @brief sets the Spawn item. - * @param The CCSpawn. + * @param spawn The CCSpawn. */ void SetSpawn(CCSpawn *spawn); @@ -584,8 +584,8 @@ public: virtual bool IsDeleted() const override; * @param wHue The hue. * @param fAvoidTrigger true to avoid trigger. * @param [in,out] pSrc (Optional) If non-null, source for the. - * @param [in,out] SourceObj (Optional) If non-null, source object. - * @param sound The sound. + * @param [in,out] pSourceObj (Optional) If non-null, source object. + * @param iSound The sound. */ void SetHue( HUE_TYPE wHue, bool fAvoidTrigger = true, CTextConsole *pSrc = nullptr, CObjBase * pSourceObj = nullptr, llong iSound = 0 ); @@ -695,8 +695,8 @@ public: virtual bool IsDeleted() const override; * @brief Adds an Effect to a map point. * @param motion The motion. * @param id The identifier. - * @param pt The map point. - * @param pSource Source for the. + * @param ptSrc The map point. + * @param ptDest Source for the. * @param bspeedseconds The bspeedseconds. * @param bloop The bloop. * @param fexplode true to fexplode. @@ -822,7 +822,7 @@ public: virtual bool IsDeleted() const override; void UpdateCanSee( PacketSend * pPacket, CClient * pClientExclude = nullptr ) const; /** - * @fn void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font = FONT_NORMAL, bool bUnicode = false ) const; + * @fn void CObjBase::UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, HUE_TYPE wHue, TALKMODE_TYPE iMode, FONT_TYPE iFont = FONT_NORMAL, bool fUnicode = false ) const; * * @brief Updates the object message. * @@ -830,26 +830,29 @@ public: virtual bool IsDeleted() const override; * @param pTextYou The text you. * @param [in,out] pClientExclude If non-null, the client exclude. * @param wHue The hue. - * @param mode The mode. - * @param font The font. - * @param bUnicode true to unicode. + * @param iMode The iMode. + * @param iFont The iFont. + * @param fUnicode true to unicode. */ - void UpdateObjMessage( lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font = FONT_NORMAL, bool bUnicode = false ) const; + void UpdateObjMessage( + lpctstr pTextThem, lpctstr pTextYou, CClient * pClientExclude, + HUE_TYPE wHue, TALKMODE_TYPE iMode, + FONT_TYPE iFont = FONT_NORMAL, bool fUnicode = false ) const; /** - * @fn TRIGRET_TYPE CObjBase::OnHearTrigger(CResourceLock &s, lpctstr pCmd, CChar *pSrc, TALKMODE_TYPE &mode, HUE_TYPE wHue = HUE_DEFAULT); + * @fn TRIGRET_TYPE CObjBase::OnHearTrigger(CResourceLock &s, lpctstr pCmd, CChar *pSrc, TALKMODE_TYPE &iModeRef, HUE_TYPE wHue = HUE_DEFAULT); * * @brief Executes the hear trigger action. * * @param [in,out] s The CResourceLock to process. * @param pCmd The command. * @param [in,out] pSrc If non-null, source for the. - * @param [in,out] mode The mode. + * @param [in,out] iModeRef The iModeRef. * @param wHue The hue. * * @return A TRIGRET_TYPE. */ - TRIGRET_TYPE OnHearTrigger(CResourceLock &s, lpctstr pCmd, CChar *pSrc, TALKMODE_TYPE &mode, HUE_TYPE wHue = HUE_DEFAULT); + TRIGRET_TYPE OnHearTrigger(CResourceLock &s, lpctstr pCmd, CChar *pSrc, TALKMODE_TYPE &iModeRef, HUE_TYPE wHue = HUE_DEFAULT); /** * @fn bool CObjBase::IsContainer() const; @@ -885,15 +888,16 @@ public: virtual bool IsDeleted() const override; * @param [in,out] pCharSrc If non-null, the character source. * @param iSkillLevel Zero-based index of the skill level. * @param [in,out] pSourceItem If non-null, source item. - * @param bReflecting true to reflecting. + * @param fReflecting true to reflecting. + * @param iDuration Duration of the spell effect (default is instant). * * @return true if it succeeds, false if it fails. */ - virtual bool OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, CItem * pSourceItem, bool bReflecting = false, int64 iDuration = 0 ) + virtual bool OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, CItem * pSourceItem, bool fReflecting = false, int64 iDuration = 0 ) = 0; /** - * @fn TRIGRET_TYPE CObjBase::Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CChar * pSrc, CScriptTriggerArgs * pArgs ); + * @fn TRIGRET_TYPE CObjBase::Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CChar * pSrc, CScriptTriggerArgsPtr const& pScriptArgs ); * * @brief Spell's trigger (@Effect, @Start...). * @@ -904,7 +908,7 @@ public: virtual bool IsDeleted() const override; * * @return A TRIGRET_TYPE. */ - TRIGRET_TYPE Spell_OnTrigger( SPELL_TYPE spell, SPTRIG_TYPE stage, CChar * pSrc, CScriptTriggerArgs * pArgs ); + TRIGRET_TYPE Spell_OnTrigger(SPELL_TYPE spell, SPTRIG_TYPE stage, CScriptTriggerArgsPtr const& pScriptArgs, CChar * pSrc); protected: virtual void _GoAwake() override; @@ -918,8 +922,12 @@ public: virtual bool IsDeleted() const override; */ virtual void OnTickStatusUpdate(); - virtual bool _CanTick() const override; - //virtual bool CanTick(bool fParentGoingToSleep = false) const override; // Not needed: the right virtual is called by CTimedObj::_CanTick. + virtual bool _TickableState() const override; + //virtual bool TickableState() const override; // Not needed: the right virtual is called by CTimedObj::_CanTick. + + std::optional _TickableStateOverride() const; + + bool _CanTick(bool fParentGoingToSleep = false) const; public: diff --git a/src/game/CObjBaseTemplate.h b/src/game/CObjBaseTemplate.h index 65c3e0bd3..62f8d904a 100644 --- a/src/game/CObjBaseTemplate.h +++ b/src/game/CObjBaseTemplate.h @@ -35,28 +35,36 @@ class CObjBaseTemplate : public CSObjContRec CObjBaseTemplate& operator=(const CObjBaseTemplate& other) = delete; public: + [[nodiscard]] const CUID& GetUID() const noexcept { return m_UID; } - bool IsItem() const noexcept { + [[nodiscard]] + bool IsItem() const noexcept { return m_UID.IsItem(); } + [[nodiscard]] bool IsChar() const noexcept { return m_UID.IsChar(); } - bool IsItemInContainer() const noexcept { + [[nodiscard]] + bool IsItemInContainer() const noexcept { return m_UID.IsItemInContainer(); } - bool IsItemEquipped() const noexcept { + [[nodiscard]] + bool IsItemEquipped() const noexcept { return m_UID.IsItemEquipped(); } - bool IsDisconnected() const noexcept { + [[nodiscard]] + bool IsDisconnected() const noexcept { return m_UID.IsObjDisconnected(); } - bool IsTopLevel() const noexcept { + [[nodiscard]] + bool IsTopLevel() const noexcept { return m_UID.IsObjTopLevel(); } - bool IsValidUID() const noexcept { + [[nodiscard]] + bool IsValidUID() const noexcept { return m_UID.IsValidUID(); } @@ -83,16 +91,19 @@ class CObjBaseTemplate : public CSObjContRec // Location + [[nodiscard]] LAYER_TYPE GetEquipLayer() const noexcept { return (LAYER_TYPE)(m_pt.m_z); } void SetEquipLayer( LAYER_TYPE layer ); + [[nodiscard]] inline byte GetContainedLayer() const noexcept { // used for corpse or Restock count as well in Vendor container. return m_pt.m_z; } void SetContainedLayer( byte layer ) noexcept; + [[nodiscard]] inline const CPointMap & GetContainedPoint() const noexcept { return m_pt; } @@ -101,45 +112,60 @@ class CObjBaseTemplate : public CSObjContRec // - *Top* methods: are virtual and may do additional checks. void SetTopPoint(const CPointMap& pt); virtual void SetTopZ(char z); + [[nodiscard]] inline const CPointMap & GetTopPoint() const noexcept { return m_pt; } - char GetTopZ() const noexcept; - uchar GetTopMap() const noexcept; + [[nodiscard]] + char GetTopZ() const noexcept; + [[nodiscard]] + uchar GetTopMap() const noexcept; + [[nodiscard]] CSector* GetTopSector() const noexcept; // can return a nullptr! // - *Unk* methods: are not virtual and get/set raw values, without any check. void SetUnkPoint(const CPointMap& pt) noexcept { m_pt = pt; } + [[nodiscard]] inline const CPointMap & GetUnkPoint() const noexcept { // don't care where this return m_pt; } - inline char GetUnkZ() const noexcept { + [[nodiscard]] + inline char GetUnkZ() const noexcept { return m_pt.m_z; } // Distance and direction + [[nodiscard]] int GetTopDist( const CPointMap & pt ) const; + [[nodiscard]] int GetTopDist( const CObjBaseTemplate * pObj ) const; + [[nodiscard]] int GetTopDistSight( const CPointMap & pt ) const; + [[nodiscard]] int GetTopDistSight( const CObjBaseTemplate * pObj ) const; + [[nodiscard]] int GetDist( const CObjBaseTemplate * pObj ) const; + [[nodiscard]] int GetTopDist3D( const CObjBaseTemplate * pObj ) const; + [[nodiscard]] DIR_TYPE GetTopDir( const CObjBaseTemplate * pObj, DIR_TYPE DirDefault = DIR_QTY ) const; // Can return DIR_QTY if are on the same tile! + [[nodiscard]] DIR_TYPE GetDir( const CObjBaseTemplate * pObj, DIR_TYPE DirDefault = DIR_QTY ) const; // Can return DIR_QTY if are on the same tile! + [[nodiscard]] virtual int GetVisualRange() const; // Names diff --git a/src/game/CPathFinder.cpp b/src/game/CPathFinder.cpp index 012992e9a..733b4717a 100644 --- a/src/game/CPathFinder.cpp +++ b/src/game/CPathFinder.cpp @@ -1,5 +1,5 @@ -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../sphere/threads.h" #include "chars/CChar.h" #include "CPathFinder.h" @@ -47,7 +47,7 @@ CPathFinderPoint::CPathFinderPoint() : } /* -CPathFinderPoint::CPathFinderPoint(const CPointMap& pt) : +CPathFinderPoint::CPathFinderPoint(const CPointMap& pt) : _Parent(nullptr), _Walkable(false), _FValue(0), _GValue(0), _HValue(0) { //ADDTOCALLSTACK("CPathFinderPoint::CPathFinderPoint"); @@ -128,7 +128,7 @@ bool CPathFinder::FindPath() //A* algorithm // Take the point with the lowest FValue CPathFinderPoint *Current = *m_Opened.begin(); - + if ( Current == End ) { // Arrived to destination: reconstruct path and save it @@ -163,7 +163,7 @@ bool CPathFinder::FindPath() //A* algorithm */ // Binary (pre-sorted) search in our sorted_vector if (m_Closed.find(Cell) != sl::scont_bad_index()) - continue; + continue; ASSERT(Cell->_Walkable); // Linear search diff --git a/src/game/CPathFinder.h b/src/game/CPathFinder.h index 7ac8d3ae1..65b76f631 100644 --- a/src/game/CPathFinder.h +++ b/src/game/CPathFinder.h @@ -95,5 +95,4 @@ class CPathFinder }; - #endif // _INC_PATHFINDER_H diff --git a/src/game/CRegion.cpp b/src/game/CRegion.cpp index 42a44f355..2c36de8c6 100644 --- a/src/game/CRegion.cpp +++ b/src/game/CRegion.cpp @@ -1,7 +1,8 @@ // Common for client and server. #include "../common/resource/sections/CRandGroupDef.h" #include "../common/resource/CResourceLock.h" -#include "../common/CExpression.h" +//#include "../common/CExpression.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../network/CClientIterator.h" #include "chars/CChar.h" #include "clients/CClient.h" @@ -17,9 +18,9 @@ CRegion::CRegion( CResourceID rid, lpctstr pszName ) : CResourceDef( rid ) { - ADDTOCALLSTACK("CRegion::CRegion()"); - m_dwFlags = 0; - m_dwModifiedFlags = 0; + ADDTOCALLSTACK("CRegion::CRegion"); + m_dwFlags = 0; + m_dwModifiedFlags = 0; m_iLinkedSectors = 0; if ( pszName ) SetName( pszName ); @@ -49,13 +50,15 @@ void CRegion::UnRealizeRegion() for ( int i = 0; ; ++i ) { - CSector * pSector = GetSector(i); + CSector * pSector = GetSectorAtIndex(i); if ( pSector == nullptr ) break; + // Does the rect overlap ? - if ( ! IsOverlapped( pSector->GetRect())) + if ( ! IsOverlapped( pSector->GetRectWorldUnits())) continue; - if ( pSector->UnLinkRegion( this )) + + if ( pSector->UnLinkRegion( this )) --m_iLinkedSectors; } @@ -67,19 +70,22 @@ bool CRegion::RealizeRegion() // Link the region to the world. RETURN: false = not valid. if ( IsRegionEmpty() ) return false; + + ASSERT(g_MapList.IsMapSupported(m_pt.m_map)); if ( !m_pt.IsValidPoint() ) m_pt = GetRegionCorner( DIR_QTY ); // center // Attach to all sectors that i overlap. ASSERT( m_iLinkedSectors == 0 ); - const CSectorList* pSectors = CSectorList::Get(); - for ( int i = 0, iMax = pSectors->GetSectorQty(m_pt.m_map); i < iMax; ++i ) + const CSectorList& pSectors = CSectorList::Get(); + for ( int i = 0, iMax = pSectors.GetMapSectorDataUnchecked(m_pt.m_map).iSectorQty; i < iMax; ++i ) { - CSector *pSector = pSectors->GetSector(m_pt.m_map, i); + // TODO: maybe provide a hint on where to start checking, instead of checking every sector in the map... + CSector *pSector = pSectors.GetSectorByIndexUnchecked(m_pt.m_map, i); - if ( pSector && IsOverlapped(pSector->GetRect()) ) + if ( pSector && IsOverlapped(pSector->GetRectWorldUnits()) ) { - // Yes, this sector overlapped, so add it to the sector list + // Yes, this sector is overlapped, so add it to the sector list if ( !pSector->LinkRegion(this) ) { g_Log.EventError("Linking sector #%d (map %d) for region %s failed (fatal for this region).\n", i, int(m_pt.m_map), GetName()); @@ -106,6 +112,12 @@ bool CRegion::AddRegionRect( const CRectMap & rect ) return true; } +bool CRegion::SetRegionRect( const CRectMap & rect ) +{ + EmptyRegion(); + return AddRegionRect( rect ); +} + void CRegion::SetName( lpctstr pszName ) { ADDTOCALLSTACK("CRegion::SetName"); @@ -314,7 +326,7 @@ bool CRegion::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc, int iClients = 0; for (int i = 0; ; ++i) { - CSector *pSector = GetSector(i); + CSector *pSector = GetSectorAtIndex(i); if (pSector == nullptr) break; iClients += pSector->m_Chars_Active.GetClientsNumber(); @@ -689,6 +701,14 @@ void CRegion::r_Write( CScript &s ) r_WriteBase( s ); } +void CRegion::TogRegionFlags( dword dwFlags, bool fSet ) noexcept +{ + if ( fSet ) + m_dwFlags |= dwFlags; + else + m_dwFlags &= ~dwFlags; + SetModified( REGMOD_FLAGS ); +} bool CRegion::IsGuarded() const { @@ -833,12 +853,13 @@ bool CRegion::SendSectorsVerb( lpctstr pszVerb, lpctstr pszArgs, CTextConsole * bool fRet = false; for ( int i=0; ; ++i ) { - CSector * pSector = GetSector(i); + // TODO: optimization, use GetSectorAtIndexWithHints instead. + CSector * pSector = GetSectorAtIndex(i); if ( pSector == nullptr ) break; // Does the rect overlap ? - if ( IsOverlapped( pSector->GetRect() ) ) + if ( IsOverlapped( pSector->GetRectWorldUnits() ) ) { CScript script( pszVerb, pszArgs ); fRet |= pSector->r_Verb( script, pSrc ); @@ -874,7 +895,7 @@ TRIGRET_TYPE CRegion::OnRegionTrigger( CTextConsole * pSrc, RTRIG_TYPE iAction ) CResourceLock s; if ( pLink->ResourceLock(s) ) { - iRet = CScriptObj::OnTriggerScript(s, sm_szTrigName[iAction], pSrc); + iRet = CScriptObj::OnTriggerScript(s, sm_szTrigName[iAction], CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc); if ( iRet == TRIGRET_RET_TRUE ) return iRet; } @@ -891,7 +912,7 @@ TRIGRET_TYPE CRegion::OnRegionTrigger( CTextConsole * pSrc, RTRIG_TYPE iAction ) if ( !pLink->ResourceLock(s) ) continue; - iRet = CScriptObj::OnTriggerScript(s, sm_szTrigName[iAction], pSrc); + iRet = CScriptObj::OnTriggerScript(s, sm_szTrigName[iAction], CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc); if ( iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT ) return iRet; } @@ -1000,7 +1021,6 @@ bool CRegionWorld::r_LoadVal( CScript &s ) } - void CRegionWorld::r_WriteModified( CScript &s ) { ADDTOCALLSTACK("CRegionWorld::r_WriteModified"); diff --git a/src/game/CRegion.h b/src/game/CRegion.h index fd8db13fc..e73c06ff2 100644 --- a/src/game/CRegion.h +++ b/src/game/CRegion.h @@ -62,17 +62,17 @@ class CRegion : public CResourceDef, public CRegionBase public: static const char *m_sClassName; CPointMap m_pt; // safe point in the region. (for teleporting to) - int m_iLinkedSectors; // just for statistics tracking. How many sectors are linked ? + int m_iLinkedSectors; // just for statistics tracking. How many sectors are linked ? dword m_dwModifiedFlags; + CItemMulti* _pMultiLink; // Does this region belong to a multi? static lpctstr const sm_szLoadKeys[]; static lpctstr const sm_szTrigName[RTRIG_QTY+1]; static lpctstr const sm_szVerbKeys[]; - CResourceRefArray m_Events; // trigger [REGION x] when entered or exited RES_REGIONTYPE - CVarDefMap m_TagDefs; // attach extra tags here. + CResourceRefArray m_Events; // trigger [REGION x] when entered or exited RES_REGIONTYPE + CVarDefMap m_TagDefs; // attach extra tags here. CVarDefMap m_BaseDefs; // New Variable storage system - CItemMulti* _pMultiLink; // Does this region belong to a multi? TRIGRET_TYPE OnRegionTrigger( CTextConsole * pChar, RTRIG_TYPE trig ); @@ -106,8 +106,6 @@ class CRegion : public CResourceDef, public CRegionBase bool SendSectorsVerb( lpctstr pszVerb, lpctstr pszArgs, CTextConsole * pSrc ); // distribute to the CSectors public: - virtual bool RealizeRegion(); - void UnRealizeRegion(); #define REGMOD_FLAGS 0x0001 #define REGMOD_EVENTS 0x0002 #define REGMOD_TAGS 0x0004 @@ -134,39 +132,33 @@ class CRegion : public CResourceDef, public CRegionBase virtual bool r_Verb( CScript & s, CTextConsole * pSrc ) override; // Execute command from script virtual void r_Write( CScript & s ); + virtual bool IsValid() const noexcept + { + return m_sName.IsValid(); + } + + virtual bool RealizeRegion(); + void UnRealizeRegion(); + virtual bool AddRegionRect( const CRectMap & rect ) override; - bool SetRegionRect( const CRectMap & rect ) - { - EmptyRegion(); - return AddRegionRect( rect ); - } - inline dword GetRegionFlags() const noexcept + bool SetRegionRect( const CRectMap & rect ); + + inline dword GetRegionFlags() const noexcept { return m_dwFlags; } - bool IsFlag( dword dwFlags ) const noexcept + inline bool IsFlag( dword dwFlags ) const noexcept { - return (( m_dwFlags & dwFlags ) ? true : false ); + return (bool( m_dwFlags & dwFlags )); } - bool IsGuarded() const; - void SetRegionFlags( dword dwFlags ) noexcept + inline void SetRegionFlags( dword dwFlags ) noexcept { m_dwFlags |= dwFlags; } - void TogRegionFlags( dword dwFlags, bool fSet ) noexcept - { - if ( fSet ) - m_dwFlags |= dwFlags; - else - m_dwFlags &= ~dwFlags; - SetModified( REGMOD_FLAGS ); - } + void TogRegionFlags( dword dwFlags, bool fSet ) noexcept; - bool CheckAntiMagic( SPELL_TYPE spell ) const; - virtual bool IsValid() const noexcept - { - return m_sName.IsValid(); - } + bool IsGuarded() const; + bool CheckAntiMagic( SPELL_TYPE spell ) const; bool MakeRegionDefname(); diff --git a/src/game/CRegionBase.cpp b/src/game/CRegionBase.cpp index d04911925..6330fef93 100644 --- a/src/game/CRegionBase.cpp +++ b/src/game/CRegionBase.cpp @@ -10,6 +10,12 @@ CRegionBase::CRegionBase() m_rectUnion.SetRectEmpty(); } +void CRegionBase::EmptyRegion() +{ + m_rectUnion.SetRectEmpty(); + m_Rects.clear(); +} + size_t CRegionBase::GetRegionRectCount() const { ADDTOCALLSTACK("CRegionBase::GetRegionRectCount"); @@ -110,17 +116,48 @@ bool CRegionBase::AddRegionRect( const CRectMap & rect ) bool CRegionBase::IsOverlapped( const CRectMap & rect ) const noexcept { // ADDTOCALLSTACK("CRegionBase::IsOverlapped"); // It's called very frequently on server startup, so avoid this call - // Does the region overlap this rectangle. - if ( !m_rectUnion.IsOverlapped(rect) ) - return false; + // Does the region overlap this rectangle. - const size_t iQty = m_Rects.size(); - if ( iQty <= 0 ) + /* + const int left = rect.m_left; + const int right = rect.m_right; + const int top = rect.m_top; + const int bottom = rect.m_bottom; + if ( + (right <= m_rectUnion.m_left) && // Left edge of rect is to the left of this rect's right edge + (m_rectUnion.m_right <= left) && // Right edge of rect is to the right of this rect's left edge + (bottom <= m_rectUnion.m_top) && // Top edge of rect is above this rect's bottom edge + (m_rectUnion.m_bottom <= top) // Bottom edge of rect is below this rect's top edge + ) + return false; + */ + + if ( !m_rectUnion.IsOverlapped(rect) ) + return false; + + const uint iQty = (uint)m_Rects.size(); + if ( iQty <= 0 ) // TODOC: Usually happens for a multi-bound CRegion? return true; - for ( size_t i = 0; i < iQty; ++i ) + + // Avoid the cost of the IsOverlapped function call and just slap the content here. + // Also, reorder conditions to prioritize early exits for non-overlapping rectangles. + const int left = rect.m_left; + const int right = rect.m_right; + const int top = rect.m_top; + const int bottom = rect.m_bottom; + + for ( uint i = 0; i < iQty; ++i ) { - if ( rect.IsOverlapped(m_Rects[i])) - return true; + CRect const& r = m_Rects[i]; + if ( + (right > r.m_left) && // Left edge of rect is to the left of this rect's right edge + (r.m_right > left) && // Right edge of rect is to the right of this rect's left edge + (bottom > r.m_top) && // Top edge of rect is above this rect's bottom edge + (r.m_bottom > top) // Bottom edge of rect is below this rect's top edge + ) + return true; + //if ( rect.IsOverlapped(m_Rects[i])) + // return true; } return false; } diff --git a/src/game/CRegionBase.h b/src/game/CRegionBase.h index 28202f871..0ea4ed34b 100644 --- a/src/game/CRegionBase.h +++ b/src/game/CRegionBase.h @@ -23,11 +23,7 @@ class CRegionBase { return m_rectUnion.IsRectEmpty(); } - void EmptyRegion() - { - m_rectUnion.SetRectEmpty(); - m_Rects.clear(); - } + void EmptyRegion(); size_t GetRegionRectCount() const; CRectMap & GetRegionRect(size_t i); const CRectMap & GetRegionRect(size_t i) const; @@ -43,18 +39,21 @@ class CRegionBase bool IsOverlapped( const CRegionBase * pRegionTest ) const; bool IsEqualRegion( const CRegionBase * pRegionTest ) const; - inline CSector * GetSector( int i ) const // get all the sectors that make up this rect. - { - return m_rectUnion.GetSector(i); + [[nodiscard]] + inline CSector * GetSectorAtIndex( int i ) const noexcept { + return m_rectUnion.GetSectorAtIndex(i); } + [[nodiscard]] + inline CSector * GetSectorAtIndexWithHints( int i, CRect::SectIndexingHints hints ) const noexcept { + return m_rectUnion.GetSectorAtIndexWithHints(i, std::move(hints)); + } public: CRegionBase(); virtual ~CRegionBase() = default; -private: - CRegionBase(const CRegionBase& copy); - CRegionBase& operator=(const CRegionBase& other); + CRegionBase(const CRegionBase& copy) = delete; + CRegionBase& operator=(const CRegionBase& other) = delete; }; diff --git a/src/game/CResourceCalc.cpp b/src/game/CResourceCalc.cpp index ad4a78e0c..d1e7851f2 100644 --- a/src/game/CResourceCalc.cpp +++ b/src/game/CResourceCalc.cpp @@ -3,7 +3,7 @@ #include "../common/sphere_library/CSRand.h" #include "chars/CChar.h" #include "chars/CCharNPC.h" -#include "../common/CExpression.h" +//#include "../common/CExpression.h" // included in the precompiled header #include "components/CCPropsChar.h" #include "items/CItem.h" #include "CServerConfig.h" @@ -16,8 +16,8 @@ int CServerConfig::Calc_MaxCarryWeight( const CChar * pChar ) const ADDTOCALLSTACK("CServerConfig::Calc_MaxCarryWeight"); // How much weight can i carry before i can carry no more. (and move at all) // Amount of weight that can be carried Max: - // based on str - // RETURN: + // based on str + // RETURN: // Weight in tenths of stones i should be able to carry. ASSERT(pChar); @@ -50,7 +50,7 @@ int CServerConfig::Calc_CombatAttackSpeed( const CChar * pChar, const CItem * pW if ( pWeapon ) // If we have a weapon, base speed should match weapon's value. iBaseSpeed = pWeapon->GetSpeed(); int iSwingSpeed = 100; - + switch ( g_Cfg.m_iCombatSpeedEra ) { case 0: @@ -362,7 +362,7 @@ int CServerConfig::Calc_KarmaKill( CChar * pKill, NOTO_TYPE NotoThem ) if ( iKarmaChange < 0 ) iKarmaChange = 0; } - + // Check if the victim is a PC, then higher gain/loss. if ( pKill->m_pPlayer ) { @@ -431,16 +431,16 @@ int CServerConfig::Calc_StealingItem( CChar * pCharThief, CItem * pItem, CChar * int iDexMark = pCharMark->Stat_GetAdjusted(STAT_DEX); int iSkillMark = pCharMark->Skill_GetAdjusted( SKILL_STEALING ); int iWeightItem = pItem->GetWeight(); - + // int iDifficulty = iDexMark/2 + (iSkillMark/5) + g_Rand.GetVal(iDexMark/2) + IMulDivLL( iWeightItem, 4, WEIGHT_UNITS ); // Melt mod: int iDifficulty = (iSkillMark/5) + g_Rand.GetVal(iDexMark/2) + IMulDiv( iWeightItem, 4, WEIGHT_UNITS ); - + if ( pItem->IsItemEquipped()) iDifficulty += iDexMark/2 + pCharMark->Stat_GetAdjusted(STAT_INT); // This is REALLY HARD to do. if ( pCharThief->IsStatFlag( STATF_WAR )) // all keyed up. iDifficulty += g_Rand.GetVal( iDexMark/2 ); - + // return( iDifficulty ); // Melt mod: return (iDifficulty / 2); @@ -546,17 +546,17 @@ ushort CServerConfig::Calc_SpellManaCost(CChar* pCharCaster, const CSpellDef* pS ushort iCost = (ushort)pSpell->m_wManaUse; if (iLowerManaCost != 0) //LowerManaCost can be negative, and thus increasing the mana cost! iCost = (ushort)(iCost - ((iCost * iLowerManaCost) / 100)); - + if ( fScroll ) return iCost / 2; //spells cast from scrolls consume half of the mana. - + return iCost; } size_t CServerConfig::Calc_SpellReagentsConsume(CChar* pCharCaster, const CSpellDef* pSpell, CObjBase* pObj, bool fTest) { ADDTOCALLSTACK("CServerConfig::Calc_SpellReagentsConsume"); - + ASSERT(pCharCaster); ASSERT(pSpell); @@ -588,7 +588,7 @@ ushort CServerConfig::Calc_SpellTithingCost(CChar* pCharCaster, const CSpellDef* //Check for tithing points. if (g_Cfg.m_fReagentsRequired && !pCharCaster->m_pNPC && (pObj == pCharCaster)) { - + const CCPropsChar* pCCPChar = pCharCaster->GetComponentProps(); const CCPropsChar* pBaseCCPChar = pCharCaster->Base_GetDef()->GetComponentProps(); const int iLowerReagentCost = (int)pCharCaster->GetPropNum(pCCPChar, PROPCH_LOWERREAGENTCOST, pBaseCCPChar); //Also used for reducing Tithing points. @@ -627,7 +627,7 @@ bool CServerConfig::Calc_CurePoisonChance(const CItem* pPoison, int iCureLevel, if (!iPoisonLevel) //Lesser Poison (iPoisonLevel 0) is always cured no matter the potion or spell/skill level value return true; - //Cure Chance taken from: + //Cure Chance taken from: if (iCureLevel < 410) //Lesser Cure Potion or our healing/veterinary/magery skill is less than 41.0 https://www.uoguide.com/Lesser_Cure_Potion { switch (iPoisonLevel) @@ -646,7 +646,7 @@ bool CServerConfig::Calc_CurePoisonChance(const CItem* pPoison, int iCureLevel, break; } } - else if (iCureLevel < 1010) //Cure Potion or our healing/veterinary/magery skill is between 41.0 and 100.9 https://www.uoguide.com/Cure_Potion + else if (iCureLevel < 1010) //Cure Potion or our healing/veterinary/magery skill is between 41.0 and 100.9 https://www.uoguide.com/Cure_Potion { switch (iPoisonLevel) { diff --git a/src/game/CSector.cpp b/src/game/CSector.cpp index a62ce8152..a96c26867 100644 --- a/src/game/CSector.cpp +++ b/src/game/CSector.cpp @@ -1,6 +1,6 @@ #include "../common/sphere_library/CSRand.h" -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CLog.h" #include "../sphere/ProfileTask.h" #include "../sphere/ProfileData.h" @@ -16,6 +16,10 @@ #include "triggers.h" #include "CSector.h" + +#define SECTOR_TICKING_PERIOD 30 * 1000 // Every 30 seconds. + + ////////////////////////////////////////////////////////////////// // -CSector @@ -186,7 +190,7 @@ void CSector::_GoSleep() for (CSObjContRec* pObjRec : m_Chars_Active) { CChar* pChar = static_cast(pObjRec); - const bool fCanTick = pChar->CanTick(); + const bool fCanTick = pChar->_CanTick(true); ASSERT(!pChar->IsDisconnected()); if (!fCanTick) pChar->GoSleep(); @@ -196,7 +200,7 @@ void CSector::_GoSleep() for (CSObjContRec* pObjRec : m_Chars_Disconnect) { CChar* pChar = static_cast(pObjRec); - const bool fCanTick = pChar->CanTick(); + const bool fCanTick = pChar->_CanTick(true); ASSERT(pChar->IsDisconnected()); if (!fCanTick) pChar->GoSleep(); @@ -206,7 +210,7 @@ void CSector::_GoSleep() for (CSObjContRec* pObjRec : m_Items) { CItem* pItem = static_cast(pObjRec); - const bool fCanTick = pItem->CanTick(); + const bool fCanTick = pItem->_CanTick(true); if (!fCanTick) pItem->GoSleep(); } @@ -216,7 +220,7 @@ void CSector::_GoSleep() void CSector::GoSleep() { ADDTOCALLSTACK("CSector::GoSleep"); - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); CSector::_GoSleep(); } @@ -263,6 +267,7 @@ void CSector::_GoAwake() * of NPCs being stop until you enter the sector, or all the spawns * generating NPCs at once. */ + // Using the 'static' keyword isn't thread safe. We assume that we are managing sectors only on a single thread... static CSector *pCentral = nullptr; // do this only for the awaken sector if (!pCentral) { @@ -287,7 +292,7 @@ void CSector::_GoAwake() void CSector::GoAwake() { ADDTOCALLSTACK("CSector::GoAwake"); - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); CSector::_GoAwake(); } @@ -428,7 +433,8 @@ void CSector::r_Write() ADDTOCALLSTACK_DEBUG("CSector::r_Write"); if ( m_fSaveParity == g_World.m_fSaveParity ) return; // already saved. - CPointMap pt = GetBasePoint(); + + CPointMap const& pt = m_BasePointSectUnits; m_fSaveParity = g_World.m_fSaveParity; bool fHeaderCreated = false; @@ -609,22 +615,23 @@ int CSector::GetLocalTime() const { ADDTOCALLSTACK("CSector::GetLocalTime"); // Get local time of the day (in minutes) - const CSectorList* pSectors = CSectorList::Get(); - const CPointMap& pt(GetBasePoint()); - int64 iLocalTime = CWorldGameTime::GetCurrentTimeInGameMinutes(); + const CPointMap& pt = m_BasePointSectUnits; + const CSectorList& pSectors = CSectorList::Get(); + const MapSectorsData& sd = pSectors.GetMapSectorDataUnchecked(pt.m_map); + int64 iLocalTime = CWorldGameTime::GetCurrentTimeInGameMinutes(); if ( !g_Cfg.m_fAllowLightOverride ) { - iLocalTime += ( pt.m_x * 24*60 ) / g_MapList.GetMapSizeX(pt.m_map); + iLocalTime += ( pt.m_x * 24*60 ) / sd.iSectorColumns; } else { // Time difference between adjacent sectors in minutes - const int iSectorTimeDiff = (24*60) / pSectors->GetSectorCols(pt.m_map); + const int iSectorTimeDiff = (24*60) / sd.iSectorColumns; // Calculate the # of columns between here and Castle Britannia ( x = 1400 ) - //int iSectorOffset = ( pt.m_x / g_MapList.GetX(pt.m_map) ) - ( (24*60) / g_MapList.GetSectorSize(pt.m_map)); - const int iSectorOffset = ( pt.m_x / pSectors->GetSectorSize(pt.m_map)); + // TODO: This code doesn't actually do that... + const int iSectorOffset = pt.m_x; // Calculate the time offset from global time const int iTimeOffset = iSectorOffset * iSectorTimeDiff; @@ -801,9 +808,9 @@ void CSector::SetLightNow( bool fFlash ) } // don't fire trigger when server is loading or light is flashing - if (( ! g_Serv.IsLoading() && fFlash == false ) && ( IsTrigUsed(TRIGGER_ENVIRONCHANGE) )) + if (( ! g_Serv.IsLoadingGeneric() && fFlash == false ) && ( IsTrigUsed(TRIGGER_ENVIRONCHANGE) )) { - pChar->OnTrigger( CTRIG_EnvironChange, pChar ); + pChar->OnTrigger( CTRIG_EnvironChange, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar ); } } } @@ -828,8 +835,9 @@ void CSector::SetLight( int light ) void CSector::SetDefaultWeatherChance() { ADDTOCALLSTACK("CSector::SetDefaultWeatherChance"); - CPointMap pt = GetBasePoint(); - byte iPercent = (byte)(IMulDiv( pt.m_y, 100, g_MapList.GetMapSizeY(pt.m_map) )); // 100 = south + const CSectorList& pSectors = CSectorList::Get(); + const MapSectorsData& sd = pSectors.GetMapSectorDataUnchecked(m_BasePointSectUnits.m_map); + byte iPercent = (byte)(IMulDiv( m_BasePointSectUnits.m_y, 100, sd.iSectorRows )); // 100 = south if ( iPercent < 50 ) { // Anywhere north of the Britain Moongate is a good candidate for snow @@ -886,7 +894,7 @@ void CSector::SetWeather( WEATHER_TYPE w ) pChar->GetClientActive()->addWeather( w ); if ( IsTrigUsed(TRIGGER_ENVIRONCHANGE) ) - pChar->OnTrigger( CTRIG_EnvironChange, pChar ); + pChar->OnTrigger( CTRIG_EnvironChange, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar ); } } @@ -907,7 +915,7 @@ void CSector::SetSeason( SEASON_TYPE season ) pChar->GetClientActive()->addSeason(season); if ( IsTrigUsed(TRIGGER_ENVIRONCHANGE) ) - pChar->OnTrigger(CTRIG_EnvironChange, pChar); + pChar->OnTrigger(CTRIG_EnvironChange, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar); } } @@ -937,6 +945,17 @@ void CSector::SetWeatherChance( bool fRain, int iChance ) SetWeather( GetWeatherCalc()); } +bool CSector::IsInDungeon() const +{ + ADDTOCALLSTACK("CSector::IsInDungeon"); + // What part of the maps are filled with dungeons. + // Used for light / weather calcs. + CRegion *pRegion = GetRegion(GetBasePointMapUnits(), REGION_TYPE_AREA); + + return ( pRegion && pRegion->IsFlag(REGION_FLAG_UNDERGROUND) ); +} + + void CSector::OnHearItem( CChar * pChar, lpctstr pszText ) { ADDTOCALLSTACK("CSector::OnHearItem"); @@ -962,7 +981,7 @@ void CSector::MoveItemToSector( CItem * pItem ) { if (_CanSleep(true)) { - if (!pItem->CanTick()) + if (!pItem->TickableState()) pItem->GoSleep(); } else @@ -1025,7 +1044,7 @@ bool CSector::MoveCharToSector( CChar * pChar ) } else if (!pChar->IsSleeping()) // An NPC entered, but the sector is sleeping { - if (!pChar->CanTick()) + if (!pChar->TickableState()) pChar->GoSleep(); // then make the NPC sleep too. } } @@ -1104,7 +1123,7 @@ void CSector::RespawnDeadNPCs() { ADDTOCALLSTACK("CSector::RespawnDeadNPCs"); // skip sectors in unsupported maps - if ( !g_MapList.IsMapSupported(m_map) ) + if ( !g_MapList.IsMapSupported(m_BasePointSectUnits.m_map) ) return; // Respawn dead NPCs @@ -1183,7 +1202,7 @@ bool CSector::_OnTick() */ // do not tick sectors on maps not supported by server - if ( !g_MapList.IsMapSupported(m_map) ) + if ( !g_MapList.IsMapSupported(m_BasePointSectUnits.m_map) ) return true; EXC_TRY("_OnTick"); @@ -1289,7 +1308,7 @@ bool CSector::_OnTick() ASSERT(pChar); if (fEnvironChange && ( IsTrigUsed(TRIGGER_ENVIRONCHANGE) )) - pChar->OnTrigger(CTRIG_EnvironChange, pChar); + pChar->OnTrigger(CTRIG_EnvironChange, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar); if ( pChar->IsClientActive()) { @@ -1319,9 +1338,10 @@ bool CSector::_OnTick() EXC_CATCHSUB("Sector"); EXC_DEBUGSUB_START; - CPointMap pt = GetBasePoint(); + CPointMap const& pt = m_BasePointSectUnits; g_Log.EventDebug("#0 char 0%x '%s'\n", (dword)(pChar->GetUID()), pChar->GetName()); g_Log.EventDebug("#0 sector #%d [%d,%d,%d,%d]\n", GetIndex(), pt.m_x, pt.m_y, pt.m_z, pt.m_map); + // TODO: add rect cords? EXC_DEBUGSUB_END; } @@ -1330,8 +1350,9 @@ bool CSector::_OnTick() _SetTimeout(SECTOR_TICKING_PERIOD); // Sector is Awake, make it tick after 30 seconds. EXC_DEBUG_START; - const CPointMap pt = GetBasePoint(); + CPointMap const& pt = m_BasePointSectUnits; g_Log.EventError("#4 sector #%d [%hd,%hd,%hhd,%hhu]\n", GetIndex(), pt.m_x, pt.m_y, pt.m_z, pt.m_map); + // TODO: add rect coords? EXC_DEBUG_END; return true; } @@ -1404,7 +1425,7 @@ bool CSector::CheckItemComplexity() const noexcept const size_t uiCount = GetItemComplexity(); if (uiCount > g_Cfg.m_iMaxSectorComplexity) { - g_Log.Event(LOGL_WARN, "%" PRIuSIZE_T " items at %s. Sector too complex!\n", uiCount, GetBasePoint().WriteUsed()); + g_Log.Event(LOGL_WARN, "%" PRIuSIZE_T " items at %s. Sector too complex!\n", uiCount, GetBasePointMapUnits().WriteUsed()); return true; } return false; @@ -1455,7 +1476,7 @@ bool CSector::CheckCharComplexity() const noexcept const size_t uiCount = GetCharComplexity(); if (uiCount > g_Cfg.m_iMaxCharComplexity) { - g_Log.Event(LOGL_WARN, "%" PRIuSIZE_T " chars at %s. Sector too complex!\n", uiCount, GetBasePoint().WriteUsed()); + g_Log.Event(LOGL_WARN, "%" PRIuSIZE_T " chars at %s. Sector too complex!\n", uiCount, GetBasePointMapUnits().WriteUsed()); return true; } return false; diff --git a/src/game/CSector.h b/src/game/CSector.h index 396e522a8..4469c9012 100644 --- a/src/game/CSector.h +++ b/src/game/CSector.h @@ -11,8 +11,6 @@ #include "CSectorTemplate.h" #include "CTimedObject.h" -#define SECTOR_TICKING_PERIOD 30 * 1000 // Every 30 seconds. - class CChar; class CItemStone; @@ -41,6 +39,7 @@ class CSector : public CScriptObj, public CSectorBase, public CTimedObject // sq void SetLightNow( bool fFlash = false ); bool IsMoonVisible( uint iPhase, int iLocalTime ) const; void SetDefaultWeatherChance(); + bool IsInDungeon() const; public: CSector(); diff --git a/src/game/CSectorList.cpp b/src/game/CSectorList.cpp index d99514400..7a300667e 100644 --- a/src/game/CSectorList.cpp +++ b/src/game/CSectorList.cpp @@ -3,9 +3,9 @@ #include "CWorld.h" #include "CSectorList.h" -CSectorList::CSectorList() : _SectorData{} +CSectorList::CSectorList() : + _SectorData{}, _fInitialized(false) { - _fInitialized = false; } CSectorList::~CSectorList() @@ -13,9 +13,9 @@ CSectorList::~CSectorList() Close(true); } -const CSectorList* CSectorList::Get() noexcept // static +const CSectorList& CSectorList::Get() noexcept // static { - return &g_World._Sectors; + return g_World._Sectors; } void CSectorList::Init() @@ -24,50 +24,58 @@ void CSectorList::Init() if ( _fInitialized ) // disable changes on-a-fly return; - // Initialize sector data for every map plane - TemporaryString ts; + + // Initialize sector data for every map plane + TemporaryString ts; TemporaryString tsConcat; for (int iMap = 0; iMap < MAP_SUPPORTED_QTY; ++iMap) { - /* - Before iSectorIndex was declared and set to 0 outside the FOR, so I moved it inside because - we need to (re)set iSectorIndex to 0 when Sphere finish to initialize every sectors in a map, otherwise - iSectorIndex will have the same value of iSectorQty when Sphere finish loading map0. - */ - int iSectorIndex = 0; - MapSectorsData& sd = _SectorData[iMap]; - sd._iSectorSize = sd._iSectorColumns = sd._iSectorRows = sd._iSectorQty = 0; + sd.iSectorSize = sd.iSectorColumns = sd.iSectorRows = sd.iSectorQty = 0; sd._pSectors.reset(); if (!g_MapList.IsMapSupported(iMap)) continue; - const int iSectorQty = g_MapList.CalcSectorQty(iMap); - const int iMaxX = g_MapList.CalcSectorCols(iMap); - const int iMaxY = g_MapList.CalcSectorRows(iMap); + const int iSectorQty= g_MapList.CalcSectorQty(iMap); + const int iMaxX = g_MapList.CalcSectorCols(iMap); + const int iMaxY = g_MapList.CalcSectorRows(iMap); snprintf(ts.buffer(), ts.capacity(), " map%d=%d", iMap, iSectorQty); Str_ConcatLimitNull(tsConcat.buffer(), ts.buffer(), tsConcat.capacity()); sd._pSectors = std::make_unique(iSectorQty); - sd._iSectorSize = g_MapList.GetSectorSize(iMap); - sd._iSectorQty = iSectorQty; - sd._iSectorColumns = iMaxX; - sd._iSectorRows = iMaxY; + ASSERT(sd._pSectors); + + sd.iSectorSize = g_MapList.GetSectorSize(iMap); + sd.iSectorQty = iSectorQty; + sd.iSectorColumns = iMaxX; + sd.iSectorRows = iMaxY; + + // Calc sector_shift (log2(iSectorSize)). We use this to do a bit shift instead of a division + // when we do sector calculations (that is, very often). + uint32 sector_shift = 0; + for (uint sz = sd.iSectorSize; sz > 1; sz >>= 1) + ++sector_shift; + sd.uiSectorSizeDivShift = (uint16)sector_shift; + short iSectorX = 0, iSectorY = 0; - for (; iSectorIndex < iSectorQty; ++iSectorIndex) + for (int iSectorIndex = 0; iSectorIndex < iSectorQty; ++iSectorIndex) { - if (iSectorX >= iMaxX) - { - iSectorX = 0; - ++iSectorY; - } + // Map sectors are ordered in row-major order: + // consecutive elements from the same row are contiguous and the inner loop varies the column index. + if (iSectorX >= iMaxX) + { + iSectorX = 0; + ++iSectorY; + } + CSector* pSector = &(sd._pSectors[iSectorIndex]); - ASSERT(pSector); pSector->Init(iSectorIndex, (uchar)iMap, iSectorX, iSectorY); + + ++iSectorX; } } @@ -77,7 +85,7 @@ void CSectorList::Init() if (!sd._pSectors) continue; - for (int iSector = 0; iSector < sd._iSectorQty; ++iSector) + for (int iSector = 0; iSector < sd.iSectorQty; ++iSector) { sd._pSectors[iSector].SetAdjacentSectors(); } @@ -97,7 +105,7 @@ void CSectorList::Close(bool fClosingWorld) continue; // Delete everything in sector - for (int iSector = 0; iSector < sd._iSectorQty; ++iSector) + for (int iSector = 0; iSector < sd.iSectorQty; ++iSector) { sd._pSectors[iSector].Close(fClosingWorld); } @@ -106,52 +114,73 @@ void CSectorList::Close(bool fClosingWorld) } -#define IsMapInvalid(map) ((map < 0) || (map >= MAP_SUPPORTED_QTY)) - -int CSectorList::GetSectorSize(int map) const noexcept +[[nodiscard]] +const MapSectorsData* CSectorList::GetMapSectorData(int map) const noexcept { - return (IsMapInvalid(map) ? -1 : _SectorData[map]._iSectorSize); -} + if (!g_MapList.IsMapSupported(map)) + return nullptr; -int CSectorList::GetSectorQty(int map) const noexcept -{ - return (IsMapInvalid(map) ? -1 : _SectorData[map]._iSectorQty); + return &_SectorData[map]; } -int CSectorList::GetSectorCols(int map) const noexcept +[[nodiscard]] +const MapSectorsData& CSectorList::GetMapSectorDataUnchecked(int map) const noexcept { - return (IsMapInvalid(map) ? -1 : _SectorData[map]._iSectorColumns); -} + // We assume we HAVE checked that's a valid map and that we are not indexing the _SectorData out of bounds. + //if (!g_MapList.IsMapSupported(map)) + // return nullptr; -int CSectorList::GetSectorRows(int map) const noexcept -{ - return (IsMapInvalid(map) ? -1 : _SectorData[map]._iSectorRows); + return _SectorData[map]; } -CSector* CSectorList::GetSector(int map, int index) const noexcept +CSector* CSectorList::GetSectorByIndexUnchecked(int map, int index) const noexcept { - if (IsMapInvalid(map) || !_SectorData[map]._pSectors) - return nullptr; + // We call this method very often and from places we ASSUME have already checked that the map number is legit. + // (it's done in CWorldMap::GetSectorByIndex). + + // Micro-optimization: Skip the function call (with the call cost) and the if statement to avoid the possible cost of a branch misprediction. + //if (!g_MapList.IsMapSupported(map) || !_SectorData[map]._pSectors) + // return nullptr; + //if ((map < 0) || ((size_t)map < sizeof(_SectorData)) ) + // return nullptr; + const MapSectorsData& sd = _SectorData[map]; - return (index < sd._iSectorQty) ? &(sd._pSectors[index]) : nullptr; + return (index < sd.iSectorQty) ? &(sd._pSectors.get()[index]) : nullptr; } -CSector* CSectorList::GetSector(int map, short x, short y) const noexcept +CSector* CSectorList::GetSectorByCoordsUnchecked(int map, short x, short y) const NOEXCEPT_NODEBUG { - if (IsMapInvalid(map) || !_SectorData[map]._pSectors) - return nullptr; + // We call this method very often and from places we ASSUME have already checked that the map number is legit. + // (it's done in CPointBase::GetSector()) + + // Micro-optimization: Skip the IsMapSupported function call and the if statement to avoid the possible cost of a branch misprediction. + //if (!g_MapList.IsMapSupported(map) || !_SectorData[map]._pSectors) + // return nullptr; const MapSectorsData& sd = _SectorData[map]; - const int xSect = (x / sd._iSectorSize), ySect = (y / sd._iSectorSize); - if ((xSect >= sd._iSectorColumns) || (ySect >= sd._iSectorRows)) - return nullptr; - const int index = ((ySect * sd._iSectorColumns) + xSect); - return (index < sd._iSectorQty) ? &(sd._pSectors[index]) : nullptr; -} + // We know that sector sizes MUST be multiple of 2, so just use bit shifts. + //const int xSectTest = (x / sd.iSectorSize), ySectTest = (y / sd.iSectorSize); + const int xSect = (x >> sd.uiSectorSizeDivShift), ySect = (y >> sd.uiSectorSizeDivShift); + //ASSERT(xSectTest == xSect); + //ASSERT(ySectTest == ySect); + +/* +#ifdef _DEBUG + // We also assume that X and Y are inside map boundaries. + if ((xSect >= sd.iSectorColumns) || (ySect >= sd.iSectorRows)) + return nullptr; -#undef IsMapInvalid + const int index = ((ySect * sd.iSectorColumns) + xSect); + return (index < sd.iSectorQty) ? &(sd._pSectors[index]) : nullptr; +#else +*/ + const int index = ((ySect * sd.iSectorColumns) + xSect); + DEBUG_ASSERT(index < sd.iSectorQty); + return &(sd._pSectors[index]); +//#endif +} int CSectorList::GetSectorAbsoluteQty() const noexcept { @@ -159,9 +188,9 @@ int CSectorList::GetSectorAbsoluteQty() const noexcept for (const MapSectorsData& sd : _SectorData) { if (!sd._pSectors) - continue; + continue; - iCount += sd._iSectorQty; + iCount += sd.iSectorQty; } return iCount; } @@ -172,15 +201,15 @@ CSector* CSectorList::GetSectorAbsolute(int index) noexcept { const MapSectorsData& sd = _SectorData[m]; if (!sd._pSectors) - continue; + continue; // WTF? - if ((base + sd._iSectorQty) > index) + if ((base + sd.iSectorQty) > index) { const int iSummation = (index - base); return &(sd._pSectors[iSummation]); } - base += sd._iSectorQty; + base += sd.iSectorQty; } return nullptr; } diff --git a/src/game/CSectorList.h b/src/game/CSectorList.h index 7f2bec4d0..d94e8cac0 100644 --- a/src/game/CSectorList.h +++ b/src/game/CSectorList.h @@ -10,24 +10,29 @@ #include "CSector.h" +// Sector data for a given map +struct MapSectorsData +{ + friend class CSectorList; + + // Pre-calculated values, for faster retrieval + int iSectorSize; + int iSectorColumns; // how much sectors are in a column (x) in a given map + int iSectorRows; // how much sectors are in a row (y) in a given map + int iSectorQty; // how much sectors are in a map + uint16 uiSectorSizeDivShift; // precalculated value to avoid a division in sector/rect calculations + +private: + std::unique_ptr _pSectors; +}; + class CSectorList { public: static const char* m_sClassName; - // Sector data - struct MapSectorsData - { - std::unique_ptr _pSectors; - - // Pre-calculated values, for faster retrieval - int _iSectorSize; - int _iSectorColumns; // how much sectors are in a column (x) in a given map - int _iSectorRows; // how much sectors are in a row (y) in a given map - int _iSectorQty; // how much sectors are in a map - }; - - //std::array _SectorData; // Use plain C-style vectors, to remove even the minimum overhead of std::array methods + // Use plain C-style vectors, to remove even the minimal overhead of std::array methods in debug builds. + //std::array _SectorData; MapSectorsData _SectorData[MAP_SUPPORTED_QTY]; bool _fInitialized; @@ -39,18 +44,25 @@ class CSectorList CSectorList& operator=(const CSectorList& other) = delete; public: - static const CSectorList* Get() noexcept; + static const CSectorList& Get() noexcept; void Init(); void Close(bool fClosingWorld); - int GetSectorSize(int map) const noexcept; - int GetSectorQty(int map) const noexcept; - int GetSectorCols(int map) const noexcept; - int GetSectorRows(int map) const noexcept; + [[nodiscard]] + const MapSectorsData* GetMapSectorData(int map) const noexcept; + [[nodiscard]] + const MapSectorsData& GetMapSectorDataUnchecked(int map) const noexcept; + + [[nodiscard]] + static + constexpr int CalcSectorIndex(int maxX, int x, int y) noexcept { + // Row-major order: index = row * numColumns + column + return (y * maxX) + x; + } - CSector* GetSector(int map, int index) const noexcept; // gets sector # from one map - CSector* GetSector(int map, short x, short y) const noexcept; + CSector* GetSectorByIndexUnchecked(int map, int index) const noexcept; // gets sector # from one map + CSector* GetSectorByCoordsUnchecked(int map, short x, short y) const NOEXCEPT_NODEBUG; int GetSectorAbsoluteQty() const noexcept; CSector* GetSectorAbsolute(int index) noexcept; diff --git a/src/game/CSectorTemplate.cpp b/src/game/CSectorTemplate.cpp index 79acf97df..64d9525ae 100644 --- a/src/game/CSectorTemplate.cpp +++ b/src/game/CSectorTemplate.cpp @@ -1,4 +1,4 @@ -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../common/CLog.h" #include "../common/CRect.h" #include "../game/CWorld.h" @@ -150,22 +150,24 @@ void CItemsList::AddItemToSector( CItem * pItem ) } - ////////////////////////////////////////////////////////////////// // -CSectorBase void CSectorBase::SetAdjacentSectors() { - const CSectorList* pSectors = CSectorList::Get(); + const CSectorList& pSectors = CSectorList::Get(); + ASSERT_ALWAYS(g_MapList.IsMapSupported(m_BasePointSectUnits.m_map)); + auto const& sd = pSectors.GetMapSectorDataUnchecked(m_BasePointSectUnits.m_map); - const int iMaxX = pSectors->GetSectorCols(m_map); + const int iMaxX = sd.iSectorColumns; ASSERT(iMaxX > 0); - [[maybe_unused]] const int iMaxY = pSectors->GetSectorRows(m_map); + const int iMaxY = sd.iSectorRows; ASSERT(iMaxY > 0); - const int iMaxSectors = pSectors->GetSectorQty(m_map); + const int iMaxSectors = sd.iSectorQty; + ASSERT(iMaxSectors > 9); - // Sectors are layed out in the array horizontally: when the row is complete (X), the subsequent sector is placed in - // the column below (Y). + // Sectors are laid out in the array horizontally (row-major order): when the row is complete (X), + // the subsequent sector is placed in the row below (Y). // Between each X coordinate there's a single sector index difference; // between each Y coordinate there's a number of sectors equal to the sectors in a row. /* @@ -197,16 +199,23 @@ void CSectorBase::SetAdjacentSectors() for (int i = 0; i < (int)DIR_QTY; ++i) { // out of bounds checks - const int iAdjX = _x + _xyDir[i].x; - const int iAdjY = _y + _xyDir[i].y; + // These checks are needed or the negative iAdjX/iAdjY can lead to a wrong index if the sector is near the map borders. + // For instance m_index = 0, iAdjY > 0 and iAdjX < 0, SW check, the index start from the first column of the second row and it goes back to the first row because there is no SW sector + const int iAdjX = m_BasePointSectUnits.m_x + _xyDir[i].x; + if ((iAdjX < 0) || (iAdjX >= iMaxX)) + continue; - int index = m_index; - index += ((iAdjY * iMaxX) + iAdjX); - if (index < 0 || (index > iMaxSectors)) - { + const int iAdjY = m_BasePointSectUnits.m_y + _xyDir[i].y; + if ((iAdjY < 0) || (iAdjY >= iMaxY)) continue; - } - _ppAdjacentSectors[(DIR_TYPE)i] = pSectors->GetSector(m_map, index); + + const int iAdjIndex = CSectorList::CalcSectorIndex(iMaxX, iAdjX, iAdjY); + if ((iAdjIndex < 0) || (iAdjIndex > iMaxSectors)) + continue; + + _ppAdjacentSectors[(DIR_TYPE)i] = pSectors.GetSectorByIndexUnchecked(m_BasePointSectUnits.m_map, iAdjIndex); + + //g_Log.EventDebug("Sector %d, Setting adjacent sector %d.\n", m_index, iAdjIndex); } } @@ -219,10 +228,8 @@ CSector *CSectorBase::_GetAdjacentSector(DIR_TYPE dir) const CSectorBase::CSectorBase() : _ppAdjacentSectors{{}} { - m_map = 0; m_index = 0; m_dwFlags = 0; - _x = _y = -1; //memset(_ppAdjacentSectors, 0, DIR_QTY * sizeof(_ppAdjacentSectors)); } @@ -232,30 +239,52 @@ void CSectorBase::Init(int index, uchar map, short x, short y) if (!g_MapList.IsMapSupported(map) || !g_MapList.IsInitialized(map)) { g_Log.EventError("Trying to initalize a sector %d in unsupported map #%d. Defaulting to 0,0.\n", index, map); + return; } - else if (( index < 0 ) || ( index >= CSectorList::Get()->GetSectorQty(map) )) + + // Sooner or later we have to change those signed values to unsigned... they should never be negative in any case + // and we're doing a number of potentially useless checks. + if (( index < 0 ) || ( index >= CSectorList::Get().GetMapSectorDataUnchecked(map).iSectorQty )) { - m_map = map; + m_BasePointSectUnits.m_map = map; g_Log.EventError("Trying to initalize a sector by sector number %d out-of-range for map #%d. Defaulting to 0,%d.\n", index, map, map); + return; } - else - { - ASSERT(x >= 0 && y >= 0); - m_index = index; - m_map = map; - _x = x; - _y = y; - } + + ASSERT(x >= 0 && y >= 0); + m_index = index; + + auto const& sd = CSectorList::Get().GetMapSectorDataUnchecked(map); + DEBUG_ASSERT((m_index >= 0) && (m_index < sd.iSectorQty)); + + m_BasePointSectUnits = + CPointBase + { + x, // x + y, // y + 0, // z + (uint8)map // m + }; + + m_MapRectWorldUnits = + CRectMap + { + m_BasePointSectUnits.m_x * sd.iSectorSize, // left + m_BasePointSectUnits.m_y * sd.iSectorSize, // top + m_BasePointSectUnits.m_x * sd.iSectorSize + sd.iSectorSize, // right: East + m_BasePointSectUnits.m_y * sd.iSectorSize + sd.iSectorSize, // bottom: South + m_BasePointSectUnits.m_map // map + }; } -bool CSectorBase::IsInDungeon() const +CPointBase CSectorBase::GetBasePointMapUnits() const noexcept { - ADDTOCALLSTACK("CSectorBase::IsInDungeon"); - // What part of the maps are filled with dungeons. - // Used for light / weather calcs. - CRegion *pRegion = GetRegion(GetBasePoint(), REGION_TYPE_AREA); - - return ( pRegion && pRegion->IsFlag(REGION_FLAG_UNDERGROUND) ); + return CPointBase { + (int16)m_MapRectWorldUnits.m_left, + (int16)m_MapRectWorldUnits.m_top, + 0, + (uint8)m_MapRectWorldUnits.m_map + }; } CRegion * CSectorBase::GetRegion( const CPointBase & pt, dword dwType ) const @@ -268,8 +297,8 @@ CRegion * CSectorBase::GetRegion( const CPointBase & pt, dword dwType ) const // REGION_TYPE_ROOM => RES_ROOM = NPC House areas only = CRegion. // REGION_TYPE_MULTI => RES_WORLDITEM = UID linked types in general = CRegionWorld - size_t iQty = m_RegionLinks.size(); - for ( size_t i = 0; i < iQty; ++i ) + const size_t uiQty = m_RegionLinks.size(); + for ( size_t i = 0; i < uiQty; ++i ) { CRegion * pRegion = m_RegionLinks[i]; ASSERT(pRegion); @@ -311,8 +340,8 @@ CRegion * CSectorBase::GetRegion( const CPointBase & pt, dword dwType ) const size_t CSectorBase::GetRegions( const CPointBase & pt, dword dwType, CRegionLinks *pRLinks ) const { //ADDTOCALLSTACK_DEBUG("CSectorBase::GetRegions"); // Called very frequently - size_t iQty = m_RegionLinks.size(); - for ( size_t i = 0; i < iQty; ++i ) + const size_t uiQty = m_RegionLinks.size(); + for ( size_t i = 0; i < uiQty; ++i ) { CRegion * pRegion = m_RegionLinks[i]; ASSERT(pRegion); @@ -370,10 +399,10 @@ bool CSectorBase::LinkRegion( CRegion * pRegionNew ) // Later added regions from the MAP file should be the smaller ones, // according to the old rules. ASSERT(pRegionNew); - ASSERT( pRegionNew->IsOverlapped(GetRect()) ); - size_t iQty = m_RegionLinks.size(); + ASSERT( pRegionNew->IsOverlapped(GetRectWorldUnits()) ); + const size_t uiQty = m_RegionLinks.size(); - for ( size_t i = 0; i < iQty; ++i ) + for ( size_t i = 0; i < uiQty; ++i ) { CRegion * pRegion = m_RegionLinks[i]; ASSERT(pRegion); @@ -398,7 +427,7 @@ bool CSectorBase::LinkRegion( CRegion * pRegionNew ) // keep item (multi) regions on top if ( pRegion->GetResourceID().IsUIDItem() && !pRegionNew->GetResourceID().IsUIDItem() ) - continue; + continue; // must insert before this. m_RegionLinks.emplace(m_RegionLinks.begin() + i, pRegionNew); @@ -415,7 +444,7 @@ CTeleport * CSectorBase::GetTeleport( const CPointMap & pt ) const ADDTOCALLSTACK("CSectorBase::GetTeleport"); // Any teleports here at this point ? - size_t i = m_Teleports.FindKey(pt.GetPointSortIndex()); + const size_t i = m_Teleports.FindKey(pt.GetPointSortIndex()); if ( i == sl::scont_bad_index() ) return nullptr; @@ -434,7 +463,7 @@ bool CSectorBase::AddTeleport( CTeleport * pTeleport ) // NOTE: can't be 2 teleports from the same place ! // ASSERT( Teleport is actually in this sector ! - size_t i = m_Teleports.FindKey( pTeleport->GetPointSortIndex()); + const size_t i = m_Teleports.FindKey( pTeleport->GetPointSortIndex()); if ( i != sl::scont_bad_index() ) { DEBUG_ERR(( "Conflicting teleport %s!\n", pTeleport->WriteUsed() )); @@ -443,46 +472,3 @@ bool CSectorBase::AddTeleport( CTeleport * pTeleport ) m_Teleports.AddSortKey( pTeleport, pTeleport->GetPointSortIndex()); return true; } - -bool CSectorBase::IsFlagSet( dword dwFlag ) const noexcept -{ - return (( m_dwFlags & dwFlag) ? true : false ); -} - -CPointMap CSectorBase::GetBasePoint() const -{ - // ADDTOCALLSTACK_DEBUG("CSectorBase::GetBasePoint"); // It's commented because it's slow and this method is called VERY often! - // What is the coord base of this sector. upper left point. - const CSectorList* pSectors = CSectorList::Get(); -#if _DEBUG - DEBUG_ASSERT( (m_index >= 0) && (m_index < pSectors->GetSectorQty(m_map)) ); - // Again this method is called very often, so call the least functions possible and do the minimum amount of checks required -#endif - const int iCols = pSectors->GetSectorCols(m_map); - const int iSize = pSectors->GetSectorSize(m_map); - - const int iQuot = (m_index % iCols), iRem = (m_index / iCols); // Help the compiler to optimize the division - return // Initializer list for CPointMap, it's the fastest way to return an object (requires less optimizations, which aren't used in debug build) - { - (short)(iQuot * iSize), // x - (short)(iRem * iSize), // y - 0, // z - m_map // m - }; -} - -CRectMap CSectorBase::GetRect() const noexcept -{ - //ADDTOCALLSTACK_DEBUG("CSectorBase::GetRect"); // It's commented because it's slow and this method is called VERY often! - // Get a rectangle for the sector. - const CPointMap& pt = GetBasePoint(); - const int iSectorSize = CSectorList::Get()->GetSectorSize(pt.m_map); - return // Initializer list for CRectMap, it's the fastest way to return an object (requires less optimizations, which aren't used in debug build) - { - pt.m_x, // left - pt.m_y, // yop - pt.m_x + iSectorSize, // right: East - pt.m_y + iSectorSize, // bottom: South - pt.m_map // map - }; -} diff --git a/src/game/CSectorTemplate.h b/src/game/CSectorTemplate.h index 8260e669c..2633c222a 100644 --- a/src/game/CSectorTemplate.h +++ b/src/game/CSectorTemplate.h @@ -99,11 +99,12 @@ class CSectorBase // world sector protected: - uchar m_map; // sector map - short _x, _y; // x and y (row and column) of the sector in the map - int m_index; // sector index + int m_index; // Sector index in the 'sector grid' + CPointBase m_BasePointSectUnits; // Sector coordinates in the 'sector grid'. + CRectMap m_MapRectWorldUnits; // Map area inside this sector, in map coordinates. private: + // TODO: store indices instead of pointers, to make the class smaller? CSector* _ppAdjacentSectors[DIR_QTY]; public: @@ -136,11 +137,11 @@ class CSectorBase // world sector virtual void Init(int index, uchar map, short x, short y); // Location map units. - int GetIndex() const noexcept { return m_index; } - int GetMap() const noexcept { return m_map; } - CPointMap GetBasePoint() const; - CRectMap GetRect() const noexcept; - bool IsInDungeon() const; + int GetIndex() const noexcept { return m_index; } + int GetMap() const noexcept { return m_BasePointSectUnits.m_map; } + CPointBase GetBasePointMapUnits() const noexcept; + constexpr const CPointBase& GetBasePointSectUnits() noexcept { return m_BasePointSectUnits; } + constexpr const CRectMap& GetRectWorldUnits() const noexcept { return m_MapRectWorldUnits; } // CRegion CRegion * GetRegion( const CPointBase & pt, dword dwType ) const; @@ -153,7 +154,9 @@ class CSectorBase // world sector CTeleport * GetTeleport( const CPointMap & pt ) const; bool AddTeleport( CTeleport * pTeleport ); - bool IsFlagSet( dword dwFlag ) const noexcept; + constexpr bool IsFlagSet( dword dwFlag ) const noexcept { + return (m_dwFlags & dwFlag); + } #define SECF_NoSleep 0x00000001 #define SECF_InstaSleep 0x00000002 diff --git a/src/game/CServer.cpp b/src/game/CServer.cpp index 9539ff4d3..07192ecd5 100644 --- a/src/game/CServer.cpp +++ b/src/game/CServer.cpp @@ -13,10 +13,10 @@ #include "../common/sphere_library/sstring.h" #include "../common/crypto/CCryptoKeyCalc.h" -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../common/CLog.h" -#include "../common/CTextConsole.h" #include "../common/CUOClientVersion.h" #include "../common/sphereversion.h" #include "../network/CClientIterator.h" @@ -362,7 +362,7 @@ CServer::CServer() : CServerDef( SPHERE_TITLE, CSocketAddressIP( SOCKET_LOCAL_AD m_fConsoleTextReadyFlag = false; // we are in start up mode. // IsLoading() - SetServerMode( SERVMODE_PreLoadingINI ); + SetServerMode( ServMode::StartupPreLoadingIni ); memset(m_PacketFilter, 0, sizeof(m_PacketFilter)); memset(m_OutPacketFilter, 0, sizeof(m_OutPacketFilter)); @@ -382,7 +382,7 @@ void CServer::SetSignals( bool fMsg ) #ifndef _WIN32 SetUnixSignals(g_Cfg.m_fSecure); LOG_TYPE lt = LOGM_INIT; - if (!IsLoading()) { + if (!IsLoadingGeneric()) { lt = (LOG_TYPE)((uint)lt | (uint)LOGL_EVENT); } if ( g_Cfg.m_fSecure ) @@ -395,7 +395,7 @@ void CServer::SetSignals( bool fMsg ) } #endif - if ( fMsg && !IsLoading() ) + if ( fMsg && !IsLoadingGeneric() ) { CWorldComm::Broadcast( g_Cfg.m_fSecure ? "The world is now running in SECURE MODE." : @@ -438,7 +438,12 @@ bool CServer::SetProcessPriority(int iPriorityLevel) return fSuccess; } -void CServer::SetServerMode( SERVMODE_TYPE mode ) +ServMode CServer::GetServerMode() const noexcept +{ + return m_iModeCode.load(std::memory_order_acquire); +} + +void CServer::SetServerMode( ServMode mode ) { ADDTOCALLSTACK("CServer::SetServerMode"); m_iModeCode.store(mode, std::memory_order_release); @@ -453,13 +458,14 @@ bool CServer::IsValidBusy() const // ? switch ( GetServerMode() ) { - case SERVMODE_Saving: + case ServMode::Saving: if ( g_World.IsSaving() ) return true; break; - case SERVMODE_Loading: - case SERVMODE_GarbageCollection: - case SERVMODE_RestockAll: // these may look stuck but are not. + case ServMode::StartupLoadingSaves: + case ServMode::StartupLoadingScripts: + case ServMode::GarbageCollection: + case ServMode::RestockAll: // these may look stuck but are not. return true; default: return false; @@ -480,14 +486,32 @@ void CServer::SetExitFlag(int iFlag) noexcept m_iExitFlag.store(iFlag, std::memory_order_release); } -bool CServer::IsLoading() const noexcept +bool CServer::IsStartupLoadingScripts() const noexcept +{ + return (GetServerMode() > ServMode::StartupLoadingScripts); +} + +/* +bool CServer::IsStartupLoadingSaves() const noexcept +{ + return (GetServerMode() > ServMode::StartupLoadingSaves); +} +*/ + +bool CServer::IsLoadingGeneric() const noexcept { - return ( m_fResyncPause || (GetServerMode() > SERVMODE_Run) ); + return ( m_fResyncPause || (GetServerMode() > ServMode::Run) ); } bool CServer::IsResyncing() const noexcept { - return m_fResyncPause || (GetServerMode() == SERVMODE_ResyncLoad); + return m_fResyncPause || (GetServerMode() == ServMode::ResyncLoad); +} + +bool CServer::IsDestroyingWorld() const noexcept +{ + const ServMode servMode = GetServerMode(); + return (servMode == ServMode::Exiting || servMode == ServMode::StartupLoadingScripts || servMode == ServMode::StartupLoadingSaves); } void CServer::Shutdown( int64 iMinutes ) // If shutdown is initialized @@ -626,25 +650,29 @@ lpctstr CServer::GetStatusString( byte iIndex ) const // typical (first time) poll response. { std::string cliver = m_ClientVersion.GetClientVer(); - snprintf(pTemp, Str_TempLength(), SPHERE_TITLE ", Name=%s, Port=%d, Ver=" SPHERE_BUILD_INFO_STR ", TZ=%d, EMail=%s, URL=%s, Lang=%s, CliVer=%s\n", + snprintf(pTemp, Str_TempLength(), + SPHERE_TITLE ", Name=%s, Port=%d, Ver=" SPHERE_BUILD_INFO_STR ", TZ=%d, EMail=%s, URL=%s, Lang=%s, CliVer=%s\n", GetName(), m_ip.GetPort(), m_TimeZone, m_sEMail.GetBuffer(), m_sURL.GetBuffer(), m_sLang.GetBuffer(), cliver.c_str()); } break; case 0x22: // '"' { // shown in the INFO page in game. - snprintf(pTemp, Str_TempLength(), SPHERE_TITLE ", Name=%s, Age=%" PRId64 ", Clients=%" PRIuSIZE_T ", Items=%" PRIuSIZE_T ", Chars=%" PRIuSIZE_T ", Mem=%" PRIuSIZE_T "K\n", + snprintf(pTemp, Str_TempLength(), + SPHERE_TITLE ", Name=%s, Age=%" PRId64 ", Clients=%" PRIuSIZE_T ", Items=%" PRIuSIZE_T ", Chars=%" PRIuSIZE_T ", Mem=%" PRIuSIZE_T "K\n", GetName(), iHours, iClients, StatGet(SERV_STAT_ITEMS), StatGet(SERV_STAT_CHARS), StatGet(SERV_STAT_MEM)); } break; case 0x24: // '$' // show at startup. - snprintf(pTemp, Str_TempLength(), "Admin=%s, URL=%s, Lang=%s, TZ=%d\n", + snprintf(pTemp, Str_TempLength(), + "Admin=%s, URL=%s, Lang=%s, TZ=%d\n", m_sEMail.GetBuffer(), m_sURL.GetBuffer(), m_sLang.GetBuffer(), m_TimeZone); break; case 0x25: // '%' // ConnectUO Status string - snprintf(pTemp, Str_TempLength(), SPHERE_TITLE " Items=%" PRIuSIZE_T ", Mobiles=%" PRIuSIZE_T ", Clients=%" PRIuSIZE_T ", Mem=%" PRIuSIZE_T, + snprintf(pTemp, Str_TempLength(), + SPHERE_TITLE " Items=%" PRIuSIZE_T ", Mobiles=%" PRIuSIZE_T ", Clients=%" PRIuSIZE_T ", Mem=%" PRIuSIZE_T, StatGet(SERV_STAT_ITEMS), StatGet(SERV_STAT_CHARS), iClients, StatGet(SERV_STAT_MEM)); break; } @@ -1030,42 +1058,44 @@ bool CServer::OnConsoleCmd( CSString & sText, CTextConsole * pSrc ) SetExitFlag( 1 ); } } break; + +// Something here makes CppCheck stop for 'syntax error'... #ifdef _TESTEXCEPTION - case '$': // call stack integrity + case '$': // call stack integrity { #ifdef _EXCEPTIONS_DEBUG - { // test without freezing the call stack + { + / test without freezing the call stack ADDTOCALLSTACK("CServer::TestException1"); EXC_TRY("Test1"); throw CSError( LOGM_DEBUG, 0, "Test Exception #1"); - } - catch (const CSError& e) - { - // the following call will destroy the stack trace on linux due to - // a call to CSFile::Close fromn CLog::EventStr. - g_Log.Event( LOGM_DEBUG, "Caught exception\n" ); - EXC_CATCH_EXCEPTION_SPHERE(&e); - } - } + } + catch (const CSError& e) + { + // the following call will destroy the stack trace on linux due to + // a call to CSFile::Close fromn CLog::EventStr. + g_Log.Event( LOGM_DEBUG, "Caught exception\n" ); + _EXC_CATCH_EXCEPTION_GENERIC(&e, "CSError"); + } - { // test pausing the call stack + { + // test pausing the call stack ADDTOCALLSTACK("CServer::TestException2"); EXC_TRY("Test2"); throw CSError( LOGM_DEBUG, 0, "Test Exception #2"); - } - catch (const CSError& e) - { - StackDebugInformation::freezeCallStack(true); - // with freezeCallStack, the following call won't be recorded - g_Log.Event( LOGM_DEBUG, "Caught exception\n" ); - StackDebugInformation::freezeCallStack(false); - EXC_CATCH_EXCEPTION_SPHERE(&e); - } - } + } + catch (const CSError& e) + { + StackDebugInformation::freezeCallStack(true); + // with freezeCallStack, the following call won't be recorded + g_Log.Event( LOGM_DEBUG, "Caught exception\n" ); + StackDebugInformation::freezeCallStack(false); + _EXC_CATCH_EXCEPTION_GENERIC(&e, "CSError"); + } #else - throw CSError(LOGL_CRIT, E_FAIL, "This test requires exception debugging enabled"); + throw CSError(LOGL_CRIT, E_FAIL, "This test requires exception debugging enabled"); #endif - } break; + } break; case '%': // throw simple exception { throw 0; @@ -1086,8 +1116,9 @@ bool CServer::OnConsoleCmd( CSString & sText, CTextConsole * pSrc ) throw CSError(LOGL_CRIT, (dword)E_FAIL, "Test Exception"); } #endif - default: - goto longcommand; + + default: + goto longcommand; } goto endconsole; @@ -1108,7 +1139,7 @@ bool CServer::OnConsoleCmd( CSString & sText, CTextConsole * pSrc ) if ( g_Cfg.m_sStripPath.IsEmpty() ) { - if (pSrc != this) + if (pSrc && pSrc != this) { pSrc->SysMessage("StripPath not defined, function aborted.\n"); } @@ -1764,8 +1795,9 @@ bool CServer::r_Verb( CScript &s, CTextConsole * pSrc ) { // RES_FUNCTION call CSString sVal; - CScriptTriggerArgs Args( s.GetArgRaw() ); - if ( r_Call( uiFunctionIndex, pSrc, &Args, &sVal ) ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(s.GetArgRaw()); + if ( r_Call( uiFunctionIndex, pScriptArgs, pSrc, &sVal ) ) return true; } @@ -1814,7 +1846,7 @@ bool CServer::r_Verb( CScript &s, CTextConsole * pSrc ) { ptcKey = s.GetArgStr(); SKIP_SEPARATORS(ptcKey); - g_Exp.m_VarGlobals.ClearKeys(ptcKey); + g_ExprGlobals.mtEngineLockedWriter()->m_VarGlobals.ClearKeys(ptcKey); return true; } } @@ -1987,7 +2019,6 @@ bool CServer::r_Verb( CScript &s, CTextConsole * pSrc ) { g_Log.Event(LOGL_EVENT, "Not allowed during world save and/or resync pause"); } - break; } else { @@ -2230,15 +2261,15 @@ bool CServer::r_Verb( CScript &s, CTextConsole * pSrc ) case SV_VARLIST: if ( ! strcmpi( s.GetArgStr(), "log" ) ) pSrc = &g_Serv; - g_Exp.m_VarGlobals.DumpKeys(pSrc, "VAR."); + g_ExprGlobals.mtEngineLockedReader()->m_VarGlobals.DumpKeys(pSrc, "VAR."); break; case SV_PRINTLISTS: if ( ! strcmpi( s.GetArgStr(), "log" ) ) pSrc = &g_Serv; - g_Exp.m_ListGlobals.DumpKeys(pSrc, "LIST."); + g_ExprGlobals.mtEngineLockedReader()->m_ListGlobals.DumpKeys(pSrc, "LIST."); break; case SV_CLEARLISTS: - g_Exp.m_ListGlobals.ClearKeys( s.GetArgStr()) ; + g_ExprGlobals.mtEngineLockedWriter()->m_ListGlobals.ClearKeys( s.GetArgStr()) ; break; default: return CScriptObj::r_Verb(s, pSrc); @@ -2384,9 +2415,10 @@ bool CServer::CommandLinePostLoad( int argc, tchar * argv[] ) return false; } - ssize_t count = ssize_t(g_Exp.m_VarDefs.GetCount()); + auto r = g_ExprGlobals.mtEngineLockedReader(); + ssize_t count = ssize_t(r->m_VarDefs.GetCount()); ssize_t i = 0; - for ( const CVarDefCont * pCont : g_Exp.m_VarDefs ) + for ( const CVarDefCont * pCont : r->m_VarDefs ) { if ( ( i % 0x1ff ) == 0 ) PrintPercent( i, count ); @@ -2403,9 +2435,9 @@ bool CServer::CommandLinePostLoad( int argc, tchar * argv[] ) return false; } - count = ssize_t(g_Exp.m_VarResDefs.GetCount()); + count = ssize_t(r->m_VarResDefs.GetCount()); i = 0; - for ( const CVarDefCont * pCont : g_Exp.m_VarResDefs ) + for ( const CVarDefCont * pCont : r->m_VarResDefs ) { if ( ( i % 0x1ff ) == 0 ) PrintPercent( i, count ); @@ -2454,12 +2486,12 @@ void CServer::SetResyncPause(bool fPause, CTextConsole * pSrc, bool fMessage) pSrc->SysMessage(g_Cfg.GetDefaultMsg(DEFMSG_SERVER_RESYNC_START)); g_Cfg.Unload(true); - SetServerMode(SERVMODE_ResyncPause); + SetServerMode(ServMode::ResyncPause); } else { g_Log.Event(LOGL_EVENT, "%s\n", g_Cfg.GetDefaultMsg(DEFMSG_SERVER_RESYNC_RESTART)); - SetServerMode(SERVMODE_ResyncLoad); + SetServerMode(ServMode::ResyncLoad); if ( !g_Cfg.Load(true) ) { @@ -2482,7 +2514,7 @@ void CServer::SetResyncPause(bool fPause, CTextConsole * pSrc, bool fMessage) g_World.SyncGameTime(); - SetServerMode(SERVMODE_Run); + SetServerMode(ServMode::Run); } } diff --git a/src/game/CServer.h b/src/game/CServer.h index cc5d8009a..9efc49fc7 100644 --- a/src/game/CServer.h +++ b/src/game/CServer.h @@ -7,7 +7,6 @@ #define _INC_CSERVER_H #include "../common/sqlite/SQLite.h" -#include "../network/CSocket.h" #include "../common/CSFileObj.h" #include "../common/CTextConsole.h" #include "../common/CDataBase.h" @@ -19,18 +18,19 @@ class CItemShip; -enum SERVMODE_TYPE +enum class ServMode { - SERVMODE_RestockAll, // Major event, potentially slow. - SERVMODE_GarbageCollection, // Executing the garbage collection, potentially demanding. - SERVMODE_Saving, // Forced save freezes the system. - SERVMODE_Run, // Game is up and running. - SERVMODE_ResyncPause, // Server paused during resync. - - SERVMODE_PreLoadingINI, // Initial (first) parsing of the sphere.ini - SERVMODE_Loading, // Initial load. - SERVMODE_ResyncLoad, // Loading after resync. - SERVMODE_Exiting // Closing down. + RestockAll, // Major event, potentially slow. + GarbageCollection, // Executing the garbage collection, potentially demanding. + Saving, // Forced save freezes the system. + Run, // Game is up and running. + ResyncPause, // Server paused during resync. + + StartupPreLoadingIni, // Initial (first) parsing of the sphere.ini + StartupLoadingScripts, // Initial load. + StartupLoadingSaves, + ResyncLoad, // Loading after resync. + Exiting // Closing down. }; @@ -40,7 +40,7 @@ extern class CServer : public CServerDef, public CTextConsole public: static const char* m_sClassName; - std::atomic m_iModeCode; // Just some error code to return to system. + std::atomic m_iModeCode; // Query the server state. std::atomic_int m_iExitFlag; // identifies who caused the exit. <0 = error bool m_fResyncPause; // Server is temporarily halted so files can be updated. CTextConsole* m_fResyncRequested; // A resync pause has been requested by this source. @@ -79,17 +79,19 @@ extern class CServer : public CServerDef, public CTextConsole CServer& operator=(const CServer& other) = delete; public: - inline SERVMODE_TYPE GetServerMode() const noexcept { - return m_iModeCode.load(std::memory_order_acquire); - } + void SetServerMode( ServMode mode ); + ServMode GetServerMode() const noexcept; + bool IsStartupLoadingScripts() const noexcept; + //bool IsStartupLoadingSaves() const noexcept; + bool IsLoadingGeneric() const noexcept; + bool IsResyncing() const noexcept; + bool IsDestroyingWorld() const noexcept; - void SetServerMode( SERVMODE_TYPE mode ); bool IsValidBusy() const; + int GetExitFlag() const noexcept; void SetExitFlag(int iFlag) noexcept; - bool IsLoading() const noexcept; - bool IsResyncing() const noexcept; - void Shutdown( int64 iMinutes ); + void Shutdown( int64 iMinutes ); void SetSignals( bool fMsg = true ); bool SetProcessPriority(int iPriorityLevel); diff --git a/src/game/CServerConfig.cpp b/src/game/CServerConfig.cpp index 34dca2b51..f62fceb54 100644 --- a/src/game/CServerConfig.cpp +++ b/src/game/CServerConfig.cpp @@ -4,9 +4,10 @@ #include "../common/resource/sections/CRandGroupDef.h" #include "../common/resource/sections/CRegionResourceDef.h" #include "../common/resource/sections/CResourceNamedDef.h" +#include "../common/sphere_library/CSFileList.h" #include "../common/sphere_library/CSRand.h" -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CUOInstall.h" #include "../common/sphereversion.h" #include "../network/CClientIterator.h" @@ -665,7 +666,7 @@ enum RC_TYPE RC_NPCTRAINCOST, // m_iTrainSkillCost RC_NPCTRAINMAX, // m_iTrainSkillMax RC_NPCTRAINPERCENT, // m_iTrainSkillPercent - RC_NPCWANDERINGLOOKAROUNDCHANCE, // m_iNPCWanderingLookAroundChance + RC_NPCWANDERLOOKAROUNDCHANCE, // m_iNPCWanderLookAroundChance RC_NTSERVICE, // m_fUseNTService RC_OPTIONFLAGS, // _uiOptionFlags RC_OVERSKILLMULTIPLY, // m_iOverSkillMultiply @@ -1059,9 +1060,9 @@ bool CServerConfig::r_LoadVal( CScript &s ) ADDTOCALLSTACK("CServerConfig::r_LoadVal"); EXC_TRY("LoadVal"); -#define DEBUG_MSG_NOINIT(x) if (g_Serv.GetServerMode() != SERVMODE_PreLoadingINI) DEBUG_MSG(x) -#define LOG_WARN_NOINIT(x) if (g_Serv.GetServerMode() != SERVMODE_PreLoadingINI) g_Log.EventWarn(x) -#define LOG_ERR_NOINIT(x) if (g_Serv.GetServerMode() != SERVMODE_PreLoadingINI) g_Log.EventError(x) +#define DEBUG_MSG_NOINIT(x) if (g_Serv.GetServerMode() != ServMode::StartupPreLoadingIni) DEBUG_MSG(x) +#define LOG_WARN_NOINIT(x) if (g_Serv.GetServerMode() != ServMode::StartupPreLoadingIni) g_Log.EventWarn(x) +#define LOG_ERR_NOINIT(x) if (g_Serv.GetServerMode() != ServMode::StartupPreLoadingIni) g_Log.EventError(x) int i = FindCAssocRegTableHeadSorted( s.GetKey(), reinterpret_cast(sm_szLoadKeys), ARRAY_COUNT( sm_szLoadKeys )-1, sizeof(sm_szLoadKeys[0])); if ( i < 0 ) @@ -1102,7 +1103,7 @@ bool CServerConfig::r_LoadVal( CScript &s ) { if ( !strnicmp(pszStr, "ALLSECTORS", 10) ) { - const int nSectors = CSectorList::Get()->GetSectorQty(nMapNumber); + const int nSectors = CSectorList::Get().GetMapSectorDataUnchecked(nMapNumber).iSectorQty; pszStr = s.GetArgRaw(); if ( pszStr && *pszStr ) @@ -1111,7 +1112,7 @@ bool CServerConfig::r_LoadVal( CScript &s ) script.CopyParseState(s); for (int nIndex = 0; nIndex < nSectors; ++nIndex) { - CSector* pSector = CWorldMap::GetSector(nMapNumber, nIndex); + CSector* pSector = CWorldMap::GetSectorByIndex(nMapNumber, nIndex); ASSERT(pSector); pSector->r_Verb(script, &g_Serv); } @@ -1128,7 +1129,7 @@ bool CServerConfig::r_LoadVal( CScript &s ) pszStr = s.GetArgRaw(); if (pszStr && *pszStr) { - CSector* pSector = CWorldMap::GetSector(nMapNumber, iSecNumber); + CSector* pSector = CWorldMap::GetSectorByIndex(nMapNumber, iSecNumber); if (pSector) { CScript script(pszStr); @@ -1416,7 +1417,7 @@ bool CServerConfig::r_LoadVal( CScript &s ) case RC_SECURE: m_fSecure = (s.GetArgVal() != 0); - if ( !g_Serv.IsLoading() ) + if ( !g_Serv.IsLoadingGeneric() ) g_Serv.SetSignals(); break; @@ -1488,7 +1489,7 @@ bool CServerConfig::r_LoadVal( CScript &s ) break; case RC_NETWORKTHREADS: - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) { int iNetThreads = s.GetArgVal(); //if (iNetThreads < 0) @@ -1732,16 +1733,17 @@ bool CServerConfig::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * else if (!strnicmp(pszCmd, "SECTOR.", 7)) { pszCmd += 7; - const CSectorList* pSectors = CSectorList::Get(); + const CSectorList& pSectors = CSectorList::Get(); + const MapSectorsData& sd = pSectors.GetMapSectorDataUnchecked(iNumber); if (!strnicmp(pszCmd, "SIZE", 4)) - sVal.FormatVal(pSectors->GetSectorSize(iNumber)); + sVal.FormatVal(sd.iSectorSize); else if (!strnicmp(pszCmd, "ROWS", 4)) - sVal.FormatVal(pSectors->GetSectorRows(iNumber)); + sVal.FormatVal(sd.iSectorRows); else if (!strnicmp(pszCmd, "COLS", 4)) - sVal.FormatVal(pSectors->GetSectorCols(iNumber)); + sVal.FormatVal(sd.iSectorColumns); else if (!strnicmp(pszCmd, "QTY", 3)) - sVal.FormatVal(pSectors->GetSectorQty(iNumber)); + sVal.FormatVal(sd.iSectorQty); else return false; } @@ -1767,7 +1769,7 @@ bool CServerConfig::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * ptcKey = ptcKey + 6; int iSecNumber = Exp_GetVal(ptcKey); SKIP_SEPARATORS(ptcKey); - CSector* pSector = CWorldMap::GetSector(iMapNumber, iSecNumber); + CSector* pSector = CWorldMap::GetSectorByIndex(iMapNumber, iSecNumber); return !pSector ? false : pSector->r_WriteVal(ptcKey, sVal, pSrc); } } @@ -3009,7 +3011,231 @@ uint CServerConfig::GetPacketFlag( bool bCharlist, RESDISPLAY_VERSION res, uchar //************************************************************* -bool CServerConfig::LoadResourceSection( CScript * pScript ) +CResourceScript * CServerConfig::GetResourceFile( size_t i ) +{ + if ( ! m_ResourceFiles.IsValidIndex(i) ) + return nullptr; // All resource files we need to get blocks from later. + return m_ResourceFiles[i]; +} + +CResourceScript * CServerConfig::FindResourceFile( lpctstr pszPath ) +{ + ADDTOCALLSTACK("CResourceHolder::FindResourceFile"); + // Just match the titles ( not the whole path) + + lpctstr pszTitle = CScript::GetFilesTitle( pszPath ); + + for ( size_t i = 0; ; ++i ) + { + CResourceScript * pResFile = GetResourceFile(i); + if ( pResFile == nullptr ) + break; + lpctstr pszTitle2 = pResFile->GetFileTitle(); + if ( ! strcmpi( pszTitle2, pszTitle )) + return pResFile; + } + return nullptr; +} + +bool CServerConfig::OpenResourceFind( CScript &s, lpctstr pszFilename, bool fCritical ) +{ + ADDTOCALLSTACK("CServerConfig::OpenResourceFind"); + // Open a single resource script file. + // Look in the specified path. + + if ( pszFilename == nullptr ) + pszFilename = s.GetFilePath(); + + // search the local dir or full path first. + if (CSFile::FileExists(pszFilename)) + { + if (s.Open(pszFilename, OF_READ | OF_NONCRIT)) + return true; + if (!fCritical) + return false; + } + + // next, check the script file path + CSString sPathName = CSFile::GetMergedFileName( m_sSCPBaseDir, pszFilename ); + if (CSFile::FileExists(sPathName)) + { + if (s.Open(sPathName, OF_READ | OF_NONCRIT)) + return true; + } + + // finally, strip the directory and re-check script file path + lpctstr pszTitle = CSFile::GetFilesTitle(pszFilename); + sPathName = CSFile::GetMergedFileName( m_sSCPBaseDir, pszTitle ); + if (CSFile::FileExists(sPathName)) + { + return s.Open(sPathName, OF_READ); + } + + g_Log.Event(LOGM_INIT|LOGL_ERROR, "Can't find file '%s' in any of the expected paths!.\n", pszFilename); + return false; +} + + +CResourceScript * CServerConfig::AddResourceFile( lpctstr pszName ) +{ + ADDTOCALLSTACK("CResourceHolder::AddResourceFile"); + ASSERT(pszName != nullptr); + // Is this really just a dir name ? + + if (strlen(pszName) >= SPHERE_MAX_PATH) + throw CSError(LOGL_ERROR, 0, "Filename too long!"); + + tchar szName[SPHERE_MAX_PATH]; + Str_CopyLimitNull(szName, pszName, sizeof(szName)); + + tchar szTitle[SPHERE_MAX_PATH]; + lpctstr ptcTitle = CScript::GetFilesTitle(szName); + ASSERT_ALWAYS(strlen(ptcTitle) < sizeof(szTitle)); + Str_CopyLimitNull(szTitle, ptcTitle, sizeof(szTitle)); + + if ( szTitle[0] == '\0' ) + { + AddResourceDir( pszName ); + return nullptr; + } + + lpctstr pszExt = CScript::GetFilesExt( szTitle ); + if ( pszExt == nullptr ) + { + // No file extension provided, so append .scp to the filename + Str_ConcatLimitNull( szName, SPHERE_SCRIPT_EXT, sizeof(szName) ); + Str_ConcatLimitNull( szTitle, SPHERE_SCRIPT_EXT, sizeof(szTitle) ); + } + + if ( ! strnicmp( szTitle, SPHERE_FILE "tables", strlen(SPHERE_FILE "tables"))) + { + // Don't dupe this. + return nullptr; + } + + // Try to prevent dupes + CResourceScript * pNewRes = FindResourceFile(szTitle); + if ( pNewRes ) + return pNewRes; + + // Find correct path + + + pNewRes = new CResourceScript(); + if (! OpenResourceFind(static_cast(*pNewRes), szName)) + { + delete pNewRes; + return nullptr; + } + + m_ResourceFiles.emplace_back(pNewRes); + pNewRes->m_iResourceFileIndex = int(m_ResourceFiles.size() -1); + return pNewRes; +} + +void CServerConfig::AddResourceDir( lpctstr pszDirName ) +{ + ADDTOCALLSTACK("CServerConfig::AddResourceDir"); + if ( pszDirName[0] == '\0' ) + return; + + CSString sFilePath = CSFile::GetMergedFileName( pszDirName, "*" SPHERE_SCRIPT_EXT ); + + CSFileList filelist; + int iRet = filelist.ReadDir( sFilePath, false ); + if ( iRet < 0 ) + { + // also check script file path + sFilePath = CSFile::GetMergedFileName(m_sSCPBaseDir, sFilePath.GetBuffer()); + + iRet = filelist.ReadDir( sFilePath, true ); + if ( iRet < 0 ) + { + DEBUG_ERR(( "DirList=%d for '%s'\n", iRet, pszDirName )); + return; + } + } + + if ( iRet <= 0 ) // no files here. + return; + + // Load the files, but preordering them by file name. + // TODO (low priority): just rework CSStringList to CSStringCont/Vec and add a method to sort it + std::vector vecFileNames; + + // Collect them from the list + CSStringListRec * psFile = filelist.GetHead(), *psFileNext = nullptr; + for ( ; psFile; psFile = psFileNext ) + { + psFileNext = psFile->GetNext(); + vecFileNames.push_back(*psFile); + } + + // Order them by name (not including path, it is added later). + std::sort(vecFileNames.begin(), vecFileNames.end(), + [](lpctstr ptcFirst, lpctstr ptcSecond) noexcept {return strcmp(ptcFirst, ptcSecond) < 0;} + ); + + for (lpctstr elem : vecFileNames) + { + sFilePath = CSFile::GetMergedFileName(pszDirName, elem); + AddResourceFile( sFilePath ); + } +} + +bool CServerConfig::LoadResources( CResourceScript * pScript, bool fAddSorted ) +{ + ADDTOCALLSTACK("CServerConfig::LoadResources"); + // Open the file then load it. + if ( pScript == nullptr ) + return false; + + if ( ! pScript->Open()) + { + g_Log.Event(LOGL_CRIT|LOGM_INIT, "[RESOURCES] '%s' not found...\n", pScript->GetFilePath()); + return false; + } + + g_Log.Event(LOGM_INIT, "Loading %s\n", pScript->GetFilePath()); + + LoadResourcesOpen( pScript, fAddSorted ); + pScript->Close(); + pScript->CloseForce(); + return true; +} + +CResourceScript * CServerConfig::LoadResourcesAdd( lpctstr pszNewFileName ) +{ + ADDTOCALLSTACK("CServerConfig::LoadResourcesAdd"); + // Make sure this is added to my list of resource files + // And load it now. + + CResourceScript * pScript = AddResourceFile( pszNewFileName ); + if ( ! LoadResources(pScript, true) ) + return nullptr; + return pScript; +} + +void CServerConfig::LoadResourcesOpen( CScript * pScript, bool fAddSorted ) +{ + ADDTOCALLSTACK("CServerConfig::LoadResourcesOpen"); + // Load an already open resource file. + + ASSERT(pScript); + ASSERT( pScript->HasCache() ); + + int iSections = 0; + while ( pScript->FindNextSection() ) + { + LoadResourceSection( pScript, fAddSorted ); + ++iSections; + } + + if ( ! iSections ) + DEBUG_WARN(( "No resource sections in '%s'\n", pScript->GetFilePath())); +} + +bool CServerConfig::LoadResourceSection( CScript * pScript, bool fInsertSorted ) { ADDTOCALLSTACK("CServerConfig::LoadResourceSection"); // Index or read any resource sections we know how to handle. @@ -3061,7 +3287,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) if (( restype == RES_WORLDSCRIPT ) || ( restype == RES_WS )) { const lpctstr pszDef = pScript->GetArgStr(); - CVarDefCont * pVarBase = g_Exp.m_VarResDefs.GetKey( pszDef ); + CVarDefCont * pVarBase = g_ExprGlobals.mtEngineLockedReader()->m_VarResDefs.GetKey( pszDef ); pVarNum = nullptr; if ( pVarBase ) pVarNum = dynamic_cast ( pVarBase ); @@ -3101,7 +3327,8 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) // Create a new index for the block. // NOTE: rid is not created for all types. // NOTE: GetArgStr() is not always the DEFNAME - rid = ResourceGetNewID( restype, pScript->GetArgStr(), &pVarNum, fNewStyleDef ); + lpctstr ptcScriptArg = pScript->GetArgStr(); + rid = ResourceGetNewID( restype, ptcScriptArg, &pVarNum, fNewStyleDef ); } if ( !rid.IsValidUID() ) @@ -3121,14 +3348,18 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) if ( m_ResourceList.ContainsKey( const_cast(pszSection) )) { // Add to DEFLIST - CListDefCont* pListBase = g_Exp.m_ListInternals.GetKey(pszSection); + auto rw = g_ExprGlobals.mtEngineLockedWriter(); + CListDefCont* pListBase = rw->m_ListInternals.GetKey(pszSection); if ( !pListBase ) - pListBase = g_Exp.m_ListInternals.AddList(pszSection); + pListBase = rw->m_ListInternals.AddList(pszSection); if ( pListBase ) pListBase->r_LoadVal(pScript->GetArgStr()); } + auto _resHashAddFunction = fInsertSorted ? &CResourceHash::AddSortKey : &CResourceHash::AddUnsortedKey; + #define RESHASH_ADD (m_ResHash.* _resHashAddFunction) + switch ( restype ) { case RES_SPHERE: @@ -3183,12 +3414,13 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) if ( fNewStyleDef ) { // search for this. + auto gread = g_ExprGlobals.mtEngineLockedReader(); size_t l; for ( l = 0; l < DEFMSG_QTY; ++l ) { - if ( !strcmpi(ptcKey, g_Exp.sm_szMsgNames[l]) ) + if ( !strcmpi(ptcKey, gread->sm_szDefMsgNames[l]) ) { - Str_CopyLimitNull(g_Exp.sm_szMessages[l], pScript->GetArgStr(), sizeof(g_Exp.sm_szMessages[l])); + Str_CopyLimitNull(gread->sm_szDefMessages[l], pScript->GetArgStr(), sizeof(CExprGlobals::sm_szDefMessages[l])); break; } } @@ -3198,20 +3430,23 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) } else { - g_Exp.m_VarDefs.SetStr(ptcKey, false, pScript->GetArgStr(), false); + g_ExprGlobals.mtEngineLockedWriter()->m_VarDefs.SetStr(ptcKey, false, pScript->GetArgStr(), false); } } + return true; case RES_RESDEFNAME: + { // just get a block of resource aliases (like a classic DEF). + auto gwrite = g_ExprGlobals.mtEngineLockedWriter(); while (pScript->ReadKeyParse()) { const lpctstr ptcKey = pScript->GetKey(); - g_Exp.m_VarResDefs.SetStr(ptcKey, false, pScript->GetArgStr(), false); + gwrite->m_VarResDefs.SetStr(ptcKey, false, pScript->GetArgStr(), false); } return true; - + } case RES_RESOURCELIST: { while ( pScript->ReadKey() ) @@ -3374,11 +3609,12 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) } else { - if ( rid.GetResIndex() >= (uint)(m_iMaxSkill) ) - m_iMaxSkill = rid.GetResIndex() + 1; + const uint uiResIdx = rid.GetResIndex(); + if ( uiResIdx >= (uint)(m_iMaxSkill) ) + m_iMaxSkill = uiResIdx + 1; // Just replace any previous CSkillDef - pSkill = new CSkillDef((SKILL_TYPE)(rid.GetResIndex())); + pSkill = new CSkillDef((SKILL_TYPE)uiResIdx); } ASSERT(pSkill); @@ -3430,7 +3666,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } ASSERT(pScript); @@ -3466,7 +3702,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } break; case RES_DIALOG: @@ -3484,7 +3720,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } break; @@ -3503,7 +3739,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } { CScriptLineContext LineContext = pScript->GetContext(); @@ -3525,7 +3761,8 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) } else { - CRegionWorld * pRegion = new CRegionWorld( rid, pScript->GetArgStr()); + lpctstr ptcScriptArg = pScript->GetArgStr(); + CRegionWorld * pRegion = new CRegionWorld(rid, ptcScriptArg); pRegion->r_Load( *pScript ); if (!pRegion->RealizeRegion()) { @@ -3535,7 +3772,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) { pNewDef = pRegion; ASSERT(pNewDef); - m_ResHash.AddSortKey( rid, pRegion ); + RESHASH_ADD( rid, pRegion ); // if it's old style but has a defname, it's already set via r_Load, // so this will do nothing, which is good // if ( !fNewStyleDef ) @@ -3557,7 +3794,8 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) } else { - CRegion * pRegion = new CRegion( rid, pScript->GetArgStr()); + lpctstr ptcScriptArg = pScript->GetArgStr(); + CRegion * pRegion = new CRegion( rid, ptcScriptArg ); pNewDef = pRegion; ASSERT(pNewDef); pRegion->r_Load(*pScript); @@ -3567,7 +3805,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) } else { - m_ResHash.AddSortKey( rid, pRegion ); + RESHASH_ADD( rid, pRegion ); // if it's old style but has a defname, it's already set via r_Load, // so this will do nothing, which is good // if ( !fNewStyleDef ) @@ -3592,7 +3830,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } { CScriptLineContext LineContext = pScript->GetContext(); @@ -3617,7 +3855,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey(rid, pNewLink); + RESHASH_ADD(rid, pNewLink); } } { @@ -3641,7 +3879,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } { CScriptLineContext LineContext = pScript->GetContext(); @@ -3677,7 +3915,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) CResourceScript* pLinkResScript = dynamic_cast(pScript); if (pLinkResScript != nullptr) pNewLink->SetLink(pLinkResScript); // So later i can retrieve m_iResourceFileIndex and m_iLineNum from the CResourceScript - m_ResHash.AddSortKey( rid, pNewLink ); + RESHASH_ADD( rid, pNewLink ); } break; @@ -3803,7 +4041,7 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) pResDef = new CItemTypeDef( ridnew ); ASSERT(pResDef); pResDef->SetResourceName( ptcName ); - m_ResHash.AddSortKey( ridnew, pResDef ); + RESHASH_ADD( ridnew, pResDef ); } } @@ -3857,16 +4095,18 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) ptcKey = ptcKey + 4; lpctstr ptcArg = pScript->GetArgStr( &fQuoted ); - g_Exp.m_VarGlobals.SetStr( ptcKey, fQuoted, ptcArg ); + g_ExprGlobals.mtEngineLockedWriter()->m_VarGlobals.SetStr( ptcKey, fQuoted, ptcArg ); } return true; case RES_WORLDLISTS: { - CListDefCont* pListBase = g_Exp.m_ListGlobals.AddList(pScript->GetArgStr()); + lpctstr ptcScriptArg = pScript->GetArgStr(); + auto gWriter = g_ExprGlobals.mtEngineLockedWriter(); + CListDefCont* pListBase = gWriter->m_ListGlobals.AddList(ptcScriptArg); if ( !pListBase ) { - DEBUG_ERR(("Unable to create list '%s'...\n", pScript->GetArgStr())); + DEBUG_ERR(("Unable to create list '%s'...\n", ptcScriptArg)); return false; } @@ -3962,7 +4202,10 @@ bool CServerConfig::LoadResourceSection( CScript * pScript ) EXC_CATCH; EXC_DEBUG_START; - g_Log.EventDebug("ExcInfo: section '%s' key '%s' args '%s'\n", pszSection, pScript ? pScript->GetKey() : "", pScript ? pScript->GetArgStr() : ""); + g_Log.EventDebug("ExcInfo: section '%s' key '%s' args '%s'\n", + pszSection, + pScript ? pScript->GetKey() : "", + pScript ? pScript->GetArgStr() : ""); EXC_DEBUG_END; return false; } @@ -4113,8 +4356,7 @@ CResourceID CServerConfig::ResourceGetNewID( RES_TYPE restype, lpctstr pszName, break; } - - int iIndex; + int iIndex; if ( pszName ) { if ( pszName[0] == '\0' ) // absence of resourceid = index 0 @@ -4158,7 +4400,7 @@ CResourceID CServerConfig::ResourceGetNewID( RES_TYPE restype, lpctstr pszName, return rid; } #ifdef _DEBUG - if ( g_Serv.GetServerMode() != SERVMODE_ResyncLoad ) // this really is ok. + if ( g_Serv.GetServerMode() != ServMode::ResyncLoad ) // this really is ok. { // Warn of duplicates. size_t duplicateIndex = m_ResHash.FindKey( rid ); @@ -4170,7 +4412,7 @@ CResourceID CServerConfig::ResourceGetNewID( RES_TYPE restype, lpctstr pszName, } - CVarDefCont * pVarBase = g_Exp.m_VarResDefs.GetKey( pszName ); + CVarDefCont * pVarBase = g_ExprGlobals.mtEngineLockedReader()->m_VarResDefs.GetKey( pszName ); if ( pVarBase ) { // An existing VarDef with the same name ? @@ -4340,7 +4582,7 @@ CResourceID CServerConfig::ResourceGetNewID( RES_TYPE restype, lpctstr pszName, int iRandIndex = iIndex + g_Rand.GetVal(iHashRange); rid = CResourceID(restype, iRandIndex, wPage); - const bool fCheckPage = (pszName && (g_Exp.m_VarResDefs.GetKeyNum(pszName) != 0)); + const bool fCheckPage = (pszName && (g_ExprGlobals.mtEngineLockedReader()->m_VarResDefs.GetKeyNum(pszName) != 0)); while (true) { if (fCheckPage) @@ -4365,7 +4607,7 @@ CResourceID CServerConfig::ResourceGetNewID( RES_TYPE restype, lpctstr pszName, if ( pszName ) { - CVarDefContNum* pVarTemp = g_Exp.m_VarResDefs.SetNum( pszName, rid.GetPrivateUID() ); + CVarDefContNum* pVarTemp = g_ExprGlobals.mtEngineLockedWriter()->m_VarResDefs.SetNum( pszName, rid.GetPrivateUID() ); ASSERT(pVarTemp); *ppVarNum = pVarTemp; } @@ -4514,7 +4756,7 @@ void CServerConfig::_OnTick( bool fNow ) { ADDTOCALLSTACK("CServerConfig::_OnTick"); // Give a tick to the less critical stuff. - if ( !fNow && ( g_Serv.IsLoading() || ( m_timePeriodic > CWorldGameTime::GetCurrentTime().GetTimeRaw()) ) ) + if ( !fNow && ( g_Serv.IsLoadingGeneric() || ( m_timePeriodic > CWorldGameTime::GetCurrentTime().GetTimeRaw()) ) ) return; if ( this->m_fUseHTTP ) @@ -4548,7 +4790,7 @@ void CServerConfig::_OnTick( bool fNow ) void CServerConfig::PrintEFOFFlags(bool bEF, bool bOF, CTextConsole *pSrc) { ADDTOCALLSTACK("CServerConfig::PrintEFOFFlags"); - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) return; #define catresname(a,b) \ @@ -4660,7 +4902,7 @@ bool CServerConfig::LoadIni(bool fTest) return false; } - LoadResourcesOpen(&m_scpIni); + LoadResourcesOpen(&m_scpIni, true); m_scpIni.Close(); m_scpIni.CloseForce(); @@ -4694,7 +4936,7 @@ bool CServerConfig::LoadCryptIni( void ) return false; } - LoadResourcesOpen(&m_scpCryptIni); + LoadResourcesOpen(&m_scpCryptIni, true); m_scpCryptIni.Close(); m_scpCryptIni.CloseForce(); @@ -4816,7 +5058,7 @@ bool CServerConfig::Load( bool fResync ) } g_Log.Event(LOGL_EVENT|LOGM_INIT, "Loading table definitions file (" SPHERE_FILE "tables" SPHERE_SCRIPT_EXT ")...\n"); - LoadResourcesOpen(&m_scpTables); + LoadResourcesOpen(&m_scpTables, true); m_scpTables.Close(); } else @@ -4851,16 +5093,28 @@ bool CServerConfig::Load( bool fResync ) if ( !pResFile ) break; - if ( !fResync ) - LoadResources( pResFile ); - else + if ( !fResync ) + { + // It's the startup load, sort everything just once at the end? + // TODO: not a good idea for now, because we might reference a resource while loading another resource, + // at the current state. + // Also, is it worth it by a performance cost? It looks like the greatest part of the cpu time is consumed in unique_ptr moving. + LoadResources( pResFile, true /*false*/ ); + } + else pResFile->ReSync(); g_Serv.PrintPercent( (size_t)(j + 1), count); } + //if (!fResync) + // m_ResHash.SortStep(); + + g_ExprGlobals.mtEngineLockedWriter()->UpdateDefMsgDependentData(); + // Now that we have parsed every script, we can end the configuration of some resources... - // ROOMs have to inherit stuff from the parent AREADEF + + // ROOMs have to inherit stuff from the parent AREADEF for (CRegion* pCurRegion : m_RegionDefs) { const RES_TYPE resType = pCurRegion->GetResourceID().GetResType(); @@ -4911,7 +5165,7 @@ bool CServerConfig::Load( bool fResync ) // must have at least 1 skill class. CSkillClassDef * pSkillClass = new CSkillClassDef( CResourceID( RES_SKILLCLASS )); ASSERT(pSkillClass); - m_ResHash.AddSortKey( CResourceID( RES_SKILLCLASS, 0 ), pSkillClass ); + m_ResHash.AddSortKey( CResourceID( RES_SKILLCLASS, 0 ), pSkillClass ); } if ( !fResync ) @@ -5002,26 +5256,47 @@ bool CServerConfig::Load( bool fResync ) lpctstr CServerConfig::GetDefaultMsg(int lKeyNum) { - ADDTOCALLSTACK("CServerConfig::GetDefaultMsg"); + ADDTOCALLSTACK("CServerConfig::GetDefaultMsg(int)"); if (( lKeyNum < 0 ) || ( lKeyNum >= DEFMSG_QTY )) { g_Log.EventError("Defmessage %d out of range [0..%d]\n", lKeyNum, DEFMSG_QTY-1); return ""; } - return g_Exp.sm_szMessages[lKeyNum]; + +#if MT_ENGINES + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + lpctstr ptcRet = Str_mtEngineGetSafeTemp(CExprGlobals::sm_szDefMessages[lKeyNum]); + return ptcRet; +#else + return Str_mtEngineGetSafeTemp(CExprGlobals::sm_szDefMessages[lKeyNum]); +#endif } lpctstr CServerConfig::GetDefaultMsg(lpctstr ptcKey) { - ADDTOCALLSTACK("CServerConfig::GetDefaultMsg"); - for (int i = 0; i < DEFMSG_QTY; ++i ) - { - if ( !strcmpi(ptcKey, g_Exp.sm_szMsgNames[i]) ) - return g_Exp.sm_szMessages[i]; + ADDTOCALLSTACK("CServerConfig::GetDefaultMsg(lpctstr)"); + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + uint i; + for (i = 0; i < DEFMSG_QTY; ++i ) + { + if ( !strcmpi(ptcKey, gReader->sm_szDefMsgNames[i]) ) + break; } - g_Log.EventError("Defmessage \"%s\" non existent\n", ptcKey); - return ""; + + if (i == DEFMSG_QTY) + { + g_Log.EventError("Defmessage \"%s\" non existent\n", ptcKey); + return ""; + } + +#if MT_ENGINES + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + lpctstr ptcRet = Str_mtEngineGetSafeTemp(CExprGlobals::sm_szDefMessages[i]); + return ptcRet; +#else + return Str_mtEngineGetSafeTemp(CExprGlobals::sm_szDefMessages[i]); +#endif } bool CServerConfig::GenerateDefname(tchar *pObjectName, size_t iInputLength, lpctstr pPrefix, TemporaryString *pOutput, bool bCheckConflict, CVarDefMap* vDefnames) @@ -5078,10 +5353,11 @@ bool CServerConfig::GenerateDefname(tchar *pObjectName, size_t iInputLength, lpc size_t iEnd = iOut; int iAttempts = 1; + auto gReader = g_ExprGlobals.mtEngineLockedReader(); for (;;) { bool isValid = true; - if (g_Exp.m_VarResDefs.GetKey(buf) != nullptr) + if (gReader->m_VarResDefs.GetKey(buf) != nullptr) { // check loaded defnames isValid = false; diff --git a/src/game/CServerConfig.h b/src/game/CServerConfig.h index 6c69d4d46..534b6b677 100644 --- a/src/game/CServerConfig.h +++ b/src/game/CServerConfig.h @@ -654,7 +654,6 @@ extern class CServerConfig : public CResourceHolder CResourceHashArray m_WebPages; // These can be linked back to the script. private: - CResourceID ResourceGetNewID( RES_TYPE restype, lpctstr pszName, CVarDefContNum ** ppVarNum, bool fNewStyleDef ); char m_iniDirectory[SPHERE_MAX_PATH]; public: @@ -665,6 +664,12 @@ extern class CServerConfig : public CResourceHolder CServerConfig(const CServerConfig& copy) = delete; CServerConfig& operator=(const CServerConfig& other) = delete; +private: + CResourceID ResourceGetNewID( RES_TYPE restype, lpctstr pszName, CVarDefContNum ** ppVarNum, bool fNewStyleDef ); + + CResourceScript * AddResourceFile( lpctstr pszName ); + void AddResourceDir( lpctstr pszDirName ); + public: virtual bool r_LoadVal( CScript &s ) override; virtual bool r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc = nullptr, bool fNoCallParent = false, bool fNoCallChildren = false ) override; @@ -706,14 +711,25 @@ extern class CServerConfig : public CResourceHolder void Unload( bool fResync ); void _OnTick( bool fNow ); + // TODO: add doxygen-style comments + CResourceScript * FindResourceFile( lpctstr pszTitle ); + CResourceScript * LoadResourcesAdd( lpctstr pszNewName ); + + void LoadResourcesOpen( CScript * pScript, bool fAddSorted ); + bool LoadResources(CResourceScript * pScript, bool fAddSorted); + + CResourceScript * GetResourceFile( size_t i ); + + bool OpenResourceFind( CScript &s, lpctstr pszFilename, bool fCritical = true ); + /** * @brief Loads resource section ([SKILL ], [SPELL ], [CHARDEF ]...). * - * @param [in,out] pScript If non-null, the script. + * @param pScript to be loaded. * * @return true if it succeeds, false if it fails. */ - virtual bool LoadResourceSection( CScript * pScript ) override; + bool LoadResourceSection( CScript * pScript, bool fInsertSorted); /** * @brief Sort all spells in order. @@ -726,7 +742,7 @@ extern class CServerConfig : public CResourceHolder * * @param restype Resource Type. * - * @param ptcName Resource Name. + * @param pszName Resource Name. * * @param wPage Resource Page attribute. * @@ -839,7 +855,7 @@ extern class CServerConfig : public CResourceHolder /** * @brief Get the def for the server in position 'index' in servers list. * - * @param pszText The server index. + * @param index The server index. * * @return CServerRef of the server. */ @@ -999,7 +1015,6 @@ extern class CServerConfig : public CResourceHolder * * @param [in,out] pChar If non-null, the character. * @param [in,out] pCharTarg If non-null, the character targ. - * @param skill The skill. * * @return The calculated combat chance to hit. */ @@ -1009,7 +1024,7 @@ extern class CServerConfig : public CResourceHolder * @brief Calculates the combat chance to parry. * * @param [in,out] pChar If non-null, the character attempting to parry. - * @param skill The skill. + * @param pItemParry @todo * * @return The calculated combat chance to parry. */ @@ -1135,7 +1150,7 @@ extern class CServerConfig : public CResourceHolder /** * @brief Gets default message (sphere_msgs.scp). * - * @param ptcKey The key. + * @param lKeyNum The key. * * @return The default message. */ @@ -1153,7 +1168,6 @@ typedef std::map KRGumpsMap; } g_Cfg; - #define IsSetEF(ef) ((g_Cfg._uiExperimentalFlags & ef) != 0) #define IsSetOF(of) ((g_Cfg._uiOptionFlags & of) != 0) #define IsSetCombatFlags(cf) ((g_Cfg.m_iCombatFlags & cf) != 0) diff --git a/src/game/CServerDef.cpp b/src/game/CServerDef.cpp index ec84ff743..911126e73 100644 --- a/src/game/CServerDef.cpp +++ b/src/game/CServerDef.cpp @@ -1,10 +1,10 @@ -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../common/sphereproto.h" #include "../common/sphereversion.h" #include "../common/CLog.h" -#include "../common/CScriptTriggerArgs.h" #include "../sphere/threads.h" #include "CServer.h" #include "CServerConfig.h" @@ -294,8 +294,8 @@ static lpctstr constexpr sm_AccAppTable[ ACCAPP_QTY ] = "CLOSED", // Closed. Not accepting more. "UNUSED", "FREE", // Anyone can just log in and create a full account. - "GUESTAUTO", // You get to be a guest and are automatically sent email with u're new password. - "GUESTTRIAL", // You get to be a guest til u're accepted for full by an Admin. + "GUESTAUTO", // You get to be a guest and are automatically sent email with your new password. + "GUESTTRIAL", // You get to be a guest until you are accepted for full by an Admin. "UNUSED", "UNSPECIFIED", // Not specified. "UNUSED", @@ -498,7 +498,7 @@ bool CServerDef::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc sVal = GetName(); break; } - sVal.Format("%s", static_cast(m_sURL), GetName()); + sVal.Format("%s", static_cast(m_sURL), GetName()); break; case SC_VERSION: sVal = SPHERE_BUILD_INFO_STR; @@ -514,8 +514,9 @@ bool CServerDef::r_WriteVal( lpctstr ptcKey, CSString &sVal, CTextConsole * pSrc if (pszArgs != nullptr) GETNONWHITESPACE(pszArgs); - CScriptTriggerArgs Args( pszArgs ? pszArgs : "" ); - if ( r_Call( uiFunctionIndex, pSrc, &Args, &sVal ) ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init( pszArgs ? pszArgs : "" ); + if ( r_Call( uiFunctionIndex, pScriptArgs, pSrc, &sVal ) ) return true; } return (fNoCallParent ? false : CScriptObj::r_WriteVal( ptcKey, sVal, pSrc, false )); diff --git a/src/game/CServerDef.h b/src/game/CServerDef.h index 120c5332f..3619d4c00 100644 --- a/src/game/CServerDef.h +++ b/src/game/CServerDef.h @@ -78,7 +78,7 @@ class CServerDef : public CScriptObj public: lpctstr GetStatus() const noexcept; - + size_t StatGet( SERV_STAT_TYPE i ) const; void StatInc( SERV_STAT_TYPE i ); void StatDec( SERV_STAT_TYPE i ); diff --git a/src/game/CServerTime.cpp b/src/game/CServerTime.cpp index 4e7ef22ce..05f04c3f2 100644 --- a/src/game/CServerTime.cpp +++ b/src/game/CServerTime.cpp @@ -1,4 +1,4 @@ -#include "../common/CExpression.h" +//#include "../common/CExpression.h" // included in the precompiled header #include "CServerConfig.h" #include "CServerTime.h" diff --git a/src/game/CStartLoc.h b/src/game/CStartLoc.h index 3321017ab..b455b17ad 100644 --- a/src/game/CStartLoc.h +++ b/src/game/CStartLoc.h @@ -19,7 +19,7 @@ class CStartLoc // The start locations for creating a new char. m_sArea(pszArea), iClilocDescription(1149559) {} ~CStartLoc() noexcept = default; - + CStartLoc(const CStartLoc& copy) = delete; CStartLoc& operator=(const CStartLoc& other) = delete; }; diff --git a/src/game/CTeleport.cpp b/src/game/CTeleport.cpp index 871ce7019..8f6cde8f1 100644 --- a/src/game/CTeleport.cpp +++ b/src/game/CTeleport.cpp @@ -31,7 +31,7 @@ CTeleport::CTeleport(tchar* pszArgs) bool CTeleport::RealizeTeleport() { ADDTOCALLSTACK("CTeleport::RealizeTeleport"); - if (!IsCharValid() || !_ptDst.IsCharValid()) + if (!IsCharValid() || !_ptDst.IsCharValid()) { DEBUG_ERR(("CTeleport bad coords %s\n", WriteUsed())); return false; diff --git a/src/game/CTimedFunction.cpp b/src/game/CTimedFunction.cpp index b81415b2e..aed86c8b6 100644 --- a/src/game/CTimedFunction.cpp +++ b/src/game/CTimedFunction.cpp @@ -26,12 +26,12 @@ bool CTimedFunction::IsDeleted() const // virtual } -bool CTimedFunction::_CanTick() const // virtual +bool CTimedFunction::_TickableState() const // virtual { return true; } -bool CTimedFunction::CanTick() const // virtual +bool CTimedFunction::TickableState() const // virtual { return true; } @@ -85,12 +85,12 @@ bool CTimedFunction::OnTick() // virtual CUID uid; CScript s; { - MT_ENGINE_SHARED_LOCK_SET; + MT_ENGINE_SHARED_LOCK_SET(this); uid.SetPrivateUID(_uidAttached); s.ParseKey(_ptcCommand); } - delete this; // This has to be the last function call to ever access this object! + delete this; // This has to be the last function/statement call to ever access this object! // From now on, this object does NOT exist anymore! return _ExecTimedFunction(std::move(uid), std::move(s)); diff --git a/src/game/CTimedFunction.h b/src/game/CTimedFunction.h index 1d21f3c0e..93933d38a 100644 --- a/src/game/CTimedFunction.h +++ b/src/game/CTimedFunction.h @@ -35,8 +35,8 @@ class CTimedFunction : public CTimedObject, CSObjContRec return _ptcCommand; } -protected: virtual bool _CanTick() const override; -public: virtual bool CanTick() const override; +protected: virtual bool _TickableState() const override; +public: virtual bool TickableState() const override; protected: virtual bool _OnTick() override; public: virtual bool OnTick() override; diff --git a/src/game/CTimedFunctionHandler.cpp b/src/game/CTimedFunctionHandler.cpp index 85b4fcd72..5175dcb4e 100644 --- a/src/game/CTimedFunctionHandler.cpp +++ b/src/game/CTimedFunctionHandler.cpp @@ -64,7 +64,7 @@ void CTimedFunctionHandler::Clear() } TRIGRET_TYPE CTimedFunctionHandler::Loop(lpctstr ptcCommand, int iLoopsMade, CScriptLineContext StartContext, - CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult) + CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult) { ADDTOCALLSTACK("CTimedFunctionHandler::Loop"); for (CSObjContRec* obj : _timedFunctions.GetIterationSafeCont()) @@ -86,7 +86,7 @@ TRIGRET_TYPE CTimedFunctionHandler::Loop(lpctstr ptcCommand, int iLoopsMade, CSc break; } - TRIGRET_TYPE iRet = pObj->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pObj->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if (iRet == TRIGRET_BREAK) { diff --git a/src/game/CTimedFunctionHandler.h b/src/game/CTimedFunctionHandler.h index f877512e0..b2085c5c4 100644 --- a/src/game/CTimedFunctionHandler.h +++ b/src/game/CTimedFunctionHandler.h @@ -40,7 +40,7 @@ class CTimedFunctionHandler void Stop(const CUID& uid, lpctstr ptcCommand); void Clear(); TRIGRET_TYPE Loop(lpctstr ptcCommand, int iLoopsMade, CScriptLineContext StartContext, - CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult); + CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult); int64 IsTimer(const CUID& uid, lpctstr ptcCommand) const; }; #endif // _INC_CTIMEDFUNCTIONHANDLER_H diff --git a/src/game/CTimedObject.cpp b/src/game/CTimedObject.cpp index 4cbd77785..aef9a92e4 100644 --- a/src/game/CTimedObject.cpp +++ b/src/game/CTimedObject.cpp @@ -1,4 +1,4 @@ -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../sphere/ProfileTask.h" #include "../sphere/threads.h" #include "CWorldGameTime.h" @@ -37,16 +37,16 @@ void CTimedObject::_GoAwake() _fIsSleeping = false; } -bool CTimedObject::_CanTick() const +bool CTimedObject::_TickableState() const { - //ADDTOCALLSTACK_DEBUG("CTimedObject::_CanTick"); + //ADDTOCALLSTACK_DEBUG("CTimedObject::_TickableState"); return !_IsSleeping(); } -bool CTimedObject::CanTick() const +bool CTimedObject::TickableState() const { - //ADDTOCALLSTACK_DEBUG("CTimedObject::CanTick"); - MT_ENGINE_SHARED_LOCK_RETURN(_CanTick()); + //ADDTOCALLSTACK_DEBUG("CTimedObject::_TickableState"); + MT_ENGINE_SHARED_LOCK_RETURN(_TickableState()); } bool CTimedObject::OnTick() @@ -92,7 +92,7 @@ void CTimedObject::_SetTimeout(int64 iDelayInMsecs) void CTimedObject::SetTimeout(int64 iDelayInMsecs) { ADDTOCALLSTACK_DEBUG("CTimedObject::SetTimeout"); - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); _SetTimeout(iDelayInMsecs); } @@ -175,13 +175,13 @@ int64 CTimedObject::GetTimerSAdjusted() const noexcept void CTimedObject::GoSleep() { - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); _GoSleep(); } void CTimedObject::GoAwake() { - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); _GoAwake(); // Call virtuals! } @@ -192,7 +192,7 @@ PROFILE_TYPE CTimedObject::GetProfileType() const noexcept void CTimedObject::ClearTimeoutRaw() noexcept { - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); CTimedObject::_ClearTimeoutRaw(); } diff --git a/src/game/CTimedObject.h b/src/game/CTimedObject.h index 9f4e78665..6b73d1be2 100644 --- a/src/game/CTimedObject.h +++ b/src/game/CTimedObject.h @@ -65,8 +65,8 @@ public: PROFILE_TYPE GetProfileType() const noexcept; /** * @brief Determine if the object is in a "tickable" state. */ -protected: virtual bool _CanTick() const; // TODO: locks need to be extended to derived classes -public: virtual bool CanTick() const; +protected: virtual bool _TickableState() const; // TODO: locks need to be extended to derived classes +public: virtual bool TickableState() const; /** * @brief Executes the tick action. @@ -101,14 +101,14 @@ public: virtual void SetTimeout(int64 iDelayInMsecs); /** * @brief < Timer. - * @param iDelayInSecs Delay in seconds. + * @param iSeconds Delay in seconds. */ protected: void _SetTimeoutS(int64 iSeconds); public: void SetTimeoutS(int64 iSeconds); /** * @brief < Timer. - * @param iDelayInTenths Delay in tenths of second. + * @param iTenths Delay in tenths of second. */ protected: void _SetTimeoutD(int64 iTenths); public: void SetTimeoutD(int64 iTenths); diff --git a/src/game/CWorld.cpp b/src/game/CWorld.cpp index be06000d4..1b1299cc8 100644 --- a/src/game/CWorld.cpp +++ b/src/game/CWorld.cpp @@ -1,5 +1,6 @@ -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../common/CLog.h" #include "../common/sphereversion.h" #include "../network/CClientIterator.h" @@ -215,7 +216,6 @@ static void ReportGarbageCollection(CObjBase * pObj, int iResultCode) } - ////////////////////////////////////////////////////////////////// // -CWorldThread @@ -413,10 +413,10 @@ void CWorldThread::ScheduleObjDeletion(CSObjContRec* obj) { // If the world is being destroyed, do not schedule the object for deletion but delete it right away. const auto servMode = g_Serv.GetServerMode(); - // I can't destroy it while SERVMODE_Loading, because the script parser can't know (without creating a global state holder, TODO) that this + // I can't destroy it while ServMode::LoadingScripts/Saves, because the script parser can't know (without creating a global state holder, TODO) that this // object was deleted/destroyed. The object pointer will become invalid, and if something uses it, even for calling a method, Sphere will crash. - //const bool fDestroy = (servMode == SERVMODE_Exiting || servMode == SERVMODE_Loading); - const bool fDestroy = (servMode == SERVMODE_Exiting); + //const bool fDestroy = g_Serv.IsDestroyingWorld(); + const bool fDestroy = (servMode == ServMode::Exiting); if (fDestroy) { @@ -615,20 +615,18 @@ void CWorldThread::GarbageCollection_UIDs() int iResultCode = FixObj(pObj, i); if ( iResultCode ) { - // FixObj directly calls Delete method - //if (pObj->_IsBeingDeleted() || pObj->IsDeleted()) - //{ + if (pObj->_IsBeingDeleted() || pObj->IsDeleted()) + { // Do an immediate delete here instead of Delete() delete pObj; FreeUID(i); // Get rid of junk uid if all fails.. - //} - /* - else + } + else { + // FixObj doesn't always delete the item on failure/result code != 0. pObj->Delete(); } - */ - continue; + continue; } if ((iCount & 0x1FF ) == 0) @@ -845,9 +843,11 @@ bool CWorld::SaveStage() // Save world state in stages. _Ticker._TimedFunctions.r_Write(m_FileData); m_FileData.WriteSection("GLOBALS"); - g_Exp.m_VarGlobals.r_WritePrefix(m_FileData, nullptr); - - g_Exp.m_ListGlobals.r_WriteSave(m_FileData); + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + gReader->m_VarGlobals.r_WritePrefix(m_FileData, nullptr); + gReader->m_ListGlobals.r_WriteSave(m_FileData); + } const size_t iQty = g_Cfg.m_RegionDefs.size(); for ( size_t i = 0; i < iQty; ++i ) @@ -895,14 +895,14 @@ bool CWorld::SaveStage() // Save world state in stages. llong llTicksStart = _iSaveTimer; TIME_PROFILE_END; - tchar * time = Str_GetTemp(); - snprintf(time, Str_TempLength(), "%lld.%04lld", TIME_PROFILE_GET_HI, TIME_PROFILE_GET_LO); + tchar * ptcTime = Str_GetTemp(); + snprintf(ptcTime, Str_TempLength(), "%lld.%04lld", TIME_PROFILE_GET_HI, TIME_PROFILE_GET_LO); - g_Log.Event(LOGM_SAVE, "World save completed, took %s seconds.\n", time); + g_Log.Event(LOGM_SAVE, "World save completed, took %s seconds.\n", ptcTime); - CScriptTriggerArgs Args; - Args.Init(time); - g_Serv.r_Call("f_onserver_save_finished", &g_Serv, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(ptcTime); + g_Serv.r_Call("f_onserver_save_finished", pScriptArgs, &g_Serv); // Now clean up all the held over UIDs SaveThreadClose(); @@ -945,7 +945,7 @@ bool CWorld::SaveForce() // Save world state if (g_NetworkManager.isOutputThreaded() == false) g_NetworkManager.flushAllClients(); - g_Serv.SetServerMode(SERVMODE_Saving); // Forced save freezes the system. + g_Serv.SetServerMode(ServMode::Saving); // Forced save freezes the system. bool fSave = true; bool fSuccess = true; @@ -1001,7 +1001,7 @@ bool CWorld::SaveForce() // Save world state fSuccess = false; } - g_Serv.SetServerMode(SERVMODE_Run); // Game is up and running + g_Serv.SetServerMode(ServMode::Run); // Game is up and running return fSuccess; } @@ -1169,6 +1169,7 @@ bool CWorld::Save( bool fForceImmediate ) // Save world state ADDTOCALLSTACK("CWorld::Save"); bool fSaved = false; + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); try { if (!CheckAvailableSpaceForSave(false)) @@ -1176,12 +1177,13 @@ bool CWorld::Save( bool fForceImmediate ) // Save world state //-- Ok we can start the save process, in which we eventually remove the previous saves and create the other. - CScriptTriggerArgs Args(fForceImmediate, _iSaveStage); - enum TRIGRET_TYPE tr; + pScriptArgs->Init(fForceImmediate, _iSaveStage, 0, nullptr); + enum TRIGRET_TYPE tr{}; - if ( g_Serv.r_Call("f_onserver_save", &g_Serv, &Args, nullptr, &tr) ) + if ( g_Serv.r_Call("f_onserver_save", pScriptArgs, &g_Serv, nullptr, &tr) ) if ( tr == TRIGRET_RET_TRUE ) return false; + //Flushing before the server should fix #2306 //The scripts fills the clients buffer and the server flush //the data during the save. @@ -1199,7 +1201,7 @@ bool CWorld::Save( bool fForceImmediate ) // Save world state #endif } - fForceImmediate = (Args.m_iN1 != 0); + fForceImmediate = (pScriptArgs->m_iN1 != 0); fSaved = SaveTry(fForceImmediate); } catch ( const CSError& e ) @@ -1223,8 +1225,8 @@ bool CWorld::Save( bool fForceImmediate ) // Save world state GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); } - CScriptTriggerArgs Args(fForceImmediate, _iSaveStage); - g_Serv.r_Call((fSaved ? "f_onserver_save_ok" : "f_onserver_save_fail"), &g_Serv, &Args); + pScriptArgs->Init(fForceImmediate, _iSaveStage, 0, nullptr); + g_Serv.r_Call((fSaved ? "f_onserver_save_ok" : "f_onserver_save_fail"), pScriptArgs, &g_Serv); return fSaved; } @@ -1256,9 +1258,9 @@ void CWorld::SaveStatics() if ( !g_MapList.IsMapSupported(m) ) continue; - for (int s = 0, qty = _Sectors.GetSectorQty(m); s < qty; ++s) + for (int s = 0, qty = _Sectors.GetMapSectorDataUnchecked(m).iSectorQty; s < qty; ++s) { - CSector* pSector = _Sectors.GetSector(m, s); + CSector* pSector = _Sectors.GetSectorByIndexUnchecked(m, s); if ( !pSector ) continue; @@ -1278,7 +1280,7 @@ void CWorld::SaveStatics() m_FileStatics.WriteSection( "EOF" ); m_FileStatics.Close(); - g_Log.Event(LOGM_SAVE, "Statics data saved (%s).\n", static_cast(m_FileStatics.GetFilePath())); + g_Log.Event(LOGM_SAVE, "Statics data saved (%s).\n", m_FileStatics.GetFilePath()); } catch (const CSError& e) { @@ -1329,7 +1331,7 @@ bool CWorld::LoadFile( lpctstr pszLoadName, bool fError ) // Load world from scr try { - g_Cfg.LoadResourceSection(&s); + g_Cfg.LoadResourceSection(&s, true); // pass true, it shouldn't really touch g_Cfg.m_ResHash } catch ( const CSError& e ) { @@ -1444,14 +1446,14 @@ bool CWorld::LoadWorld() // Load world from script bool CWorld::LoadAll() // Load world from script { - // start count. (will grow as needed) + // start count (will grow as needed). _GameClock.Init(); // will be loaded from the world file. // Load all the accounts. if ( !g_Accounts.Account_LoadAll(false) ) return false; - // Try to load the world and chars files . + // Try to load the world and chars files. if ( !LoadWorld() ) return false; @@ -1466,11 +1468,11 @@ bool CWorld::LoadAll() // Load world from script if (!g_MapList.IsMapSupported(m)) continue; - for (int s = 0, qty = _Sectors.GetSectorQty(m); s < qty; ++s) + for (int s = 0, qty = _Sectors.GetMapSectorDataUnchecked(m).iSectorQty; s < qty; ++s) { EXC_TRYSUB("Load"); - CSector* pSector = _Sectors.GetSector(m, s); + CSector* pSector = _Sectors.GetSectorByIndexUnchecked(m, s); ASSERT(pSector); if (!pSector->IsLightOverriden()) @@ -1614,7 +1616,7 @@ bool CWorld::r_LoadVal( CScript &s ) m_iSaveCountID = s.GetArgVal(); break; case WC_TIME: // "TIME" - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { g_Log.EventError("Can't set TIME while server is running.\n"); return false; @@ -1622,7 +1624,7 @@ bool CWorld::r_LoadVal( CScript &s ) _GameClock.InitTime( s.GetArgLLVal() * MSECS_PER_SEC); break; case WC_TIMEHIRES: // "TIMEHIRES" - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { g_Log.EventError("Can't set TIMEHIRES while server is running.\n"); return false; @@ -1648,24 +1650,24 @@ void CWorld::RespawnDeadNPCs() { ADDTOCALLSTACK("CWorld::RespawnDeadNPCs"); // Respawn dead story NPC's - g_Serv.SetServerMode(SERVMODE_RestockAll); + g_Serv.SetServerMode(ServMode::RestockAll); for ( int m = 0; m < MAP_SUPPORTED_QTY; ++m ) { if ( !g_MapList.IsMapSupported(m) ) continue; - for (int s = 0, qty = _Sectors.GetSectorQty(m); s < qty; ++s) + for (int s = 0, qty = _Sectors.GetMapSectorDataUnchecked(m).iSectorQty; s < qty; ++s) { EXC_TRY("OnSector"); - CSector* pSector = _Sectors.GetSector(m, s); + CSector* pSector = _Sectors.GetSectorByIndexUnchecked(m, s); ASSERT(pSector); pSector->RespawnDeadNPCs(); EXC_CATCH; } } - g_Serv.SetServerMode(SERVMODE_Run); + g_Serv.SetServerMode(ServMode::Run); } void CWorld::Restock() @@ -1673,7 +1675,7 @@ void CWorld::Restock() ADDTOCALLSTACK("CWorld::Restock"); // Recalc all the base items as well. g_Log.Event(LOGL_EVENT, "World Restock: started.\n"); - g_Serv.SetServerMode(SERVMODE_RestockAll); + g_Serv.SetServerMode(ServMode::RestockAll); for ( size_t i = 0; i < ARRAY_COUNT(g_Cfg.m_ResHash.m_Array); ++i ) { @@ -1694,11 +1696,11 @@ void CWorld::Restock() if ( !g_MapList.IsMapSupported(m) ) continue; - for ( int s = 0, qty = _Sectors.GetSectorQty(m); s < qty; ++s ) + for ( int s = 0, qty = _Sectors.GetMapSectorDataUnchecked(m).iSectorQty; s < qty; ++s ) { EXC_TRY("OnSector"); - CSector *pSector = _Sectors.GetSector(m, s); + CSector *pSector = _Sectors.GetSectorByIndexUnchecked(m, s); ASSERT(pSector); pSector->Restock(); @@ -1706,7 +1708,7 @@ void CWorld::Restock() } } - g_Serv.SetServerMode(SERVMODE_Run); + g_Serv.SetServerMode(ServMode::Run); g_Log.Event(LOGL_EVENT, "World Restock: done.\n"); } @@ -1756,10 +1758,10 @@ void CWorld::GarbageCollection() { ADDTOCALLSTACK("CWorld::GarbageCollection"); g_Log.Flush(); - g_Serv.SetServerMode(SERVMODE_GarbageCollection); + g_Serv.SetServerMode(ServMode::GarbageCollection); g_Log.Event(LOGL_EVENT|LOGM_NOCONTEXT, "Garbage Collection: started.\n"); GarbageCollection_UIDs(); - g_Serv.SetServerMode(SERVMODE_Run); + g_Serv.SetServerMode(ServMode::Run); g_Log.Flush(); } @@ -1769,7 +1771,7 @@ void CWorld::_OnTick() // 256 real secs = 1 server hour. 19 light levels. check every 10 minutes or so. // Do not tick while loading (startup, resync, exiting...) or when double ticking in the same msec?. - if (g_Serv.IsLoading() || !_GameClock.Advance()) + if (g_Serv.IsLoadingGeneric() || !_GameClock.Advance()) return; EXC_TRY("CWorld Tick"); @@ -1826,8 +1828,10 @@ void CWorld::_OnTick() { EXC_SET_BLOCK("f_onserver_timer"); _iTimeLastCallUserFunc = iCurTime + g_Cfg._iTimerCall; - CScriptTriggerArgs args(g_Cfg._iTimerCallUnit ? g_Cfg._iTimerCall / (MSECS_PER_SEC) : g_Cfg._iTimerCall / (60 * MSECS_PER_SEC)); - g_Serv.r_Call("f_onserver_timer", &g_Serv, &args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = g_Cfg._iTimerCall / + (g_Cfg._iTimerCallUnit ? (MSECS_PER_SEC) : (60 * MSECS_PER_SEC)); + g_Serv.r_Call("f_onserver_timer", pScriptArgs, &g_Serv); } } diff --git a/src/game/CWorldCache.h b/src/game/CWorldCache.h index 6d3f3e384..0c2bb2bf7 100644 --- a/src/game/CWorldCache.h +++ b/src/game/CWorldCache.h @@ -13,7 +13,7 @@ class CWorldCache friend class CWorldMap; int64 _iTimeLastMapBlockCacheCheck; - + using MapBlockCacheCont = std::unique_ptr; // Info about a single map block using MapBlockCache = std::unique_ptr; // An element per each map block MapBlockCache _mapBlocks[MAP_SUPPORTED_QTY]; // An element per each map diff --git a/src/game/CWorldComm.cpp b/src/game/CWorldComm.cpp index 78713a5ca..db9f003d6 100644 --- a/src/game/CWorldComm.cpp +++ b/src/game/CWorldComm.cpp @@ -1,4 +1,5 @@ #include "../common/sphere_library/CSRand.h" +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../network/CClientIterator.h" #include "chars/CChar.h" #include "clients/CClient.h" @@ -224,13 +225,13 @@ void CWorldComm::Broadcast(lpctstr pMsg) // static // System broadcast in bold text ADDTOCALLSTACK("CWorldComm::Broadcast"); - CScriptTriggerArgs args; - args.Init(pMsg); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pMsg); TRIGRET_TYPE iRet = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onserver_broadcast", &g_Serv, &args, nullptr, &iRet); + g_Serv.r_Call("f_onserver_broadcast", pScriptArgs, &g_Serv, nullptr, &iRet); if (iRet == TRIGRET_RET_TRUE) return; - pMsg = args.m_s1; + pMsg = pScriptArgs->m_s1; Speak( nullptr, pMsg, HUE_TEXT_DEF, TALKMODE_BROADCAST, FONT_BOLD ); } diff --git a/src/game/CWorldGameTime.cpp b/src/game/CWorldGameTime.cpp index caf99086f..248b196e0 100644 --- a/src/game/CWorldGameTime.cpp +++ b/src/game/CWorldGameTime.cpp @@ -22,7 +22,7 @@ int64 CWorldGameTime::GetCurrentTimeInGameMinutes( int64 basetime ) noexcept // // 8 real world seconds = 1 game minute. // 1 real minute = 7.5 game minutes // 3.2 hours = 1 game day. - + return ( basetime / g_Cfg.m_iGameMinuteLength ); } @@ -45,7 +45,7 @@ int64 CWorldGameTime::GetNextNewMoon( bool fMoonIndex ) // static // Get the game time when this cycle will start int64 iNewStart = (int64)(iNextMonth - (double)(iNextMonth % iSynodic)); return iNewStart * g_Cfg.m_iGameMinuteLength; - + } uint CWorldGameTime::GetMoonPhase(bool fMoonIndex) // static diff --git a/src/game/CWorldGameTime.h b/src/game/CWorldGameTime.h index 1832fc980..9637fd0ab 100644 --- a/src/game/CWorldGameTime.h +++ b/src/game/CWorldGameTime.h @@ -19,7 +19,7 @@ class CWorldGameTime static int64 GetCurrentTimeInGameMinutes(int64 basetime) noexcept; // return game world minutes static int64 GetCurrentTimeInGameMinutes() noexcept; - + static uint GetMoonPhase( bool fMoonIndex = false ); static int64 GetNextNewMoon( bool fMoonIndex ); }; diff --git a/src/game/CWorldImport.cpp b/src/game/CWorldImport.cpp index 98ee09c38..92bb658b7 100644 --- a/src/game/CWorldImport.cpp +++ b/src/game/CWorldImport.cpp @@ -1,4 +1,4 @@ -#include "../common/CExpression.h" +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CLog.h" #include "chars/CChar.h" #include "items/CItem.h" @@ -249,7 +249,7 @@ bool CImportFile::ImportSCP( CScript & s, word wModeFlags ) CheckLast(); if ( s.IsSectionType( "ACCOUNT" )) { - g_Cfg.LoadResourceSection( &s ); + g_Cfg.LoadResourceSection( &s, true ); continue; } else if ( s.IsSectionType( "WORLDCHAR" ) || s.IsSectionType("WC")) @@ -836,7 +836,6 @@ bool CWorld::Import( lpctstr pszFilename, const CChar * pSrc, word wModeFlags, i } - bool CWorld::DumpAreas( CTextConsole * pSrc, lpctstr pszFilename ) { ADDTOCALLSTACK("CWorld::DumpAreas"); @@ -866,7 +865,6 @@ bool CWorld::DumpAreas( CTextConsole * pSrc, lpctstr pszFilename ) } - bool CWorld::Export( lpctstr pszFilename, const CChar * pSrc, word wModeFlags, int iDist, short dx, short dy ) { ADDTOCALLSTACK("CWorld::Export"); diff --git a/src/game/CWorldMap.cpp b/src/game/CWorldMap.cpp index 90acaf6dc..f14ebe125 100644 --- a/src/game/CWorldMap.cpp +++ b/src/game/CWorldMap.cpp @@ -1,8 +1,9 @@ #include "../common/resource/sections/CItemTypeDef.h" #include "../common/resource/sections/CRandGroupDef.h" #include "../common/resource/sections/CRegionResourceDef.h" -#include "../common/CException.h" -#include "../common/CScriptTriggerArgs.h" +#include "../common/sphere_library/sfastmath.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../common/CRect.h" #include "../common/CLog.h" #include "../sphere/threads.h" @@ -149,12 +150,14 @@ CItem * CWorldMap::CheckNaturalResource(const CPointMap & pt, IT_TYPE iType, boo EXC_SET_BLOCK("resourcefound"); if ( pCharSrc != nullptr ) { - CScriptTriggerArgs Args(0, 0, pResBit); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(0, 0, 0, pResBit); TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; + if ( IsTrigUsed(TRIGGER_REGIONRESOURCEFOUND) ) - tRet = pCharSrc->OnTrigger(CTRIG_RegionResourceFound, pCharSrc, &Args); + tRet = pCharSrc->OnTrigger(CTRIG_RegionResourceFound, pScriptArgs, pCharSrc); if ( IsTrigUsed(TRIGGER_RESOURCEFOUND) ) - tRet = pOreDef->OnTrigger("@ResourceFound", pCharSrc, &Args); + tRet = pOreDef->OnTrigger("@ResourceFound", pScriptArgs, pCharSrc); if (tRet == TRIGRET_RET_TRUE) { @@ -219,29 +222,35 @@ IT_TYPE CWorldMap::GetTerrainItemType(dword dwTerrainIndex) // static } - ////////////////////////////////////////////////////////////////// // Map reading and blocking. // gets sector # from one map -CSector* CWorldMap::GetSector(int map, int index) noexcept // static +CSector* CWorldMap::GetSectorByIndex(int map, int index) noexcept // static { - //ADDTOCALLSTACK_DEBUG("CWorldMap::GetSector(index)"); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetSectorByIndex(index)"); + + const MapSectorsData * pSd = g_World._Sectors.GetMapSectorData(map); + if (!pSd) + { + g_Log.EventError("Requested sector #%d for unsupported map #%d.\n", index, map); + return nullptr; + } - const int iMapSectorQty = g_World._Sectors.GetSectorQty(map); + const int iMapSectorQty = pSd->iSectorQty; if (index >= iMapSectorQty) { g_Log.EventError("Unsupported sector #%d for map #%d specified.\n", index, map); return nullptr; } - return g_World._Sectors.GetSector(map, index); + return g_World._Sectors.GetSectorByIndexUnchecked(map, index); } -CSector* CWorldMap::GetSector(int map, short x, short y) noexcept // static +CSector* CWorldMap::GetSectorByCoordsUnchecked(int map, short x, short y) noexcept // static { - //ADDTOCALLSTACK_DEBUG("CWorldMap::GetSector(x,y)"); - return g_World._Sectors.GetSector(map, x, y); + //ADDTOCALLSTACK_DEBUG("CWorldMap::GetSectorByCoords(x,y)"); + return g_World._Sectors.GetSectorByCoordsUnchecked(map, x, y); } @@ -291,7 +300,10 @@ const CUOMapMeter* CWorldMap::GetMapMeter(const CPointMap& pt) // static if (!pMapBlock) return nullptr; - return pMapBlock->GetTerrain(UO_BLOCK_OFFSET(pt.m_x), UO_BLOCK_OFFSET(pt.m_y)); + return pMapBlock->GetTerrain( + UO_BLOCK_OFFSET(pt.m_x), + UO_BLOCK_OFFSET(pt.m_y) + ); } std::optional CWorldMap::GetMapMeterAdjusted(const CPointMap& pt) @@ -301,16 +313,28 @@ std::optional CWorldMap::GetMapMeterAdjusted(const CPointMap& pt) return std::nullopt; CUOMapMeter pMapTop(*pMeter); - const CUOMapMeter pMapLeft(CheckMapTerrain(pMapTop, pt.m_x, pt.m_y + 1, pt.m_map)); + const CUOMapMeter pMapLeft (CheckMapTerrain(pMapTop, pt.m_x, pt.m_y + 1, pt.m_map)); const CUOMapMeter pMapBottom(CheckMapTerrain(pMapTop, pt.m_x + 1, pt.m_y + 1, pt.m_map)); - const CUOMapMeter pMapRight(CheckMapTerrain(pMapTop, pt.m_x + 1, pt.m_y, pt.m_map)); + const CUOMapMeter pMapRight (CheckMapTerrain(pMapTop, pt.m_x + 1, pt.m_y, pt.m_map)); - const short iAverage = GetAreaAverage(pMapTop.m_z, pMapLeft.m_z, pMapBottom.m_z, pMapRight.m_z); - if ((char)abs((short)pMapTop.m_z - (short)pMapBottom.m_z) > (char)abs((short)pMapLeft.m_z - (short)pMapRight.m_z)) - pMapTop.m_z = GetFloorAvarage(pMapLeft.m_z, pMapRight.m_z, iAverage); - else - pMapTop.m_z = GetFloorAvarage(pMapTop.m_z, pMapBottom.m_z, iAverage); - return std::make_optional(pMapTop); + const int iAverage = GetAreaAverageHeight(pMapTop.m_z, pMapLeft.m_z, pMapBottom.m_z, pMapRight.m_z); + //const auto iAbsDiffTopBottom = (char)abs((short)pMapTop.m_z - (short)pMapBottom.m_z); + //const auto iAbsDiffLeftRight = (char)abs((short)pMapLeft.m_z - (short)pMapRight.m_z); + const int iAbsDiffTopBottom = sl::fmath::sAbsDiff((int)pMapTop.m_z, (int)pMapBottom.m_z); + const int iAbsDiffLeftRight = sl::fmath::sAbsDiff((int)pMapLeft.m_z, (int)pMapRight.m_z); + + //if (iAbsDiffTopBottom > iAbsDiffLeftRight) + // pMapTop.m_z = GetFloorAverageHeight(pMapLeft.m_z, pMapRight.m_z, iAverage); + //else + // pMapTop.m_z = GetFloorAverageHeight(pMapTop.m_z, pMapBottom.m_z, iAverage); + + const bool useLeftRight = (iAbsDiffTopBottom > iAbsDiffLeftRight); + const int value1 = useLeftRight ? pMapLeft.m_z : pMapTop.m_z; + const int value2 = useLeftRight ? pMapRight.m_z : pMapBottom.m_z; + + pMapTop.m_z = (char)GetFloorAverageHeight(value1, value2, iAverage); + + return std::make_optional(pMapTop); } bool CWorldMap::IsTypeNear_Top( const CPointMap & pt, IT_TYPE iType, int iDistance ) // static @@ -318,6 +342,7 @@ bool CWorldMap::IsTypeNear_Top( const CPointMap & pt, IT_TYPE iType, int iDistan ADDTOCALLSTACK("CWorldMap::IsTypeNear_Top"); if ( !pt.IsValidPoint() ) return false; + const CPointMap ptn = FindTypeNear_Top( pt, iType, iDistance ); return ptn.IsValidPoint(); } @@ -370,7 +395,8 @@ CPointMap CWorldMap::FindTypeNear_Top( const CPointMap & pt, IT_TYPE iType, int pDupeDef = CItemBaseDupe::GetDupeRef((ITEMID_TYPE)(pItem->GetDispID())); if ( ! pDupeDef ) { - g_Log.EventDebug("Failed to get non-parent reference (dynamic) (DispID 0%x) (X: %d Y: %d Z: %d)\n",pItem->GetDispID(),ptTest.m_x,ptTest.m_y,ptTest.m_z); + g_Log.EventDebug("Failed to get non-parent reference (dynamic) (DispID 0%x) (X: %d Y: %d Z: %d)\n", + pItem->GetDispID(),ptTest.m_x,ptTest.m_y,ptTest.m_z); Height = pItemDef->GetHeight(); } else @@ -383,10 +409,11 @@ CPointMap CWorldMap::FindTypeNear_Top( const CPointMap & pt, IT_TYPE iType, int continue; //if ( ((( pItem->GetTopPoint().m_z - pt.m_z ) > 0) && ( pItem->GetTopPoint().m_z - pt.m_z ) > RESOURCE_Z_CHECK ) || ((( pt.m_z - pItem->GetTopPoint().m_z ) < 0) && (( pt.m_z - pItem->GetTopPoint().m_z ) < - RESOURCE_Z_CHECK ))) - if ( ((( z - pt.m_z ) > 0) && ( z - pt.m_z ) > RESOURCE_Z_CHECK ) || ((( pt.m_z - z ) < 0) && (( pt.m_z - z ) < - RESOURCE_Z_CHECK ))) + if ( ((( z - pt.m_z ) > 0) && ( z - pt.m_z ) > RESOURCE_Z_CHECK ) || + ((( pt.m_z - z ) < 0) && (( pt.m_z - z ) < - RESOURCE_Z_CHECK )) ) continue; - if (( z < ptElem[0].m_z ) || (( z == ptElem[0].m_z ) && ( fElem[0] ))) + if (( z < ptElem[0].m_z ) || (( z == ptElem[0].m_z ) && fElem[0])) continue; ptElem[0] = ptItemTop; @@ -1496,43 +1523,60 @@ void CWorldMap::GetHeightPoint(const CPointMap & pt, CServerMapBlockingState & b } } -char CWorldMap::GetFloorAvarage(char pPoint1, char pPoint2, short iAverage) +int CWorldMap::GetFloorAverageHeight(int iPoint1, int iPoint2, int iAverage) { - //We can't use char here, because higher points like hills has 64+ heights and adding 64+65 each other exceed char limit and causes returns minus values. - const short pTotal = pPoint1 + pPoint2, pHalf = pTotal / 2, pEven = pTotal % 2, pAverage = iAverage - pHalf; - return static_cast(pHalf + (pEven != 0 && pAverage > 5)); + // We can't use char here, because higher points like hills has 64+ heights and adding 64+65 each other exceed char limit and causes returns minus values. + const int iTotal = iPoint1 + iPoint2; + const int iHalf = iTotal / 2; + + // Leaving the old, less efficient algorithm just because it's more clear. + //const short pEven = pTotal % 2, pAverage = iAverage - pHalf; + //return static_cast(pHalf + (pEven != 0 && pAverage > 5)); + + // If total is odd and (iAverage - half) > 5, round up + // const bool fRoundUp = (iTotal % 2 != 0) && ((iAverage - iHalf) > 5); + // return static_cast(iHalf + (fRoundUp ? 1 : 0)); + + const int iRoundUp = (iTotal & 1 /* odd number */) && ((iAverage - iHalf) > 5); + return iHalf + iRoundUp; } -short CWorldMap::GetAreaAverage(char pTop, char pLeft, char pBottom, char pRight) +int CWorldMap::GetAreaAverageHeight(int iTop, int iLeft, int iBottom, int iRight) { - const short iHighest1 = maximum(pTop, pBottom); - const short iLowest1 = minimum(pTop, pBottom); + // Leaving the old, less efficient algorithm just because it's more clear. + /* + const short iHighest1 = maximum(iTop, iBottom); + const short iLowest1 = minimum(iTop, iBottom); - const short iHighest2 = maximum(pLeft, pRight); - const short iLowest2 = minimum(pLeft, pRight); + const short iHighest2 = maximum(iLeft, iRight); + const short iLowest2 = minimum(iLeft, iRight); return maximum(iHighest1, iHighest2) - minimum(iLowest1, iLowest2); + */ + + sl::fmath::sSortPair(iBottom, iTop); // iTop will be >= iBottom + sl::fmath::sSortPair(iLeft, iRight); // iRight will be >= iLeft + const int iHighest = sl::fmath::sMax(iTop, iRight); + const int iLowest = sl::fmath::sMin(iBottom, iLeft); + + return iHighest - iLowest; } -CUOMapMeter CWorldMap::CheckMapTerrain(CUOMapMeter pDefault, short x, short y, uchar map) +CUOMapMeter CWorldMap::CheckMapTerrain(const CUOMapMeter &meterDefault, short x, short y, uchar map) { - CPointMap pt = { x, y, 0, map }; + const CPointMap pt = { x, y, 0, map }; const CServerMapBlock* pMapBlock = GetMapBlock(pt); if (!pMapBlock) - return pDefault; + return meterDefault; const CUOMapMeter* pMeter = pMapBlock->GetTerrain(UO_BLOCK_OFFSET(pt.m_x), UO_BLOCK_OFFSET(pt.m_y)); - if (!pMeter) - return pDefault; - - if (CUOMapMeter::IsTerrainNull(pMeter->m_wTerrainIndex)) - return pDefault; - else - { - const CUOTerrainInfo land(pMeter->m_wTerrainIndex, false); - if ((land.m_flags & UFLAG1_WATER)) - return pDefault; - } - return *pMeter; + if (!pMeter || CUOMapMeter::IsTerrainNull(pMeter->m_wTerrainIndex)) + return meterDefault; + + const CUOTerrainInfo land(pMeter->m_wTerrainIndex, false); + //if ((land.m_flags & UFLAG1_WATER)) + // return meterDefault; + //return *pMeter; + return (land.m_flags & UFLAG1_WATER) ? meterDefault : *pMeter; } char CWorldMap::GetHeightPoint(const CPointMap & pt, uint64 & uiBlockFlags, bool fHouseCheck) // static diff --git a/src/game/CWorldMap.h b/src/game/CWorldMap.h index 9b22f4140..c58ac7b0c 100644 --- a/src/game/CWorldMap.h +++ b/src/game/CWorldMap.h @@ -27,8 +27,8 @@ class CWorldMap // Sectors - static CSector* GetSector(int map, int index) noexcept; // gets sector # from one map - static CSector* GetSector(int map, short x, short y) noexcept; + static CSector* GetSectorByIndex(int map, int index) noexcept; // gets sector # from one map + static CSector* GetSectorByCoordsUnchecked(int map, short x, short y) noexcept; // Map blocks (for caching) and terrain @@ -37,9 +37,9 @@ class CWorldMap static const CUOMapMeter* GetMapMeter(const CPointMap& pt); // Height of MAP0.MUL at given coordinates static std::optional GetMapMeterAdjusted(const CPointMap& pt); - static CUOMapMeter CheckMapTerrain(CUOMapMeter pDefault, short x, short y, uchar map); - static char GetFloorAvarage(char pPoint1, char pPoint2, short iAverage); - static short GetAreaAverage(char pTop, char pLeft, char pBottom, char pRight); + static CUOMapMeter CheckMapTerrain(const CUOMapMeter& meterDefault, short x, short y, uchar map); + static int GetFloorAverageHeight(int iPoint1, int iPoint2, int iAverage); + static int GetAreaAverageHeight(int iTop, int iLeft, int iBottom, int iRight); static CItemTypeDef* GetTerrainItemTypeDef(dword dwIndex); static IT_TYPE GetTerrainItemType(dword dwIndex); diff --git a/src/game/CWorldSearch.cpp b/src/game/CWorldSearch.cpp index 91fdecb74..c631e6b42 100644 --- a/src/game/CWorldSearch.cpp +++ b/src/game/CWorldSearch.cpp @@ -1,4 +1,4 @@ -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../common/CLog.h" #include "../game/chars/CChar.h" #include "../game/items/CItem.h" @@ -9,6 +9,7 @@ static constexpr size_t kuiContainerScaleFactor = 2; +// This was done before we added ObjectPool class. class CWorldSearchHolderImpl { static constexpr size_t kuiPreallocateSize = 20; @@ -41,17 +42,19 @@ class CWorldSearchHolderImpl CSReferenceCounted CWorldSearchHolder::GetInstance(const CPointMap& pt, int iDist) // static { + // Thread-unsafe! static CWorldSearchHolderImpl holder; return holder.GetOne(pt, iDist); } CWorldSearch::CWorldSearch() noexcept : _iDist(0), _fAllShow(false), _fSearchSquare(false), - _eSearchType(ws_search_e::None), _fInertToggle(false), + _fInertToggle(false), _eSearchType(ws_search_e::None), _ppCurContObjs(nullptr), _pObj(nullptr), _uiCurObjIndex(0), _uiObjArrayCapacity(0), _uiObjArraySize(0), _iSectorCur(0), // Get upper left of search rect. - _pSectorBase(nullptr), _pSector(nullptr) + _pSectorBase(nullptr), _pSector(nullptr), + _distanceFunction(nullptr) { } @@ -76,18 +79,16 @@ void CWorldSearch::Reset(const CPointMap& pt, int iDist) //ADDTOCALLSTACK("CWorldSearch::Reset"); // define a search of the world. - _fAllShow = false; - _fSearchSquare = false; + _fAllShow = _fSearchSquare = _fInertToggle = false; _eSearchType = ws_search_e::None; - _fInertToggle = false; _pObj = nullptr; - _uiCurObjIndex = 0; - _uiObjArraySize = 0; + _uiCurObjIndex = _uiObjArraySize = 0; //_uiObjArrayCapacity = 0; // Don't! Recycle the allocated space for _ppCurContObjs. _iSectorCur = 0; // Get upper left of search rect. _pt = pt; _iDist = iDist; + _pSectorBase = _pSector = pt.GetSector(); _rectSector.SetRect( pt.m_x - iDist, @@ -95,18 +96,30 @@ void CWorldSearch::Reset(const CPointMap& pt, int iDist) pt.m_x + iDist + 1, pt.m_y + iDist + 1, pt.m_map); + + _rectSectorIndexingHints = _rectSector.PrecomputeSectorIndexingHints(); + + SetDistanceFunction(); } void CWorldSearch::SetAllShow(bool fView) { //ADDTOCALLSTACK_DEBUG("CWorldSearch::SetAllShow"); - _fAllShow = fView; + if (_fAllShow == fView) + return; + + _fAllShow = fView; + SetDistanceFunction(); } void CWorldSearch::SetSearchSquare(bool fSquareSearch) { //ADDTOCALLSTACK_DEBUG("CWorldSearch::SetSearchSquare"); - _fSearchSquare = fSquareSearch; + if (_fSearchSquare == fSquareSearch) + return; + + _fSearchSquare = fSquareSearch; + SetDistanceFunction(); } void CWorldSearch::RestartSearch() @@ -129,7 +142,8 @@ bool CWorldSearch::GetNextSector() while (true) { - _pSector = _rectSector.GetSector(_iSectorCur++); + _pSector = _rectSector.GetSectorAtIndexWithHints(_iSectorCur, _rectSectorIndexingHints); + _iSectorCur += 1; if (_pSector == nullptr) return false; // done searching. if (_pSectorBase == _pSector) @@ -176,6 +190,24 @@ void CWorldSearch::LoadSectorObjs(CSObjCont const& pSectorObjList) _uiCurObjIndex = 0; } +void CWorldSearch::SetDistanceFunction() +{ + if (_fAllShow) + { + if (_fSearchSquare) + _distanceFunction = &CPointMap::GetDistSightBase; + else + _distanceFunction = &CPointMap::GetDistBase; + } + else + { + if (_fSearchSquare) + _distanceFunction = &CPointMap::GetDistSight; + else + _distanceFunction = &CPointMap::GetDist; + } +} + CItem* CWorldSearch::GetItem() { // This method is called very frequently, ADDTOCALLSTACK unneededly sucks cpu @@ -187,7 +219,7 @@ CItem* CWorldSearch::GetItem() { ASSERT(_eSearchType == ws_search_e::None); _eSearchType = ws_search_e::Items; - + LoadSectorObjs(_pSector->m_Items); } else @@ -196,42 +228,18 @@ CItem* CWorldSearch::GetItem() } ASSERT(_eSearchType == ws_search_e::Items); - _pObj = (_uiCurObjIndex >= _uiObjArraySize) ? nullptr : static_cast(_ppCurContObjs[_uiCurObjIndex]); - if (_pObj == nullptr) + if (_uiCurObjIndex >= _uiObjArraySize) { + _pObj = nullptr; if (GetNextSector()) continue; return nullptr; } - const CPointMap& ptObj(_pObj->GetTopPoint()); - if (_fSearchSquare) - { - if (_fAllShow) - { - if (_pt.GetDistSightBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDistSight(ptObj) <= _iDist) - return static_cast (_pObj); - } - } - else - { - if (_fAllShow) - { - if (_pt.GetDistBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDist(ptObj) <= _iDist) - return static_cast (_pObj); - } - } + _pObj = static_cast(_ppCurContObjs[_uiCurObjIndex]); + if ((_pt.* _distanceFunction)(_pObj->GetTopPoint()) <= _iDist) + return static_cast (_pObj); } } @@ -256,17 +264,17 @@ CChar* CWorldSearch::GetChar() } ASSERT(_eSearchType == ws_search_e::Chars); - _pObj = (_uiCurObjIndex >= _uiObjArraySize) ? nullptr : static_cast(_ppCurContObjs[_uiCurObjIndex]); - if (_pObj == nullptr) + if (_uiCurObjIndex >= _uiObjArraySize) { + _pObj = nullptr; if (!_fInertToggle && _fAllShow) { _fInertToggle = true; LoadSectorObjs(_pSector->m_Chars_Disconnect); - _pObj = (_uiCurObjIndex >= _uiObjArraySize) ? nullptr : static_cast(_ppCurContObjs[_uiCurObjIndex]); - if (_pObj != nullptr) + if (_uiCurObjIndex < _uiObjArraySize) goto jumpover; + _pObj = nullptr; } if (GetNextSector()) @@ -276,33 +284,8 @@ CChar* CWorldSearch::GetChar() } jumpover: - const CPointMap& ptObj = _pObj->GetTopPoint(); - - if (_fSearchSquare) - { - if (_fAllShow) - { - if (_pt.GetDistSightBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDistSight(ptObj) <= _iDist) - return static_cast (_pObj); - } - } - else - { - if (_fAllShow) - { - if (_pt.GetDistBase(ptObj) <= _iDist) - return static_cast (_pObj); - } - else - { - if (_pt.GetDist(ptObj) <= _iDist) - return static_cast (_pObj); - } - } + _pObj = static_cast(_ppCurContObjs[_uiCurObjIndex]); + if ((_pt.* _distanceFunction)(_pObj->GetTopPoint()) <= _iDist) + return static_cast (_pObj); } } diff --git a/src/game/CWorldSearch.h b/src/game/CWorldSearch.h index 382207d9b..27121ad65 100644 --- a/src/game/CWorldSearch.h +++ b/src/game/CWorldSearch.h @@ -36,17 +36,17 @@ class CWorldSearch Chars }; - CPointMap _pt; // Base point of our search. - int _iDist; // How far from the point are we interested in + int _iDist; // How far from the point are we interested in + CPointMap _pt; // Base point of our search. bool _fAllShow; // Include Even inert items. bool _fSearchSquare; // Search in a square (uo-sight distance) rather than a circle (standard distance). - ws_search_e _eSearchType; bool _fInertToggle; // We are now doing the inert chars. + ws_search_e _eSearchType; CSObjContRec** _ppCurContObjs; // Array with a copy of the pointers to the objects inside the sector we are in searching right now. CObjBase* _pObj; // The current object of interest. - size_t _uiCurObjIndex; + size_t _uiCurObjIndex; size_t _uiObjArrayCapacity; size_t _uiObjArraySize; @@ -54,6 +54,11 @@ class CWorldSearch CSector* _pSectorBase; // Don't search the center sector 2 times. CSector* _pSector; // current Sector CRectMap _rectSector; // A rectangle containing our sectors we can search. + CRectMap::SectIndexingHints _rectSectorIndexingHints; + + // Pointer to distance function: + // The ::* operator means "pointer to member of CPointMap". + int (CPointMap::* _distanceFunction)(const CPointBase&) const noexcept; public: static const char* m_sClassName; @@ -65,21 +70,24 @@ class CWorldSearch ~CWorldSearch() noexcept; public: - //static cdrc::rc_ptr GetInstance(const CPointMap& pt, int iDist = 0); void Reset(const CPointMap& pt, int iDist = 0); void SetAllShow(bool fView); void SetSearchSquare(bool fSquareSearch); void RestartSearch(); + CChar* GetChar(); CItem* GetItem(); private: + void SetDistanceFunction(); + bool GetNextSector(); void LoadSectorObjs(CSObjCont const& pSectorObjList); }; struct CWorldSearchHolder { + // Thread-unsafe! static CSReferenceCounted GetInstance(const CPointMap& pt, int iDist = 0); }; diff --git a/src/game/CWorldTicker.cpp b/src/game/CWorldTicker.cpp index 3b55fcbe2..3b6b9b756 100644 --- a/src/game/CWorldTicker.cpp +++ b/src/game/CWorldTicker.cpp @@ -1,5 +1,5 @@ #include "../common/sphere_library/scontainer_ops.h" -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../sphere/threads.h" #include "../sphere/ProfileTask.h" #include "chars/CChar.h" @@ -442,7 +442,7 @@ bool CWorldTicker::AddTimedObject(const int64 iTimeout, CTimedObject* pTimedObje } else { - fCanTick = pTimedObject->_CanTick(); + fCanTick = pTimedObject->_TickableState(); /* if (!fCanTick) { @@ -1094,7 +1094,7 @@ void CWorldTicker::ProcessTimedObjects() ++it, ++uiProgressive) { CTimedObject* pTimedObj = it->second; - if (!pTimedObj->_IsTimerSet() || !pTimedObj->_CanTick()) + if (!pTimedObj->_IsTimerSet() || !pTimedObj->_TickableState()) continue; //if (pTimedObj->_GetTimeoutRaw() > _iCurTickStartTime) @@ -1337,7 +1337,7 @@ void CWorldTicker::ProcessCharPeriodicTicks() { ASSERT(it->first != 0); CChar* pChar = it->second; - if (!pChar->_CanTick() || pChar->_IsBeingDeleted()) + if (!pChar->_TickableState() || pChar->_IsBeingDeleted()) continue; _vPeriodicCharsTicksBuffer.emplace_back(pChar); diff --git a/src/game/CWorldTickingList.cpp b/src/game/CWorldTickingList.cpp index f36177415..63ebb224f 100644 --- a/src/game/CWorldTickingList.cpp +++ b/src/game/CWorldTickingList.cpp @@ -4,7 +4,6 @@ #include "CWorldTicker.h" - void CWorldTickingList::AddObjSingle(int64 iTimeout, CTimedObject* pObj, bool fForce) // static { //The lock on pObj should already be acquired by CTimedObject::SetTimeout diff --git a/src/game/CWorldTimedFunctions.cpp b/src/game/CWorldTimedFunctions.cpp index 04f27f765..f51fac4f5 100644 --- a/src/game/CWorldTimedFunctions.cpp +++ b/src/game/CWorldTimedFunctions.cpp @@ -24,9 +24,9 @@ void CWorldTimedFunctions::Clear() // static } TRIGRET_TYPE CWorldTimedFunctions::Loop(lpctstr ptcCommand, int LoopsMade, CScriptLineContext StartContext, - CScript& s, CTextConsole* pSrc, CScriptTriggerArgs* pArgs, CSString* pResult) // static + CScript& s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc, CSString* pResult) // static { - return g_World._Ticker._TimedFunctions.Loop(ptcCommand, LoopsMade, StartContext, s, pSrc, pArgs, pResult); + return g_World._Ticker._TimedFunctions.Loop(ptcCommand, LoopsMade, StartContext, s, pScriptArgs, pSrc, pResult); } void CWorldTimedFunctions::Add(const CUID& uid, int64 iTimeout, lpctstr ptcCommand) // static diff --git a/src/game/CWorldTimedFunctions.h b/src/game/CWorldTimedFunctions.h index f145fa39d..efaa01afc 100644 --- a/src/game/CWorldTimedFunctions.h +++ b/src/game/CWorldTimedFunctions.h @@ -23,7 +23,7 @@ class CWorldTimedFunctions static void Stop(const CUID& uid, lpctstr ptcCommand); static void Clear(); static TRIGRET_TYPE Loop(lpctstr ptcCommand, int LoopsMade, CScriptLineContext StartContext, - CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult); + CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult); static int64 IsTimer(const CUID& uid, lpctstr funcname); }; #endif // _INC_CWORLDTIMEDFUNCTIONS_H diff --git a/src/game/chars/CChar.cpp b/src/game/chars/CChar.cpp index c712e956e..caa426626 100644 --- a/src/game/chars/CChar.cpp +++ b/src/game/chars/CChar.cpp @@ -2,8 +2,9 @@ #include "../../common/sphere_library/CSRand.h" #include "../../common/resource/CResourceLock.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CUID.h" #include "../../common/CRect.h" #include "../../common/CLog.h" @@ -353,7 +354,7 @@ CChar::~CChar() if (m_pParty) { - m_pParty->RemoveMember( GetUID(), GetUID() ); + m_pParty->RemoveMember(GetUID(), GetUID(), false); m_pParty = nullptr; } //Guild_Resign(MEMORY_GUILD); Moved to the ClearPlayer method otherwise it will cause a server crash because the deleted player will still be found in the guild list. @@ -416,7 +417,8 @@ bool CChar::NotifyDelete(bool fForce) { //We can forbid the deletion in here with no pain //If Delete is forced, we must avoid the possibility to block deletion (will create infinite loop) - if (CChar::OnTrigger(CTRIG_Destroy, &g_Serv) == TRIGRET_RET_TRUE && !fForce) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + if (CChar::OnTrigger(CTRIG_Destroy, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE && !fForce) return false; } @@ -424,10 +426,10 @@ bool CChar::NotifyDelete(bool fForce) if (m_pPlayer) { TRIGRET_TYPE trigReturn; - CScriptTriggerArgs Args; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); if (m_pClient) - Args.m_pO1 = m_pClient; - r_Call("f_onchar_delete", this, &Args, nullptr, &trigReturn); + pScriptArgs->m_pO1 = m_pClient; + r_Call("f_onchar_delete", pScriptArgs, this, nullptr, &trigReturn); //If Delete is forced, we must avoid the possibility to block deletion (will create infinite loop) if (trigReturn == TRIGRET_RET_TRUE && !fForce) return false; @@ -491,13 +493,6 @@ void CChar::ClientDetach() if ( !IsClientActive() ) return; - if ( m_pParty && m_pParty->IsPartyMaster( this )) - { - // Party must disband if the master is logged out. - m_pParty->Disband(GetUID()); - m_pParty = nullptr; - } - // If this char is on a IT_SHIP then we need to stop the ship ! if ( m_pArea && m_pArea->IsFlag( REGION_FLAG_SHIP )) { @@ -546,7 +541,7 @@ void CChar::SetDisconnected(CSector* pNewSector) if (m_pParty) { - m_pParty->RemoveMember( GetUID(), GetUID() ); + m_pParty->RemoveMember(GetUID(), GetUID(), false); m_pParty = nullptr; } @@ -577,7 +572,7 @@ void CChar::SetDisconnected(CSector* pNewSector) else SetUIDContainerFlags(UID_O_DISCONNECT); - IsDisconnected(); + ASSERT(IsDisconnected()); } } @@ -594,7 +589,7 @@ void CChar::ClearPlayer() } else { - if (g_Serv.GetServerMode() != SERVMODE_Exiting) + if (g_Serv.GetServerMode() != ServMode::Exiting) { g_Log.EventWarn("Character '%s'(UID 0%x) on account '%s' as been deleted.\n", GetName(), (dword)GetUID(), pAccount->GetName()); } @@ -807,7 +802,7 @@ bool CChar::_IsStatFlag(uint64 uiStatFlag) const noexcept #if MT_ENGINES bool CChar::IsStatFlag( uint64 uiStatFlag) const noexcept { - MT_ENGINE_SHARED_LOCK_SET; + MT_ENGINE_SHARED_LOCK_SET(this); return (_uiStatFlag & uiStatFlag); } #endif @@ -820,7 +815,7 @@ void CChar::_StatFlag_Set( uint64 uiStatFlag) noexcept */ void CChar::StatFlag_Set(uint64 uiStatFlag) noexcept { - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); _uiStatFlag |= uiStatFlag; } @@ -832,7 +827,7 @@ void CChar::_StatFlag_Clear(uint64 uiStatFlag) noexcept */ void CChar::StatFlag_Clear(uint64 uiStatFlag) noexcept { - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); _uiStatFlag &= ~uiStatFlag; } @@ -847,7 +842,7 @@ void CChar::_StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept */ void CChar::StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept { -// MT_ENGINE_UNIQUE_LOCK_SET; +// MT_ENGINE_UNIQUE_LOCK_SET(this); // _StatFlag_Mod(uiStatFlag, fMod); if (fMod) _uiStatFlag |= uiStatFlag; @@ -895,7 +890,7 @@ void CChar::SetVisualRange(byte newSight) { CClient* pClient; { - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); // max value is 18 on classic clients prior 7.0.55.27 version and 24 on enhanced clients and latest classic clients m_iVisualRange = minimum(newSight, g_Cfg.m_iMapViewSizeMax); pClient = GetClientActive(); @@ -1319,17 +1314,18 @@ bool CChar::ReadScriptReduced(CResourceLock &s, bool fVendor) continue; lptstr ptcFunctionName = s.GetArgRaw(); - std::unique_ptr pScriptArgs; - // Locate arguments for the called function + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + // Locate arguments for the called function tchar* ptcArgs = strchr(ptcFunctionName, ' '); if (ptcArgs) { *ptcArgs = 0; ++ptcArgs; GETNONWHITESPACE(ptcArgs); - pScriptArgs = std::make_unique(ptcArgs); - } - pItem->r_Call(ptcFunctionName, this, pScriptArgs.get()); + pScriptArgs->Init(ptcArgs); + } + pItem->r_Call(ptcFunctionName, pScriptArgs, this); if (pItem->IsDeleted()) { pItem = nullptr; @@ -1452,7 +1448,8 @@ bool CChar::ReadScriptReduced(CResourceLock &s, bool fVendor) else if (!fItemCreated) { // I'm setting an attribute to myself, not the item (e.g. @Create trigger). Run that script line. - TRIGRET_TYPE tRet = OnTriggerRun( s, TRIGRUN_SINGLE_EXEC, &g_Serv, nullptr, nullptr ); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + TRIGRET_TYPE tRet = OnTriggerRun( s, TRIGRUN_SINGLE_EXEC, pScriptArgs, &g_Serv, nullptr ); if ((tRet == TRIGRET_RET_FALSE) && fFullInterp) ; else if ( tRet != TRIGRET_RET_DEFAULT ) @@ -1523,16 +1520,16 @@ height_t CChar::GetHeight() const return tmpHeight; // This is SLOW (since this method is called very frequently)! Move those defs value to CharDef! + auto gReader = g_ExprGlobals.mtEngineLockedReader(); const uint uiDispID = (uint)pCharDef->GetDispID(); - char heightDef[20]{"height_"}; Str_FromUI(uint(uiDispID), heightDef + 7, sizeof(heightDef) - 7, 16); - tmpHeight = (height_t)(g_Exp.m_VarDefs.GetKeyNum(heightDef)); + tmpHeight = (height_t)(gReader->m_VarDefs.GetKeyNum(heightDef)); if ( tmpHeight ) //set by a defname ([DEFNAME charheight] height_0a) return tmpHeight; Str_FromUI(uint(uiDispID), heightDef + 7, sizeof(heightDef) - 7, 10); - tmpHeight = (height_t)(g_Exp.m_VarDefs.GetKeyNum(heightDef)); + tmpHeight = (height_t)(gReader->m_VarDefs.GetKeyNum(heightDef)); if ( tmpHeight ) //set by a defname ([DEFNAME charheight] height_10) return tmpHeight; @@ -1730,7 +1727,8 @@ void CChar::InitPlayer( CClient *pClient, const char *pszCharname, bool fFemale, tchar *zCharName = Str_GetTemp(); Str_CopyLimitNull(zCharName, pszCharname, MAX_NAME_SIZE); - if ( !strlen(zCharName) || g_Cfg.IsObscene(zCharName) || Str_CheckName(zCharName) ||!strnicmp(zCharName, "lord ", 5) || !strnicmp(zCharName, "lady ", 5) || + if ( !strlen(zCharName) || Str_Untrusted_InvalidName(zCharName) || + g_Cfg.IsObscene(zCharName) ||!strnicmp(zCharName, "lord ", 5) || !strnicmp(zCharName, "lady ", 5) || !strnicmp(zCharName, "seer ", 5) || !strnicmp(zCharName, "gm ", 3) || !strnicmp(zCharName, "admin ", 6) || !strnicmp(zCharName, "counselor ", 10) ) { fNameIsAccepted = false; @@ -1738,10 +1736,10 @@ void CChar::InitPlayer( CClient *pClient, const char *pszCharname, bool fFemale, if ( fNameIsAccepted && IsTrigUsed(TRIGGER_RENAME) ) { - CScriptTriggerArgs args; - args.m_s1 = zCharName; - args.m_pO1 = this; - if ( OnTrigger(CTRIG_Rename, this, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_s1 = zCharName; + pScriptArgs->m_pO1 = this; + if ( OnTrigger(CTRIG_Rename, pScriptArgs, this) == TRIGRET_RET_TRUE ) fNameIsAccepted = false; } @@ -3356,7 +3354,6 @@ bool CChar::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSrc, bo } - bool CChar::r_LoadVal( CScript & s ) { ADDTOCALLSTACK("CChar::r_LoadVal"); @@ -3829,7 +3826,7 @@ bool CChar::r_LoadVal( CScript & s ) }break; case CHC_CREATE: { - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) { _iTimeCreate = (CWorldGameTime::GetCurrentTime().GetTimeRaw() - (s.GetArgLLVal() * MSECS_PER_TENTH)); break; @@ -3861,7 +3858,7 @@ bool CChar::r_LoadVal( CScript & s ) } case CHC_FLAGS: { - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) { // Don't set STATF_SAVEPARITY at server startup, otherwise the first worldsave will not save these chars _uiStatFlag = s.GetArgLLVal() & ~ (uint64)STATF_SAVEPARITY; @@ -3875,7 +3872,7 @@ bool CChar::r_LoadVal( CScript & s ) _uiStatFlag = (_uiStatFlag & uiFlagsNoChange) | (uiNewFlags & ~uiFlagsNoChange); if (uiCurFlags != uiNewFlags) { - const bool fDoFullUpdate = (uiCurFlags & uiFlagsRequireFullUpdate) != (uiNewFlags & uiFlagsRequireFullUpdate); + const bool fDoFullUpdate = HAS_FLAGS_ANY(uiCurFlags, uiFlagsRequireFullUpdate); NotoSave_Update(fDoFullUpdate); } break; @@ -3930,12 +3927,12 @@ bool CChar::r_LoadVal( CScript & s ) { if (IsTrigUsed(TRIGGER_RENAME)) { - CScriptTriggerArgs args; - args.m_s1 = s.GetArgStr(); - args.m_pO1 = this; - if (this->OnTrigger(CTRIG_Rename, this, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_s1 = s.GetArgStr(); + pScriptArgs->m_pO1 = this; + if (this->OnTrigger(CTRIG_Rename, pScriptArgs, this) == TRIGRET_RET_TRUE) return false; - SetName(args.m_s1); + SetName(pScriptArgs->m_s1); } else SetName(s.GetArgStr()); @@ -3954,14 +3951,16 @@ bool CChar::r_LoadVal( CScript & s ) if ( s.GetArgStr() ) { tchar * ppArgs[4]; - int iQty = Str_ParseCmds(const_cast(s.GetArgStr()), ppArgs, ARRAY_COUNT( ppArgs )); + int iQty = Str_ParseCmds(s.GetArgStr(), ppArgs, ARRAY_COUNT( ppArgs )); if ( iQty >= 2 ) { SKILL_TYPE iSkill = g_Cfg.FindSkillKey( ppArgs[0] ); if ( iSkill == SKILL_NONE ) return false; - Skill_UseQuick( iSkill, Exp_GetVal( ppArgs[1] ), true, (Exp_GetVal(ppArgs[2]) != 0 ? false : true), (Exp_GetVal(ppArgs[3]) != 0 ? true : false)); + Skill_UseQuick( iSkill, Exp_GetVal( ppArgs[1] ), true, + (Exp_GetVal(ppArgs[2]) != 0 ? false : true), + (Exp_GetVal(ppArgs[3]) != 0 ? true : false)); return true; } } @@ -4418,10 +4417,11 @@ bool CChar::r_Verb( CScript &s, CTextConsole * pSrc ) // Execute command from sc if (IsTrigUsed(TRIGGER_AFKMODE)) { - CScriptTriggerArgs args(fAFK, fMode); - TRIGRET_TYPE iRet = OnTrigger(CTRIG_AfkMode, this, &args); - fAFK = args.m_iN1 > 0 ? true : false; - fMode = args.m_iN2 > 0 ? true : false; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(fAFK, fMode, 0, nullptr); + TRIGRET_TYPE iRet = OnTrigger(CTRIG_AfkMode, pScriptArgs, this); + fAFK = pScriptArgs->m_iN1 > 0 ? true : false; + fMode = pScriptArgs->m_iN2 > 0 ? true : false; if (iRet == TRIGRET_RET_TRUE) //Block AFK mode switching if RETURN 1 in Trigger. break; @@ -5108,7 +5108,7 @@ static uint Calc_ExpGet_Level(uint exp) } } -void CChar::ChangeExperience(llong delta, CChar *pCharDead) +void CChar::ChangeExperience(llong iExpDelta, CChar *pCharDead) { ADDTOCALLSTACK("CChar::ChangeExperience"); @@ -5127,9 +5127,9 @@ void CChar::ChangeExperience(llong delta, CChar *pCharDead) DEFMSG_MSG_EXP_CHANGE_8 }; - if (delta != 0 || pCharDead)// zero call will sync the exp level + if (iExpDelta != 0 || pCharDead)// zero call will sync the exp level { - if (delta < 0) + if (iExpDelta < 0) { if (!(g_Cfg.m_iExperienceMode&EXP_MODE_ALLOW_DOWN)) // do not allow changes to minus return; @@ -5137,39 +5137,40 @@ void CChar::ChangeExperience(llong delta, CChar *pCharDead) if (g_Cfg.m_fLevelSystem && g_Cfg.m_iExperienceMode&EXP_MODE_DOWN_NOLEVEL) { uint exp = Calc_ExpGet_Exp(m_level); - if (delta + m_exp < exp) - delta = (llong)exp - m_exp; + if (iExpDelta + m_exp < exp) + iExpDelta = (llong)exp - m_exp; } } - if ((g_Cfg.m_iDebugFlags & DEBUGF_EXP) && (delta != 0)) + if ((g_Cfg.m_iDebugFlags & DEBUGF_EXP) && (iExpDelta != 0)) { g_Log.EventDebug("%s %s experience change (was %u, delta %lld, now %u)\n", - (m_pNPC ? "NPC" : "Player"), GetName(), m_exp, delta, (uint)(m_exp + delta)); + (m_pNPC ? "NPC" : "Player"), GetName(), m_exp, iExpDelta, (uint)(m_exp + iExpDelta)); } bool fShowMsg = (m_pClient != nullptr); if (IsTrigUsed(TRIGGER_EXPCHANGE)) { - CScriptTriggerArgs args(delta, fShowMsg); - args.m_pO1 = pCharDead; - if (OnTrigger(CTRIG_ExpChange, this, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iExpDelta, fShowMsg, 0, nullptr); + pScriptArgs->m_pO1 = pCharDead; + if (OnTrigger(CTRIG_ExpChange, pScriptArgs, this) == TRIGRET_RET_TRUE) return; - delta = args.m_iN1; - fShowMsg = (args.m_iN2 != 0); + iExpDelta = pScriptArgs->m_iN1; + fShowMsg = (pScriptArgs->m_iN2 != 0); } // Do not allow an underflow due to negative Exp Change. - if( delta < 0 && m_exp < abs(delta) ) + if( iExpDelta < 0 && m_exp < abs(iExpDelta) ) m_exp = 0; else - m_exp = (uint)(m_exp + delta); + m_exp = (uint)(m_exp + iExpDelta); - if (m_pClient && fShowMsg && delta) + if (m_pClient && fShowMsg && iExpDelta) { int iWord = 0; - llong absval = abs(delta); + llong absval = abs(iExpDelta); llong maxval = (g_Cfg.m_fLevelSystem && g_Cfg.m_iLevelNextAt) ? maximum(g_Cfg.m_iLevelNextAt, 1000) : 1000; if (absval >= maxval) // 100% @@ -5188,7 +5189,7 @@ void CChar::ChangeExperience(llong delta, CChar *pCharDead) iWord = 1; m_pClient->SysMessagef(g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_CHANGE_0), - (delta > 0) ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_CHANGE_GAIN) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_CHANGE_LOST), + (iExpDelta > 0) ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_CHANGE_GAIN) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_CHANGE_LOST), g_Cfg.GetDefaultMsg(keyWords[iWord])); } } @@ -5199,23 +5200,24 @@ void CChar::ChangeExperience(llong delta, CChar *pCharDead) if (level != m_level) { - delta = level - m_level; + iExpDelta = level - m_level; bool fShowMsg = (m_pClient != nullptr); if (IsTrigUsed(TRIGGER_EXPLEVELCHANGE)) { - CScriptTriggerArgs args(delta); - if (OnTrigger(CTRIG_ExpLevelChange, this, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iExpDelta, 0, 0, nullptr); + if (OnTrigger(CTRIG_ExpLevelChange, pScriptArgs, this) == TRIGRET_RET_TRUE) return; - delta = args.m_iN1; - fShowMsg = (args.m_iN2 != 0); + iExpDelta = pScriptArgs->m_iN1; + fShowMsg = (pScriptArgs->m_iN2 != 0); } - level = delta + m_level; + level = iExpDelta + m_level; // Prevent integer underflow due to negative level change - if (delta < 0 && abs(delta) > m_level) + if (iExpDelta < 0 && abs(iExpDelta) > m_level) { level = 0; } @@ -5223,15 +5225,15 @@ void CChar::ChangeExperience(llong delta, CChar *pCharDead) if (g_Cfg.m_iDebugFlags & DEBUGF_LEVEL) { g_Log.EventDebug("%s %s level change (was %u, delta %lld, now %u)\n", - (m_pNPC ? "NPC" : "Player"), GetName(), m_level, delta, (uint)level); + (m_pNPC ? "NPC" : "Player"), GetName(), m_level, iExpDelta, (uint)level); } m_level = (uint)level; if (m_pClient && fShowMsg) { m_pClient->SysMessagef( - ((abs(delta) == 1) ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_0) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_1)), - ((delta > 0) ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_GAIN) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_LOST)) + ((abs(iExpDelta) == 1) ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_0) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_1)), + ((iExpDelta > 0) ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_GAIN) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_EXP_LVLCHANGE_LOST)) ); } } diff --git a/src/game/chars/CChar.h b/src/game/chars/CChar.h index 6f2c5eec0..3af9ab0e6 100644 --- a/src/game/chars/CChar.h +++ b/src/game/chars/CChar.h @@ -42,7 +42,7 @@ enum NPCBRAIN_TYPE // General AI type. NPCBRAIN_STABLE, // 7 = will store your animals for you. NPCBRAIN_MONSTER, // 8 = not tamable. normally evil. NPCBRAIN_BERSERK, // 9 = attack closest (blades, vortex) - NPCBRAIN_DRAGON, // 10 = we can breath fire. may be tamable ? hirable ? + NPCBRAIN_DRAGON, // 10 = we can breathe fire. may be tamable ? hirable ? NPCBRAIN_QTY }; @@ -81,15 +81,15 @@ class CChar : public CObjBase, public CContainer, public CTextConsole #define STATF_INCOGNITO 0x00000800 // Dont show skill titles #define STATF_SPIRITSPEAK 0x00001000 // I can hear ghosts clearly. #define STATF_INSUBSTANTIAL 0x00002000 // Ghost has not manifest. or GM hidden -#define STATF_EMOTEACTION 0x00004000 // The creature will emote its actions to it's owners. -#define STATF_COMM_CRYSTAL 0x00008000 // I have a IT_COMM_CRYSTAL or listening item on me. +#define STATF_EMOTEACTION 0x00004000 // The creature will emote its actions to its owners. +#define STATF_COMM_CRYSTAL 0x00008000 // I have an IT_COMM_CRYSTAL or listening item on me. #define STATF_HASSHIELD 0x00010000 // Using a shield #define STATF_ARCHERCANMOVE 0x00020000 // Can move with archery #define STATF_STONE 0x00040000 // turned to stone. #define STATF_HOVERING 0x00080000 // hovering (flying gargoyle) #define STATF_FLY 0x00100000 // Flying or running ? (anim) // 0x00200000 -#define STATF_HALLUCINATING 0x00400000 // eat 'shrooms or bad food. +#define STATF_HALLUCINATING 0x00400000 // eat mushrooms or bad food. #define STATF_HIDDEN 0x00800000 // Hidden (non-magical) #define STATF_INDOORS 0x01000000 // we are covered from the rain. #define STATF_CRIMINAL 0x02000000 // The guards will attack me. (someone has called guards) @@ -117,7 +117,7 @@ class CChar : public CObjBase, public CContainer, public CTextConsole struct NotoSaves { - int64 time; // Updaete timer + int64 time; // Update timer dword charUID; // Character viewing me NOTO_TYPE color; // Color sent on movement packets NOTO_TYPE value; // Notoriety type @@ -128,7 +128,7 @@ class CChar : public CObjBase, public CContainer, public CTextConsole CCharPlayer * m_pPlayer; // May even be an off-line player ! CCharNPC * m_pNPC; // we can be both a player and an NPC if "controlled" ? - CPartyDef * m_pParty; // What party am i in ? + CPartyDef * m_pParty; // What party am I in ? CRegionWorld * m_pArea; // What region are we in now. (for guarded message) CRegion * m_pRoom; // What room we are in now. @@ -143,7 +143,7 @@ class CChar : public CObjBase, public CContainer, public CTextConsole word m_defense; // calculated armor worn (NOT intrinsic armor) ushort _iRegenTickCount; // ticks until next regen. - CUID m_UIDLastNewItem; // Last item created, used to store on this CChar the UID of the last created item via ITEM or ITEMNEWBIe in @Create and @Restock to prevent COLOR, etc properties to be called with no reference when the item was not really created, ie: ITEM=i_dagger,R5 + CUID m_UIDLastNewItem; // Last item created, used to store on this CChar the UID of the last created item via ITEM or ITEMNEWBIe in @Create and @Restock to prevent COLOR, etc. properties to be called with no reference when the item was not really created, ie: ITEM=i_dagger,R5 uint m_exp; // character experience uint m_level; // character experience level //DIR_TYPE m_dirClimb; // we are standing on a CAN_I_CLIMB or UFLAG2_CLIMBABLE, DIR_QTY = not on climbable @@ -160,9 +160,9 @@ class CChar : public CObjBase, public CContainer, public CTextConsole CSString m_sTitle; // Special title such as "the guard" (replaces the normal skill title) CPointMap m_ptHome; // What is our "home" region. (towns and bounding of NPC's) - int64 _iTimeCreate; // When was i created ? + int64 _iTimeCreate; // When was I created ? int64 _iTimePeriodicTick; - int64 _iTimeNextRegen; // When did i get my last regen tick ? + int64 _iTimeNextRegen; // When did I get my last regen tick ? int64 _iTimeLastHitsUpdate; int64 _iTimeLastCallGuards; @@ -191,7 +191,7 @@ class CChar : public CObjBase, public CContainer, public CTextConsole ushort m_val; // Hits, Mana, Stam ushort m_max; // MaxVal: MaxHits, MaxMana, MaxStam ushort m_regenVal; // Amount of Stat to gain at each regen - int64 m_regenRate; // Regen each this much milliseconds. + int64 m_regenRate; // Regen each this many milliseconds. int64 m_regenLast; // Time of the last regen. } m_Stat[STAT_QTY]; @@ -285,7 +285,7 @@ class CChar : public CObjBase, public CContainer, public CTextConsole int16 m_iRecoilDelay; // ACTARG2 & 0x0000FFFF = Duration (in tenth of secs) of the previous swing recoil time. int16 m_iSwingAnimationDelay; // ACTARG2 & 0xFFFF0000 = Duration (in tenth of secs) of the previous swing animation duration. int16 m_iSwingAnimation; // ACTARG3 & 0x0000FFFF = hit animation id. - int16 m_iSwingIgnoreLastHitTag; // ACTARG3 & 0xFFFF0000. Internally used by PreHit. If == 1 (which happens only for the hit after the first instahit), for this hit TAG.LastHit delay checking will be ignored. + int16 m_iSwingIgnoreLastHitTag; // ACTARG3 & 0xFFFF0000. Internally used by PreHit. If == 1 (which happens only for the hit after the first instant hit), for this hit TAG.LastHit delay checking will be ignored. } m_atFight; // SKILL_ENTICEMENT @@ -321,9 +321,9 @@ class CChar : public CObjBase, public CContainer, public CTextConsole // NPCACT_TALK_FOLLOW struct { - dword m_dwHearUnknown; // ACTARG1 = Speaking NPC has no idea what u're saying. - dword m_dwWaitCount; // ACTARG2 = How long have i been waiting (xN sec) - // m_Act_UID = who am i talking to ? + dword m_dwHearUnknown; // ACTARG1 = Speaking NPC has no idea what you are saying. + dword m_dwWaitCount; // ACTARG2 = How long have I been waiting (xN sec) + // m_Act_UID = who am I talking to ? } m_atTalk; // NPCACT_FLEE @@ -331,7 +331,7 @@ class CChar : public CObjBase, public CContainer, public CTextConsole { dword m_iStepsMax; // ACTARG1 = How long should it take to get there. dword m_iStepsCurrent; // ACTARG2 = How long has it taken ? - // m_Act_UID = who am i fleeing from ? + // m_Act_UID = who am I fleeing from ? } m_atFlee; }; @@ -381,7 +381,9 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; CCharBase * Char_GetDef() const; CRegionWorld * GetRegion() const; CRegion * GetRoom() const; - virtual int GetVisualRange() const override; + + [[nodiscard]] + virtual int GetVisualRange() const override; void SetVisualRange(byte newSight); virtual bool IsResourceMatch( const CResourceID& rid, dword dwArg ) const override; @@ -589,8 +591,8 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; CItem * LayerFind( LAYER_TYPE layer ) const; void LayerAdd( CItem * pItem, LAYER_TYPE layer = LAYER_QTY ); - TRIGRET_TYPE OnCharTrigForLayerLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, LAYER_TYPE layer ); - TRIGRET_TYPE OnCharTrigForMemTypeLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, word wMemType ); + TRIGRET_TYPE OnCharTrigForLayerLoop(CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult, LAYER_TYPE layer ); + TRIGRET_TYPE OnCharTrigForMemTypeLoop( CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult, word wMemType ); virtual void OnWeightChange( int iChange ) override; virtual int GetWeight(word amount = 0) const override; @@ -618,8 +620,8 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; */ void SetTriggerActive(lpctstr trig = nullptr); - virtual TRIGRET_TYPE OnTrigger( lpctstr pTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) override; - TRIGRET_TYPE OnTrigger( CTRIG_TYPE trigger, CTextConsole * pSrc, CScriptTriggerArgs * pArgs = nullptr ); + virtual TRIGRET_TYPE OnTrigger( lpctstr pTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) override; + TRIGRET_TYPE OnTrigger( CTRIG_TYPE trigger, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc); public: // Load/Save---------------------------------- @@ -640,11 +642,12 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * @brief Update Karma with the given values. * * Used to increase/decrease Karma values, checks if you can have the resultant values, - * fire @KarmaChange trigger and show a message as result of the change (if procceed). - * Can't never be greater than g_Cfg.m_iMaxKarma or lower than g_Cfg.m_iMinKarma or iBottom. - * @param iKarma Amount of karma to change, can be possitive and negative. + * fire @KarmaChange trigger and show a message as result of the change (if proceed). + * Can't be greater than g_Cfg.m_iMaxKarma or lower than g_Cfg.m_iMinKarma or iBottom. + * @param iKarmaChange Amount of karma to change, can be positive and negative. * @param iBottom is the lower value you can have for this execution. - * @param bMessage show message to the char or not. + * @param fMessage show message to the char or not. + * @param pNPC The character whose fame is changing. */ void Noto_Karma( int iKarmaChange, int iBottom = INT32_MIN, bool fMessage = false, CChar* pNPC = nullptr ); @@ -652,22 +655,23 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * @brief Update Fame with the given value. * * Used to increase/decrease Fame, it fires @FameChange trigger. - * Can't never exceed g_Cfg.m_iMaxFame and can't never be lower than 0. + * Can't exceed g_Cfg.m_iMaxFame and can't be lower than 0. * @param iFameChange is the amount of fame to change over the current one. + * @param pNPC The character whose fame is changing. */ void Noto_Fame( int iFameChange, CChar* pNPC = nullptr ); /** * @brief I have a new notoriety Level? check it and show a message if so. * - * @param iPriv The 'new' notoriety level, it will be checked against the current level to see if it changed. + * @param iPrv The 'new' notoriety level, it will be checked against the current level to see if it changed. */ void Noto_ChangeNewMsg( int iPrv ); /** * @brief I've become murderer or criminal, let's see a message for it. * - * MSG_NOTO_CHANGE_0-8 contains the strings 'you have gained a bit/a lot/etc of', so the given iDelta is used to check wether of these MSG should be used. + * MSG_NOTO_CHANGE_0-8 contains the strings 'you have gained a bit/a lot/etc of', so the given iDelta is used to check which of these MSG should be used. * @param iDelta Amount of Karma/Fame changed. * @param pszType String containing 'Karma' or 'Fame' to pass as argument to the given text. */ @@ -680,11 +684,11 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * @brief Returns what is this char to the viewer. * * This allows the noto attack check in the client. - * Notoriety handler using std::vector, it's saved and readed here but calcs are being made in Noto_CalcFlag(). + * Notoriety handler using std::vector, it's saved and read here but calcs are being made in Noto_CalcFlag(). * Actually 2 values are stored in this vectored list: Notoriety (the notoriety level) and Color (the color we are showing in the HP bar and in our character for the viewer). * Calls @NotoSend trigger with src = pChar, argn1 = notoriety level, argn2 = color to send. * @param pChar is the CChar that needs to know what I am (good, evil, criminal, neutral...) to him. - * @param fIncog if set to true and he has STATF_INCOGNITO, this character will be gray for the viever (pChar). + * @param fIncog if set to true, and he has STATF_INCOGNITO, this character will be gray for the viewer (pChar). * @param fInvul if set to true invulnerable characters will return NOTO_INVUL (yellow bar, etc). * @param fGetColor if set to true only the color will be returned and not the notoriety (note that they can differ if set to so in the @NotoSend trigger). * @return NOTO_TYPE notoriety level. @@ -692,23 +696,23 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; NOTO_TYPE Noto_GetFlag( const CChar * pChar, bool fIncog = true, bool fInvul = false, bool fGetColor = false ) const; /** - * @brief Notoriety calculations + * Notoriety calculations. What is this char to the viewer? This allows the noto attack check in the client. * * TAG.OVERRIDE.NOTO will override everything and use the value in the tag for everyone, regardless of what I really are for them. - * If this char is a pet, check if notoriety must be inherited from it's master or do regular checks for it. - * @param pChar is the CChar that needs to know what I am (good, evil, criminal, neutral...) to him. - * @param fIncog if set to true (usually because of Incognito spell), this character will be gray for the viever (pChar). - * @param fInvul if set to true invulnerable characters will return NOTO_INVUL (yellow bar, etc). + * If this char is a pet, check if notoriety must be inherited from its master or do regular checks for it. + * @param pCharViewer is the CChar that needs to know what I am (good, evil, criminal, neutral...) to him. + * @param fAllowIncog if set to true (usually because of Incognito spell), this character will be gray for the viewer (pChar). + * @param fAllowInvul if set to true invulnerable characters will return NOTO_INVUL (yellow bar, etc). * @return NOTO_TYPE notoriety level. */ - NOTO_TYPE Noto_CalcFlag( const CChar * pChar, bool fIncog = false, bool fInvul = false ) const; + NOTO_TYPE Noto_CalcFlag( const CChar * pCharViewer, bool fAllowIncog = false, bool fAllowInvul = false ) const; /** * @brief What color should the viewer see from me? * * Used to retrieve color for character and corpse's names. * @param pChar is the CChar that needs to know what I am (good, evil, criminal, neutral...) to him. - * @param fIncog if set to true (usually because of Incognito spell), this character will be gray for the viever (pChar). + * @param fIncog if set to true (usually because of Incognito spell), this character will be gray for the viewer (pChar). * @return HUE_TYPE my color. */ HUE_TYPE Noto_GetHue( const CChar * pChar, bool fIncog = false ) const; @@ -769,7 +773,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; * @brief I killed someone, should I have credits? and penalties? * * Here fires the @MurderMark trigger, also gives exp if level system is enabled to give exp on killing. - * @param pKill, the chara I killed (or participated to kill). + * @param pKill the chara I killed (or participated to kill). * @param iTotalKillers how many characters participated in this kill. */ void Noto_Kill(CChar * pKill, int iTotalKillers = 0); @@ -831,14 +835,14 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; /** * @brief Deleting myself and sending data again for given char. * - * @param pChar, the CChar* of the char of which we want to resend the noto. + * @param pChar the CChar* of the char of which we want to resend the noto. */ void NotoSave_Resend( CChar *pChar ); /** * @brief Gets the entry list of the given CChar. * - * @param pChar, CChar to retrieve the entry number for. + * @param pChar CChar to retrieve the entry number for. * @return the entry number. */ int NotoSave_GetID( CChar * pChar ) const; @@ -846,16 +850,35 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; /** * @brief Removing stored data for pChar. * - * @param pChar, the CChar I want to remove from my list. + * @param pChar the CChar I want to remove from my list. * @return true if successfully removed it. */ bool NotoSave_Delete( CChar * pChar ); /** - * @brief Removing expired notorieties. + * @brief Removing expired notoriety. */ void NotoSave_CheckTimeout(); + /** + * Helper for checking guild / town war status. + */ + enum NOTO_WAR_STATUS: byte + { + NOTO_WAR_NONE = 0, + NOTO_WAR_ALLY, + NOTO_WAR_ENEMY, + }; + + /** + * Checks ally / war status between two guild / town stones. + * + * @param pMyStone My Guild/Town stone. + * @param pViewerStone The character's looking at me Guild/Town stone. + * @return War status (none, ally, enemy) + */ + NOTO_WAR_STATUS Noto_GetWarStatus(const CItemStone* pMyStone, const CItemStone* pViewerStone) const; + /** * @brief We are snooping or stealing, is taking this item a crime ? * @@ -865,16 +888,15 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; */ bool IsTakeCrime( const CItem * pItem, CChar ** ppCharMark = nullptr ) const; - /** * @brief We killed a character, starting exp calcs * * Main function for default Level system. * Triggers @ExpChange and @LevelChange if needed - * @param delta, amount of exp gaining (or losing?) - * @param ppCharDead from who we gained the experience. + * @param iExpDelta amount of exp gaining (or losing?) + * @param pCharDead from who we gained the experience. */ - void ChangeExperience(llong delta = 0, CChar *pCharDead = nullptr); + void ChangeExperience(llong iExpDelta = 0, CChar *pCharDead = nullptr); uint GetSkillTotal(int what = 0, bool how = true); /* @@ -916,7 +938,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; void Skill_SetBase( SKILL_TYPE skill, ushort uiValue ); void Skill_AddBase( SKILL_TYPE skill, int iChange ); - bool Skill_UseQuick( SKILL_TYPE skill, int64 difficulty, bool fAllowGain = true, bool fUseBellCurve = true, bool fForceCheck = false); + bool Skill_UseQuick( SKILL_TYPE skill, int64 iDifficulty, bool fAllowGain = true, bool fUseBellCurve = true, bool fForceCheck = false); bool Skill_CheckSuccess(SKILL_TYPE skill, int iDifficulty, bool fUseBellCurve = true ) const; bool Skill_Wait( SKILL_TYPE skilltry ); @@ -926,11 +948,9 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; ANIM_TYPE Skill_GetAnim( SKILL_TYPE skill); SOUND_TYPE Skill_GetSound( SKILL_TYPE skill); int Skill_Stage( SKTRIG_TYPE stage ); - TRIGRET_TYPE Skill_OnTrigger( SKILL_TYPE skill, SKTRIG_TYPE stage); - TRIGRET_TYPE Skill_OnTrigger( SKILL_TYPE skill, SKTRIG_TYPE stage, CScriptTriggerArgs * pArgs); //pArgs.m_iN1 will be rewritten with skill - TRIGRET_TYPE Skill_OnCharTrigger( SKILL_TYPE skill, CTRIG_TYPE ctrig); - TRIGRET_TYPE Skill_OnCharTrigger( SKILL_TYPE skill, CTRIG_TYPE ctrig, CScriptTriggerArgs * pArgs); //pArgs.m_iN1 will be rewritten with skill + TRIGRET_TYPE Skill_OnTrigger(SKILL_TYPE skill, SKTRIG_TYPE stage, CScriptTriggerArgsPtr const& pScriptArgs); //pScriptArgs.m_iN1 will be rewritten with skill + TRIGRET_TYPE Skill_OnCharTrigger( SKILL_TYPE skill, CTRIG_TYPE ctrig, CScriptTriggerArgsPtr const& pScriptArgs); //pArgs.m_iN1 will be rewritten with skill bool Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ); bool Skill_Tracking( CUID uidTarg, DIR_TYPE & dirPrv, int iDistMax = INT16_MAX ); @@ -1114,7 +1134,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; inline int GetAttackersCount() { return (int)m_lastAttackers.size(); } - bool Attacker_Add(CChar * pChar, int threat = 0); + bool Attacker_Add(CChar * pChar, int iThreat = 0); CChar * Attacker_GetLast() const; bool Attacker_Delete(std::vector::iterator &itAttacker, bool fForced = false, ATTACKER_CLEAR_TYPE type = ATTACKER_CLEAR_FORCED); bool Attacker_Delete(int attackerIndex, bool fForced = false, ATTACKER_CLEAR_TYPE type = ATTACKER_CLEAR_FORCED); @@ -1378,9 +1398,9 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; bool IsPeriodicTickPending() const; - virtual bool _CanTick() const override final; + virtual bool _TickableState() const override final; -protected: virtual bool _OnTick() override final; // _OnTick timeout for skills, AI, etc +protected: virtual bool _OnTick() override final; // _OnTick timeout for skills, AI, etc. //public: virtual bool _OnTick() override final; public: @@ -1388,7 +1408,7 @@ protected: virtual bool _OnTick() override final; // _OnTick timeout for skills void OnTickFood( ushort uiVal, int HitsHungerLoss ); virtual void OnTickStatusUpdate() override; - bool OnTickPeriodic(); // Periodic tick calls (update stats, status bar, notoriety & attackers, death check, etc) + bool OnTickPeriodic(); // Periodic tick calls (update stats, status bar, notoriety & attackers, death check, etc.) void OnTickSkill(); // _OnTick timeout specific for the skill behavior diff --git a/src/game/chars/CCharAct.cpp b/src/game/chars/CCharAct.cpp index 62e40e1be..85070eed8 100644 --- a/src/game/chars/CCharAct.cpp +++ b/src/game/chars/CCharAct.cpp @@ -1,7 +1,8 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CUOInstall.h" #include "../../network/CClientIterator.h" #include "../../network/send.h" @@ -24,7 +25,6 @@ #include "../triggers.h" #include "CChar.h" #include "CCharNPC.h" -#include // "GONAME", "GOTYPE", "GOCHAR" @@ -154,16 +154,17 @@ void CChar::Jail( CTextConsole * pSrc, bool fSet, int iCell ) { ADDTOCALLSTACK("CChar::Jail"); - CScriptTriggerArgs Args( fSet? 1 : 0, (int64)(iCell), (int64)(0)); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(fSet, (int64)iCell, 0, nullptr); + + if ( IsTrigUsed(TRIGGER_JAILED) ) + { + if ( OnTrigger( CTRIG_Jailed, pScriptArgs, pSrc ) == TRIGRET_RET_TRUE ) + return; + } if ( fSet ) // set the jailed flag. { - if ( IsTrigUsed(TRIGGER_JAILED) ) - { - if ( OnTrigger( CTRIG_Jailed, pSrc, &Args ) == TRIGRET_RET_TRUE ) - return; - } - if ( m_pPlayer ) // allow setting of this to offline chars. { CAccount *pAccount = m_pPlayer->GetAccount(); @@ -191,12 +192,6 @@ void CChar::Jail( CTextConsole * pSrc, bool fSet, int iCell ) } else // forgive. { - if ( IsTrigUsed(TRIGGER_JAILED) ) - { - if ( OnTrigger( CTRIG_Jailed, pSrc, &Args ) == TRIGRET_RET_TRUE ) - return; - } - if ( IsClientActive()) { if ( ! m_pClient->IsPriv( PRIV_JAILED )) @@ -225,9 +220,9 @@ void CChar::AddGoldToPack( int iAmount, CItemContainer * pPack, bool fForceNoSta if ( pPack == nullptr ) pPack = GetPackSafe(); - if (iAmount > 25000000) + if (iAmount > 25'000'000) { - iAmount = 25000000; + iAmount = 25'000'000; g_Log.EventWarn("The amount of the new Gold to create is too big. Limited to 25.000.000.\n"); } word iMax = 0; @@ -268,7 +263,7 @@ void CChar::LayerAdd( CItem * pItem, LAYER_TYPE layer ) // NOTE: CanEquipLayer may bounce an item . If it stacks with this we are in trouble ! } - if ( g_Serv.IsLoading() == false ) + if ( g_Serv.IsLoadingGeneric() == false ) { // This takes care of any conflicting items in the slot ! layer = CanEquipLayer(pItem, layer, nullptr, false); @@ -286,10 +281,9 @@ void CChar::LayerAdd( CItem * pItem, LAYER_TYPE layer ) { if ((IsTrigUsed(TRIGGER_MEMORYEQUIP)) || (IsTrigUsed(TRIGGER_ITEMMEMORYEQUIP))) { - //CScriptTriggerArgs pArgs; - CScriptTriggerArgs pArgs(pItem); // added "argo" argument - pArgs.m_iN1 = layer; - if (pItem->OnTrigger(ITRIG_MemoryEquip, this, &pArgs) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(layer, 0, 0, pItem); // added "argo" argument + if (pItem->OnTrigger(ITRIG_MemoryEquip, pScriptArgs, this) == TRIGRET_RET_TRUE) { pItem->Delete(); return; @@ -412,8 +406,8 @@ void CChar::OnRemoveObj( CSObjContRec* pObRec ) // Override this = called when r LAYER_TYPE layer = pItem->GetEquipLayer(); if (( IsTrigUsed(TRIGGER_UNEQUIP) ) || ( IsTrigUsed(TRIGGER_ITEMUNEQUIP) )) { - if ( layer != LAYER_DRAGGING && ! g_Serv.IsLoading()) - pItem->OnTrigger( ITRIG_UNEQUIP, this ); + if ( layer != LAYER_DRAGGING && ! g_Serv.IsLoadingGeneric()) + pItem->OnTrigger( ITRIG_UNEQUIP, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this ); } CContainer::OnRemoveObj( pObRec ); @@ -706,7 +700,7 @@ void CChar::SysMessage( lpctstr pMsg ) const // Push a message back to the clien void CChar::UpdateStatsFlag() const { ADDTOCALLSTACK("CChar::UpdateStatsFlag"); - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) return; if ( IsClientActive() ) @@ -718,7 +712,7 @@ void CChar::UpdateStatsFlag() const void CChar::UpdateHitsFlag() { ADDTOCALLSTACK("CChar::UpdateHitsFlag"); - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) return; m_fStatusUpdate |= SU_UPDATE_HITS; @@ -730,7 +724,7 @@ void CChar::UpdateHitsFlag() void CChar::UpdateModeFlag() { ADDTOCALLSTACK("CChar::UpdateModeFlag"); - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) return; m_fStatusUpdate |= SU_UPDATE_MODE; @@ -739,7 +733,7 @@ void CChar::UpdateModeFlag() void CChar::UpdateManaFlag() const { ADDTOCALLSTACK("CChar::UpdateManaFlag"); - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) return; if ( IsClientActive() ) @@ -749,7 +743,7 @@ void CChar::UpdateManaFlag() const void CChar::UpdateStamFlag() const { ADDTOCALLSTACK("CChar::UpdateStamFlag"); - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) return; if ( IsClientActive() ) @@ -2466,7 +2460,7 @@ void CChar::UpdateMode( bool fFull, CClient * pExcludeClient ) void CChar::UpdateSpeedMode() { ADDTOCALLSTACK("CChar::UpdateSpeedMode"); - if ( g_Serv.IsLoading() || !m_pPlayer ) + if ( g_Serv.IsLoadingGeneric() || !m_pPlayer ) return; if ( IsClientActive() ) @@ -2476,7 +2470,7 @@ void CChar::UpdateSpeedMode() void CChar::UpdateVisualRange() { ADDTOCALLSTACK("CChar::UpdateVisualRange"); - if ( g_Serv.IsLoading() || !m_pPlayer ) + if ( g_Serv.IsLoadingGeneric() || !m_pPlayer ) return; DEBUG_WARN(("CChar::UpdateVisualRange called, m_iVisualRange is %d\n", m_iVisualRange)); @@ -2992,8 +2986,9 @@ int CChar::ItemPickup(CItem * pItem, word amount) { if (( IsTrigUsed(CItem::sm_szTrigName[trigger]) ) || ( IsTrigUsed(sm_szTrigName[(CTRIG_itemAfterClick - 1) + trigger]) )) //ITRIG_PICKUP_GROUND, ITRIG_PICKUP_PACK { - CScriptTriggerArgs Args( amount ); - if ( pItem->OnTrigger( trigger, this, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(amount, 0, 0, nullptr); + if ( pItem->OnTrigger( trigger, pScriptArgs, this) == TRIGRET_RET_TRUE ) return -1; } if (( trigger == ITRIG_PICKUP_PACK ) && (( IsTrigUsed(TRIGGER_PICKUP_SELF) ) || ( IsTrigUsed(TRIGGER_ITEMPICKUP_SELF) ))) @@ -3001,14 +2996,14 @@ int CChar::ItemPickup(CItem * pItem, word amount) CItem * pContItem = dynamic_cast ( pItem->GetContainer() ); if ( pContItem ) { - CScriptTriggerArgs Args1(pItem); - if ( pContItem->OnTrigger(ITRIG_PICKUP_SELF, this, &Args1) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pItem); + if ( pContItem->OnTrigger(ITRIG_PICKUP_SELF, pScriptArgs, this) == TRIGRET_RET_TRUE ) return -1; } } } - if ( amount < iAmountMax && pItem->Item_GetDef()->IsStackableType() && pItem->CanSendAmount() ) { // Create an leftover item when pick up only part of the stack @@ -3018,8 +3013,9 @@ int CChar::ItemPickup(CItem * pItem, word amount) if (IsTrigUsed(TRIGGER_PICKUP_STACK) || IsTrigUsed(TRIGGER_ITEMPICKUP_STACK)) { - CScriptTriggerArgs Args2(pItemNew); - if (pItem->OnTrigger(ITRIG_PICKUP_STACK, this, &Args2) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pItemNew); + if (pItem->OnTrigger(ITRIG_PICKUP_STACK, pScriptArgs, this) == TRIGRET_RET_TRUE) return false; } } @@ -3096,8 +3092,9 @@ bool CChar::ItemBounce( CItem * pItem, bool fDisplayMsg ) fCanAddToPack = true; if (IsTrigUsed(TRIGGER_DROPON_ITEM)) { - CScriptTriggerArgs Args(pPack); - pItem->OnTrigger(ITRIG_DROPON_ITEM, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pPack); + pItem->OnTrigger(ITRIG_DROPON_ITEM, pScriptArgs, this); if (pItem->IsDeleted()) // the trigger had deleted the item return false; @@ -3106,11 +3103,15 @@ bool CChar::ItemBounce( CItem * pItem, bool fDisplayMsg ) if (IsTrigUsed(TRIGGER_DROPON_SELF) || IsTrigUsed(TRIGGER_ITEMDROPON_SELF)) { const CItem* pPrevCont = dynamic_cast(pItem->GetContainer()); - CScriptTriggerArgs Args(pItem); - const TRIGRET_TYPE ret = pPack->OnTrigger(ITRIG_DROPON_SELF, this, &Args); - if (pItem->IsDeleted()) // the trigger had deleted the item + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pItem); + const TRIGRET_TYPE ret = pPack->OnTrigger(ITRIG_DROPON_SELF, pScriptArgs, this); + + if (pItem->IsDeleted()) // the trigger had deleted the item return false; - if (ret == TRIGRET_RET_TRUE) + + if (ret == TRIGRET_RET_TRUE) { fCanAddToPack = false; const CItem* pCont = dynamic_cast(pItem->GetContainer()); @@ -3159,19 +3160,19 @@ bool CChar::ItemBounce( CItem * pItem, bool fDisplayMsg ) TRIGRET_TYPE ttResult = TRIGRET_RET_DEFAULT; if (IsTrigUsed(TRIGGER_DROPON_GROUND) || IsTrigUsed(TRIGGER_ITEMDROPON_GROUND)) { - CScriptTriggerArgs args; - args.m_iN1 = iDecayTime / MSECS_PER_TENTH; // ARGN1 = Decay time for the dropped item (in tenths of second) - args.m_iN2 = 1; - args.m_s1 = ptDrop.WriteUsed(); - ttResult = pItem->OnTrigger(ITRIG_DROPON_GROUND, this, &args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = iDecayTime / MSECS_PER_TENTH; // ARGN1 = Decay time for the dropped item (in tenths of second) + pScriptArgs->m_iN2 = 1; + pScriptArgs->m_s1 = ptDrop.WriteUsed(); + ttResult = pItem->OnTrigger(ITRIG_DROPON_GROUND, pScriptArgs, this); if (IsDeleted()) return false; - iDecayTime = args.m_iN1 * MSECS_PER_TENTH; + iDecayTime = pScriptArgs->m_iN1 * MSECS_PER_TENTH; // Warning: here we ignore the read-onlyness of CSString's buffer only because we know that CPointMap constructor won't write past the end, but only replace some characters with '\0'. It's not worth it to build another string just for that. - tchar* ptcArgs = const_cast(args.m_s1.GetBuffer()); + tchar* ptcArgs = const_cast(pScriptArgs->m_s1.GetBuffer()); const CPointMap ptDropNew(ptcArgs); if (!ptDropNew.IsValidPoint()) g_Log.EventError("Trying to override item drop P with an invalid P. Using the original one.\n"); @@ -3285,9 +3286,12 @@ bool CChar::ItemEquip( CItem * pItem, CChar * pCharMsg, bool fFromDClick ) if ( !pCharMsg ) pCharMsg = this; + // Remember original layer, because we need to return to it after equiptest trigger. + LAYER_TYPE const requestedLayer = pItem->GetEquipLayer(); + if ( pItem->GetParent() == this ) { - if ( pItem->GetEquipLayer() != LAYER_DRAGGING ) // already equipped. + if ( requestedLayer != LAYER_DRAGGING ) // already equipped. return true; } @@ -3303,8 +3307,14 @@ bool CChar::ItemEquip( CItem * pItem, CChar * pCharMsg, bool fFromDClick ) if (IsTrigUsed(TRIGGER_EQUIPTEST) || IsTrigUsed(TRIGGER_ITEMEQUIPTEST)) { - if (pItem->OnTrigger(ITRIG_EQUIPTEST, this) == TRIGRET_RET_TRUE) + // Swap the layer for real one, because if we don't use dclick for equip, real layer gets rewritten with LAYER_DRAGGING. + pItem->SetContainedLayer(layer); + + if (pItem->OnTrigger(ITRIG_EQUIPTEST, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this) == TRIGRET_RET_TRUE) { + // Reset layer to the original value, that item doesn't get misplaced. + pItem->SetContainedLayer(requestedLayer); + // since this trigger is called also when creating an item via ITEM=, if the created item has a RETURN 1 in @EquipTest // (or if the NPC has a RETURN 1 in @ItemEquipTest), the item will be created but not placed in the world. // so, if this is an NPC, even if there's a RETURN 1 i need to bounce the item inside his pack @@ -3315,6 +3325,9 @@ bool CChar::ItemEquip( CItem * pItem, CChar * pCharMsg, bool fFromDClick ) return false; } + // Reset layer to the original value. + pItem->SetContainedLayer(requestedLayer); + if (pItem->IsDeleted()) return false; } @@ -3332,7 +3345,7 @@ bool CChar::ItemEquip( CItem * pItem, CChar * pCharMsg, bool fFromDClick ) if (( IsTrigUsed(TRIGGER_EQUIP) ) || ( IsTrigUsed(TRIGGER_ITEMEQUIP) )) { - if ( pItem->OnTrigger(ITRIG_EQUIP, this) == TRIGRET_RET_TRUE ) + if ( pItem->OnTrigger(ITRIG_EQUIP, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this) == TRIGRET_RET_TRUE ) return false; } @@ -3446,20 +3459,21 @@ void CChar::EatAnim(CItem* pItem, ushort uiQty) ushort uiStatsLimit = 0; if (IsTrigUsed(TRIGGER_EAT)) { - CScriptTriggerArgs Args(uiStatsLimit); - Args.m_VarsLocal.SetNumNew("Hits", uiHits); - Args.m_VarsLocal.SetNumNew("Mana", uiMana); - Args.m_VarsLocal.SetNumNew("Stam", uiStam); - Args.m_VarsLocal.SetNumNew("Food", uiFood); - Args.m_pO1 = pItem; - if ( OnTrigger(CTRIG_Eat, this, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = uiStatsLimit; + pScriptArgs->m_VarsLocal.SetNumNew("Hits", uiHits); + pScriptArgs->m_VarsLocal.SetNumNew("Mana", uiMana); + pScriptArgs->m_VarsLocal.SetNumNew("Stam", uiStam); + pScriptArgs->m_VarsLocal.SetNumNew("Food", uiFood); + pScriptArgs->m_pO1 = pItem; + if ( OnTrigger(CTRIG_Eat, pScriptArgs, this) == TRIGRET_RET_TRUE ) return; - uiHits = (ushort)(Args.m_VarsLocal.GetKeyNum("Hits")) + Stat_GetVal(STAT_STR); - uiMana = (ushort)(Args.m_VarsLocal.GetKeyNum("Mana")) + Stat_GetVal(STAT_INT); - uiStam = (ushort)(Args.m_VarsLocal.GetKeyNum("Stam")) + Stat_GetVal(STAT_DEX); - uiFood = (ushort)(Args.m_VarsLocal.GetKeyNum("Food")) + Stat_GetVal(STAT_FOOD); - uiStatsLimit = (ushort)(Args.m_iN1); + uiHits = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("Hits")) + Stat_GetVal(STAT_STR); + uiMana = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("Mana")) + Stat_GetVal(STAT_INT); + uiStam = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("Stam")) + Stat_GetVal(STAT_DEX); + uiFood = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("Food")) + Stat_GetVal(STAT_FOOD); + uiStatsLimit = (ushort)(pScriptArgs->m_iN1); } if ( uiHits ) @@ -3485,7 +3499,7 @@ bool CChar::Reveal( uint64 iFlags ) if (IsTrigUsed(TRIGGER_REVEAL)) { - if (OnTrigger(CTRIG_Reveal, this, nullptr) == TRIGRET_RET_TRUE) + if (OnTrigger(CTRIG_Reveal, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this) == TRIGRET_RET_TRUE) return false; } @@ -3910,7 +3924,7 @@ ITEMID_TYPE CChar::Horse_GetMountItemID() const { tchar* ptcMountID = Str_GetTemp(); snprintf(ptcMountID, Str_TempLength(), "mount_0x%x", GetDispID()); - lpctstr ptcMemoryID = g_Exp.m_VarDefs.GetKeyStr(ptcMountID); // get the mount item defname from the mount_0x** defname + lpctstr ptcMemoryID = g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKeyStr(ptcMountID); // get the mount item defname from the mount_0x** defname CResourceID memoryRid = g_Cfg.ResourceGetID(RES_ITEMDEF, ptcMemoryID); return (ITEMID_TYPE)(memoryRid.GetResIndex()); // get the ID of the memory (mount item) @@ -3962,12 +3976,13 @@ bool CChar::Horse_Mount(CChar *pHorse) if ( IsTrigUsed(TRIGGER_MOUNT) ) { - CScriptTriggerArgs Args(pHorse); - Args.m_iN1 = memoryId; - if ( OnTrigger(CTRIG_Mount, this, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pHorse); + pScriptArgs->m_iN1 = memoryId; + if ( OnTrigger(CTRIG_Mount, pScriptArgs, this) == TRIGRET_RET_TRUE ) return false; else - memoryId = ITEMID_TYPE(Args.m_iN1);//(ITEMID_TYPE) Args.m_iN1; + memoryId = ITEMID_TYPE(pScriptArgs->m_iN1);//(ITEMID_TYPE) Args.m_iN1; } // Create the figurine for the horse (Memory item equiped on layer 25 of the player) @@ -4006,8 +4021,9 @@ bool CChar::Horse_UnMount() CChar * pPet = pMountItem->m_itFigurine.m_UID.CharFind(); if (pPet && IsTrigUsed(TRIGGER_DISMOUNT) && pPet->IsDisconnected() && !pPet->IsDeleted() ) // valid horse for trigger { - CScriptTriggerArgs Args(pPet); - if ( OnTrigger(CTRIG_Dismount, this, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pPet); + if ( OnTrigger(CTRIG_Dismount, pScriptArgs, this) == TRIGRET_RET_TRUE ) return false; } @@ -4100,21 +4116,21 @@ bool CChar::OnTickEquip( CItem * pItem ) if ( ! m_pPlayer || m_pPlayer->m_wMurders <= 0 ) return false; - CScriptTriggerArgs args; - args.m_iN1 = m_pPlayer->m_wMurders - 1; - args.m_iN2 = g_Cfg.m_iMurderDecayTime; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = m_pPlayer->m_wMurders - 1; + pScriptArgs->m_iN2 = g_Cfg.m_iMurderDecayTime; if ( IsTrigUsed(TRIGGER_MURDERDECAY) ) { - OnTrigger(CTRIG_MurderDecay, this, &args); - if ( args.m_iN1 < 0 ) args.m_iN1 = 0; - if ( args.m_iN2 < 1 ) args.m_iN2 = g_Cfg.m_iMurderDecayTime; + OnTrigger(CTRIG_MurderDecay, pScriptArgs, this); + if ( pScriptArgs->m_iN1 < 0 ) pScriptArgs->m_iN1 = 0; + if ( pScriptArgs->m_iN2 < 1 ) pScriptArgs->m_iN2 = g_Cfg.m_iMurderDecayTime; } - m_pPlayer->m_wMurders = (word)(args.m_iN1); + m_pPlayer->m_wMurders = (word)(pScriptArgs->m_iN1); NotoSave_Update(); if ( m_pPlayer->m_wMurders == 0 ) return false; - pItem->SetTimeout(args.m_iN2); // update it's decay time. + pItem->SetTimeout(pScriptArgs->m_iN2); // update it's decay time. return true; } @@ -4302,7 +4318,7 @@ CChar::DeathRequestResult CChar::Death() if ( IsTrigUsed(TRIGGER_DEATH) ) { - if ( OnTrigger(CTRIG_Death, this) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_Death, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this) == TRIGRET_RET_TRUE ) return DeathRequestResult::Aborted; } //Dismount now. Later is may be too late and cause problems @@ -4346,10 +4362,9 @@ CChar::DeathRequestResult CChar::Death() { if ( IsTrigUsed(TRIGGER_KILL) ) { - CScriptTriggerArgs args(this); - args.m_iN1 = GetAttackersCount(); - args.m_pO1 = this; - if ( pKiller->OnTrigger(CTRIG_Kill, pKiller, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(GetAttackersCount(), 0, 0, this); + if ( pKiller->OnTrigger(CTRIG_Kill, pScriptArgs, pKiller) == TRIGRET_RET_TRUE ) continue; } @@ -4397,8 +4412,9 @@ CChar::DeathRequestResult CChar::Death() { if ( IsTrigUsed(TRIGGER_DEATHCORPSE) ) { - CScriptTriggerArgs Args(pCorpse); - OnTrigger(CTRIG_DeathCorpse, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pCorpse); + OnTrigger(CTRIG_DeathCorpse, pScriptArgs, this); } } m_lastAttackers.clear(); // clear list of attackers @@ -4592,6 +4608,7 @@ bool CChar::ShoveCharAtPosition(CPointMap const& ptDst, ushort *uiStaminaRequire // If i'm not pathfinding, ensure that i pass a valid uiStaminaRequirement, since i'll need it for the walk checks. ASSERT(fPathFinding || (nullptr != uiStaminaRequirement)); ushort uiLocalStamReq = 0; + bool fRequireFullStamina = true; CItem *pPoly = LayerFind(LAYER_SPELL_Polymorph); auto AreaChars = CWorldSearchHolder::GetInstance(ptDst); @@ -4613,28 +4630,34 @@ bool CChar::ShoveCharAtPosition(CPointMap const& ptDst, ushort *uiStaminaRequire else if ((pPoly && pPoly->m_itSpell.m_spell == SPELL_Wraith_Form) && (GetTopMap() == 0)) // chars under Wraith Form effect can always walk through chars in Felucca uiLocalStamReq = 0; + fRequireFullStamina = true; + TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; if (!fPathFinding) //You want to avoid to trig the triggers if it's only a pathfinding evaluation { if (IsTrigUsed(TRIGGER_PERSONALSPACE)) { - CScriptTriggerArgs Args(uiLocalStamReq); - iRet = pChar->OnTrigger(CTRIG_PersonalSpace, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = uiLocalStamReq; + pScriptArgs->m_iN3 = fRequireFullStamina; + iRet = pChar->OnTrigger(CTRIG_PersonalSpace, pScriptArgs, this); if (iRet == TRIGRET_RET_TRUE) goto set_and_return_false; - uiLocalStamReq = (ushort)(Args.m_iN1); + uiLocalStamReq = (ushort)(pScriptArgs->m_iN1); + fRequireFullStamina = static_cast(pScriptArgs->m_iN3); } if (IsTrigUsed(TRIGGER_CHARSHOVE)) { - CScriptTriggerArgs Args(uiLocalStamReq); - iRet = this->OnTrigger(CTRIG_charShove, pChar, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = uiLocalStamReq; + iRet = this->OnTrigger(CTRIG_charShove, pScriptArgs, pChar); if (iRet == TRIGRET_RET_TRUE) goto set_and_return_false; - uiLocalStamReq = (ushort)(Args.m_iN1); + uiLocalStamReq = (ushort)(pScriptArgs->m_iN1); } } - if ((uiLocalStamReq > 0) && (Stat_GetVal(STAT_DEX) < Stat_GetMaxAdjusted(STAT_DEX))) + if ((uiLocalStamReq > 0) && fRequireFullStamina && (Stat_GetVal(STAT_DEX) < Stat_GetMaxAdjusted(STAT_DEX))) goto set_and_return_false; if (Stat_GetVal(STAT_DEX) < uiLocalStamReq) // check if we have enough stamina to push the char @@ -4755,8 +4778,9 @@ CRegion * CChar::CanMoveWalkTo( CPointMap & ptDst, bool fCheckChars, bool fCheck //char is falling if ( IsTrigUsed(TRIGGER_FALLING) ) { - CScriptTriggerArgs Args(ptDst.m_x, ptDst.m_y, ptDst.m_z); - OnTrigger(CTRIG_Falling, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(ptDst.m_x, ptDst.m_y, ptDst.m_z, nullptr); + OnTrigger(CTRIG_Falling, pScriptArgs, this); } } @@ -4821,7 +4845,7 @@ void CChar::CheckRevealOnMove() return; if ( IsTrigUsed(TRIGGER_STEPSTEALTH) ) - OnTrigger(CTRIG_StepStealth, this); + OnTrigger(CTRIG_StepStealth, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this); if (g_Cfg.m_iRevealFlags & REVEALF_ONHORSE && IsStatFlag(STATF_ONHORSE)) Reveal(); @@ -4924,8 +4948,9 @@ TRIGRET_TYPE CChar::CheckLocationEffects(bool fStanding) else { _uiRecursingItemStep += 1; - CScriptTriggerArgs Args(fStanding ? 1 : 0); - TRIGRET_TYPE iRet = pItem->OnTrigger(ITRIG_STEP, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = fStanding ? 1 : 0; + TRIGRET_TYPE iRet = pItem->OnTrigger(ITRIG_STEP, pScriptArgs, this); _uiRecursingItemStep -= 1; if (iRet == TRIGRET_RET_TRUE) // block walk { @@ -5080,7 +5105,7 @@ bool CChar::MoveToRegion( CRegionWorld * pNewArea, bool fAllowReject ) if ( m_pArea == pNewArea ) return true; - if ( ! g_Serv.IsLoading()) + if ( ! g_Serv.IsLoadingGeneric()) { if ( fAllowReject && IsPriv( PRIV_GM )) { @@ -5101,8 +5126,9 @@ bool CChar::MoveToRegion( CRegionWorld * pNewArea, bool fAllowReject ) if ( IsTrigUsed(TRIGGER_REGIONLEAVE) ) { - CScriptTriggerArgs Args(m_pArea); - if ( OnTrigger(CTRIG_RegionLeave, this, & Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(m_pArea); + if ( OnTrigger(CTRIG_RegionLeave, pScriptArgs, this) == TRIGRET_RET_TRUE ) { if ( pNewArea && fAllowReject ) return false; @@ -5162,8 +5188,9 @@ bool CChar::MoveToRegion( CRegionWorld * pNewArea, bool fAllowReject ) } if ( IsTrigUsed(TRIGGER_REGIONENTER) ) { - CScriptTriggerArgs Args(pNewArea); - if ( OnTrigger(CTRIG_RegionEnter, this, & Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pNewArea); + if ( OnTrigger(CTRIG_RegionEnter, pScriptArgs, this) == TRIGRET_RET_TRUE ) { if ( m_pArea && fAllowReject ) return false; @@ -5186,7 +5213,7 @@ bool CChar::MoveToRoom( CRegion * pNewRoom, bool fAllowReject) if ( m_pRoom == pNewRoom ) return true; - if ( ! g_Serv.IsLoading()) + if ( ! g_Serv.IsLoadingGeneric()) { if ( fAllowReject && IsPriv( PRIV_GM )) { @@ -5207,8 +5234,9 @@ bool CChar::MoveToRoom( CRegion * pNewRoom, bool fAllowReject) if ( IsTrigUsed(TRIGGER_REGIONLEAVE) ) { - CScriptTriggerArgs Args(m_pRoom); - if ( OnTrigger(CTRIG_RegionLeave, this, & Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(m_pRoom); + if ( OnTrigger(CTRIG_RegionLeave, pScriptArgs, this) == TRIGRET_RET_TRUE ) { if (fAllowReject ) return false; @@ -5229,8 +5257,9 @@ bool CChar::MoveToRoom( CRegion * pNewRoom, bool fAllowReject) } if ( IsTrigUsed(TRIGGER_REGIONENTER) ) { - CScriptTriggerArgs Args(pNewRoom); - if ( OnTrigger(CTRIG_RegionEnter, this, & Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pNewRoom); + if ( OnTrigger(CTRIG_RegionEnter, pScriptArgs, this) == TRIGRET_RET_TRUE ) { if (fAllowReject ) return false; @@ -5298,12 +5327,17 @@ bool CChar::MoveToChar(const CPointMap& pt, bool fStanding, bool fCheckLocationE if ( !m_fClimbUpdated || fForceFix ) FixClimbHeight(); - if ( fSectorChanged && !g_Serv.IsLoading() ) + if ( fSectorChanged && !g_Serv.IsLoadingGeneric() ) { if ( IsTrigUsed(TRIGGER_ENVIRONCHANGE) ) { - CScriptTriggerArgs Args(ptOld.m_x, ptOld.m_y, ((uchar)ptOld.m_z << 16) | ptOld.m_map); - OnTrigger(CTRIG_EnvironChange, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init( + ptOld.m_x, + ptOld.m_y, + ((uchar)ptOld.m_z << 16) | ptOld.m_map, + nullptr); + OnTrigger(CTRIG_EnvironChange, pScriptArgs, this); } } @@ -5494,13 +5528,14 @@ void CChar::SetTriggerActive(lpctstr trig) // 4) CHARDEF // 5) EVENTSPET/EVENTSPLAYER set on .ini file // RETURNS = TRIGRET_TYPE (in cscriptobj.h) -TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) { ADDTOCALLSTACK("CChar::OnTrigger"); if ( IsTriggerActive( pszTrigName ) ) //This should protect any char trigger from infinite loop return TRIGRET_RET_DEFAULT; + ASSERT(pScriptArgs); if ( !pSrc ) pSrc = &g_Serv; @@ -5528,7 +5563,7 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript EXC_SET_BLOCK("chardef"); const CUID uidOldAct = pChar->m_Act_UID; pChar->m_Act_UID = GetUID(); - iRet = pChar->OnTrigger(ptcCharTrigName, pSrc, pArgs); + iRet = pChar->OnTrigger(ptcCharTrigName, pScriptArgs, pSrc); pChar->m_Act_UID = uidOldAct; if (iRet == TRIGRET_RET_TRUE) goto stopandret; // Block further action. @@ -5542,7 +5577,12 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript // if ( IsTrigUsed(pszTrigName) ) { - fc::vector_set executedEvents; + std::vector executedEvents; + auto fnShouldSkipLink = [&executedEvents, &iAction](const CResourceLink *pLink) -> bool + { + return (!pLink || !pLink->HasTrigger(iAction) + || (executedEvents.end() != std::find(executedEvents.begin(), executedEvents.end(), pLink))); + }; { EXC_SET_BLOCK("events"); @@ -5551,15 +5591,15 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript for (size_t i = 0; i < curEvents; ++i) // EVENTS (could be modifyed ingame!) { CResourceLink* pLink = m_OEvents[i].GetRef(); - if (!pLink || !pLink->HasTrigger(iAction) || (executedEvents.find(pLink) != executedEvents.end())) + if (fnShouldSkipLink(pLink)) continue; CResourceLock s; if (!pLink->ResourceLock(s)) continue; - executedEvents.emplace(pLink); - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + executedEvents.emplace_back(pLink); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if (iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT) goto stopandret; @@ -5579,15 +5619,15 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript for ( size_t i = 0; i < pCharDef->m_TEvents.size(); ++i ) { CResourceLink * pLink = pCharDef->m_TEvents[i].GetRef(); - if (!pLink || !pLink->HasTrigger(iAction) || (executedEvents.find(pLink) != executedEvents.end())) + if (fnShouldSkipLink(pLink)) continue; CResourceLock s; if (!pLink->ResourceLock(s)) continue; - executedEvents.emplace(pLink); - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + executedEvents.emplace_back(pLink); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if ( iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT ) goto stopandret; } @@ -5602,7 +5642,7 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript CResourceLock s; if ( pCharDef->ResourceLock(s) ) { - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if (( iRet != TRIGRET_RET_FALSE ) && ( iRet != TRIGRET_RET_DEFAULT )) goto stopandret; } @@ -5616,15 +5656,15 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript for (size_t i = 0; i < g_Cfg.m_pEventsPetLink.size(); ++i) { CResourceLink * pLink = g_Cfg.m_pEventsPetLink[i].GetRef(); - if (!pLink || !pLink->HasTrigger(iAction) || (executedEvents.find(pLink) != executedEvents.end())) + if (fnShouldSkipLink(pLink)) continue; CResourceLock s; if (!pLink->ResourceLock(s)) continue; - executedEvents.emplace(pLink); - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + executedEvents.emplace_back(pLink); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if (iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT) goto stopandret; } @@ -5638,15 +5678,15 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript for ( size_t i = 0; i < g_Cfg.m_pEventsPlayerLink.size(); ++i ) { CResourceLink *pLink = g_Cfg.m_pEventsPlayerLink[i].GetRef(); - if (!pLink || !pLink->HasTrigger(iAction) || (executedEvents.find(pLink) != executedEvents.end())) + if (fnShouldSkipLink(pLink)) continue; CResourceLock s; if (!pLink->ResourceLock(s)) continue; - executedEvents.emplace(pLink); - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + executedEvents.emplace_back(pLink); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if ( iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT ) goto stopandret; } @@ -5664,10 +5704,10 @@ TRIGRET_TYPE CChar::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript return iRet; } -TRIGRET_TYPE CChar::OnTrigger( CTRIG_TYPE trigger, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CChar::OnTrigger( CTRIG_TYPE trigger, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) { ASSERT( (trigger > CTRIG_AAAUNUSED) && (trigger < CTRIG_QTY) ); - return OnTrigger( CChar::sm_szTrigName[trigger], pSrc, pArgs ); + return OnTrigger( CChar::sm_szTrigName[trigger], pScriptArgs, pSrc); } // process m_fStatusUpdate flags @@ -5776,10 +5816,10 @@ void CChar::OnTickSkill() EXC_CATCHSUB("Skill tick"); } -bool CChar::_CanTick() const +bool CChar::_TickableState() const { - //ADDTOCALLSTACK_DEBUG("CChar::_CanTick"); - EXC_TRY("Can tick?"); + //ADDTOCALLSTACK_DEBUG("CChar::_TickableState"); + EXC_TRY("Able to tick?"); if (IsDisconnected()) { @@ -5790,7 +5830,7 @@ bool CChar::_CanTick() const return false; } - return CObjBase::_CanTick(); + return CObjBase::_TickableState(); EXC_CATCH; @@ -5848,24 +5888,43 @@ bool CChar::_OnTick() return true; } - if (!_CanTick()) + const bool fTickableState = _TickableState(); + const bool fSleeping = _IsSleeping(); + +//#ifdef _DEBUG + if (!fTickableState || fSleeping) + { + g_Log.EventDebug("[Temporary msg] Char '%s' (UID=0x%" PRIx32 ") at P=%s is in the ticking list with unusual TickableState=%d, SleepingState=%d.\n", + GetName(), GetUID().GetObjUID(), + GetTopPoint().WriteUsed(), + (int)fTickableState, (int)fSleeping + ); + } +//#endif + + if (!fTickableState) { // It can happen that i'm in the ticking list, but for various reasons right now i'm in a non-tickable state. // Among the reasons why i can't tick, though, there cannot be being in a sleeping state: when a char goes into sleeping state // it should also be removed from the list (it happens in _GoSleep()). - ASSERT(!_IsSleeping()); + ASSERT(!fSleeping); if (GetTopSector()->IsSleeping() && !g_Rand.Get16ValFast(15)) { // Do not make the char sleep right when it enters a sleeping sector. Doing this // will lead to an accumulation of npcs at the edge of the new sector. +//#ifdef _DEBUG + g_Log.EventDebug("Sent to sleep (random), to be awaken alongside its sector.\n"); +//#endif + _SetTimeout(1); //Make it tick after sector's awakening. _GoSleep(); - return true; } + return true; } - ASSERT(!_IsSleeping()); + if (fSleeping) + return true; EXC_SET_BLOCK("Components Tick"); /* @@ -6024,7 +6083,12 @@ bool CChar::OnTickPeriodic() int64 CChar::PayGold(CChar * pCharSrc, int64 iGold, CItem * pGold, ePayGold iReason) { ADDTOCALLSTACK("CChar::PayGold"); - CScriptTriggerArgs Args(iGold,iReason,pGold); - OnTrigger(CTRIG_PayGold,pCharSrc,&Args); - return Args.m_iN1; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init( + iGold, + iReason, + 0, + pGold); + OnTrigger(CTRIG_PayGold, pScriptArgs, pCharSrc); + return pScriptArgs->m_iN1; } diff --git a/src/game/chars/CCharAttacker.cpp b/src/game/chars/CCharAttacker.cpp index d899bde75..eb3b81489 100644 --- a/src/game/chars/CCharAttacker.cpp +++ b/src/game/chars/CCharAttacker.cpp @@ -1,12 +1,14 @@ // Actions specific to an NPC. -#include "../../common/CExpression.h" + +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../clients/CClient.h" #include "../triggers.h" #include "CChar.h" // Add some enemy to my Attacker list -bool CChar::Attacker_Add(CChar * pChar, int threat) +bool CChar::Attacker_Add(CChar * pChar, int iThreat) { ADDTOCALLSTACK("CChar::Attacker_Add"); const dword uid = pChar->GetUID().GetObjUID(); @@ -21,7 +23,7 @@ bool CChar::Attacker_Add(CChar * pChar, int threat) } else if (IsTrigUsed(TRIGGER_COMBATSTART)) { - TRIGRET_TYPE tRet = OnTrigger(CTRIG_CombatStart, pChar, nullptr); + TRIGRET_TYPE tRet = OnTrigger(CTRIG_CombatStart, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar); if (tRet == TRIGRET_RET_TRUE) return false; else @@ -31,30 +33,30 @@ bool CChar::Attacker_Add(CChar * pChar, int threat) } } - CScriptTriggerArgs Args; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); bool fIgnore = false; - Args.m_iN1 = threat; - Args.m_iN2 = fIgnore; + pScriptArgs->m_iN1 = iThreat; + pScriptArgs->m_iN2 = fIgnore; if (IsTrigUsed(TRIGGER_COMBATADD)) { - TRIGRET_TYPE tRet = OnTrigger(CTRIG_CombatAdd, pChar, &Args); + TRIGRET_TYPE tRet = OnTrigger(CTRIG_CombatAdd, pScriptArgs, pChar); if (tRet == TRIGRET_RET_TRUE) return false; - threat = (int)Args.m_iN1; - fIgnore = (Args.m_iN2 != 0); + iThreat = (int)pScriptArgs->m_iN1; + fIgnore = (pScriptArgs->m_iN2 != 0); } - LastAttackers attacker; - attacker.amountDone = 0; - attacker.charUID = uid; - attacker.elapsed = 0; - attacker.threat = (m_pPlayer) ? 0 : threat; - attacker.ignore = fIgnore; - m_lastAttackers.emplace_back(std::move(attacker)); + m_lastAttackers.emplace_back(LastAttackers{ + .elapsed = 0, + .charUID = uid, + .amountDone = 0, + .threat = (m_pPlayer) ? 0 : iThreat, + .ignore = fIgnore + }); // Record the start of the fight. Memory_Fight_Start(pChar); - if (!attacker.ignore) + if (!fIgnore) { tchar *z = Str_GetTemp(); CClient *pClient = pChar->GetClientActive(); @@ -66,7 +68,7 @@ bool CChar::Attacker_Add(CChar * pChar, int threat) if (m_EmoteHueOverride != 0) //Set EMOTECOLOROVERRIDE to ATTACKERS emoteHue = m_EmoteHueOverride; else - emoteHue = (HUE_TYPE)(g_Exp.m_VarDefs.GetKeyNum("EMOTE_DEF_COLOR")); + emoteHue = (HUE_TYPE)(g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKeyNum("EMOTE_DEF_COLOR")); snprintf(z, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_COMBAT_ATTACKO), GetName(), pChar->GetName()); UpdateObjMessage(z, nullptr, pClient, emoteHue, TALKMODE_EMOTE); } @@ -256,7 +258,7 @@ void CChar::Attacker_Clear() { if (m_lastAttackers.empty() || !Fight_IsActive() || !m_Fight_Targ_UID.IsValidUID() || !m_Fight_Targ_UID.CharFind()) { - OnTrigger(CTRIG_CombatEnd, this, nullptr); + OnTrigger(CTRIG_CombatEnd, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this); } } @@ -323,10 +325,10 @@ bool CChar::Attacker_Delete(std::vector::iterator &itAttacker, bo { if (IsTrigUsed(TRIGGER_COMBATDELETE)) { - CScriptTriggerArgs Args; - Args.m_iN1 = fForced; - Args.m_iN2 = (int)type; - TRIGRET_TYPE tRet = OnTrigger(CTRIG_CombatDelete, pChar, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = fForced; + pScriptArgs->m_iN2 = (int)type; + TRIGRET_TYPE tRet = OnTrigger(CTRIG_CombatDelete, pScriptArgs, pChar); if ((tRet == TRIGRET_RET_TRUE) && !fForced) return false; } diff --git a/src/game/chars/CCharBase.cpp b/src/game/chars/CCharBase.cpp index 30ecdb6e2..3a61720f7 100644 --- a/src/game/chars/CCharBase.cpp +++ b/src/game/chars/CCharBase.cpp @@ -1,6 +1,6 @@ #include "../../common/resource/CResourceLock.h" -#include "../../common/CException.h" +//#include "../../common/CException.h" // included in the precompiled header #include "../../common/CLog.h" #include "../components/CCPropsChar.h" #include "../components/CCPropsItemChar.h" diff --git a/src/game/chars/CCharFight.cpp b/src/game/chars/CCharFight.cpp index 6e7164b23..10e6451f8 100644 --- a/src/game/chars/CCharFight.cpp +++ b/src/game/chars/CCharFight.cpp @@ -2,7 +2,8 @@ // Fight/Criminal actions/Noto. #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/send.h" #include "../clients/CClient.h" #include "../components/CCPropsChar.h" @@ -44,11 +45,11 @@ void CChar::OnNoticeCrime( CChar * pCriminal, CChar * pCharMark ) bool fMakeCriminal = false; //We don't need to call guards automatically in default. if (IsTrigUsed(TRIGGER_SEECRIME)) { - CScriptTriggerArgs Args; - Args.m_iN1 = fMakeCriminal; - Args.m_pO1 = pCharMark; - OnTrigger(CTRIG_SeeCrime, pCriminal, &Args); - fMakeCriminal = Args.m_iN1 ? true : false; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = fMakeCriminal; + pScriptArgs->m_pO1 = pCharMark; + OnTrigger(CTRIG_SeeCrime, pScriptArgs, pCriminal); + fMakeCriminal = pScriptArgs->m_iN1 ? true : false; } Memory_AddObjTypes(pCriminal, MEMORY_SAWCRIME); //Memory should always be added to the player. if (fMakeCriminal) //We call guards automatically if ARGN1 set to 1 (true) in trigger. @@ -139,11 +140,11 @@ bool CChar::CheckCrimeSeen( SKILL_TYPE SkillToSee, CChar * pCharMark, const CObj { if (IsTrigUsed(TRIGGER_SEESNOOP)) { - CScriptTriggerArgs Args(ptcAction); - Args.m_iN1 = SkillToSee; - Args.m_iN2 = pItem ? (dword)pItem->GetUID() : 0; // here i can modify pItem via scripts, so it isn't really const - Args.m_pO1 = pCharMark; - TRIGRET_TYPE iRet = pChar->OnTrigger(CTRIG_SeeSnoop, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = SkillToSee; + pScriptArgs->m_iN2 = pItem ? (dword)pItem->GetUID() : 0; // here i can modify pItem via scripts, so it isn't really const + pScriptArgs->m_pO1 = pCharMark; + TRIGRET_TYPE iRet = pChar->OnTrigger(CTRIG_SeeSnoop, pScriptArgs, this); if (iRet == TRIGRET_RET_TRUE) continue; @@ -254,17 +255,17 @@ bool CChar::CallGuards( CChar * pCriminal ) CResourceID rid = g_Cfg.ResourceGetIDType(RES_CHARDEF, (pVarDefGuards ? pVarDefGuards->GetValStr() : "GUARDS")); if (IsTrigUsed(TRIGGER_CALLGUARDS)) { - CScriptTriggerArgs Args(pGuard); - Args.m_iN1 = rid.GetResIndex(); - Args.m_iN2 = 0; - Args.m_VarObjs.Insert(1, pCriminal, true); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = rid.GetResIndex(); + pScriptArgs->m_iN2 = 0; + pScriptArgs->m_VarObjs.Insert(1, pCriminal, true); - if (OnTrigger(CTRIG_CallGuards, pCriminal, &Args) == TRIGRET_RET_TRUE) + if (OnTrigger(CTRIG_CallGuards, pScriptArgs, pCriminal) == TRIGRET_RET_TRUE) return false; - if ( (uint)Args.m_iN1 != rid.GetResIndex()) - rid = CResourceID(RES_CHARDEF, (int)Args.m_iN1); - if (Args.m_iN2 > 0) //ARGN2: If set to 1, a new guard will be spawned regardless of whether a nearby guard is available. + if ( (uint)pScriptArgs->m_iN1 != rid.GetResIndex()) + rid = CResourceID(RES_CHARDEF, (int)pScriptArgs->m_iN1); + if (pScriptArgs->m_iN2 > 0) //ARGN2: If set to 1, a new guard will be spawned regardless of whether a nearby guard is available. pGuard = nullptr; } if (!pGuard) // spawn a new guard @@ -384,18 +385,20 @@ bool CChar::OnAttackedBy(CChar * pCharSrc, bool fCommandPet, bool fShouldReveal) // Armor layers that can be damaged on combat // PS: Hand layers (weapons/shields) are not included here -static const LAYER_TYPE sm_ArmorDamageLayers[] = { LAYER_SHOES, LAYER_PANTS, LAYER_SHIRT, LAYER_HELM, LAYER_GLOVES, LAYER_COLLAR, LAYER_HALF_APRON, LAYER_CHEST, LAYER_TUNIC, LAYER_ARMS, LAYER_CAPE, LAYER_ROBE, LAYER_SKIRT, LAYER_LEGS }; +static constexpr LAYER_TYPE sm_ArmorDamageLayers[] = { + LAYER_SHOES, LAYER_PANTS, LAYER_SHIRT, LAYER_HELM, LAYER_GLOVES, LAYER_COLLAR, + LAYER_HALF_APRON, LAYER_CHEST, LAYER_TUNIC, LAYER_ARMS, LAYER_CAPE, LAYER_ROBE, LAYER_SKIRT, LAYER_LEGS }; // Layers covering the armor zone -static const LAYER_TYPE sm_ArmorLayerHead[] = { LAYER_HELM }; // ARMOR_HEAD -static const LAYER_TYPE sm_ArmorLayerNeck[] = { LAYER_COLLAR }; // ARMOR_NECK -static const LAYER_TYPE sm_ArmorLayerBack[] = { LAYER_SHIRT, LAYER_CHEST, LAYER_TUNIC, LAYER_CAPE, LAYER_ROBE }; // ARMOR_BACK -static const LAYER_TYPE sm_ArmorLayerChest[] = { LAYER_SHIRT, LAYER_CHEST, LAYER_TUNIC, LAYER_ROBE }; // ARMOR_CHEST -static const LAYER_TYPE sm_ArmorLayerArms[] = { LAYER_ARMS, LAYER_CAPE, LAYER_ROBE }; // ARMOR_ARMS -static const LAYER_TYPE sm_ArmorLayerHands[] = { LAYER_GLOVES }; // ARMOR_HANDS -static const LAYER_TYPE sm_ArmorLayerLegs[] = { LAYER_PANTS, LAYER_SKIRT, LAYER_HALF_APRON, LAYER_ROBE, LAYER_LEGS }; // ARMOR_LEGS -static const LAYER_TYPE sm_ArmorLayerFeet[] = { LAYER_SHOES, LAYER_LEGS }; // ARMOR_FEET -static const LAYER_TYPE sm_ArmorLayerShield[] = { LAYER_HAND2 }; +static constexpr LAYER_TYPE sm_ArmorLayerHead[] = { LAYER_HELM }; // ARMOR_HEAD +static constexpr LAYER_TYPE sm_ArmorLayerNeck[] = { LAYER_COLLAR }; // ARMOR_NECK +static constexpr LAYER_TYPE sm_ArmorLayerBack[] = { LAYER_SHIRT, LAYER_CHEST, LAYER_TUNIC, LAYER_CAPE, LAYER_ROBE }; // ARMOR_BACK +static constexpr LAYER_TYPE sm_ArmorLayerChest[] = { LAYER_SHIRT, LAYER_CHEST, LAYER_TUNIC, LAYER_ROBE }; // ARMOR_CHEST +static constexpr LAYER_TYPE sm_ArmorLayerArms[] = { LAYER_ARMS, LAYER_CAPE, LAYER_ROBE }; // ARMOR_ARMS +static constexpr LAYER_TYPE sm_ArmorLayerHands[] = { LAYER_GLOVES }; // ARMOR_HANDS +static constexpr LAYER_TYPE sm_ArmorLayerLegs[] = { LAYER_PANTS, LAYER_SKIRT, LAYER_HALF_APRON, LAYER_ROBE, LAYER_LEGS }; // ARMOR_LEGS +static constexpr LAYER_TYPE sm_ArmorLayerFeet[] = { LAYER_SHOES, LAYER_LEGS }; // ARMOR_FEET +static constexpr LAYER_TYPE sm_ArmorLayerShield[] = { LAYER_HAND2 }; struct CArmorLayerType { @@ -403,7 +406,7 @@ struct CArmorLayerType const LAYER_TYPE * m_pLayers; }; -static const CArmorLayerType sm_ArmorLayers[ARMOR_QTY] = +static constexpr CArmorLayerType sm_ArmorLayers[ARMOR_QTY] = { { 15, sm_ArmorLayerHead }, // ARMOR_HEAD, 15% of the armour value will be applied. { 7, sm_ArmorLayerNeck }, // ARMOR_NECK, 7% of the armour value will be applied. @@ -744,48 +747,51 @@ int CChar::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uiType, int iDmgPhy } } - CScriptTriggerArgs Args( iDmg, uiType, (int64)(0) ); - Args.m_VarsLocal.SetNum("ItemDamageLayer", sm_ArmorDamageLayers[(size_t)g_Rand.Get16ValFast(ARRAY_COUNT(sm_ArmorDamageLayers))]); - Args.m_VarsLocal.SetNum("ItemDamageChance", 25); - Args.m_VarsLocal.SetNum("Spell", (int)spell); - - if ( fElemental ) - { - Args.m_VarsLocal.SetNum("DamagePercentPhysical", iDmgPhysical); - Args.m_VarsLocal.SetNum("DamagePercentFire", iDmgFire); - Args.m_VarsLocal.SetNum("DamagePercentCold", iDmgCold); - Args.m_VarsLocal.SetNum("DamagePercentPoison", iDmgPoison); - Args.m_VarsLocal.SetNum("DamagePercentEnergy", iDmgEnergy); - } - - CItem* pItemHit = nullptr; - if ( IsTrigUsed(TRIGGER_GETHIT) ) - { - if ( OnTrigger( CTRIG_GetHit, pSrc, &Args ) == TRIGRET_RET_TRUE ) - return 0; - iDmg = (int)(Args.m_iN1); - uiType = (DAMAGE_TYPE)(Args.m_iN2); + { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iDmg, uiType, 0, nullptr); + pScriptArgs->m_VarsLocal.SetNum("ItemDamageLayer", sm_ArmorDamageLayers[(uint)g_Rand.Get16ValFast(ARRAY_COUNT(sm_ArmorDamageLayers))]); + pScriptArgs->m_VarsLocal.SetNum("ItemDamageChance", 25); + pScriptArgs->m_VarsLocal.SetNum("Spell", (int)spell); + if ( fElemental ) + { + pScriptArgs->m_VarsLocal.SetNum("DamagePercentPhysical", iDmgPhysical); + pScriptArgs->m_VarsLocal.SetNum("DamagePercentFire", iDmgFire); + pScriptArgs->m_VarsLocal.SetNum("DamagePercentCold", iDmgCold); + pScriptArgs->m_VarsLocal.SetNum("DamagePercentPoison", iDmgPoison); + pScriptArgs->m_VarsLocal.SetNum("DamagePercentEnergy", iDmgEnergy); + } - LAYER_TYPE iHitLayer = (LAYER_TYPE)(Args.m_VarsLocal.GetKeyNum("ItemDamageLayer")); - pItemHit = LayerFind(iHitLayer); - if (pItemHit) + CItem* pItemHit = nullptr; + if ( IsTrigUsed(TRIGGER_GETHIT) ) { - Args.m_pO1 = this; - // "ItemDamageLayer" will only be readable. - if (pItemHit->OnTrigger(ITRIG_GetHit, pSrc, &Args) == TRIGRET_RET_TRUE) + + if ( OnTrigger( CTRIG_GetHit, pScriptArgs, pSrc ) == TRIGRET_RET_TRUE ) return 0; - iDmg = (int)(Args.m_iN1); //Update damage amount and type again after @Hit trigger under item. - uiType = (DAMAGE_TYPE)(Args.m_iN2); - // We don't need to update iHitLayer as it's already called on item + iDmg = (int)(pScriptArgs->m_iN1); + uiType = (DAMAGE_TYPE)(pScriptArgs->m_iN2); + + LAYER_TYPE iHitLayer = (LAYER_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("ItemDamageLayer")); + pItemHit = LayerFind(iHitLayer); + if (pItemHit) + { + pScriptArgs->m_pO1 = this; + // "ItemDamageLayer" will only be readable. + if (pItemHit->OnTrigger(ITRIG_GetHit, pScriptArgs, pSrc) == TRIGRET_RET_TRUE) + return 0; + iDmg = (int)(pScriptArgs->m_iN1); //Update damage amount and type again after @Hit trigger under item. + uiType = (DAMAGE_TYPE)(pScriptArgs->m_iN2); + // We don't need to update iHitLayer as it's already called on item + } } - } - int iItemDamageChance = (int)(Args.m_VarsLocal.GetKeyNum("ItemDamageChance")); - if ( (iItemDamageChance > g_Rand.GetVal(100)) && !Can(CAN_C_NONHUMANOID) ) - { - if ( pItemHit ) - pItemHit->OnTakeDamage(iDmg, pSrc, uiType); - } + int iItemDamageChance = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("ItemDamageChance")); + if ( (iItemDamageChance > g_Rand.GetVal(100)) && !Can(CAN_C_NONHUMANOID) ) + { + if ( pItemHit ) + pItemHit->OnTakeDamage(iDmg, pSrc, uiType); + } + } CSpellDef* pSpellDef = nullptr; // Remove stuck/paralyze effect @@ -898,8 +904,9 @@ int CChar::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uiType, int iDmgPhy bool fInterrupt = true; if (IsTrigUsed(TRIGGER_SPELLINTERRUPT)) { - CScriptTriggerArgs ArgsInterrupt(m_atMagery.m_iSpell, iDisturbChance); - if (pSrc->OnTrigger(CTRIG_SpellInterrupt, this, &ArgsInterrupt) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pArgsInterrupt = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pArgsInterrupt->Init(m_atMagery.m_iSpell, iDisturbChance, 0, nullptr); + if (pSrc->OnTrigger(CTRIG_SpellInterrupt, pArgsInterrupt, this) == TRIGRET_RET_TRUE) fInterrupt = false; } @@ -957,21 +964,21 @@ int CChar::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uiType, int iDmgPhy if (IsTrigUsed(TRIGGER_HITREACTIVE)) { - CScriptTriggerArgs HitReactiveArgs; - HitReactiveArgs.m_VarsLocal.SetNum("Sound", ReactiveSnd); // SOUND - HitReactiveArgs.m_VarsLocal.SetNum("EffectID", ReactiveEffectID); // EFFECTID - HitReactiveArgs.m_VarsLocal.SetNum("Damage", iReactiveDamage); // DAMAGE VALUE - HitReactiveArgs.m_VarsLocal.SetNum("ReflectDamage", iReactiveRefDam); // REFLECTED DAM - HitReactiveArgs.m_VarsLocal.SetNum("ReduceDamage", iReactiveRedDam); // REDUCED DAM - HitReactiveArgs.m_VarsLocal.SetNum("DamageType", ReactiveDamType); // DAMAGE TYPE - OnTrigger(CTRIG_HitReactive, pSrc, &HitReactiveArgs); - - ReactiveSnd = (SOUND_TYPE)HitReactiveArgs.m_VarsLocal.GetKeyNum("Sound"); // SOUND - ReactiveEffectID = (ITEMID_TYPE)HitReactiveArgs.m_VarsLocal.GetKeyNum("EffectID"); // EFFECTID - iReactiveDamage = (int)HitReactiveArgs.m_VarsLocal.GetKeyNum("Damage"); // DAMAGE VALUE - iReactiveRefDam = (int)HitReactiveArgs.m_VarsLocal.GetKeyNum("ReflectDamage"); // REFLECTED DAMAGE VALUE - iReactiveRedDam = (int)HitReactiveArgs.m_VarsLocal.GetKeyNum("ReduceDamage"); // REDUCED DAMAGE VALUE - ReactiveDamType = (DAMAGE_TYPE)HitReactiveArgs.m_VarsLocal.GetKeyNum("DamageType"); // DAMAGE TYPE + CScriptTriggerArgsPtr pHitReactiveArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pHitReactiveArgs->m_VarsLocal.SetNum("Sound", ReactiveSnd); // SOUND + pHitReactiveArgs->m_VarsLocal.SetNum("EffectID", ReactiveEffectID); // EFFECTID + pHitReactiveArgs->m_VarsLocal.SetNum("Damage", iReactiveDamage); // DAMAGE VALUE + pHitReactiveArgs->m_VarsLocal.SetNum("ReflectDamage", iReactiveRefDam); // REFLECTED DAM + pHitReactiveArgs->m_VarsLocal.SetNum("ReduceDamage", iReactiveRedDam); // REDUCED DAM + pHitReactiveArgs->m_VarsLocal.SetNum("DamageType", ReactiveDamType); // DAMAGE TYPE + OnTrigger(CTRIG_HitReactive, pHitReactiveArgs, pSrc); + + ReactiveSnd = (SOUND_TYPE)pHitReactiveArgs->m_VarsLocal.GetKeyNum("Sound"); // SOUND + ReactiveEffectID = (ITEMID_TYPE)pHitReactiveArgs->m_VarsLocal.GetKeyNum("EffectID"); // EFFECTID + iReactiveDamage = (int)pHitReactiveArgs->m_VarsLocal.GetKeyNum("Damage"); // DAMAGE VALUE + iReactiveRefDam = (int)pHitReactiveArgs->m_VarsLocal.GetKeyNum("ReflectDamage"); // REFLECTED DAMAGE VALUE + iReactiveRedDam = (int)pHitReactiveArgs->m_VarsLocal.GetKeyNum("ReduceDamage"); // REDUCED DAMAGE VALUE + ReactiveDamType = (DAMAGE_TYPE)pHitReactiveArgs->m_VarsLocal.GetKeyNum("DamageType"); // DAMAGE TYPE // should it be zero ? //if (iReactiveDamage < 1) // iReactiveDamage = 1; @@ -1422,13 +1429,13 @@ bool CChar::Fight_Attack( CChar *pCharTarg, bool fToldByMaster ) bool ignored = Attacker_GetIgnore(pTarget); if ( ((IsTrigUsed(TRIGGER_ATTACK)) || (IsTrigUsed(TRIGGER_CHARATTACK))) && m_Fight_Targ_UID != pCharTarg->GetUID() ) { - CScriptTriggerArgs Args; - Args.m_iN1 = threat; - Args.m_iN2 = (int)ignored; - if ( OnTrigger(CTRIG_Attack, pTarget, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = threat; + pScriptArgs->m_iN2 = (int)ignored; + if ( OnTrigger(CTRIG_Attack, pScriptArgs, pTarget) == TRIGRET_RET_TRUE ) return false; - threat = (int)Args.m_iN1; - ignored = (bool)Args.m_iN2; + threat = (int)pScriptArgs->m_iN1; + ignored = (bool)pScriptArgs->m_iN2; } Attacker_SetIgnore(pTarget, ignored); @@ -1754,19 +1761,20 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( IsTrigUsed(TRIGGER_HITCHECK) ) { - CScriptTriggerArgs pArgs; - pArgs.m_iN1 = m_atFight.m_iWarSwingState; - pArgs.m_iN2 = iDmgType; - pArgs.m_VarsLocal.SetNum("Recoil_NoRange", (int)fSwingNoRange); - TRIGRET_TYPE tRet = OnTrigger(CTRIG_HitCheck, pCharTarg, &pArgs); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = m_atFight.m_iWarSwingState; + pScriptArgs->m_iN2 = iDmgType; + pScriptArgs->m_VarsLocal.SetNum("Recoil_NoRange", (int)fSwingNoRange); + + TRIGRET_TYPE tRet = OnTrigger(CTRIG_HitCheck, pScriptArgs, pCharTarg); if ( tRet == TRIGRET_RET_TRUE ) - return (WAR_SWING_TYPE)pArgs.m_iN1; + return (WAR_SWING_TYPE)pScriptArgs->m_iN1; if ( tRet == -1 ) return WAR_SWING_INVALID; - m_atFight.m_iWarSwingState = (WAR_SWING_TYPE)(pArgs.m_iN1); - iDmgType = (DAMAGE_TYPE)(pArgs.m_iN2); - fSwingNoRange = (bool)pArgs.m_VarsLocal.GetKeyNum("Recoil_NoRange"); + m_atFight.m_iWarSwingState = (WAR_SWING_TYPE)(pScriptArgs->m_iN1); + iDmgType = (DAMAGE_TYPE)(pScriptArgs->m_iN2); + fSwingNoRange = (bool)pScriptArgs->m_VarsLocal.GetKeyNum("Recoil_NoRange"); if (tRet != -2) // if @HitCheck returns -2, just continue with the hardcoded stuff { @@ -1921,15 +1929,17 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) int16& iARGN1Var = IsSetCombatFlags(COMBAT_ANIM_HIT_SMOOTH) ? m_atFight.m_iSwingAnimationDelay : m_atFight.m_iRecoilDelay; int16& iAnimDelayVar = IsSetCombatFlags(COMBAT_ANIM_HIT_SMOOTH) ? m_atFight.m_iRecoilDelay : m_atFight.m_iSwingAnimationDelay; - CScriptTriggerArgs Args(iARGN1Var, 0, pWeapon); - Args.m_VarsLocal.SetNum("Anim", m_atFight.m_iSwingAnimation); - Args.m_VarsLocal.SetNum("AnimDelay", iAnimDelayVar); - if ( OnTrigger(CTRIG_HitTry, pCharTarg, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iARGN1Var, 0, 0, pWeapon); + pScriptArgs->m_VarsLocal.SetNum("Anim", m_atFight.m_iSwingAnimation); + pScriptArgs->m_VarsLocal.SetNum("AnimDelay", iAnimDelayVar); + + if ( OnTrigger(CTRIG_HitTry, pScriptArgs, pCharTarg) == TRIGRET_RET_TRUE ) return WAR_SWING_READY; - m_atFight.m_iSwingAnimation = (int16)(Args.m_VarsLocal.GetKeyNum("Anim")); - iARGN1Var = (int16)(Args.m_iN1); - iAnimDelayVar = (int16)(Args.m_VarsLocal.GetKeyNum("AnimDelay")); + m_atFight.m_iSwingAnimation = (int16)(pScriptArgs->m_VarsLocal.GetKeyNum("Anim")); + iARGN1Var = (int16)(pScriptArgs->m_iN1); + iAnimDelayVar = (int16)(pScriptArgs->m_VarsLocal.GetKeyNum("AnimDelay")); //if (m_atFight.m_iSwingAnimation < (ANIM_TYPE)-1) // -1 is a valid value // m_atFight.m_iSwingAnimation = (int16)animSwingDefault; if ( m_atFight.m_iRecoilDelay < 1 ) @@ -2016,13 +2026,15 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) { if ( IsTrigUsed(TRIGGER_HITMISS) ) { - CScriptTriggerArgs Args(0, 0, pWeapon); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pWeapon; if ( pAmmo && pAmmo->GetUID().IsValidUID()) - Args.m_VarsLocal.SetNum("Arrow", (dword)pAmmo->GetUID()); - if ( OnTrigger(CTRIG_HitMiss, pCharTarg, &Args) == TRIGRET_RET_TRUE ) + pScriptArgs->m_VarsLocal.SetNum("Arrow", (dword)pAmmo->GetUID()); + + if ( OnTrigger(CTRIG_HitMiss, pScriptArgs, pCharTarg) == TRIGRET_RET_TRUE ) return WAR_SWING_EQUIPPING_NOWAIT; - if ( Args.m_VarsLocal.GetKeyNum("ArrowHandled") != 0 ) // if arrow is handled by script, do nothing with it further! + if ( pScriptArgs->m_VarsLocal.GetKeyNum("ArrowHandled") != 0 ) // if arrow is handled by script, do nothing with it further! pAmmo = nullptr; } @@ -2088,22 +2100,23 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) local.ItemParryDamage = The chance that the parrying item will be damaged. local.Damage = The amount of damage (raw) before parrying reduction. */ - CScriptTriggerArgs Args(iParryReduction, iDmgType, pItemHit); - Args.m_VarsLocal.SetNum("ParryChance", iParryChance); - Args.m_VarsLocal.SetNum("ParrySkillID", ParrySkill); - Args.m_VarsLocal.SetNum("ItemParryDamageChance", 100); - Args.m_VarsLocal.SetNum("Damage", iDmg); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iParryReduction, iDmgType, 0, pItemHit); + pScriptArgs->m_VarsLocal.SetNum("ParryChance", iParryChance); + pScriptArgs->m_VarsLocal.SetNum("ParrySkillID", ParrySkill); + pScriptArgs->m_VarsLocal.SetNum("ItemParryDamageChance", 100); + pScriptArgs->m_VarsLocal.SetNum("Damage", iDmg); if (IsTrigUsed(TRIGGER_HITPARRY)) { - if (pCharTarg->OnTrigger(CTRIG_HitParry, this, &Args) == TRIGRET_RET_TRUE) + if (pCharTarg->OnTrigger(CTRIG_HitParry, pScriptArgs, this) == TRIGRET_RET_TRUE) return WAR_SWING_EQUIPPING_NOWAIT; - iParryReduction = (int)(Args.m_iN1); - iDmgType = (DAMAGE_TYPE)(Args.m_iN2); - iDmg = (int)Args.m_VarsLocal.GetKeyNum("Damage"); - iParryChance = (int)Args.m_VarsLocal.GetKeyNum("ParryChance"); - ParrySkill = (SKILL_TYPE)Args.m_VarsLocal.GetKeyNum("ParrySkillID"); + iParryReduction = (int)(pScriptArgs->m_iN1); + iDmgType = (DAMAGE_TYPE)(pScriptArgs->m_iN2); + iDmg = (int)pScriptArgs->m_VarsLocal.GetKeyNum("Damage"); + iParryChance = (int)pScriptArgs->m_VarsLocal.GetKeyNum("ParryChance"); + ParrySkill = (SKILL_TYPE)pScriptArgs->m_VarsLocal.GetKeyNum("ParrySkillID"); } if (iParryChance > 0 && pCharTarg->Skill_UseQuick(ParrySkill, iParryChance, true, false)) @@ -2115,7 +2128,7 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if (g_Cfg.m_iFeatureSE & FEATURE_SE_NINJASAM && g_Cfg.m_iCombatParryingEra & PARRYERA_SEFORMULA && !pCharTarg->IsStatFlag(STATF_HASSHIELD)) pCharTarg->Skill_Experience(SKILL_BUSHIDO, iParryChance); - int iParryDamageChance = (int)(Args.m_VarsLocal.GetKeyNum("ItemParryDamageChance")); + int iParryDamageChance = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("ItemParryDamageChance")); if ( pItemHit && (iParryDamageChance > g_Rand.GetVal(100)) ) pItemHit->OnTakeDamage(1, this, iDmgType); @@ -2130,24 +2143,24 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) } - - CScriptTriggerArgs Args(iDmg, iDmgType, pWeapon); - Args.m_VarsLocal.SetNum("ItemDamageChance", 25); - Args.m_VarsLocal.SetNum("ItemPoisonReductionChance", 100); - Args.m_VarsLocal.SetNum("ItemPoisonReductionAmount", 1); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iDmg, iDmgType, 0, pWeapon); + pScriptArgs->m_VarsLocal.SetNum("ItemDamageChance", 25); + pScriptArgs->m_VarsLocal.SetNum("ItemPoisonReductionChance", 100); + pScriptArgs->m_VarsLocal.SetNum("ItemPoisonReductionAmount", 1); int32 iPoison = 0; if (pWeapon) { iPoison = g_Rand.GetVal(pWeapon->m_itWeapon.m_poison_skill); - Args.m_VarsLocal.SetNum("ItemPoisonReductionAmount", iPoison / 2); + pScriptArgs->m_VarsLocal.SetNum("ItemPoisonReductionAmount", iPoison / 2); } if ( pAmmo && pAmmo->GetUID().IsValidUID() ) - Args.m_VarsLocal.SetNum("Arrow",(dword)pAmmo->GetUID()); + pScriptArgs->m_VarsLocal.SetNum("Arrow",(dword)pAmmo->GetUID()); if ( IsTrigUsed(TRIGGER_SKILLSUCCESS) ) { - if ( Skill_OnCharTrigger(skill, CTRIG_SkillSuccess) == TRIGRET_RET_TRUE ) + if ( Skill_OnCharTrigger(skill, CTRIG_SkillSuccess, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { Skill_Cleanup(); return WAR_SWING_EQUIPPING; // ok, so no hit - skill failed. Pah! @@ -2155,7 +2168,7 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) } if ( IsTrigUsed(TRIGGER_SUCCESS) ) { - if ( Skill_OnTrigger(skill, SKTRIG_SUCCESS) == TRIGRET_RET_TRUE ) + if ( Skill_OnTrigger(skill, SKTRIG_SUCCESS, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { Skill_Cleanup(); return WAR_SWING_EQUIPPING; // ok, so no hit - skill failed. Pah! @@ -2164,26 +2177,26 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) if ( IsTrigUsed(TRIGGER_HIT) ) { - if ( OnTrigger(CTRIG_Hit, pCharTarg, &Args) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_Hit, pScriptArgs, pCharTarg) == TRIGRET_RET_TRUE ) return WAR_SWING_EQUIPPING; - if ( Args.m_VarsLocal.GetKeyNum("ArrowHandled") != 0 ) // if arrow is handled by script, do nothing with it further + if ( pScriptArgs->m_VarsLocal.GetKeyNum("ArrowHandled") != 0 ) // if arrow is handled by script, do nothing with it further pAmmo = nullptr; - iDmg = (int)(Args.m_iN1); - iDmgType = (DAMAGE_TYPE)(Args.m_iN2); + iDmg = (int)(pScriptArgs->m_iN1); + iDmgType = (DAMAGE_TYPE)(pScriptArgs->m_iN2); if (pWeapon) { - Args.m_pO1 = this; - if (pWeapon->OnTrigger(ITRIG_Hit, pCharTarg, &Args) == TRIGRET_RET_TRUE) + pScriptArgs->m_pO1 = this; + if (pWeapon->OnTrigger(ITRIG_Hit, pScriptArgs, pCharTarg) == TRIGRET_RET_TRUE) return WAR_SWING_EQUIPPING; - if (Args.m_VarsLocal.GetKeyNum("ArrowHandled") != 0) // if arrow is handled by script, do nothing with it further + if (pScriptArgs->m_VarsLocal.GetKeyNum("ArrowHandled") != 0) // if arrow is handled by script, do nothing with it further pAmmo = nullptr; - iDmg = (int)(Args.m_iN1); - iDmgType = (DAMAGE_TYPE)(Args.m_iN2); + iDmg = (int)(pScriptArgs->m_iN1); + iDmgType = (DAMAGE_TYPE)(pScriptArgs->m_iN2); } } @@ -2217,15 +2230,15 @@ WAR_SWING_TYPE CChar::Fight_Hit( CChar * pCharTarg ) byte iPoisonDeliver = (byte)(iPoison); pCharTarg->SetPoison(10 * iPoisonDeliver, iPoisonDeliver / 5, this); - if (Args.m_VarsLocal.GetKeyNum("ItemPoisonReductionChance") > g_Rand.GetVal(100)) + if (pScriptArgs->m_VarsLocal.GetKeyNum("ItemPoisonReductionChance") > g_Rand.GetVal(100)) { - pWeapon->m_itWeapon.m_poison_skill -= (byte)(Args.m_VarsLocal.GetKeyNum("ItemPoisonReductionAmount")); // reduce weapon poison charges + pWeapon->m_itWeapon.m_poison_skill -= (byte)(pScriptArgs->m_VarsLocal.GetKeyNum("ItemPoisonReductionAmount")); // reduce weapon poison charges pWeapon->UpdatePropertyFlag(); } } // Check if the weapon will be damaged - int iDamageChance = (int)(Args.m_VarsLocal.GetKeyNum("ItemDamageChance")); + int iDamageChance = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("ItemDamageChance")); if ( iDamageChance > g_Rand.GetVal(100) ) pWeapon->OnTakeDamage(iDmg, pCharTarg); } diff --git a/src/game/chars/CCharMemory.cpp b/src/game/chars/CCharMemory.cpp index aec3dc846..327b2631c 100644 --- a/src/game/chars/CCharMemory.cpp +++ b/src/game/chars/CCharMemory.cpp @@ -1,6 +1,7 @@ - // Actions specific to an NPC. -#include "../../common/CExpression.h" + +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/send.h" #include "../items/CItemMemory.h" #include "../items/CItemStone.h" @@ -132,7 +133,7 @@ bool CChar::Memory_UpdateFlags(CItemMemory * pMemory) else iCheckTime = 20 * 60 * MSECS_PER_SEC; - pMemory->SetTimeout(iCheckTime); // update its decay time. + pMemory->SetTimeout(iCheckTime); // update its decay time. CChar * pCharLink = pMemory->m_uidLink.CharFind(); if (pCharLink) { @@ -299,7 +300,7 @@ CItemMemory * CChar::Memory_AddObj( const CObjBase * pObj, word MemTypes ) } // Looping through all memories ( ForCharMemoryType ). -TRIGRET_TYPE CChar::OnCharTrigForMemTypeLoop( CScript &s, CTextConsole * pSrc, CScriptTriggerArgs * pArgs, CSString * pResult, word wMemType ) +TRIGRET_TYPE CChar::OnCharTrigForMemTypeLoop( CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc, CSString * pResult, word wMemType ) { ADDTOCALLSTACK("CChar::OnCharTrigForMemTypeLoop"); const CScriptLineContext StartContext = s.GetContext(); @@ -312,7 +313,7 @@ TRIGRET_TYPE CChar::OnCharTrigForMemTypeLoop( CScript &s, CTextConsole * pSrc, C CItem* pItem = static_cast(pObjRec); if ( !pItem->IsMemoryTypes(wMemType) ) continue; - TRIGRET_TYPE iRet = pItem->OnTriggerRun( s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult ); + TRIGRET_TYPE iRet = pItem->OnTriggerRun( s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult ); if ( iRet == TRIGRET_BREAK ) { EndContext = StartContext; @@ -330,7 +331,7 @@ TRIGRET_TYPE CChar::OnCharTrigForMemTypeLoop( CScript &s, CTextConsole * pSrc, C if ( EndContext.m_iOffset <= StartContext.m_iOffset ) { // just skip to the end. - TRIGRET_TYPE iRet = OnTriggerRun( s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult ); + TRIGRET_TYPE iRet = OnTriggerRun( s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult ); if ( iRet != TRIGRET_ENDIF ) return iRet; } @@ -402,7 +403,11 @@ void CChar::Memory_Fight_Retreat( CChar * pTarg, CItemMemory * pFight ) return; } - SysMessagef(fCowardice ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_COWARD_1) : g_Cfg.GetDefaultMsg(DEFMSG_MSG_COWARD_2), pTarg->GetName()); + SysMessagef( + fCowardice + ? g_Cfg.GetDefaultMsg(DEFMSG_MSG_COWARD_1) + : g_Cfg.GetDefaultMsg(DEFMSG_MSG_COWARD_2), + pTarg->GetName()); // Lose some fame. if ( fCowardice ) diff --git a/src/game/chars/CCharNPC.cpp b/src/game/chars/CCharNPC.cpp index 8f855ef37..5ce9efb1a 100644 --- a/src/game/chars/CCharNPC.cpp +++ b/src/game/chars/CCharNPC.cpp @@ -1,8 +1,7 @@ // Actions specific to an NPC. -#include #include "../../common/resource/CResourceLock.h" -#include "../../common/CException.h" +//#include "../../common/CException.h" // included in the precompiled header #include "../clients/CClient.h" #include "../items/CItemContainer.h" #include "../CServer.h" @@ -43,7 +42,7 @@ CCharNPC::CCharNPC( CChar * pChar, NPCBRAIN_TYPE NPCBrain ) { UnreferencedParameter(pChar); m_Brain = NPCBrain; - m_Home_Dist_Wander = INT16_MAX; // as far as i want. + m_Home_Dist_Wander = INT16_MAX; // as far as I want. m_Act_Motivation = 0; m_bonded = 0; #ifndef _WIN32 @@ -79,7 +78,7 @@ bool CCharNPC::r_LoadVal( CChar * pChar, CScript &s ) //Set as numbers only case CNC_BONDED: m_bonded = (s.GetArgVal() > 0); - if ( !g_Serv.IsLoading() ) + if ( !g_Serv.IsLoadingGeneric() ) pChar->UpdatePropertyFlag(); break; case CNC_ACTPRI: @@ -151,7 +150,7 @@ bool CCharNPC::r_WriteVal( CChar * pChar, lpctstr ptcKey, CSString & sVal ) { //return as string or hex number or nullptr if not set - //On these ones, check BaseDef too if not found on dynamic + // On these, check BaseDef too if not found on dynamic. case CNC_THROWDAM: case CNC_THROWDAMTYPE: case CNC_THROWOBJ: @@ -159,7 +158,7 @@ bool CCharNPC::r_WriteVal( CChar * pChar, lpctstr ptcKey, CSString & sVal ) sVal = pChar->GetDefStr(ptcKey, false, true); break; //return as decimal number or 0 if not set - //On these ones, check BaseDef if not found on dynamic + // On these, check BaseDef if not found on dynamic. case CNC_BONDED: sVal.FormatVal( m_bonded ); break; @@ -254,7 +253,7 @@ bool CCharNPC::IsVendor() const return ( (m_Brain == NPCBRAIN_HEALER) || (m_Brain == NPCBRAIN_BANKER) || (m_Brain == NPCBRAIN_VENDOR) || (m_Brain == NPCBRAIN_STABLE) ); } -int CCharNPC::GetNpcAiFlags( const CChar *pChar ) const +int CCharNPC::GetNpcAiFlags( const CChar *pChar ) const { CVarDefCont *pVar = pChar->GetKey("OVERRIDE.NPCAI", true ); if (pVar != nullptr) @@ -270,8 +269,8 @@ void CChar::NPC_LoadScript( bool fRestock ) { // Set a default brain type til we get the real one from scripts. // should have a default brain. watch out for override vendor. - SetNPCBrain(GetNPCBrainAuto()); - } + SetNPCBrain(GetNPCBrainAuto()); + } CCharBase * pCharDef = Char_GetDef(); @@ -290,7 +289,7 @@ void CChar::NPC_LoadScript( bool fRestock ) if ( fRestock && IsTrigUsed(TRIGGER_NPCRESTOCK) ) pChar->ReadScriptReducedTrig(pCharDef, CTRIG_NPCRestock); } - CreateNewCharCheck(); //This one is giving stats, etc to the char, so we can read/set them in the next triggers. + CreateNewCharCheck(); // This one is giving stats, etc. to the char, so we can read/set them in the next triggers. } // @Create trigger, NPC version @@ -299,26 +298,27 @@ void CChar::NPC_CreateTrigger() ADDTOCALLSTACK("CChar::NPC_CreateTrigger"); ASSERT(m_pNPC); - fc::vector_set executedEvents; + std::vector executedEvents; CCharBase *pCharDef = Char_GetDef(); TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; lpctstr pszTrigName = "@Create"; - CTRIG_TYPE iAction = (CTRIG_TYPE)FindTableSorted(pszTrigName, sm_szTrigName, ARRAY_COUNT(sm_szTrigName) - 1); + CTRIG_TYPE iAction = (CTRIG_TYPE)FindTableSorted(pszTrigName, sm_szTrigName, ARRAY_COUNT(sm_szTrigName) - 1); // 2) TEVENTS for (size_t i = 0; i < pCharDef->m_TEvents.size(); ++i) { CResourceLink * pLink = pCharDef->m_TEvents[i].GetRef(); - if (!pLink || !pLink->HasTrigger(iAction) || (executedEvents.find(pLink) != executedEvents.end())) + if (!pLink || !pLink->HasTrigger(iAction) + || (executedEvents.end() != std::find(executedEvents.begin(), executedEvents.end(), pLink))) continue; CResourceLock s; if (!pLink->ResourceLock(s)) continue; - executedEvents.emplace(pLink); - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, this, nullptr); + executedEvents.emplace_back(pLink); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this); if (iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT) return; } @@ -327,15 +327,16 @@ void CChar::NPC_CreateTrigger() for (size_t i = 0; i < g_Cfg.m_pEventsPetLink.size(); ++i) { CResourceLink * pLink = g_Cfg.m_pEventsPetLink[i].GetRef(); - if (!pLink || !pLink->HasTrigger(iAction) || (executedEvents.find(pLink) != executedEvents.end())) - continue; + if (!pLink || !pLink->HasTrigger(iAction) + || (executedEvents.end() != std::find(executedEvents.begin(), executedEvents.end(), pLink))) + continue; CResourceLock s; if (!pLink->ResourceLock(s)) continue; - executedEvents.emplace(pLink); - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, this, nullptr); + executedEvents.emplace_back(pLink); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this); if (iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT) return; } diff --git a/src/game/chars/CCharNPC.h b/src/game/chars/CCharNPC.h index 94ce26bb2..d3e21bb87 100644 --- a/src/game/chars/CCharNPC.h +++ b/src/game/chars/CCharNPC.h @@ -1,6 +1,6 @@ /** * @file CCharNPC.h -* +* */ #ifndef _INC_CCHARNPC_H @@ -30,13 +30,13 @@ class CCharNPC NPCBRAIN_TYPE m_Brain; // For NPCs: Number of the assigned basic AI block word m_Home_Dist_Wander; // Distance to allow to "wander". - byte m_Act_Motivation; // 0-100 (100=very greatly) how bad do i want to do the current action. + byte m_Act_Motivation; // 0-100 (100=very greatly) how bad do I want to do the current action. bool m_bonded; // Bonded pet int64 m_timeRestock; // when last restock happened in sell/buy container CResourceRefArray m_Speech; // Speech fragment list (other stuff we know): We respond to what we hear with this. - CResourceQty m_Need; // What items might i need/Desire ? (coded as resource scripts) ex "10 gold,20 logs" etc. + CResourceQty m_Need; // What items might I need/Desire ? (coded as resource scripts) ex "10 gold,20 logs" etc. short m_nextX[MAX_NPC_PATH_STORAGE_SIZE]; // array of X coords of the next step short m_nextY[MAX_NPC_PATH_STORAGE_SIZE]; // array of Y coords of the next step diff --git a/src/game/chars/CCharNPCAct.cpp b/src/game/chars/CCharNPCAct.cpp index bc2987b9b..e133177ea 100644 --- a/src/game/chars/CCharNPCAct.cpp +++ b/src/game/chars/CCharNPCAct.cpp @@ -3,8 +3,9 @@ #include "../../common/sphere_library/CSRand.h" #include "../../common/resource/CResourceLock.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +#include "../../common/CScriptParserBufs.h" #include "../../network/receive.h" #include "../clients/CClient.h" #include "../items/CItemCorpse.h" @@ -67,7 +68,7 @@ void CChar::Action_StartSpecial( CREID_TYPE id ) ASSERT(m_pNPC); // Take the special creature action. // lay egg, breath weapon (fire, lightning, acid, code, paralyze), - // create web, fire patch, fire ball, + // create web, fire patch, fireball, // steal, teleport, level drain, absorb magic, curse items, // rust items, stealing, charge, hiding, grab, regenerate, play dead. // Water = put out fire ! @@ -243,8 +244,8 @@ void CChar::NPC_ActStart_SpeakTo( CChar * pSrc ) { ADDTOCALLSTACK("CChar::NPC_ActStart_SpeakTo"); ASSERT(m_pNPC); - // My new action is that i am speaking to this person. - // Or just update the amount of time i will wait for this person. + // My new action is that I am speaking to this person. + // Or just update the amount of time I will wait for this person. m_Act_UID = pSrc->GetUID(); m_atTalk.m_dwWaitCount = 20; m_atTalk.m_dwHearUnknown = 0; @@ -266,7 +267,7 @@ void CChar::NPC_OnHear( lpctstr pszCmd, CChar * pSrc, bool fAllPets ) if ( NPC_OnHearPetCmd(pszCmd, pSrc, fAllPets) || !NPC_CanSpeak() ) return; - // What where we doing ? + // What were we doing ? // too busy to talk ? switch ( Skill_GetActive()) @@ -303,7 +304,7 @@ void CChar::NPC_OnHear( lpctstr pszCmd, CChar * pSrc, bool fAllPets ) // This or CTRIG_SeeNewPlayer will be our first contact with people. if ( IsTrigUsed(TRIGGER_NPCHEARGREETING) ) { - if ( OnTrigger( CTRIG_NPCHearGreeting, pSrc ) == TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_NPCHearGreeting, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc ) == TRIGRET_RET_TRUE ) return; } @@ -367,7 +368,7 @@ void CChar::NPC_OnHear( lpctstr pszCmd, CChar * pSrc, bool fAllPets ) // can't figure you out. if ( IsTrigUsed(TRIGGER_NPCHEARUNKNOWN) ) { - if ( OnTrigger( CTRIG_NPCHearUnknown, pSrc ) == TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_NPCHearUnknown, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc ) == TRIGRET_RET_TRUE ) return; } @@ -518,9 +519,9 @@ int CChar::NPC_WalkToPoint( bool fRun ) if ( ! CanMoveWalkTo(pMe, true, false, Dir )) { bool fClearedWay = false; - // Some object in my way that i could move ? Try to move it. + // Some object in my way that I could move ? Try to move it. if ( !Can(CAN_C_USEHANDS) || Can(CAN_C_STATUE) || IsStatFlag(STATF_DEAD|STATF_SLEEPING|STATF_FREEZE|STATF_STONE) ) - ; // i cannot use hands or i am frozen, so cannot move objects + ; // I cannot use hands, or I am frozen, so cannot move objects. else if ( (NPC_GetAiFlags() & NPC_AI_MOVEOBSTACLES) && (iInt > iRand) ) { int i; @@ -740,7 +741,7 @@ bool CChar::NPC_LookAtCharGuard( CChar * pChar, bool bFromTrigger ) if ( g_Cfg.m_fGuardsInstantKill || pChar->Skill_GetBase(SKILL_MAGERY) ) Spell_Teleport(pChar->GetTopPoint(), false, false); - // If we got intant kill guards enabled, allow the guards to swing immidiately + // If we got instant kill guards enabled, allow the guards to swing immediately. if ( g_Cfg.m_fGuardsInstantKill ) { pChar->Stat_SetVal(STAT_STR, 1); @@ -764,7 +765,7 @@ bool CChar::NPC_LookAtCharMonster( CChar * pChar ) // false = continue with any previous action. // motivation level = // 0 = not at all. - // 100 = definitly. + // 100 = definitely. // int iFoodLevel = Food_GetLevelPercent(); @@ -773,7 +774,7 @@ bool CChar::NPC_LookAtCharMonster( CChar * pChar ) if ( ! Noto_IsCriminal() && (iFoodLevel > 40) ) // Am I not evil ? return NPC_LookAtCharHuman( pChar ); - // Attack if i am stronger, if it's the same target i was attacking, or i'm just stupid. + // Attack if I am stronger, if it's the same target I was attacking, or I'm just stupid. int iActMotivation = NPC_GetAttackMotivation(pChar); if (iActMotivation <= 0) return false; @@ -782,7 +783,7 @@ bool CChar::NPC_LookAtCharMonster( CChar * pChar ) int iDist = GetTopDist3D( pChar ); if ( IsStatFlag( STATF_HIDDEN ) && ! NPC_FightMayCast() && (iDist > 1) ) - return false; // element of suprise. + return false; // element of surprise. if ( Fight_Attack( pChar ) == false ) return false; @@ -822,7 +823,7 @@ bool CChar::NPC_LookAtCharHuman( CChar * pChar ) { Speak(pChar->IsStatFlag(STATF_CRIMINAL) ? g_Cfg.GetDefaultMsg(DEFMSG_NPC_GENERIC_SEECRIM) : - g_Cfg.GetDefaultMsg(DEFMSG_NPC_GENERIC_SEEMONS)); // only speak if can really call the guards. + g_Cfg.GetDefaultMsg(DEFMSG_NPC_GENERIC_SEEMONS)); // only speak if I can really call the guards. } if (IsStatFlag(STATF_WAR)) return false; @@ -949,15 +950,15 @@ bool CChar::NPC_LookAtItem( CItem * pItem, int iDist ) { if ( IsTrigUsed(TRIGGER_NPCLOOKATITEM) && !pItem->IsAttr(ATTR_MOVE_NEVER|ATTR_LOCKEDDOWN|ATTR_SECURE) ) { - - CScriptTriggerArgs Args(iDist, iWantThisItem, pItem); - switch ( OnTrigger(CTRIG_NPCLookAtItem, this, &Args) ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iDist, iWantThisItem, 0, pItem); + switch ( OnTrigger(CTRIG_NPCLookAtItem, pScriptArgs, this) ) { case TRIGRET_RET_TRUE: return true; case TRIGRET_RET_FALSE: return false; default: break; } - iWantThisItem = (int)(Args.m_iN2); + iWantThisItem = (int)(pScriptArgs->m_iN2); } } @@ -1006,9 +1007,9 @@ bool CChar::NPC_LookAtChar( CChar * pChar, int iDist ) ADDTOCALLSTACK("CChar::NPC_LookAtChar"); ASSERT(m_pNPC); // I see a char. - // Do I want to do something to this char (more that what i'm already doing ?) + // Do I want to do something to this char (more that what I'm already doing ?) // RETURN: - // true = yes i do want to take a new action. + // true = yes I do want to take a new action. // Don't call this function frequently, since CanSeeLOS is an expensive function (lot of calculations but // most importantly it has to read the UO files, and file I/O is slow) @@ -1017,7 +1018,7 @@ bool CChar::NPC_LookAtChar( CChar * pChar, int iDist ) if ( IsTrigUsed(TRIGGER_NPCLOOKATCHAR) ) { - switch ( OnTrigger(CTRIG_NPCLookAtChar, pChar) ) + switch ( OnTrigger(CTRIG_NPCLookAtChar, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar) ) { case TRIGRET_RET_TRUE: return true; case TRIGRET_RET_FALSE: return false; @@ -1045,7 +1046,7 @@ bool CChar::NPC_LookAtChar( CChar * pChar, int iDist ) { if ( IsTrigUsed(TRIGGER_NPCSEENEWPLAYER) ) { - if ( OnTrigger( CTRIG_NPCSeeNewPlayer, pChar ) != TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_NPCSeeNewPlayer, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar ) != TRIGRET_RET_TRUE ) { // record that we attempted to speak to them. CItemMemory * pMemory = Memory_AddObjTypes( pChar, MEMORY_SPEAK ); @@ -1057,7 +1058,7 @@ bool CChar::NPC_LookAtChar( CChar * pChar, int iDist ) } } } - + if (IsStatFlag(STATF_DEAD)) return false; @@ -1128,19 +1129,19 @@ bool CChar::NPC_LookAround( bool fForceCheckItems ) // Call the rand function once, since repeated calls can be expensive (and this function is called a LOT of times, if there are lots of active NPCs) const int iRand = g_Rand.Get16ValFast(g_Cfg.m_iMapViewRadar); const CPointMap& ptTop(GetTopPoint()); - + int iRange = std::min(GetVisualRange(), int(g_Cfg.m_iMapViewRadar)); int iRangeBlur = UO_MAP_VIEW_SIGHT; // If I can't move don't look too far. if ( Can(CAN_C_NONMOVER) || !Can(CAN_C_MOVEMENTCAPABLEMASK) || IsStatFlag(STATF_FREEZE|STATF_STONE) ) { - if ( !NPC_FightMayCast() ) // And i have no distance attack. + if ( !NPC_FightMayCast() ) // And I have no distance attack. iRange = iRangeBlur = 2; } else { - // I'm mobile. do basic check if i would move here first. + // I'm mobile. do basic check if I would move here first. if ( !NPC_CheckWalkHere(ptTop, m_pArea) ) { // I should move. Someone lit a fire under me. @@ -1231,12 +1232,12 @@ void CChar::NPC_Act_Wander() if ( Can(CAN_C_NONMOVER) ) return; - // Call the rand function once, since repeated calls can be expensive (and this function is called a LOT of times, if there are lots of active NPCs) + // Call the rand function once, since repeated calls can be expensive (and this function is called a LOT of times, if there are lots of active NPCs). const uint uiRand = uint32_t(g_Rand.Get16ValFast(100)); int iStopWandering = 0; if ( !(uiRand % (7u + (Stat_GetVal(STAT_DEX) / 30u))) ) - iStopWandering = 1; // i'm stopping to wander "for the dexterity". + iStopWandering = 1; // I'm stopping to wander "for the dexterity". const CVarDefCont* pTagOverride = GetKey("OVERRIDE.LOOKAROUNDCHANCE", true); uint uiLookAroundChance = pTagOverride @@ -1250,7 +1251,7 @@ void CChar::NPC_Act_Wander() // NPC_LookAround() is very expensive, so since NPC_Act_Wander is called every tick for every char with ACTION == NPCACT_WANDER, // don't look around every time. if ( NPC_LookAround() ) - iStopWandering = 2; // i'm stopping to wander because i have seen something interesting + iStopWandering = 2; // I'm stopping to wander because I have seen something interesting. } // Staggering Walk around. @@ -1267,12 +1268,13 @@ void CChar::NPC_Act_Wander() if (IsTrigUsed(TRIGGER_NPCACTWANDER)) { - CScriptTriggerArgs Args(iStopWandering, iReturnToHome); - if (OnTrigger(CTRIG_NPCActWander, this, &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iStopWandering, iReturnToHome, 0, nullptr); + if (OnTrigger(CTRIG_NPCActWander, pScriptArgs, this) == TRIGRET_RET_TRUE) return; - iStopWandering = (int)Args.m_iN1; - iReturnToHome = (int)Args.m_iN2; + iStopWandering = (int)pScriptArgs->m_iN1; + iReturnToHome = (int)pScriptArgs->m_iN2; } if (iStopWandering) @@ -1312,14 +1314,14 @@ bool CChar::NPC_Act_Follow(bool fFlee, int maxDistance, bool fMoveAway) ADDTOCALLSTACK("CChar::NPC_Act_Follow"); ASSERT(m_pNPC); // Follow our target or owner (m_Act_UID), we may be fighting (m_Fight_Targ_UID). - // false = can't follow any more, give up. + // false = can't follow anymore, give up. if (Can(CAN_C_NONMOVER)) { /* If the NPC has the MT_NONMOVER flag we need to check if it is actually in combat, otherwise it will spam the attack because it constantly "forget" the character is attacking (See NPCAct_Fight method). - */ + */ if (!Fight_IsActive()) return false; else @@ -1332,12 +1334,12 @@ bool CChar::NPC_Act_Follow(bool fFlee, int maxDistance, bool fMoveAway) * Replaced the Fight_IsActive() check with a check on m_fight_targ_UID. * Red npcs usually never interact "in a peaceful way" with the players and thus m_act_UID is usually never set preventing the creature from fleeing and putting it in an immobile "state". * Fight_IsActive returns true if the character is actively fighting (using a combat skill) in this case it's false because of the NPC'sFleeing - * Action and thus it will never pass the Fight_IsActive() check + * Action, and thus it will never pass the Fight_IsActive() check */ //CChar * pChar = Fight_IsActive() ? m_Fight_Targ_UID.CharFind() : m_Act_UID.CharFind(); CChar* pChar = nullptr; - //If the NPC action is following somebody, directly assign the character from the m_Act_UID value. + //If the NPC action is following somebody, directly assign the character from the m_Act_UID value. if (Skill_GetActive() == NPCACT_FOLLOW_TARG) pChar = m_Act_UID.CharFind(); else if (Fight_IsActive() || Skill_GetActive() == NPCACT_FLEE) @@ -1346,7 +1348,7 @@ bool CChar::NPC_Act_Follow(bool fFlee, int maxDistance, bool fMoveAway) pChar = m_Act_UID.CharFind(); if (pChar == nullptr) { - // free to do as i wish ! + // free to do as I wish ! Skill_Start(SKILL_NONE); return false; } @@ -1354,8 +1356,9 @@ bool CChar::NPC_Act_Follow(bool fFlee, int maxDistance, bool fMoveAway) EXC_SET_BLOCK("Trigger"); if (IsTrigUsed(TRIGGER_NPCACTFOLLOW)) { - CScriptTriggerArgs Args(fFlee, maxDistance, fMoveAway); - switch (OnTrigger(CTRIG_NPCActFollow, pChar, &Args)) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(fFlee, maxDistance, fMoveAway, nullptr); + switch (OnTrigger(CTRIG_NPCActFollow, pScriptArgs, pChar)) { case TRIGRET_RET_TRUE: { @@ -1371,9 +1374,9 @@ bool CChar::NPC_Act_Follow(bool fFlee, int maxDistance, bool fMoveAway) } } - fFlee = (Args.m_iN1 != 0); - maxDistance = static_cast(Args.m_iN2); - fMoveAway = (Args.m_iN3 != 0); + fFlee = (pScriptArgs->m_iN1 != 0); + maxDistance = static_cast(pScriptArgs->m_iN2); + fMoveAway = (pScriptArgs->m_iN3 != 0); } EXC_SET_BLOCK("CanSee"); @@ -1502,8 +1505,8 @@ void CChar::NPC_Act_GoHome() if ( m_pNPC->m_Brain == NPCBRAIN_GUARD ) { - // Had to change this guards were still roaming the forests - // this goes hand in hand with the change that guards arent + // Had to change this. Guards were still roaming the forests + // this goes hand in hand with the change that guards aren't // called if the criminal makes it outside guarded territory. const CRegion * pAreaHome = m_ptHome.GetRegion( REGION_TYPE_AREA ); @@ -1522,7 +1525,7 @@ void CChar::NPC_Act_GoHome() { g_Log.Event( LOGL_WARN, "Guard 0%x '%s' has no guard post (%s)! Removing it.\n", (dword)GetUID(), GetName(), GetTopPoint().WriteUsed()); - // If we arent conjured and still got no valid home + // If we aren't conjured and still got no valid home // then set our status to conjured and take our life. if ( ! IsStatFlag(STATF_CONJURED)) { @@ -1548,8 +1551,9 @@ void CChar::NPC_Act_GoHome() { if ( IsTrigUsed(TRIGGER_NPCLOSTTELEPORT) ) { - CScriptTriggerArgs Args(iDistance); // ARGN1 - distance - if ( OnTrigger(CTRIG_NPCLostTeleport, this, &Args) != TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = iDistance; // ARGN1 - distance + if ( OnTrigger(CTRIG_NPCLostTeleport, pScriptArgs, this) != TRIGRET_RET_TRUE ) Spell_Teleport(m_ptHome, true, false); } else @@ -1593,7 +1597,7 @@ void CChar::NPC_Act_Looting() // We killed something, let's take a look on the corpse. // Or we find something interesting on ground // - // m_Act_UID = UID of the item/corpse that we trying to loot + // m_Act_UID = UID of the item/corpse that we're trying to loot if ( m_pArea->IsFlag(REGION_FLAG_SAFE|REGION_FLAG_GUARDED) ) return; @@ -1624,8 +1628,9 @@ void CChar::NPC_Act_Looting() if ( IsTrigUsed(TRIGGER_NPCSEEWANTITEM) ) { - CScriptTriggerArgs Args(pItem); - if ( OnTrigger(CTRIG_NPCSeeWantItem, this, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pItem; + if ( OnTrigger(CTRIG_NPCSeeWantItem, pScriptArgs, this) == TRIGRET_RET_TRUE ) return; } @@ -1772,7 +1777,7 @@ bool CChar::NPC_Act_Food() int iSearchDistance = 2; CItem *pClosestFood = nullptr; int iClosestFood = 100; - + bool fSearchGrass = false; CItem *pCropItem = nullptr; @@ -1782,7 +1787,7 @@ bool CChar::NPC_Act_Food() for (CSObjContRec* pObjRec : *pPack) { CItem* pFood = static_cast(pObjRec); - // I have some food personaly, so no need to search for something + // I have some food personally, so no need to search for anything. if ( pFood->IsType(IT_FOOD) ) { if ( (uiEatAmount = Food_CanEat(pFood)) > 0 ) @@ -1884,7 +1889,7 @@ bool CChar::NPC_Act_Food() } else { - // no food around, but maybe i am ok with grass? Or shall I try to pick crops? + // no food around, but maybe I am ok with grass? Or shall I try to pick crops? const NPCBRAIN_TYPE brain = GetNPCBrainGroup(); if ( brain == NPCBRAIN_ANIMAL ) // animals eat grass always @@ -1983,7 +1988,7 @@ void CChar::NPC_Act_Idle() { if ( IsTrigUsed(TRIGGER_NPCSPECIALACTION) ) { - if ( OnTrigger( CTRIG_NPCSpecialAction, this ) == TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_NPCSpecialAction, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this ) == TRIGRET_RET_TRUE ) return; } @@ -2062,10 +2067,11 @@ bool CChar::NPC_OnItemGive( CChar *pCharSrc, CItem *pItem ) if ( !pCharSrc ) return false; - CScriptTriggerArgs Args(pItem); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pItem; if ( IsTrigUsed(TRIGGER_RECEIVEITEM) ) { - if ( OnTrigger(CTRIG_ReceiveItem, pCharSrc, &Args) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_ReceiveItem, pScriptArgs, pCharSrc) == TRIGRET_RET_TRUE ) return false; } @@ -2141,7 +2147,7 @@ bool CChar::NPC_OnItemGive( CChar *pCharSrc, CItem *pItem ) if (Use_Item(pItem)) return true; else - return false; //If can't use item, return it inside player's backpack. + return false; // If I can't use item, return it inside player's backpack. } } @@ -2194,7 +2200,7 @@ bool CChar::NPC_OnItemGive( CChar *pCharSrc, CItem *pItem ) { if ( IsTrigUsed(TRIGGER_NPCREFUSEITEM) ) { - if ( OnTrigger(CTRIG_NPCRefuseItem, pCharSrc, &Args) != TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_NPCRefuseItem, pScriptArgs, pCharSrc) != TRIGRET_RET_TRUE ) { pCharSrc->SysMessage(g_Cfg.GetDefaultMsg(DEFMSG_NPC_GENERIC_DONTWANT)); return false; @@ -2206,7 +2212,7 @@ bool CChar::NPC_OnItemGive( CChar *pCharSrc, CItem *pItem ) if ( IsTrigUsed(TRIGGER_NPCACCEPTITEM) ) { - if ( OnTrigger(CTRIG_NPCAcceptItem, pCharSrc, &Args) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_NPCAcceptItem, pScriptArgs, pCharSrc) == TRIGRET_RET_TRUE ) return false; } @@ -2246,7 +2252,7 @@ void CChar::NPC_OnTickAction() return; } - + bool fSkillFight = false; if ( g_Cfg.IsSkillFlag( iSkillActive, SKF_SCRIPTED ) ) { @@ -2282,7 +2288,7 @@ void CChar::NPC_OnTickAction() // just remain hidden unless we find something new to do. if ( g_Rand.GetVal( Skill_GetBase(SKILL_HIDING))) break; - EXC_SET_BLOCK("idle: Hidding"); + EXC_SET_BLOCK("idle: Hiding"); NPC_Act_Idle(); break; @@ -2302,7 +2308,7 @@ void CChar::NPC_OnTickAction() case SKILL_CHIVALRY: case SKILL_SPELLWEAVING: EXC_SET_BLOCK("magic"); - NPC_Act_Fight(); // May be we can split Fight and Magic from here? + NPC_Act_Fight(); // Maybe we can split Fight and Magic from here? break; case NPCACT_GUARD_TARG: @@ -2377,7 +2383,7 @@ void CChar::NPC_OnTickAction() } EXC_SET_BLOCK("timer expired (NPC)"); - if ( _IsTimerExpired() && !fSkillFight) // If i'm fighting, i don't want to wait to start another swing + if ( _IsTimerExpired() && !fSkillFight) // If I'm fighting, I don't want to wait to start another swing { int32 timeout = (150-Stat_GetAdjusted(STAT_DEX))/2; timeout = maximum(timeout, 0); @@ -2411,16 +2417,16 @@ void CChar::NPC_Pathfinding() const int dist = ptLocal.GetDist(ptTarg); // If NPC_AI_ALWAYSINT is set, just make it as smart as possible. const int iInt = ( NPC_GetAiFlags() & NPC_AI_ALWAYSINT ) ? 300 : Stat_GetAdjusted(STAT_INT); - + // do we really need to find the path? if ( iInt < 30 ) // too dumb - return; + return; if ( m_pNPC->m_nextPt == ptTarg ) // we have path to that position already saved in m_NextX/Y - return; + return; if ( !ptTarg.IsValidPoint() ) // invalid point return; if (( ptTarg.m_x == ptLocal.m_x ) && ( ptTarg.m_y == ptLocal.m_y )) // same spot - return; + return; if ( ptTarg.m_map != ptLocal.m_map ) // cannot just move to another map return; if ( dist >= MAX_NPC_PATH_STORAGE_SIZE/2 ) // skip too far locations which should be too slow @@ -2504,7 +2510,7 @@ void CChar::NPC_Food() for (CSObjContRec* pObjRec : *pPack) { CItem* pFood = static_cast(pObjRec); - // i have some food personaly, so no need to search for something + // I have some food personally, so no need to search for anything. if ( pFood->IsType(IT_FOOD) ) { if ( (uiEatAmount = Food_CanEat(pFood)) > 0 ) @@ -2591,7 +2597,7 @@ void CChar::NPC_Food() } } } - // no food around, but maybe i am ok with grass? + // No food around, but maybe I am ok with grass? else { const NPCBRAIN_TYPE brain = GetNPCBrainGroup(); @@ -2607,7 +2613,7 @@ void CChar::NPC_Food() const CResourceID rid = CResourceID(RES_TYPEDEF, IT_GRASS); EXC_SET_BLOCK("searching grass"); - if ( pCharDef->m_FoodType.ContainsResourceID(rid) ) // do I accept grass as a food? + if ( pCharDef->m_FoodType.ContainsResourceID(rid) ) // do I accept grass as food? { CItem *pResBit = CWorldMap::CheckNaturalResource(ptMe, IT_GRASS, true, this); if ( pResBit && pResBit->GetAmount() && ( pResBit->GetTopPoint().m_z == iMyZ ) ) @@ -2675,7 +2681,7 @@ void CChar::NPC_ExtraAI() EXC_SET_BLOCK("init"); if ( IsTrigUsed(TRIGGER_NPCACTION) ) { - if ( OnTrigger( CTRIG_NPCAction, this ) == TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_NPCAction, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this ) == TRIGRET_RET_TRUE ) return; } @@ -2708,7 +2714,7 @@ void CChar::NPC_ExtraAI() return; } - // Equip lightsource at night time + // Equip light source at nighttime. EXC_SET_BLOCK("light source"); const CPointMap& pt = GetTopPoint(); const CSector *pSector = pt.GetSector(); diff --git a/src/game/chars/CCharNPCAct_Fight.cpp b/src/game/chars/CCharNPCAct_Fight.cpp index f13374ace..dbf211d3c 100644 --- a/src/game/chars/CCharNPCAct_Fight.cpp +++ b/src/game/chars/CCharNPCAct_Fight.cpp @@ -1,7 +1,8 @@ // Actions specific to an NPC. + #include "../../common/sphere_library/CSRand.h" #include "../../common/CLog.h" -#include "../../common/CScriptTriggerArgs.h" +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../components/CCPropsItemWeapon.h" #include "../items/item_types.h" #include "../uo_files/uofiles_enums_creid.h" @@ -98,15 +99,15 @@ CChar * CChar::NPC_FightFindBestTarget(const std::vector* pvExcludeList) if (refAttacker.ignore) { - bool bIgnore = true; + bool fIgnore = true; if (IsTrigUsed(TRIGGER_HITIGNORE)) { - CScriptTriggerArgs Args; - Args.m_iN1 = bIgnore; - OnTrigger(CTRIG_HitIgnore, pChar, &Args); - bIgnore = Args.m_iN1 ? true : false; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = fIgnore; + OnTrigger(CTRIG_HitIgnore, pScriptArgs, pChar); + fIgnore = pScriptArgs->m_iN1 ? true : false; } - if (bIgnore) + if (fIgnore) { ++i; continue; @@ -222,20 +223,21 @@ void CChar::NPC_Act_Fight() bool fSkipHardcoded = false; if (IsTrigUsed(TRIGGER_NPCACTFIGHT)) { - CScriptTriggerArgs Args(iDist, iMotivation); - switch (OnTrigger(CTRIG_NPCActFight, pChar, &Args)) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iDist, iMotivation, 0, nullptr); + switch (OnTrigger(CTRIG_NPCActFight, pScriptArgs, pChar)) { case TRIGRET_RET_TRUE: return; case TRIGRET_RET_FALSE: fSkipHardcoded = true; break; - case (TRIGRET_TYPE)(2) : + case TRIGRET_RET_DEFAULT: //(TRIGRET_TYPE)(2) : { - SKILL_TYPE iSkillforced = (SKILL_TYPE)ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("skill")); + SKILL_TYPE iSkillforced = (SKILL_TYPE)ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("skill")); if (iSkillforced) { - SPELL_TYPE iSpellforced = (SPELL_TYPE)ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("spell")); + SPELL_TYPE iSpellforced = (SPELL_TYPE)ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("spell")); if (g_Cfg.IsSkillFlag(iSkillforced, SKF_MAGIC)) { m_atMagery.m_iSpell = iSpellforced; @@ -250,8 +252,8 @@ void CChar::NPC_Act_Fight() break; } - iDist = (int)(Args.m_iN1); - iMotivation = (int)(Args.m_iN2); + iDist = (int)(pScriptArgs->m_iN1); + iMotivation = (int)(pScriptArgs->m_iN2); } if (!IsStatFlag(STATF_PET)) diff --git a/src/game/chars/CCharNPCAct_Magic.cpp b/src/game/chars/CCharNPCAct_Magic.cpp index 1f7f5814d..f1d3513e1 100644 --- a/src/game/chars/CCharNPCAct_Magic.cpp +++ b/src/game/chars/CCharNPCAct_Magic.cpp @@ -1,7 +1,7 @@ // Actions specific to an NPC. #include "../../common/sphere_library/CSRand.h" -#include "../../common/CScriptTriggerArgs.h" +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../items/CItemContainer.h" #include "../items/CItemMemory.h" #include "../triggers.h" @@ -143,8 +143,8 @@ void CChar::NPC_AddSpellsFromBook(CItem * pBook) } } -// cast a spell if i can ? -// or i can throw or use archery ? +// Cast a spell if I can? +// Or I can throw or use archery? // RETURN: // false = revert to melee type fighting. bool CChar::NPC_FightMagery(CChar * pChar) @@ -152,7 +152,7 @@ bool CChar::NPC_FightMagery(CChar * pChar) ADDTOCALLSTACK("CChar::NPC_FightMagery"); ASSERT(m_pNPC); - if (!NPC_FightMayCast(false)) // not checking skill here since it will do a search later and it's an expensive function. + if (!NPC_FightMayCast(false)) // Not checking skill here since it will do a search later, and it's an expensive function. { return false; } @@ -162,7 +162,7 @@ bool CChar::NPC_FightMagery(CChar * pChar) CObjBase * pTarg = pChar; if (pWand) { - // If the item is really a wand and have it charges it's a valid wand, if not ... we get rid of it. + // If the item is really a wand and have its charges it's a valid wand, if not ... we get rid of it. if (pWand->GetType() != IT_WAND || pWand->m_itWeapon.m_spellcharges <= 0 || !pWand->IsAttr(ATTR_MAGIC)) pWand = nullptr; } @@ -193,13 +193,13 @@ bool CChar::NPC_FightMagery(CChar * pChar) { if (iDist < 4 || iDist > 8) // Here is fine? NPC_Act_Follow(false, g_Rand.GetVal(3) + 2, true); - + return true; } return false; } - // We have the total count of spells inside iSpellCount, so we use 'iRandSpell' to store a rand representing the spell that will be casted + // We have the total count of spells inside iSpellCount, so we use 'iRandSpell' to store a rand representing the spell that will be cast. uchar iRandSpell = (uchar)(g_Rand.GetVal2(0, iSpellCount - 1)); //Spells are being stored using a vector, so it's assumed to be zero-based. bool bSpellSuccess = false, bWandUse = false, bIgnoreAITargetChoice = false; int iHealThreshold = g_Cfg.m_iNPCHealthreshold; @@ -212,7 +212,7 @@ bool CChar::NPC_FightMagery(CChar * pChar) while( iRandSpell < iSpellCount || bWandUse ) { SPELL_TYPE spell = SPELL_NONE; - + if (!bWandUse) spell = m_pNPC->Spells_GetAt(iRandSpell); else @@ -222,17 +222,18 @@ bool CChar::NPC_FightMagery(CChar * pChar) } if (IsTrigUsed(TRIGGER_NPCACTCAST)) { - CScriptTriggerArgs Args((int)spell, (int)bWandUse, pTarg); - Args.m_VarsLocal.SetNum("HealThreshold", iHealThreshold); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init((int)spell, (int)bWandUse, 0, pTarg); + pScriptArgs->m_VarsLocal.SetNum("HealThreshold", iHealThreshold); - switch (OnTrigger(CTRIG_NPCActCast, this, &Args)) + switch (OnTrigger(CTRIG_NPCActCast, pScriptArgs, this)) { case TRIGRET_RET_TRUE: return false; default: break; } - spell = (SPELL_TYPE)Args.m_iN1; - iHealThreshold = (int)Args.m_VarsLocal.GetKeyNum("HealThreshold"); - CObjBase* pNewTarg = Args.m_VarObjs.Get(1); //We switch to a new targ if REF1 is set in the trigger. + spell = (SPELL_TYPE)pScriptArgs->m_iN1; + iHealThreshold = (int)pScriptArgs->m_VarsLocal.GetKeyNum("HealThreshold"); + CObjBase* pNewTarg = pScriptArgs->m_VarObjs.Get(1); //We switch to a new targ if REF1 is set in the trigger. if (pNewTarg) { pTarg = pNewTarg; @@ -246,7 +247,7 @@ bool CChar::NPC_FightMagery(CChar * pChar) break; } iRandSpell++; - + } if (!bSpellSuccess) return false; @@ -282,7 +283,7 @@ bool CChar::NPC_FightCast(CObjBase * &pTarg, CObjBase * pSrc, SPELL_TYPE &spell, return false; int iSkillReq = 0; - + if (!pSpellDef->GetPrimarySkill(&skill, &iSkillReq)) skill = SKILL_MAGERY; @@ -337,7 +338,7 @@ bool CChar::NPC_FightCast(CObjBase * &pTarg, CObjBase * pSrc, SPELL_TYPE &spell, } } - // i cannot cast this on self. ok, then friends only + // I cannot cast this on self. ok, then friends only. if (pSpellDef->IsSpellType(SPELLFLAG_TARG_NOSELF)) { pFriend[0] = pFriend[1]; diff --git a/src/game/chars/CCharNPCAct_Vendor.cpp b/src/game/chars/CCharNPCAct_Vendor.cpp index f9406109f..215dc4328 100644 --- a/src/game/chars/CCharNPCAct_Vendor.cpp +++ b/src/game/chars/CCharNPCAct_Vendor.cpp @@ -1,6 +1,6 @@ // Actions specific to an NPC. -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header #include "../clients/CClient.h" #include "../items/CItemContainer.h" #include "../items/CItemMemory.h" diff --git a/src/game/chars/CCharNPCPet.cpp b/src/game/chars/CCharNPCPet.cpp index 0b9ef724c..b7a8a626b 100644 --- a/src/game/chars/CCharNPCPet.cpp +++ b/src/game/chars/CCharNPCPet.cpp @@ -1,7 +1,7 @@ // Actions specific to an NPC. #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header #include "../clients/CClient.h" #include "../components/CCSpawn.h" #include "../items/CItemContainer.h" @@ -23,7 +23,7 @@ void CChar::NPC_OnPetCommand( bool fSuccess, CChar * pMaster ) if ( !CanSee( pMaster ) ) return; - // i take a command from my master. + // I take a command from my master. if ( NPC_CanSpeak() ) Speak( fSuccess ? g_Cfg.GetDefaultMsg( DEFMSG_NPC_PET_SUCCESS ) : g_Cfg.GetDefaultMsg( DEFMSG_NPC_PET_FAILURE ) ); else @@ -412,14 +412,14 @@ bool CChar::NPC_OnHearPetCmdTarg( int iCmd, CChar *pSrc, CObjBase *pObj, const C case PC_TRANSFER: // Can't transfer ownership of a conjured monster, it can be controlled only by its summoner if (IsStatFlag(STATF_CONJURED)) - { + { pSrc->SysMessage(g_Cfg.GetDefaultMsg(DEFMSG_NPC_PET_TARG_TRANSFER_SUMMONED)); return false; } break; default: { - // All others commands are avaible only to pet owner + // All others commands are available only to pet owner. if ( !NPC_IsOwnedBy(pSrc, true) ) return false; } @@ -642,7 +642,7 @@ bool CChar::NPC_CheckHirelingStatus() { ADDTOCALLSTACK("CChar::NPC_CheckHirelingStatus"); ASSERT(m_pNPC); - // Am i happy at the moment ? + // Am I happy at the moment? // If not then free myself. // // RETURN: @@ -762,7 +762,7 @@ bool CChar::NPC_OnHirePay( CChar * pCharSrc, CItemMemory * pMemory, CItem * pGol // Put all my loot cash away. ContentConsume( CResourceID(RES_TYPEDEF,IT_GOLD), INT32_MAX, 0 ); - // Mark all my stuff ATTR_OWNED - i won't give it away. + // Mark all my stuff ATTR_OWNED - I won't give it away. ContentAttrMod( ATTR_OWNED, true ); NPC_PetSetOwner( pCharSrc ); @@ -791,7 +791,7 @@ bool CChar::NPC_OnHireHear( CChar * pCharSrc ) { if ( pMemory->IsMemoryTypes(MEMORY_IPET|MEMORY_FRIEND)) { - // Next gold i get goes toward hire. + // Next gold I get goes toward hire. pMemory->m_itEqMemory.m_Action = NPC_MEM_ACT_SPEAK_HIRE; NPC_OnHirePayMore( nullptr, uiWage, false ); return true; @@ -826,7 +826,7 @@ bool CChar::NPC_SetVendorPrice( CItem * pItem, int iPrice ) ASSERT(m_pNPC); // player vendors. // CLIMODE_PROMPT_VENDOR_PRICE - // This does not check who is setting the price if if it is valid for them to do so. + // This does not check who is setting the price if it is valid for them to do so. if ( ! NPC_IsVendor()) return false; @@ -869,7 +869,7 @@ void CChar::NPC_PetRelease() if (IsTrigUsed(TRIGGER_PETRELEASE)) { - if (OnTrigger(CTRIG_PetRelease, pCharOwn, nullptr) == TRIGRET_RET_TRUE) + if (OnTrigger(CTRIG_PetRelease, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharOwn) == TRIGRET_RET_TRUE) return; } @@ -901,7 +901,7 @@ void CChar::NPC_PetDesert() ASSERT(m_pNPC); // If the monster has brain_berserk (i.e. energy vortex): when the player summons it, his CurFollower property rises. // If the master attacks the berserk pet, the pet deserts him and the master's CurFollower goes down. If we don't prevent - // berserk monsters to desert the master, he can do this trick to summon a lot of energy vortexes without any cost to his followers property. + // berserk monsters to desert the master, he can do this trick to summon a lot of energy vortexes without any cost to his followers' property. if (m_pNPC->m_Brain == NPCBRAIN_BERSERK) return; @@ -911,7 +911,7 @@ void CChar::NPC_PetDesert() if ( IsTrigUsed(TRIGGER_PETDESERT) ) { - if ( OnTrigger( CTRIG_PetDesert, pCharOwn, nullptr ) == TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_PetDesert, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharOwn) == TRIGRET_RET_TRUE ) return; } @@ -922,6 +922,6 @@ void CChar::NPC_PetDesert() snprintf(pszMsg, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_NPC_PET_DECIDE_MASTER), GetName()); Speak(pszMsg); - // free to do as i wish ! + // Free to do as I wish! NPC_PetRelease(); } diff --git a/src/game/chars/CCharNPCStatus.cpp b/src/game/chars/CCharNPCStatus.cpp index 6f3a9d46f..d33505b94 100644 --- a/src/game/chars/CCharNPCStatus.cpp +++ b/src/game/chars/CCharNPCStatus.cpp @@ -1,7 +1,7 @@ // Test things to judge what an NPC might be thinking. (want to do) // But take no actions here. -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header #include "../../common/CLog.h" #include "../items/CItemContainer.h" #include "../items/CItemMemory.h" @@ -432,7 +432,7 @@ bool CChar::NPC_FightMayCast(bool fCheckSkill) const // This NPC could cast spells if they wanted to? // Check mana, anti-magic and tag.noCastTill. // Don't check for skill if !fCheckSkill. - + //Don't cast the spell if tag.NPCNoCastTill is > than CurrentTime. if (GetKeyNum("NPCNoCastTill") > (CWorldGameTime::GetCurrentTime().GetTimeRaw() / MSECS_PER_TENTH)) return false; @@ -472,7 +472,7 @@ CChar * CChar::NPC_PetGetOwner() const { ADDTOCALLSTACK("CChar::NPC_PetGetOwner"); ASSERT(m_pNPC); - // Assume i am a pet. Get my owner. not just friends. used for blame. + // Assume I am a pet. Get my owner. not just friends. used for blame. if ( !IsStatFlag(STATF_PET) ) return nullptr; @@ -488,7 +488,7 @@ CChar * CChar::NPC_PetGetOwnerRecursive() const { ADDTOCALLSTACK("CChar::NPC_PetGetOwnerRecursive"); ASSERT(m_pNPC); - // Assume i am a pet. Get the primary owner (the owner of the owner of my owner and so on). + // Assume I am a pet. Get the primary owner (the owner of the owner of my owner and so on). static int iReentrantCheck_PetGetOwnerRecursive = 0; @@ -515,7 +515,7 @@ ushort CChar::NPC_GetTrainMax( const CChar * pStudent, SKILL_TYPE Skill ) const { ADDTOCALLSTACK("CChar::NPC_GetTrainMax"); ASSERT(m_pNPC); - + // What is the max I can train to ? ushort uiMax; ushort uiMaxAllowed; @@ -556,7 +556,7 @@ bool CChar::NPC_CheckWalkHere( const CPointMap & pt, const CRegion * pArea ) con /* if (IsSetOF(OF_GuardOutsideGuardedArea)) { - // I come from a guarded area, so i don't want to leave it unprotected; otherwise, i don't care if my destination region is guarded or not + // I come from a guarded area, so I don't want to leave it unprotected; otherwise, I don't care if my destination region is guarded or not const CRegion * pAreaHome = m_ptHome.GetRegion( REGION_TYPE_AREA ); if (pAreaHome->IsGuarded() && !pArea->IsGuarded()) return false; @@ -633,8 +633,8 @@ int CChar::NPC_WantThisItem( CItem * pItem ) const // May not want to use it but rather just put it in my pack. // // NOTE: - // Don't check if i can see this or i can reach it. - // Don't check if i can carry this ? + // Don't check if I can see this, or I can reach it. + // Don't check if I can carry this ? // // RETURN: // 0-100 percent = how bad do we want it ? @@ -672,7 +672,7 @@ int CChar::NPC_GetWeaponUseScore( CItem * pWeapon ) { ADDTOCALLSTACK("CChar::NPC_GetWeaponUseScore"); ASSERT(m_pNPC); - // How good would i be at this weapon ? + // How good would I be at this weapon ? SKILL_TYPE skill; @@ -688,7 +688,7 @@ int CChar::NPC_GetWeaponUseScore( CItem * pWeapon ) // I can't equip this anyhow. if ( CanEquipLayer( pWeapon, LAYER_QTY, nullptr, true ) == LAYER_NONE ) return 0; - // How much damage could i do with this ? + // How much damage could I do with this ? } int iDmg = Fight_CalcDamage( pWeapon ); @@ -704,15 +704,15 @@ int CChar::NPC_GetHostilityLevelToward( const CChar * pCharTarg ) const // What is my general hostility level toward this type of creature ? // // based on: - // npc vs player, (evil npc's don't like players regurdless of align, except in town) + // npc vs player, (evil npc's don't like players regardless of align, except in town) // karma (we are of different alignments) // creature body type. (allie groups) // hunger, (they could be food) // memories of this creature. // // DO NOT consider: - // strength, he is far stronger or waeker than me. - // health, i may be near death. + // strength, he is far stronger or weaker than me. + // health, I may be near death. // location (guarded area), (xcept in the case that evil people like other evils in town) // loot, etc. // @@ -749,22 +749,22 @@ int CChar::NPC_GetHostilityLevelToward( const CChar * pCharTarg ) const bool fDoMemBase = false; - if ( Noto_IsEvil() && // i am evil. + if ( Noto_IsEvil() && // I am evil. (m_pArea && !m_pArea->IsGuarded()) && // we are not in an evil town. pCharTarg->m_pPlayer ) // my target is a player. { - // If i'm evil i give no benefit to players with bad karma. + // If I'm evil I give no benefit to players with bad karma. // I hate all players. - // Unless i'm in a guarded area. then they are cool. + // Unless I'm in a guarded area. then they are cool. iHostility = 51; } - else if ( m_pNPC->m_Brain == NPCBRAIN_BERSERK ) // i'm beserk. + else if ( m_pNPC->m_Brain == NPCBRAIN_BERSERK ) // I'm berserk. { - // beserks just hate everyone all the time. + // Berserk just hate everyone all the time. iHostility = 100; } else if ( pCharTarg->m_pNPC && // my target is an NPC - pCharTarg->m_pNPC->m_Brain != NPCBRAIN_BERSERK && // ok to hate beserks. + pCharTarg->m_pNPC->m_Brain != NPCBRAIN_BERSERK && // Ok to hate berserk. ! g_Cfg.m_fMonsterFight ) // monsters are not supposed to fight other monsters ! { iHostility = -50; @@ -772,7 +772,7 @@ int CChar::NPC_GetHostilityLevelToward( const CChar * pCharTarg ) const } else { - // base hostillity on karma diff. + // Base hostility on karma diff. int iKarmaTarg = pCharTarg->GetKarma(); if ( Noto_IsEvil()) @@ -798,7 +798,7 @@ int CChar::NPC_GetHostilityLevelToward( const CChar * pCharTarg ) const { if ( pCharTarg->m_pNPC ) { - // Human NPC's will attack humans . + // Human NPC's will attack humans. if ( GetDispID() == pCharTarg->GetDispID()) { @@ -817,7 +817,7 @@ int CChar::NPC_GetHostilityLevelToward( const CChar * pCharTarg ) const } else { - // Not immediately hostile if looks the same as me. + // Not immediately hostile if it looks the same as me. if ( ! IsPlayableCharacter() && NPC_GetAllyGroupType( GetDispID()) == NPC_GetAllyGroupType(pCharTarg->GetDispID())) { iHostility -= 51; @@ -841,14 +841,14 @@ int CChar::NPC_GetAttackContinueMotivation( CChar * pChar, int iMotivation ) con ADDTOCALLSTACK("CChar::NPC_GetAttackContinueMotivation"); ASSERT(m_pNPC); // I have seen fit to attack them. - // How much do i want to continue an existing fight ? cowardice ? + // How much do I want to continue an existing fight ? cowardice ? // ARGS: // iMotivation = My base motivation toward this creature. // // RETURN: // -101 = ? dead meat. (run away) // - // 0 = I'm have no interest. + // 0 = I have no interest. // 50 = even match. // 100 = he's a push over. if ( !pChar ) @@ -892,7 +892,7 @@ int CChar::NPC_GetAttackMotivation( CChar * pChar, int iMotivation ) const // Take into consideration AC, health, skills, etc.. // RETURN: // < 0 = dead meat. (run away) - // 0 = I'm have no interest. + // 0 = I have no interest. // 50 = even match. // 100 = he's a push over. @@ -903,10 +903,10 @@ int CChar::NPC_GetAttackMotivation( CChar * pChar, int iMotivation ) const if ( pChar->m_pArea->IsFlag(REGION_FLAG_SAFE) ) return 0; - if (Fight_IsActive() && (m_Fight_Targ_UID == pChar->GetUID())) // Am i attacking the same target as before? + if (Fight_IsActive() && (m_Fight_Targ_UID == pChar->GetUID())) // Am I attacking the same target as before? { - // Was i told to attack by my master? This is the only hardcoded case in which we have such an high threat value. - // In this case, i want that my pet attacks the target without doing any other consideration. + // Was I told to attack by my master? This is the only hardcoded case in which we have such a high threat value. + // In this case, I want that my pet attacks the target without doing any other consideration. const int iCharID = Attacker_GetID(pChar); ASSERT(iCharID != -1); if (Attacker_GetThreat(iCharID) >= ATTACKER_THREAT_TOLDBYMASTER) diff --git a/src/game/chars/CCharNotoriety.cpp b/src/game/chars/CCharNotoriety.cpp index 310bd26fd..64926d1cc 100644 --- a/src/game/chars/CCharNotoriety.cpp +++ b/src/game/chars/CCharNotoriety.cpp @@ -1,7 +1,7 @@ // Actions specific to an NPC. -#include "../../common/CExpression.h" -#include "../../common/CException.h" -#include "../../common/CScriptTriggerArgs.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../items/CItemMemory.h" #include "../items/CItemStone.h" #include "../triggers.h" @@ -99,6 +99,7 @@ bool CChar::Noto_IsNeutral() const NOTO_TYPE CChar::Noto_GetFlag(const CChar * pCharViewer, bool fAllowIncog, bool fAllowInvul, bool fOnlyColor) const { ADDTOCALLSTACK("CChar::Noto_GetFlag"); + // TODO: CONST-CORRECTNESS! CChar * pThis = const_cast(this); CChar * pTarget = const_cast(pCharViewer); NOTO_TYPE iNoto = NOTO_INVALID; @@ -118,10 +119,10 @@ NOTO_TYPE CChar::Noto_GetFlag(const CChar * pCharViewer, bool fAllowIncog, bool if (IsTrigUsed(TRIGGER_NOTOSEND)) { - CScriptTriggerArgs args; - pThis->OnTrigger(CTRIG_NotoSend, pTarget, &args); - iNoto = (NOTO_TYPE)(args.m_iN1); - iColor = (NOTO_TYPE)(args.m_iN2); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pThis->OnTrigger(CTRIG_NotoSend, pScriptArgs, pTarget); + iNoto = (NOTO_TYPE)(pScriptArgs->m_iN1); + iColor = (NOTO_TYPE)(pScriptArgs->m_iN2); } if (iNoto == NOTO_INVALID) @@ -140,14 +141,11 @@ NOTO_TYPE CChar::Noto_GetFlag(const CChar * pCharViewer, bool fAllowIncog, bool // NOTO_GUILD_WAR 5 // NOTO_EVIL 6 // NOTO_INVUL 7 -NOTO_TYPE CChar::Noto_CalcFlag(const CChar * pCharViewer, bool fAllowIncog, bool fAllowInvul) const +NOTO_TYPE CChar::Noto_CalcFlag(const CChar * pCharViewer, const bool fAllowIncog, const bool fAllowInvul) const { ADDTOCALLSTACK("CChar::Noto_GetFlag"); - // What is this char to the viewer ? - // This allows the noto attack check in the client. - // NOTO_GOOD = it is criminal to attack me. - NOTO_TYPE iNotoFlag = (NOTO_TYPE)(m_TagDefs.GetKeyNum("OVERRIDE.NOTO")); + const auto iNotoFlag = static_cast(m_TagDefs.GetKeyNum("OVERRIDE.NOTO")); if (iNotoFlag != NOTO_INVALID) return iNotoFlag; @@ -157,113 +155,116 @@ NOTO_TYPE CChar::Noto_CalcFlag(const CChar * pCharViewer, bool fAllowIncog, bool if (fAllowInvul && IsStatFlag(STATF_INVUL)) return NOTO_INVUL; - if (m_pArea && m_pArea->IsFlag(REGION_FLAG_ARENA)) // everyone is neutral here. + // Everyone is neutral here. + if (m_pArea && m_pArea->IsFlag(REGION_FLAG_ARENA)) return NOTO_NEUTRAL; - if (this != pCharViewer) // Am I checking myself? - { - if (m_pNPC) - { - if (g_Cfg.m_iPetsInheritNotoriety != 0) - { - // Do we have a master to inherit notoriety from? - CChar* pMaster = NPC_PetGetOwnerRecursive(); - if (pMaster && (pMaster != pCharViewer)) // master doesn't want to see their own status - { - // return master's notoriety - NOTO_TYPE notoMaster = pMaster->Noto_GetFlag(pCharViewer, fAllowIncog, fAllowInvul); - - // check if notoriety is inheritable based on bitmask setting: - // NOTO_GOOD = 0x01 - // NOTO_GUILD_SAME = 0x02 - // NOTO_NEUTRAL = 0x04 - // NOTO_CRIMINAL = 0x08 - // NOTO_GUILD_WAR = 0x10 - // NOTO_EVIL = 0x20 - int iPetNotoFlag = 1 << (notoMaster - 1); - if ((g_Cfg.m_iPetsInheritNotoriety & iPetNotoFlag) == iPetNotoFlag) - return notoMaster; - } - } + const bool fSelfCheck = (this == pCharViewer); - if (!IsSetOF(OF_PetBehaviorOwnerNeutral) && NPC_IsOwnedBy(pCharViewer, false)) // All pets are neutral to their owners. - return NOTO_NEUTRAL; - } + // Player is looking at himself. + if (fSelfCheck) + goto skip_guilds; - // Are we in the same party ? - if (m_pParty && (m_pParty == pCharViewer->m_pParty) ) - { - //if (m_pParty->GetLootFlag(this)) - return NOTO_GUILD_SAME; - } + // We are in the same party. + if (m_pParty && m_pParty == pCharViewer->m_pParty) + return NOTO_GUILD_SAME; + + // We are looking at NPC. + if (m_pNPC) + { + // Flag to display true notoriety is disabled. + if (!IsSetOF(OF_PetBehaviorOwnerNeutral) && NPC_IsOwnedBy(pCharViewer, false)) + return NOTO_NEUTRAL; - if (m_pPlayer) + // Pets inheriting notoriety from master. + if (g_Cfg.m_iPetsInheritNotoriety != 0) { - // Check the guild/town stuff - const CItemStone * pMyTown = Guild_Find(MEMORY_TOWN); - const CItemStone * pMyGuild = Guild_Find(MEMORY_GUILD); - if (pMyGuild || pMyTown) + // Get master and his notoriety. + const CChar * pMaster = NPC_PetGetOwnerRecursive(); + if (pMaster && pMaster != pCharViewer) { - const CItemStone * pViewerGuild = pCharViewer->Guild_Find(MEMORY_GUILD); - const CItemStone * pViewerTown = pCharViewer->Guild_Find(MEMORY_TOWN); - // Are we both in a guild? - if (pViewerGuild || pViewerTown) - { - if (pMyGuild && pMyGuild->IsPrivMember(this)) - { - if (pViewerGuild && pViewerGuild->IsPrivMember(pCharViewer)) - { - if (IsSetOF(OF_EnableGuildAlignNotoriety)) - { - if (pViewerGuild->GetAlignType() != STONEALIGN_STANDARD && pMyGuild->GetAlignType() != STONEALIGN_STANDARD) //We have to check if my guild is also not STONEALIGN_STANDART. - { - if (pViewerGuild->GetAlignType() == pMyGuild->GetAlignType()) - { - return NOTO_GUILD_SAME; - } - return NOTO_GUILD_WAR; - } - } - - if (pViewerGuild == pMyGuild) // Same guild? - return NOTO_GUILD_SAME; // return green - if (pMyGuild->IsAlliedWith(pViewerGuild)) - return NOTO_GUILD_SAME; - // Are we in different guilds but at war? (not actually a crime right?) - if (pMyGuild->IsAtWarWith(pViewerGuild)) - return NOTO_GUILD_WAR; // return orange - } - if (pMyGuild->IsAtWarWith(pViewerTown)) - return NOTO_GUILD_WAR; // return orange - } - if (pMyTown && pMyTown->IsPrivMember(this)) - { - if (pViewerGuild && pViewerGuild->IsPrivMember(pCharViewer)) - { - if (pMyTown->IsAtWarWith(pViewerGuild)) - return NOTO_GUILD_WAR; // return orange - } - if (pMyTown->IsAtWarWith(pViewerTown)) - return NOTO_GUILD_WAR; // return orange - } - } + const NOTO_TYPE notoMaster = pMaster->Noto_GetFlag(pCharViewer, fAllowIncog, fAllowInvul); + + // Get notoriety based on bit flag defined in sphere.ini's `PetsInheritNotoriety`. + const int iPetNotoFlag = 1 << (notoMaster - 1); + if ((g_Cfg.m_iPetsInheritNotoriety & iPetNotoFlag) == iPetNotoFlag) + return notoMaster; } } - } + // Guild/Town relations. + { + const CItemStone * pMyTown = Guild_Find(MEMORY_TOWN); + const CItemStone * pMyGuild = Guild_Find(MEMORY_GUILD); + + // I don't have any town or guild. + if (!pMyTown && !pMyGuild) + goto skip_guilds; + + const CItemStone * pViewerTown = pCharViewer->Guild_Find(MEMORY_TOWN); + const CItemStone * pViewerGuild = pCharViewer->Guild_Find(MEMORY_GUILD); + + // Player looking at me doesn't have town or guild either. + if (!pViewerTown && !pViewerGuild) + goto skip_guilds; + + // We are both in a guild. + if (pMyGuild && pMyGuild->IsPrivMember(this) && pViewerGuild && pViewerGuild->IsPrivMember(pCharViewer)) + { + // Same guild. + if (pViewerGuild == pMyGuild) + return NOTO_GUILD_SAME; + + // Check standard relationships first (war/ally). + const NOTO_WAR_STATUS warStatus = Noto_GetWarStatus(pMyGuild, pViewerGuild); + if (warStatus == NOTO_WAR_ENEMY) + return NOTO_GUILD_WAR; + if (warStatus == NOTO_WAR_ALLY) + return NOTO_GUILD_SAME; + + // Guild alignment. + const STONEALIGN_TYPE myAlign = pMyGuild->GetAlignType(); + const STONEALIGN_TYPE viewerAlign = pViewerGuild->GetAlignType(); + + // We both aren't in a neutral guild. + if (myAlign != STONEALIGN_STANDARD && viewerAlign != STONEALIGN_STANDARD) + { + // Same guilds share notoriety and we are in the same fraction. + if (IsSetOF(OF_EnableGuildAlignNotoriety) && myAlign == viewerAlign) + return NOTO_GUILD_SAME; + + // Or not. + if (myAlign != viewerAlign) + return NOTO_GUILD_WAR; + } + } + + // Town wars / town vs guild wars. + if (pMyGuild && pMyGuild->IsPrivMember(this) && Noto_GetWarStatus(pMyGuild, pViewerTown) == NOTO_WAR_ENEMY) + return NOTO_GUILD_WAR; + if (pMyTown && pMyTown->IsPrivMember(this)) + { + if (pViewerGuild && pViewerGuild->IsPrivMember(pCharViewer) && Noto_GetWarStatus(pMyTown, pViewerGuild) == NOTO_WAR_ENEMY) + return NOTO_GUILD_WAR; + if (Noto_GetWarStatus(pMyTown, pViewerTown) == NOTO_WAR_ENEMY) + return NOTO_GUILD_WAR; + } + } + +skip_guilds: if (Noto_IsEvil()) return NOTO_EVIL; - if (this != pCharViewer) // Am I checking myself? + if (!fSelfCheck) { - // If they saw me commit a crime or I am their aggressor then criminal to just them. + // If viewer saw me commit a crime, or I am his aggressor, then criminal to just them. const CItemMemory * pMemory = pCharViewer->Memory_FindObjTypes(this, MEMORY_SAWCRIME | MEMORY_AGGREIVED); if (pMemory != nullptr) return NOTO_CRIMINAL; } - if (IsStatFlag(STATF_CRIMINAL)) // criminal to everyone. + if (IsStatFlag(STATF_CRIMINAL)) return NOTO_CRIMINAL; if (Noto_IsNeutral() || m_TagDefs.GetKeyNum("NOTO.PERMAGREY")) @@ -396,12 +397,12 @@ bool CChar::Noto_Criminal( CChar * pCharViewer, bool fFromSawCrime ) TRIGRET_TYPE retCriminal = TRIGRET_RET_DEFAULT; if ( IsTrigUsed(TRIGGER_CRIMINAL) ) { - CScriptTriggerArgs Args; - Args.m_iN1 = decay / (60*MSECS_PER_SEC); // convert in minutes - Args.m_iN2 = fFromSawCrime; - Args.m_pO1 = pCharViewer; - retCriminal = OnTrigger(CTRIG_Criminal, this, &Args); - decay = (Args.m_iN1 * (60*MSECS_PER_SEC)); // back in ms + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = decay / (60*MSECS_PER_SEC); // convert in minutes + pScriptArgs->m_iN2 = fFromSawCrime; + pScriptArgs->m_pO1 = pCharViewer; + retCriminal = OnTrigger(CTRIG_Criminal, pScriptArgs, this); + decay = (pScriptArgs->m_iN1 * (60*MSECS_PER_SEC)); // back in ms } if (retCriminal == TRIGRET_RET_TRUE) @@ -498,7 +499,7 @@ void CChar::Noto_Fame( int iFameChange, CChar* pNPC ) // if ( retType == TRIGRET_RET_TRUE ) // return; - // iFameChange = (int)(Args.m_iN1); + // iFameChange = (int)(pScriptArgs->m_iN1); //} //if ( ! iFameChange ) @@ -536,7 +537,7 @@ void CChar::Noto_Karma( int iKarmaChange, int iBottom, bool fMessage, CChar* pNP // if ( retType == TRIGRET_RET_TRUE ) // return; - // iKarmaChange = (int)(Args.m_iN1); + // iKarmaChange = (int)(pScriptArgs->m_iN1); //} //if ( ! iKarmaChange ) @@ -582,23 +583,23 @@ void CChar::Noto_Kill(CChar * pKill, int iTotalKillers) // I'm a murderer ! if (!IsPriv(PRIV_GM)) { - CScriptTriggerArgs args; - args.m_iN1 = m_pPlayer->m_wMurders + 1LL; - args.m_iN2 = true; - args.m_iN3 = false; - args.m_pO1 = pKill; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = m_pPlayer->m_wMurders + 1LL; + pScriptArgs->m_iN2 = true; + pScriptArgs->m_iN3 = false; + pScriptArgs->m_pO1 = pKill; if ( IsTrigUsed(TRIGGER_MURDERMARK) ) { - OnTrigger(CTRIG_MurderMark, this, &args); - if (args.m_iN1 < 0) - args.m_iN1 = 0; + OnTrigger(CTRIG_MurderMark, pScriptArgs, this); + if (pScriptArgs->m_iN1 < 0) + pScriptArgs->m_iN1 = 0; } - if (args.m_iN3 < 1) + if (pScriptArgs->m_iN3 < 1) { - m_pPlayer->m_wMurders = (word)(args.m_iN1); - if (args.m_iN2) + m_pPlayer->m_wMurders = (word)(pScriptArgs->m_iN1); + if (pScriptArgs->m_iN2) Noto_Criminal(); Noto_Murder(); @@ -652,7 +653,8 @@ void CChar::NotoSave_Add( CChar * pChar, NOTO_TYPE value, NOTO_TYPE color ) ADDTOCALLSTACK("CChar::NotoSave_Add"); if ( !pChar ) return; - const CUID& uid = pChar->GetUID(); + + const CUID& uid(pChar->GetUID()); if ( !m_notoSaves.empty() ) // Checking if I already have him in the list, only if there 's any list. { for (std::vector::iterator it = m_notoSaves.begin(), end = m_notoSaves.end(); it != end; ++it) @@ -669,12 +671,13 @@ void CChar::NotoSave_Add( CChar * pChar, NOTO_TYPE value, NOTO_TYPE color ) } } } - NotoSaves refNoto; - refNoto.charUID = pChar->GetUID().GetObjUID(); - refNoto.time = 0; - refNoto.value = value; - refNoto.color = color; - m_notoSaves.emplace_back(std::move(refNoto)); + + m_notoSaves.emplace_back(NotoSaves{ + .time = 0, + .charUID = pChar->GetUID().GetObjUID(), + .color = color, + .value = value + }); } NOTO_TYPE CChar::NotoSave_GetValue(int id, bool fGetColor ) @@ -753,6 +756,20 @@ void CChar::NotoSave_CheckTimeout() } } +CChar::NOTO_WAR_STATUS CChar::Noto_GetWarStatus(const CItemStone* pMyStone, const CItemStone* pViewerStone) const +{ + if (!pMyStone || !pViewerStone) + return NOTO_WAR_NONE; + + if (pMyStone->IsAtWarWith(pViewerStone)) + return NOTO_WAR_ENEMY; + + if (pMyStone->IsAlliedWith(pViewerStone)) + return NOTO_WAR_ALLY; + + return NOTO_WAR_NONE; +} + void CChar::NotoSave_Resend(CChar * pChar) { ADDTOCALLSTACK("CChar::NotoSave_Resend"); diff --git a/src/game/chars/CCharPlayer.cpp b/src/game/chars/CCharPlayer.cpp index 5fa0e5ba6..343c3e33d 100644 --- a/src/game/chars/CCharPlayer.cpp +++ b/src/game/chars/CCharPlayer.cpp @@ -3,8 +3,8 @@ #include "../../common/resource/sections/CSkillClassDef.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "../clients/CClient.h" #include "../items/CItemMulti.h" #include "../items/CItemStone.h" @@ -374,7 +374,7 @@ bool CCharPlayer::r_LoadVal( CChar * pChar, CScript &s ) case CPC_ADDHOUSE: { - if (g_Serv.IsLoading()) //Prevent to load it from saves, it may cause a crash when accessing to a non-yet existant multi + if (g_Serv.IsLoadingGeneric()) //Prevent to load it from saves, it may cause a crash when accessing to a non-yet existant multi { break; } @@ -403,7 +403,7 @@ bool CCharPlayer::r_LoadVal( CChar * pChar, CScript &s ) } case CPC_ADDSHIP: { - if (g_Serv.IsLoading()) //Prevent to load it from saves, it may cause a crash when accessing to a non-yet existant multi + if (g_Serv.IsLoadingGeneric()) //Prevent to load it from saves, it may cause a crash when accessing to a non-yet existant multi { break; } diff --git a/src/game/chars/CCharPlayer.h b/src/game/chars/CCharPlayer.h index cac375775..d3447300b 100644 --- a/src/game/chars/CCharPlayer.h +++ b/src/game/chars/CCharPlayer.h @@ -84,7 +84,7 @@ struct CCharPlayer void r_WriteChar( CChar * pChar, CScript & s ); bool r_WriteVal( CChar * pChar, lpctstr ptcKey, CSString & s ); bool r_LoadVal( CChar * pChar, CScript & s ); - + public: CCharPlayer( CChar * pChar, CAccount * pAccount ); diff --git a/src/game/chars/CCharSkill.cpp b/src/game/chars/CCharSkill.cpp index 7a1f58f55..d02d6d81f 100644 --- a/src/game/chars/CCharSkill.cpp +++ b/src/game/chars/CCharSkill.cpp @@ -4,7 +4,8 @@ #include "../../common/resource/sections/CRegionResourceDef.h" #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../clients/CClient.h" #include "../items/CItemCorpse.h" @@ -24,7 +25,7 @@ SKILL_TYPE CChar::Skill_GetBest( uint iRank ) const { ADDTOCALLSTACK("CChar::Skill_GetBest"); - // Get the top n best skills. + // Get the top and best skills. if ( iRank >= g_Cfg.m_iMaxSkill ) iRank = 0; @@ -158,9 +159,10 @@ ushort CChar::Skill_GetAdjusted( SKILL_TYPE skill ) const if (pSkillDef != nullptr) { - uint uiPureBonus =( pSkillDef->m_StatBonus[STAT_STR] * Stat_GetAdjusted(STAT_STR) ) + - ( pSkillDef->m_StatBonus[STAT_INT] * Stat_GetAdjusted(STAT_INT) ) + - ( pSkillDef->m_StatBonus[STAT_DEX] * Stat_GetAdjusted(STAT_DEX) ); + const uint uiPureBonus = + ( pSkillDef->m_StatBonus[STAT_STR] * Stat_GetAdjusted(STAT_STR) ) + + ( pSkillDef->m_StatBonus[STAT_INT] * Stat_GetAdjusted(STAT_INT) ) + + ( pSkillDef->m_StatBonus[STAT_DEX] * Stat_GetAdjusted(STAT_DEX) ); uiAdjSkill = (ushort)IMulDiv( pSkillDef->m_StatPercent, uiPureBonus, 10000 ); } @@ -193,27 +195,27 @@ void CChar::Skill_SetBase( SKILL_TYPE skill, ushort uiValue ) bool fUpdateStats = false; if ( IsTrigUsed(TRIGGER_SKILLCHANGE) ) { - CScriptTriggerArgs args; - args.m_iN1 = (int64)skill; - args.m_iN2 = (int64)uiValue; - if ( OnTrigger(CTRIG_SkillChange, this, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = (int64)skill; + pScriptArgs->m_iN2 = (int64)uiValue; + if ( OnTrigger(CTRIG_SkillChange, pScriptArgs, this) == TRIGRET_RET_TRUE ) return; - const llong iN2Old = args.m_iN2; - if (args.m_iN2 > UINT16_MAX) + const llong iN2Old = pScriptArgs->m_iN2; + if (pScriptArgs->m_iN2 > UINT16_MAX) { - args.m_iN2 = UINT16_MAX; + pScriptArgs->m_iN2 = UINT16_MAX; } - else if (args.m_iN2 < 0) + else if (pScriptArgs->m_iN2 < 0) { - args.m_iN2 = 0; + pScriptArgs->m_iN2 = 0; } - if (iN2Old != args.m_iN2) + if (iN2Old != pScriptArgs->m_iN2) { - g_Log.EventWarn("Trying to set skill '%s' to invalid value=%lld. Defaulting it to %" PRId64 ".\n", Skill_GetName(skill), iN2Old, args.m_iN2); + g_Log.EventWarn("Trying to set skill '%s' to invalid value=%lld. Defaulting it to %" PRId64 ".\n", Skill_GetName(skill), iN2Old, pScriptArgs->m_iN2); } - uiValue = (ushort)(args.m_iN2); + uiValue = (ushort)(pScriptArgs->m_iN2); } m_Skill[skill] = uiValue; @@ -412,18 +414,19 @@ void CChar::Skill_Experience( SKILL_TYPE skill, int iDifficulty ) int64 iChance = pSkillDef->m_AdvRate.GetChancePercent(uiSkillLevel); int64 iSkillMax = Skill_GetMax(skill); // max advance for this skill. - CScriptTriggerArgs pArgs(0, iChance, iSkillMax); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(0, iChance, iSkillMax, nullptr); if ( IsTrigUsed(TRIGGER_SKILLGAIN) ) { - if ( Skill_OnCharTrigger( skill, CTRIG_SkillGain, &pArgs ) == TRIGRET_RET_TRUE ) + if ( Skill_OnCharTrigger( skill, CTRIG_SkillGain, pScriptArgs ) == TRIGRET_RET_TRUE ) return; } if ( IsTrigUsed(TRIGGER_GAIN) ) { - if ( Skill_OnTrigger( skill, SKTRIG_GAIN, &pArgs ) == TRIGRET_RET_TRUE ) + if ( Skill_OnTrigger( skill, SKTRIG_GAIN, pScriptArgs) == TRIGRET_RET_TRUE ) return; } - pArgs.GetArgNs( nullptr, &iChance, &iSkillMax ); + pScriptArgs->GetArgNs( nullptr, &iChance, &iSkillMax ); if ( iChance <= 0 ) return; @@ -520,7 +523,7 @@ bool CChar::Skill_CheckSuccess( SKILL_TYPE skill, int iDifficulty, bool fUseBell // RETURN: // true = success in skill. - if ( IsPriv(PRIV_GM) && skill != SKILL_PARRYING ) // GM's can't always succeed Parrying or they won't receive any damage on combat even without STATF_Invul set + if ( IsPriv(PRIV_GM) && skill != SKILL_PARRYING ) // GM's can't always succeed Parrying, or they won't receive any damage on combat even without STATF_Invul set return true; if ( !IsSkillBase(skill) || (iDifficulty < 0) ) // auto failure. @@ -536,7 +539,7 @@ bool CChar::Skill_CheckSuccess( SKILL_TYPE skill, int iDifficulty, bool fUseBell return ( iSuccessChance >= g_Rand.GetVal(1000) ); } -bool CChar::Skill_UseQuick(SKILL_TYPE skill, int64 difficulty, bool fAllowGain, bool fUseBellCurve, bool fForceCheck ) +bool CChar::Skill_UseQuick(SKILL_TYPE skill, int64 iDifficulty, bool fAllowGain, bool fUseBellCurve, bool fForceCheck ) { ADDTOCALLSTACK("CChar::Skill_UseQuick"); // ARGS: @@ -545,19 +548,20 @@ bool CChar::Skill_UseQuick(SKILL_TYPE skill, int64 difficulty, bool fAllowGain, // bAllowGain = can gain skill from this? // bUseBellCurve = check skill success chance using bell curve or a simple percent check? // Use a skill instantly. No wait at all. - // No interference with other skills. + // No interference with other skills. if (g_Cfg.IsSkillFlag(skill, SKF_SCRIPTED) && !fForceCheck) return false; - int64 result = Skill_CheckSuccess( skill, (int)difficulty, fUseBellCurve ); - CScriptTriggerArgs pArgs( 0 , difficulty, result); - TRIGRET_TYPE ret = TRIGRET_RET_DEFAULT; + int64 result = Skill_CheckSuccess( skill, (int)iDifficulty, fUseBellCurve ); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(0, iDifficulty, result, nullptr); + TRIGRET_TYPE ret = TRIGRET_RET_DEFAULT; if ( IsTrigUsed(TRIGGER_SKILLUSEQUICK) ) { - ret = Skill_OnCharTrigger( skill, CTRIG_SkillUseQuick, &pArgs ); - pArgs.GetArgNs( nullptr, &difficulty, &result); + ret = Skill_OnCharTrigger( skill, CTRIG_SkillUseQuick, pScriptArgs ); + pScriptArgs->GetArgNs( nullptr, &iDifficulty, &result); if ( ret == TRIGRET_RET_TRUE ) return true; @@ -566,8 +570,8 @@ bool CChar::Skill_UseQuick(SKILL_TYPE skill, int64 difficulty, bool fAllowGain, } if ( IsTrigUsed(TRIGGER_USEQUICK) ) { - ret = Skill_OnTrigger( skill, SKTRIG_USEQUICK, &pArgs ); - pArgs.GetArgNs( nullptr, &difficulty, &result ); + ret = Skill_OnTrigger( skill, SKTRIG_USEQUICK, pScriptArgs ); + pScriptArgs->GetArgNs( nullptr, &iDifficulty, &result ); if ( ret == TRIGRET_RET_TRUE ) return true; @@ -578,13 +582,13 @@ bool CChar::Skill_UseQuick(SKILL_TYPE skill, int64 difficulty, bool fAllowGain, if ( result ) // success { if ( fAllowGain ) - Skill_Experience( skill, (int)(difficulty) ); + Skill_Experience( skill, (int)(iDifficulty) ); return true; } else // fail { if ( fAllowGain ) - Skill_Experience( skill, (int)(-difficulty) ); + Skill_Experience( skill, (int)(-iDifficulty) ); return false; } } @@ -811,10 +815,13 @@ bool CChar::Skill_MakeItem_Success() bool notify = true; if ( IsTrigUsed(TRIGGER_SKILLMAKEITEM) ) { - CScriptTriggerArgs Args(iSkillLevel, quality, uidOldAct.ObjFind()); - Args.m_VarsLocal.SetNum("Notify", 1); - iRet = OnTrigger(CTRIG_SkillMakeItem, this, &Args); - notify = Args.m_VarsLocal.GetKeyNum("Notify") ? true : false; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iSkillLevel, quality, 0, uidOldAct.ObjFind()); + pScriptArgs->m_VarsLocal.SetNum("Notify", 1); + + iRet = OnTrigger(CTRIG_SkillMakeItem, pScriptArgs, this); + + notify = pScriptArgs->m_VarsLocal.GetKeyNum("Notify") ? true : false; } m_Act_UID = uidOldAct; // restore @@ -838,7 +845,7 @@ bool CChar::Skill_MakeItem_Success() SysMessage(pszMsg); } - // Experience gain on craftings + // Experience gains on crafting. if ( g_Cfg.m_fExperienceSystem && (g_Cfg.m_iExperienceMode & EXP_MODE_RAISE_CRAFT) ) { int exp = 0; @@ -879,7 +886,7 @@ bool CChar::Skill_MakeItem( ITEMID_TYPE id, CUID uidTarg, SKTRIG_TYPE stage, boo // Fail = do a partial consume of the resources. // // ARGS: - // uidTarg = item targetted to try to make this . (this item should be used to make somehow) + // uidTarg = item targeted to try to make this (this item should be used to make somehow). // Skill_GetActive() // // RETURN: @@ -950,7 +957,7 @@ bool CChar::Skill_MakeItem( ITEMID_TYPE id, CUID uidTarg, SKTRIG_TYPE stage, boo return false; } - m_Act_UID = uidTarg; // targetted item to start the make process + m_Act_UID = uidTarg; // Targeted item to start the make process. m_atCreate.m_iItemID = id; m_atCreate.m_dwAmount = (word)(iReplicationQty); @@ -960,7 +967,7 @@ bool CChar::Skill_MakeItem( ITEMID_TYPE id, CUID uidTarg, SKTRIG_TYPE stage, boo if ( stage == SKTRIG_SUCCESS ) { - m_atCreate.m_dwAmount = (word)(iReplicationQty); // how much resources we really consumed + m_atCreate.m_dwAmount = (word)(iReplicationQty); // how many resources we really consumed return Skill_MakeItem_Success(); } @@ -1001,7 +1008,7 @@ CItem * CChar::Skill_NaturalResource_Create( CItem * pResBit, SKILL_TYPE skill ) if ( !pOreDef ) return nullptr; - // Skill effects how much of the ore i can get all at once. + // Skill effects how much of the ore I can get all at once. if ( pOreDef->m_ReapItem == ITEMID_NOTHING ) return nullptr; // I intended for there to be nothing here @@ -1020,23 +1027,23 @@ CItem * CChar::Skill_NaturalResource_Create( CItem * pResBit, SKILL_TYPE skill ) wAmount = pResBit->GetAmount(); //(Region)ResourceGather behaviour - CScriptTriggerArgs Args(0, 0, pResBit); - Args.m_VarsLocal.SetNum("ResourceID",pOreDef->m_ReapItem); - Args.m_pO1 = pResBit; - Args.m_iN1 = wAmount; - TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(wAmount, 0, 0, pResBit); + pScriptArgs->m_VarsLocal.SetNum("ResourceID",pOreDef->m_ReapItem); + + TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; if ( IsTrigUsed(TRIGGER_REGIONRESOURCEGATHER) ) - tRet = this->OnTrigger(CTRIG_RegionResourceGather, this, &Args); + tRet = this->OnTrigger(CTRIG_RegionResourceGather, pScriptArgs, this); if ( IsTrigUsed(TRIGGER_RESOURCEGATHER) ) - tRet = pOreDef->OnTrigger("@ResourceGather", this, &Args); + tRet = pOreDef->OnTrigger("@ResourceGather", pScriptArgs, this); if ( tRet == TRIGRET_RET_TRUE ) return nullptr; //Creating the 'id' variable with the local given through->by the trigger(s) instead on top of method - ITEMID_TYPE id = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("ResourceID"))); + ITEMID_TYPE id = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("ResourceID"))); - wAmount = pResBit->ConsumeAmount( (word)(Args.m_iN1) ); // amount i used up. + wAmount = pResBit->ConsumeAmount( (word)(pScriptArgs->m_iN1) ); // amount I used up. if ( wAmount <= 0 ) return nullptr; @@ -1134,22 +1141,23 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) word iResourceQty = 0; size_t iResourceTotalQty = pOreDef->m_BaseResources.size(); //This is the total amount of different resources obtained from smelting. - CScriptTriggerArgs Args(iMiningSkill, iResourceTotalQty); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iMiningSkill, iResourceTotalQty, 0, nullptr); if ( pOreDef->IsType( IT_ORE )) { ITEMID_TYPE idIngot = (ITEMID_TYPE)(ResGetIndex( pOreDef->m_ttOre.m_idIngot)); - const CItemBase* pBaseDef = CItemBase::FindItemBase(idIngot); //Usually a lingot, but could be a a gem also. + const CItemBase* pBaseDef = CItemBase::FindItemBase(idIngot); //Usually a lingot, but could be a gem also. if (!pBaseDef) { SysMessageDefault(DEFMSG_MINING_NOTHING); return false; } iResourceQty = 1; // ingots per ore. - iResourceTotalQty = 1; //Ores only gives one type of resouce. - Args.m_iN2 = iResourceTotalQty; - Args.m_VarsLocal.SetNum("resource.0.ID", pBaseDef->GetID()); - Args.m_VarsLocal.SetNum("resource.0.amount", iResourceQty); + iResourceTotalQty = 1; // Ores only gives one type of resource. + pScriptArgs->m_iN2 = iResourceTotalQty; + pScriptArgs->m_VarsLocal.SetNum("resource.0.ID", pBaseDef->GetID()); + pScriptArgs->m_VarsLocal.SetNum("resource.0.amount", iResourceQty); } else { @@ -1167,32 +1175,32 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) tchar* pszTmp = Str_GetTemp(); snprintf(pszTmp, Str_TempLength(), "resource.%u.ID", (int)i); - Args.m_VarsLocal.SetNum(pszTmp,(int64)id); + pScriptArgs->m_VarsLocal.SetNum(pszTmp,(int64)id); iResourceQty = (word)(pOreDef->m_BaseResources[i].GetResQty()); snprintf(pszTmp, Str_TempLength(), "resource.%u.amount", (int)i); - Args.m_VarsLocal.SetNum(pszTmp, iResourceQty); + pScriptArgs->m_VarsLocal.SetNum(pszTmp, iResourceQty); } } if (IsTrigUsed(TRIGGER_SMELT) || IsTrigUsed(TRIGGER_ITEMSMELT)) { - switch (pItemOre->OnTrigger(ITRIG_Smelt, this, &Args)) + switch (pItemOre->OnTrigger(ITRIG_Smelt, pScriptArgs, this)) { case TRIGRET_RET_TRUE: return false; default: break; } } - iMiningSkill = (ushort)Args.m_iN1; - fSkipMiningSmeltReq = (bool)Args.m_iN3; + iMiningSkill = (ushort)pScriptArgs->m_iN1; + fSkipMiningSmeltReq = (bool)pScriptArgs->m_iN3; std::vector ingots; for (size_t i = 0; i < iResourceTotalQty; ++i) { tchar* pszTmp = Str_GetTemp(); snprintf(pszTmp, Str_TempLength(), "resource.%u.ID", (int)i); - const CItemBase* pBaseDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum(pszTmp)))); + const CItemBase* pBaseDef = CItemBase::FindItemBase((ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum(pszTmp)))); //We have finished the ore or the item being smelted. if (iOreQty <= 0) @@ -1208,7 +1216,7 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) } snprintf(pszTmp, Str_TempLength(), "resource.%u.amount", (int)i); - iResourceQty =(word)Args.m_VarsLocal.GetKeyNum(pszTmp); + iResourceQty =(word)pScriptArgs->m_VarsLocal.GetKeyNum(pszTmp); iResourceQty *= iOreQty; // max amount if (pBaseDef->IsType(IT_GEM)) @@ -1227,7 +1235,7 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) if (iMiningSkill < pBaseDef->m_ttIngot.m_iSkillMin && !fSkipMiningSmeltReq) { SysMessagef(g_Cfg.GetDefaultMsg(DEFMSG_MINING_SKILL), pBaseDef->GetName()); - if (iResourceTotalQty > 1) // This is a niche scenario where an item can provide more than one type ingots, so we continue to loop for because we can successfull get the other type of lingots. + if (iResourceTotalQty > 1) // This is a niche scenario where an item can provide more than one type ingots, so we continue to loop for because we can successfully get the other type of lingots. continue; return false; } @@ -1242,11 +1250,11 @@ bool CChar::Skill_Mining_Smelt( CItem * pItemOre, CItem * pItemTarg ) word iAmountLost = (word)(g_Rand.GetVal(pItemOre->GetAmount() / 2) + 1); pItemOre->ConsumeAmount(iAmountLost); // lose up to half the resources. iOreQty -= iAmountLost; - if ( iResourceTotalQty > 1 ) // This is a niche scenario where an item can provide more than one type ingots, so we continue to loop for because we can successfull get the other type of lingots. + if ( iResourceTotalQty > 1 ) // This is a niche scenario where an item can provide more than one type ingots, so we continue to loop for because we can successfully get the other type of lingots. continue; return false; } - // Payoff - Amount of ingots i get. + // Payoff - Amount of ingots I get. ingots.emplace_back(CItem::CreateScript(pBaseDef->GetID(), this)); if (ingots.at(i) == nullptr) { @@ -1341,7 +1349,7 @@ int CChar::Skill_Tracking( SKTRIG_TYPE stage ) { ADDTOCALLSTACK("CChar::Skill_Tracking"); // SKILL_TRACKING - // m_Act_UID = what am i tracking ? + // m_Act_UID = what am I tracking ? // m_atTracking.m_iPrvDir = the previous dir it was in. // m_atTracking.m_dwDistMax = the maximum tracking distance. @@ -1478,7 +1486,7 @@ int CChar::Skill_Fishing( SKTRIG_TYPE stage ) // SKILL_FISHING // m_Act_p = where to fish. // NOTE: don't check LOS else you can't fish off boats. - // Check that we dont stand too far away + // Check that we are not standing too far away // Make sure we aren't in a house // // RETURN: @@ -1764,7 +1772,7 @@ int CChar::Skill_Cartography( SKTRIG_TYPE stage ) int CChar::Skill_Musicianship( SKTRIG_TYPE stage ) { ADDTOCALLSTACK("CChar::Skill_Musicianship"); - // m_Act_UID = the intrument i targetted to play. + // m_Act_UID = the instrument i targeted to play. if ( stage == SKTRIG_ABORT ) return -SKTRIG_ABORT; @@ -1798,7 +1806,7 @@ int CChar::Skill_Peacemaking( SKTRIG_TYPE stage ) { case SKTRIG_START: { - // ACTARG1: UID of the instrument i want to play, instead of picking a random one + // ACTARG1: UID of the instrument I want to play, instead of picking a random one CItem * pInstrument = nullptr; if (m_atBard.m_dwInstrumentUID != 0) { @@ -1911,7 +1919,7 @@ int CChar::Skill_Enticement( SKTRIG_TYPE stage ) { case SKTRIG_START: { - // ACTARG1: UID of the instrument i want to play, instead of picking a random one + // ACTARG1: UID of the instrument I want to play, instead of picking a random one CItem * pInstrument = nullptr; if (m_atBard.m_dwInstrumentUID != 0) { @@ -2027,7 +2035,7 @@ int CChar::Skill_Provocation(SKTRIG_TYPE stage) { case SKTRIG_START: { - // ACTARG1: UID of the instrument i want to play, instead of picking a random one + // ACTARG1: UID of the instrument I want to play, instead of picking a random one CItem * pInstrument = nullptr; if (m_atBard.m_dwInstrumentUID != 0) { @@ -2104,7 +2112,7 @@ int CChar::Skill_Provocation(SKTRIG_TYPE stage) iMaxRange = UO_MAP_VIEW_SIGHT; } - // If out of range we might get attacked ourself. + // If out of range we might get attacked ourselves. if ((pCharProv->GetTopDist3D(pCharTarg) > iMaxRange) || (pCharProv->GetTopDist3D(this) > iMaxRange)) { // Check that only "evil" monsters attack provoker back @@ -2114,7 +2122,7 @@ int CChar::Skill_Provocation(SKTRIG_TYPE stage) return -SKTRIG_ABORT; } - // If the npcs are in the same ally groups, both can attack you + // If the npcs are in the same groups, both can attack you. if ( m_atProvocation.m_dwIsAlly != 0 ) { if ( pCharProv->Noto_IsEvil() ) @@ -2301,7 +2309,7 @@ int CChar::Skill_Taming( SKTRIG_TYPE stage ) ASSERT( pChar->m_pNPC ); int iTameBase = pChar->Skill_GetBase(SKILL_TAMING); - if ( !IsPriv( PRIV_GM )) // if its a gm doing it, just check that its not + if ( !IsPriv( PRIV_GM )) // if it's a gm doing it, just check that it's not { if ( pChar->IsStatFlag( STATF_PET )) // is it tamable ? { @@ -2467,7 +2475,7 @@ int CChar::Skill_Hiding( SKTRIG_TYPE stage ) if ( stage == SKTRIG_ABORT ) return -SKTRIG_ABORT; - if ( stage == SKTRIG_STROKE ) // we shoud just stay in HIDING skill ? + if ( stage == SKTRIG_STROKE ) // We should just stay in HIDING skill? return 0; if ( stage == SKTRIG_FAIL ) @@ -2921,7 +2929,7 @@ int CChar::Skill_RemoveTrap( SKTRIG_TYPE stage ) int CChar::Skill_Begging( SKTRIG_TYPE stage ) { ADDTOCALLSTACK("CChar::Skill_Begging"); - // m_Act_UID = Our begging target.. + // m_Act_UID = Our begging target. CChar * pChar = m_Act_UID.CharFind(); if ( pChar == nullptr || pChar == this ) @@ -3016,7 +3024,7 @@ int CChar::Skill_Fighting( SKTRIG_TYPE stage ) if ( stage == SKTRIG_START ) { /* - The two lines below need to be moved after the @SkillStart/@Start triggers, otherwise if we are using a custom combat system + The two lines below need to be moved after the @SkillStart/@Start triggers, otherwise if we are using a custom combat system, and we are forcing a miss (actdiff < 0) combat will be blocked because it will not pass the @SkillStart/@Start triggers check. */ //m_atFight.m_iWarSwingState = WAR_SWING_EQUIPPING; @@ -3049,7 +3057,7 @@ int CChar::Skill_Fighting( SKTRIG_TYPE stage ) { /*Super cheap fix : When we are casting the SUMMON CREATURE spell while we are in an active combat (we have an active fighting skill going on) - resetting both the RecoilDelay and the SwingAnimationDelay will also cause the ID of the summoned creatured to be resetted. + resetting both the RecoilDelay and the SwingAnimationDelay will also cause the ID of the summoned created to reset. This only happens when the creature to be summoned is chosen on the default "summon menu". */ if ( !m_atMagery.m_uiSummonID ) @@ -3301,8 +3309,8 @@ int CChar::Skill_Act_Breath( SKTRIG_TYPE stage ) iDamage = (Stat_GetVal(STAT_STR) * 5) / 100; if ( iDamage < 1 ) iDamage = 1; - else if ( iDamage > 200 ) - iDamage = 200; + else if ( iDamage > UINT16_MAX ) + iDamage = UINT16_MAX; } HUE_TYPE hue = (HUE_TYPE)(GetDefNum("BREATH.HUE", true)); @@ -3474,7 +3482,7 @@ int CChar::Skill_Act_Training( SKTRIG_TYPE stage ) { ADDTOCALLSTACK("CChar::Skill_Act_Training"); // NPCACT_TRAINING - // finished some traing maneuver. + // Finished some training maneuver. if ( stage == SKTRIG_ABORT ) return -SKTRIG_ABORT; @@ -3579,29 +3587,29 @@ int CChar::Skill_Stroke() if ( IsTrigUsed(TRIGGER_SKILLSTROKE) || (IsTrigUsed(TRIGGER_STROKE)) ) { - CScriptTriggerArgs args; - args.m_VarsLocal.SetNum("Skill", skill); - args.m_VarsLocal.SetNum("Sound", sound); - args.m_VarsLocal.SetNum("Delay", delay); - args.m_VarsLocal.SetNum("Anim", anim); - args.m_iN1 = 1; //UpdateDir() ? - args.m_VarsLocal.SetNum("Strokes", uiStroke); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetNum("Skill", skill); + pScriptArgs->m_VarsLocal.SetNum("Sound", sound); + pScriptArgs->m_VarsLocal.SetNum("Delay", delay); + pScriptArgs->m_VarsLocal.SetNum("Anim", anim); + pScriptArgs->m_iN1 = 1; //UpdateDir() ? + pScriptArgs->m_VarsLocal.SetNum("Strokes", uiStroke); - if ( OnTrigger(CTRIG_SkillStroke, this, &args) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_SkillStroke, pScriptArgs, this) == TRIGRET_RET_TRUE ) return -SKTRIG_ABORT; - if ( Skill_OnTrigger(skill, SKTRIG_STROKE, &args) == TRIGRET_RET_TRUE ) + if ( Skill_OnTrigger(skill, SKTRIG_STROKE, pScriptArgs) == TRIGRET_RET_TRUE ) return -SKTRIG_ABORT; - sound = (SOUND_TYPE)(args.m_VarsLocal.GetKeyNum("Sound")); - delay = args.m_VarsLocal.GetKeyNum("Delay"); - anim = static_cast(args.m_VarsLocal.GetKeyNum("Anim")); + sound = (SOUND_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("Sound")); + delay = pScriptArgs->m_VarsLocal.GetKeyNum("Delay"); + anim = static_cast(pScriptArgs->m_VarsLocal.GetKeyNum("Anim")); - if ( args.m_iN1 == 1 ) + if ( pScriptArgs->m_iN1 == 1 ) UpdateDir(m_Act_p); if ( fResource ) - m_atResource.m_dwStrokeCount = (word)(args.m_VarsLocal.GetKeyNum("Strokes")); + m_atResource.m_dwStrokeCount = (word)(pScriptArgs->m_VarsLocal.GetKeyNum("Strokes")); else - m_atCreate.m_dwStrokeCount = (word)(args.m_VarsLocal.GetKeyNum("Strokes")); + m_atCreate.m_dwStrokeCount = (word)(pScriptArgs->m_VarsLocal.GetKeyNum("Strokes")); } if ( sound ) @@ -3797,15 +3805,15 @@ void CChar::Skill_Fail( bool fCancel ) m_Act_Difficulty = -m_Act_Difficulty; if ( !fCancel ) - { + { if ( IsTrigUsed(TRIGGER_SKILLFAIL) ) { - if ( Skill_OnCharTrigger(skill, CTRIG_SkillFail) == TRIGRET_RET_TRUE ) + if ( Skill_OnCharTrigger(skill, CTRIG_SkillFail, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) fCancel = true; } if ( IsTrigUsed(TRIGGER_FAIL) && !fCancel ) { - if ( Skill_OnTrigger(skill, SKTRIG_FAIL) == TRIGRET_RET_TRUE ) + if ( Skill_OnTrigger(skill, SKTRIG_FAIL, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) fCancel = true; } } @@ -3813,7 +3821,7 @@ void CChar::Skill_Fail( bool fCancel ) { if ( IsTrigUsed(TRIGGER_SKILLABORT) ) { - if ( Skill_OnCharTrigger(skill, CTRIG_SkillAbort) == TRIGRET_RET_TRUE ) + if ( Skill_OnCharTrigger(skill, CTRIG_SkillAbort, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { Skill_Cleanup(); return; @@ -3821,7 +3829,7 @@ void CChar::Skill_Fail( bool fCancel ) } if ( IsTrigUsed(TRIGGER_ABORT) ) { - if ( Skill_OnTrigger(skill, SKTRIG_ABORT) == TRIGRET_RET_TRUE ) + if ( Skill_OnTrigger(skill, SKTRIG_ABORT, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { Skill_Cleanup(); return; @@ -3829,7 +3837,7 @@ void CChar::Skill_Fail( bool fCancel ) } } - if ( Skill_Stage((fCancel)? SKTRIG_ABORT:SKTRIG_FAIL) >= 0 ) + if ( Skill_Stage(fCancel ? SKTRIG_ABORT : SKTRIG_FAIL) >= 0 ) { // Get some experience for failure ? if ( !fCancel ) @@ -3839,31 +3847,21 @@ void CChar::Skill_Fail( bool fCancel ) Skill_Cleanup(); } -TRIGRET_TYPE CChar::Skill_OnTrigger( SKILL_TYPE skill, SKTRIG_TYPE stage ) -{ - CScriptTriggerArgs pArgs; - return Skill_OnTrigger(skill, stage, &pArgs); -} - -TRIGRET_TYPE CChar::Skill_OnCharTrigger( SKILL_TYPE skill, CTRIG_TYPE stage ) -{ - CScriptTriggerArgs pArgs; - return Skill_OnCharTrigger(skill, stage, &pArgs); -} - -TRIGRET_TYPE CChar::Skill_OnTrigger( SKILL_TYPE skill, SKTRIG_TYPE stage, CScriptTriggerArgs *pArgs ) +TRIGRET_TYPE CChar::Skill_OnTrigger( SKILL_TYPE skill, SKTRIG_TYPE stage, CScriptTriggerArgsPtr const& pScriptArgs ) { ADDTOCALLSTACK("CChar::Skill_OnTrigger"); if ( !IsSkillBase(skill) ) return TRIGRET_RET_DEFAULT; + ASSERT(pScriptArgs); + if ( !(stage == SKTRIG_SELECT || stage == SKTRIG_GAIN || stage == SKTRIG_USEQUICK || stage == SKTRIG_WAIT || stage == SKTRIG_TARGETCANCEL) ) m_Act_SkillCurrent = skill; - pArgs->m_iN1 = skill; + pScriptArgs->m_iN1 = skill; if ( g_Cfg.IsSkillFlag(skill, SKF_MAGIC) ) - pArgs->m_VarsLocal.SetNum("spell", m_atMagery.m_iSpell, true, false); + pScriptArgs->m_VarsLocal.SetNum("spell", m_atMagery.m_iSpell, true, false); TRIGRET_TYPE iRet = TRIGRET_RET_DEFAULT; @@ -3873,26 +3871,28 @@ TRIGRET_TYPE CChar::Skill_OnTrigger( SKILL_TYPE skill, SKTRIG_TYPE stage, CScri // RES_SKILL CResourceLock s; if ( pSkillDef->ResourceLock(s) ) - iRet = CScriptObj::OnTriggerScript(s, CSkillDef::sm_szTrigName[stage], this, pArgs); + iRet = CScriptObj::OnTriggerScript(s, CSkillDef::sm_szTrigName[stage], pScriptArgs, this); } return iRet; } -TRIGRET_TYPE CChar::Skill_OnCharTrigger( SKILL_TYPE skill, CTRIG_TYPE ctrig, CScriptTriggerArgs *pArgs ) +TRIGRET_TYPE CChar::Skill_OnCharTrigger(SKILL_TYPE skill, CTRIG_TYPE ctrig, CScriptTriggerArgsPtr const& pScriptArgs ) { ADDTOCALLSTACK("CChar::Skill_OnCharTrigger"); if ( !IsSkillBase(skill) ) return TRIGRET_RET_DEFAULT; + ASSERT(pScriptArgs); + if ( !(ctrig == CTRIG_SkillSelect || ctrig == CTRIG_SkillGain || ctrig == CTRIG_SkillUseQuick || ctrig == CTRIG_SkillWait || ctrig == CTRIG_SkillTargetCancel) ) m_Act_SkillCurrent = skill; - pArgs->m_iN1 = skill; + pScriptArgs->m_iN1 = skill; if ( g_Cfg.IsSkillFlag(skill, SKF_MAGIC) ) - pArgs->m_VarsLocal.SetNum("spell", m_atMagery.m_iSpell, true, false); + pScriptArgs->m_VarsLocal.SetNum("spell", m_atMagery.m_iSpell, true, false); - return OnTrigger(ctrig, this, pArgs); + return OnTrigger(ctrig, pScriptArgs, this); } @@ -3928,22 +3928,25 @@ int CChar::Skill_Done() if ( m_Act_Difficulty < 0 ) // was Bound to fail, but we had to wait for the timer anyhow. return -SKTRIG_FAIL; - CScriptTriggerArgs args; - args.m_VarsLocal.SetNumNew("ITEMDAMAGECHANCE", 25); - args.m_VarsLocal.SetNumNew("ITEMDAMAGEAMOUNT", 1); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetNumNew("ITEMDAMAGECHANCE", 25); + pScriptArgs->m_VarsLocal.SetNumNew("ITEMDAMAGEAMOUNT", 1); + if ( IsTrigUsed(TRIGGER_SKILLSUCCESS) ) { - if (Skill_OnCharTrigger(skill, CTRIG_SkillSuccess, &args) == TRIGRET_RET_TRUE) + if (Skill_OnCharTrigger(skill, CTRIG_SkillSuccess, pScriptArgs) == TRIGRET_RET_TRUE) return -SKTRIG_ABORT; } if ( IsTrigUsed(TRIGGER_SUCCESS) ) { - if (Skill_OnTrigger(skill, SKTRIG_SUCCESS, &args) == TRIGRET_RET_TRUE) + if (Skill_OnTrigger(skill, SKTRIG_SUCCESS, pScriptArgs) == TRIGRET_RET_TRUE) return -SKTRIG_ABORT; } - int chance = std::min(std::max((int)args.m_VarsLocal.GetKeyNum("ITEMDAMAGECHANCE"), (int)0), (int)100); - if (IsSetEF(EF_DamageTools) && g_Cfg.IsSkillFlag(skill, SKF_GATHER) && chance > 0) + const int iChance = (int)std::clamp( + pScriptArgs->m_VarsLocal.GetKeyNum("ITEMDAMAGECHANCE"), + (int64)0, (int64)100); + if (IsSetEF(EF_DamageTools) && g_Cfg.IsSkillFlag(skill, SKF_GATHER) && iChance > 0) { CItem* pTool = LayerFind(LAYER_HAND1); if (!pTool || !pTool->IsTypeWeapon()) @@ -3951,10 +3954,10 @@ int CChar::Skill_Done() if (pTool && pTool->IsTypeWeapon()) { - if (g_Rand.GetVal(100) < chance) + if (g_Rand.GetVal(100) < iChance) { - int amount = std::max(std::min((int)args.m_VarsLocal.GetKeyNum("ITEMDAMAGEAMOUNT"), (int)pTool->m_itWeapon.m_wHitsCur), 0); - pTool->OnTakeDamage(amount, nullptr, DAMAGE_GOD); + const int iAmount = std::max(std::min((int)pScriptArgs->m_VarsLocal.GetKeyNum("ITEMDAMAGEAMOUNT"), (int)pTool->m_itWeapon.m_wHitsCur), 0); + pTool->OnTakeDamage(iAmount, nullptr, DAMAGE_GOD); } } } @@ -3979,11 +3982,12 @@ bool CChar::Skill_Wait( SKILL_TYPE skilltry ) // If this is the same skill then tell them to wait. SKILL_TYPE skill = Skill_GetActive(); - CScriptTriggerArgs pArgs(skilltry, skill); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(skilltry, skill, 0, nullptr); if ( IsTrigUsed(TRIGGER_SKILLWAIT) ) { - switch ( Skill_OnCharTrigger(skilltry, CTRIG_SkillWait, &pArgs) ) + switch ( Skill_OnCharTrigger(skilltry, CTRIG_SkillWait, pScriptArgs) ) { case TRIGRET_RET_TRUE: return true; @@ -3996,7 +4000,7 @@ bool CChar::Skill_Wait( SKILL_TYPE skilltry ) } if ( IsTrigUsed(TRIGGER_WAIT) ) { - switch ( Skill_OnTrigger(skilltry, SKTRIG_WAIT, &pArgs) ) + switch ( Skill_OnTrigger(skilltry, SKTRIG_WAIT, pScriptArgs) ) { case TRIGRET_RET_TRUE: return true; @@ -4016,7 +4020,9 @@ bool CChar::Skill_Wait( SKILL_TYPE skilltry ) if ( skill == SKILL_NONE ) // not currently doing anything. { - if ((skilltry == SKILL_STEALTH) || ((skilltry == SKILL_SNOOPING) && (g_Cfg.m_iRevealFlags & REVEALF_SNOOPING)) || ((skilltry == SKILL_STEALING) && (g_Cfg.m_iRevealFlags & REVEALF_STEALING))) + if ((skilltry == SKILL_STEALTH) + || ((skilltry == SKILL_SNOOPING) && (g_Cfg.m_iRevealFlags & REVEALF_SNOOPING)) + || ((skilltry == SKILL_STEALING) && (g_Cfg.m_iRevealFlags & REVEALF_STEALING))) return false; else Reveal(); @@ -4319,7 +4325,7 @@ int CChar::Skill_Stealing(SKTRIG_TYPE stage) if (pItem->GetParent() != pPack && pPack) { pItem->RemoveFromView(); - // Put in my invent. + // Put in my invention. pPack->ContentAdd(pItem); } } @@ -4340,7 +4346,7 @@ int CChar::Skill_Stealing(SKTRIG_TYPE stage) } /* -The Focus skill is used passively and it works automatically only if FEATURES_AOS_UPDATE_B is enabled. +The Focus skill is used passively, and it works automatically only if FEATURES_AOS_UPDATE_B is enabled. The skill increase the amount of stamina gained by 1 for each 10% points of Focus and increase the amount of mana by 1 for each 20% points of Focus. The Skill_Focus method is called from Stats_Regen64 method found in CCharStat.cpp @@ -4377,14 +4383,14 @@ int CChar::Skill_Focus(STAT_TYPE stat) bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) { ADDTOCALLSTACK("CChar::Skill_Start"); - // We have all the info we need to do the skill. (targeting etc) + // We have all the info we need to do the skill. (targeting etc.) // Set up how long we have to wait before we get the desired results from this skill. - // Set up any animations/sounds in the mean time. + // Set up any animations/sounds in the meantime. // Calc if we will succeed or fail. // RETURN: // false = failed outright with no wait. "You have no chance of taming this" - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) { if ( skill != SKILL_NONE && !IsSkillBase(skill) && !IsSkillNPC(skill) ) { @@ -4419,16 +4425,11 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) anim = Skill_GetAnim(skill); } - CScriptTriggerArgs pArgs; - pArgs.m_iN1 = skill; - pArgs.m_VarsLocal.SetNumNew("Sound", sound); - pArgs.m_VarsLocal.SetNumNew("Anim", anim); - - // Some skill can start right away. Need no targetting. + // Some skill can start right away. Need no targeting. // 0-100 scale of Difficulty if ( IsTrigUsed(TRIGGER_SKILLPRESTART) ) { - if ( Skill_OnCharTrigger(skill, CTRIG_SkillPreStart) == TRIGRET_RET_TRUE ) + if ( Skill_OnCharTrigger(skill, CTRIG_SkillPreStart, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { Skill_Cleanup(); return false; @@ -4436,7 +4437,7 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) } if ( IsTrigUsed(TRIGGER_PRESTART) ) { - if ( Skill_OnTrigger(skill, SKTRIG_PRESTART) == TRIGRET_RET_TRUE ) + if ( Skill_OnTrigger(skill, SKTRIG_PRESTART, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { Skill_Cleanup(); return false; @@ -4469,18 +4470,23 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) m_Act_Effect = pSkillDef->m_vcEffect.GetRandom(); } } - pArgs.m_iN2 = iWaitTime; - // Execute the @START trigger and pass various craft parameters there - CResourceID pResBase(RES_ITEMDEF, (fCraftSkill ? m_atCreate.m_iItemID : ITEMID_NOTHING), 0); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = skill; + pScriptArgs->m_iN2 = iWaitTime; + pScriptArgs->m_VarsLocal.SetNumNew("Sound", sound); + pScriptArgs->m_VarsLocal.SetNumNew("Anim", anim); + // Execute the @START trigger and pass various craft parameters there if ( fCraftSkill ) { - m_atCreate.m_dwStrokeCount = 1; // This matches the new strokes amount used on OSI. - pArgs.m_VarsLocal.SetNum("CraftItemdef", pResBase.GetPrivateUID()); - // pArgs.m_VarsLocal.SetStr("CraftItemdef", g_Cfg.ResourceGetName(pResBase), false); - pArgs.m_VarsLocal.SetNum("CraftStrokeCnt", m_atCreate.m_dwStrokeCount); - pArgs.m_VarsLocal.SetNum("CraftAmount", m_atCreate.m_dwAmount); + m_atCreate.m_dwStrokeCount = 1; // This matches the new strokes amount used on OSI. + + const CResourceID ridResBase(RES_ITEMDEF, (fCraftSkill ? m_atCreate.m_iItemID : ITEMID_NOTHING), 0); + pScriptArgs->m_VarsLocal.SetNum("CraftItemdef", ridResBase.GetPrivateUID()); + // ppScriptArgs->m_VarsLocal.SetStr("CraftItemdef", g_Cfg.ResourceGetName(pResBase), false); + pScriptArgs->m_VarsLocal.SetNum("CraftStrokeCnt", m_atCreate.m_dwStrokeCount); + pScriptArgs->m_VarsLocal.SetNum("CraftAmount", m_atCreate.m_dwAmount); } if ( fGatherSkill ) { @@ -4489,52 +4495,52 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) m_Act_UID = pResBit->GetUID(); m_atResource.m_dwBounceItem = 1; - pArgs.m_VarsLocal.SetNum("GatherStrokeCnt", m_atResource.m_dwStrokeCount); + pScriptArgs->m_VarsLocal.SetNum("GatherStrokeCnt", m_atResource.m_dwStrokeCount); } if ( IsTrigUsed(TRIGGER_SKILLSTART) ) { //If we are using a combat skill and m_Act_Difficult(actdiff) is < 0 combat will be blocked. - if ( (Skill_OnCharTrigger(skill, CTRIG_SkillStart, &pArgs) == TRIGRET_RET_TRUE) || (m_Act_Difficulty < 0) ) + if ( (Skill_OnCharTrigger(skill, CTRIG_SkillStart, pScriptArgs) == TRIGRET_RET_TRUE) || (m_Act_Difficulty < 0) ) { Skill_Cleanup(); return false; } - sound = (SOUND_TYPE)(pArgs.m_VarsLocal.GetKeyNum("Sound")); - anim = static_cast(pArgs.m_VarsLocal.GetKeyNum("Anim")); + sound = (SOUND_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("Sound")); + anim = (ANIM_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("Anim")); } if ( IsTrigUsed(TRIGGER_START) ) { //If we are using a combat skill and m_Act_Difficulty(actdiff) is < 0 combat will be blocked. - if ( (Skill_OnTrigger(skill, SKTRIG_START, &pArgs) == TRIGRET_RET_TRUE) || (m_Act_Difficulty < 0) ) + if ( (Skill_OnTrigger(skill, SKTRIG_START, pScriptArgs) == TRIGRET_RET_TRUE) || (m_Act_Difficulty < 0) ) { Skill_Cleanup(); return false; } - sound = (SOUND_TYPE)(pArgs.m_VarsLocal.GetKeyNum("Sound")); - anim = static_cast(pArgs.m_VarsLocal.GetKeyNum("Anim")); + sound = (SOUND_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("Sound")); + anim = (ANIM_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("Anim")); } - iWaitTime = (int)pArgs.m_iN2; + iWaitTime = (int)pScriptArgs->m_iN2; if (IsSkillBase(skill) && iWaitTime > 0) SetTimeoutD(iWaitTime); // How long before complete skill. if (_IsTimerExpired()) { - _SetTimeoutD(1); // the skill should have set it's own delay!? + _SetTimeoutD(1); // the skill should have set its own delay!? } if ( fCraftSkill ) { // read crafting parameters - pResBase = CResourceID( (dword)(pArgs.m_VarsLocal.GetKeyNum("CraftItemdef")), 0 ); - m_atCreate.m_dwStrokeCount = (word)pArgs.m_VarsLocal.GetKeyNum("CraftStrokeCnt"); + const CResourceID ridResBase( (dword)(pScriptArgs->m_VarsLocal.GetKeyNum("CraftItemdef")), 0 ); + m_atCreate.m_dwStrokeCount = (word)pScriptArgs->m_VarsLocal.GetKeyNum("CraftStrokeCnt"); m_atCreate.m_dwStrokeCount = maximum(1,m_atCreate.m_dwStrokeCount); - m_atCreate.m_iItemID = (ITEMID_TYPE)(pResBase.GetResIndex()); - m_atCreate.m_dwAmount = (word)(pArgs.m_VarsLocal.GetKeyNum("CraftAmount")); + m_atCreate.m_iItemID = (ITEMID_TYPE)(ridResBase.GetResIndex()); + m_atCreate.m_dwAmount = (word)(pScriptArgs->m_VarsLocal.GetKeyNum("CraftAmount")); } if ( fGatherSkill ) - m_atResource.m_dwStrokeCount = (word)(pArgs.m_VarsLocal.GetKeyNum("GatherStrokeCnt")); + m_atResource.m_dwStrokeCount = (word)(pScriptArgs->m_VarsLocal.GetKeyNum("GatherStrokeCnt")); // Casting sound & animation when starting, Skill_Stroke() will do it the next times. if ( fCraftSkill || fGatherSkill ) @@ -4564,7 +4570,7 @@ bool CChar::Skill_Start( SKILL_TYPE skill, int iDifficultyIncrease ) } } - // emote the action i am taking. + // emote the action I am taking. if ( (g_Cfg.m_iDebugFlags & DEBUGF_NPC_EMOTE) || IsStatFlag(STATF_EMOTEACTION) ) Emote(Skill_GetName(true)); diff --git a/src/game/chars/CCharSpell.cpp b/src/game/chars/CCharSpell.cpp index 62910f1f3..7c063ea16 100644 --- a/src/game/chars/CCharSpell.cpp +++ b/src/game/chars/CCharSpell.cpp @@ -1,5 +1,6 @@ #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/CClientIterator.h" #include "../../network/send.h" #include "../components/CCPropsChar.h" @@ -110,7 +111,7 @@ bool CChar::Spell_Teleport( CPointMap ptNew, bool fTakePets, bool fCheckAntiMagi // ex. ships plank. // RETURN: true = it worked. - if ( !ptNew.IsCharValid() ) + if ( !ptNew.IsCharValid() ) return false; ptNew.m_z = GetFixZ(ptNew); @@ -350,7 +351,14 @@ CChar * CChar::Spell_Summon_Place( CChar * pChar, CPointMap ptTarg, int64 iDurat } pChar->StatFlag_Set(STATF_CONJURED); // conjured creates have no loot pChar->NPC_LoadScript(false); - ASSERT(FollowersUpdate(pChar, pChar->GetFollowerSlots(), true)); + + if (IsSetOF(OF_PetSlots)) + { + const bool followers = FollowersUpdate(pChar, pChar->GetFollowerSlots(), true); + ASSERT(followers); + UnreferencedParameter(followers); + } + pChar->NPC_PetSetOwner(this); pChar->MoveToChar(ptTarg); pChar->m_ptHome = ptTarg; @@ -371,10 +379,10 @@ bool CChar::Spell_Recall(CItem * pRune, bool fGate) ADDTOCALLSTACK("CChar::Spell_Recall"); if (pRune && (IsTrigUsed(TRIGGER_SPELLEFFECT) || IsTrigUsed(TRIGGER_ITEMSPELL))) { - CScriptTriggerArgs Args; - Args.m_iN1 = fGate ? SPELL_Gate_Travel : SPELL_Recall; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = fGate ? SPELL_Gate_Travel : SPELL_Recall; - if (pRune->OnTrigger(ITRIG_SPELLEFFECT, this, &Args) == TRIGRET_RET_FALSE) + if (pRune->OnTrigger(ITRIG_SPELLEFFECT, pScriptArgs, this) == TRIGRET_RET_FALSE) return true; } @@ -439,22 +447,25 @@ bool CChar::Spell_Resurrection(CItemCorpse * pCorpse, CChar * pCharSrc, bool fNo return false; } - ushort hits = (ushort)IMulDiv(Stat_GetMaxAdjusted(STAT_STR), g_Cfg.m_iHitpointPercentOnRez, 100); + ushort uiHits = (ushort)IMulDiv(Stat_GetMaxAdjusted(STAT_STR), g_Cfg.m_iHitpointPercentOnRez, 100); if (!pCorpse) pCorpse = FindMyCorpse(); if (IsTrigUsed(TRIGGER_RESURRECT)) { - CScriptTriggerArgs Args(hits, 0, pCorpse); - if (OnTrigger(CTRIG_Resurrect, pCharSrc, &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(uiHits, 0, 0, pCorpse); + + if (OnTrigger(CTRIG_Resurrect, pScriptArgs, pCharSrc) == TRIGRET_RET_TRUE) return false; - hits = (ushort)(Args.m_iN1); + + uiHits = (ushort)(pScriptArgs->m_iN1); } SetID(_iPrev_id); SetHue(_wPrev_Hue); StatFlag_Clear(STATF_DEAD|STATF_INSUBSTANTIAL); - Stat_SetVal(STAT_STR, maximum(hits, 1)); + Stat_SetVal(STAT_STR, maximum(uiHits, 1)); if (m_pNPC && m_pNPC->m_bonded) m_CanMask &= ~CAN_C_GHOST; @@ -546,19 +557,21 @@ void CChar::Spell_Effect_Remove(CItem * pSpell) if (IsTrigUsed(TRIGGER_SPELLEFFECTREMOVE)) { - CScriptTriggerArgs Args; - Args.m_pO1 = pSpell; - Args.m_iN1 = spell; - TRIGRET_TYPE iRet = OnTrigger(CTRIG_SpellEffectRemove, pCaster, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pSpell; + pScriptArgs->m_iN1 = spell; + + TRIGRET_TYPE iRet = OnTrigger(CTRIG_SpellEffectRemove, pScriptArgs, pCaster); if (iRet == TRIGRET_RET_FALSE) // Return 0: remove the spell memory item but don't execute the default spell behaviour. return; } if (IsTrigUsed(TRIGGER_EFFECTREMOVE)) { - CScriptTriggerArgs Args; - Args.m_pO1 = pSpell; - Args.m_iN1 = spell; - TRIGRET_TYPE iRet = Spell_OnTrigger(spell, SPTRIG_EFFECTREMOVE, pCaster, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pSpell; + pScriptArgs->m_iN1 = spell; + + TRIGRET_TYPE iRet = Spell_OnTrigger(spell, SPTRIG_EFFECTREMOVE, pScriptArgs, pCaster); if (iRet == TRIGRET_RET_FALSE) // Return 0: remove the spell memory item but don't execute the default spell behaviour. return; } @@ -577,7 +590,7 @@ void CChar::Spell_Effect_Remove(CItem * pSpell) { if (m_pPlayer) // summoned players ? thats odd. return; - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { Effect(EFFECT_XYZ, ITEMID_FX_TELE_VANISH, this, 8, 20); Sound(0x201); @@ -970,10 +983,11 @@ void CChar::Spell_Effect_Add( CItem * pSpell ) if (IsTrigUsed(TRIGGER_SPELLEFFECTADD)) { - CScriptTriggerArgs Args; - Args.m_pO1 = pSpell; - Args.m_iN1 = spell; - TRIGRET_TYPE iRet = OnTrigger(CTRIG_SpellEffectAdd, pCaster, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pSpell; + pScriptArgs->m_iN1 = spell; + + TRIGRET_TYPE iRet = OnTrigger(CTRIG_SpellEffectAdd, pScriptArgs, pCaster); if (iRet == TRIGRET_RET_TRUE) // Return 1: We don't want nothing to happen, removing memory also. { pSpell->Delete(true); @@ -985,10 +999,11 @@ void CChar::Spell_Effect_Add( CItem * pSpell ) if (IsTrigUsed(TRIGGER_EFFECTADD)) { - CScriptTriggerArgs Args; - Args.m_pO1 = pSpell; - Args.m_iN1 = spell; - TRIGRET_TYPE iRet = Spell_OnTrigger(spell,SPTRIG_EFFECTADD, pCaster, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pSpell; + pScriptArgs->m_iN1 = spell; + + TRIGRET_TYPE iRet = Spell_OnTrigger(spell,SPTRIG_EFFECTADD, pScriptArgs, pCaster); if (iRet == TRIGRET_RET_TRUE) // Return 1: We don't want nothing to happen, removing memory also. { pSpell->Delete(true); @@ -1955,15 +1970,17 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) } break; } - CScriptTriggerArgs Args((int)(spell), iLevel, pItem); - Args.m_VarsLocal.SetNum("Charges", iCharges); - Args.m_VarsLocal.SetNum("Delay", iSecondsDelay); - Args.m_VarsLocal.SetNum("DamageType", iDmgType); - Args.m_VarsLocal.SetNum("Effect", iEffect); + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init((int)spell, iLevel, 0, pItem); + pScriptArgs->m_VarsLocal.SetNum("Charges", iCharges); + pScriptArgs->m_VarsLocal.SetNum("Delay", iSecondsDelay); + pScriptArgs->m_VarsLocal.SetNum("DamageType", iDmgType); + pScriptArgs->m_VarsLocal.SetNum("Effect", iEffect); if (IsTrigUsed(TRIGGER_SPELLEFFECTTICK)) { - switch (OnTrigger(CTRIG_SpellEffectTick, this, &Args)) + switch (OnTrigger(CTRIG_SpellEffectTick, pScriptArgs, this)) { case TRIGRET_RET_TRUE: pItem->Delete(true); return false; case TRIGRET_RET_FALSE: if (pSpellDef->IsSpellType(SPELLFLAG_SCRIPTED)) return true; @@ -1973,21 +1990,21 @@ bool CChar::Spell_Equip_OnTick( CItem * pItem ) if (IsTrigUsed(TRIGGER_EFFECTTICK)) { - switch (Spell_OnTrigger(spell, SPTRIG_EFFECTTICK, this, &Args)) + switch (Spell_OnTrigger(spell, SPTRIG_EFFECTTICK, pScriptArgs, this)) { case TRIGRET_RET_TRUE: pItem->Delete(true); return false; case TRIGRET_RET_FALSE: if (pSpellDef->IsSpellType(SPELLFLAG_SCRIPTED)) return true; default: break; } } - iLevel = (int)(Args.m_iN2); //This is probably not necessary. - iSecondsDelay = (int64)(Args.m_VarsLocal.GetKeyNum("Delay")); - iEffect = (int)(Args.m_VarsLocal.GetKeyNum("Effect")); - iCharges = (int)(Args.m_VarsLocal.GetKeyNum("Charges")); + iLevel = (int)(pScriptArgs->m_iN2); //This is probably not necessary. + iSecondsDelay = (int64)(pScriptArgs->m_VarsLocal.GetKeyNum("Delay")); + iEffect = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("Effect")); + iCharges = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("Charges")); if (pSpellDef->IsSpellType(SPELLFLAG_HARM)) { - iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("DamageType"))); + iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("DamageType"))); if (iDmgType > 0 && iEffect > 0) // This is necessary if we have a spell that is harmful but does no damage periodically. { // @@ -2347,16 +2364,17 @@ bool CChar::Spell_CanCast( SPELL_TYPE &spellRef, bool fTest, CObjBase * pSrc, bo ushort uiManaUse = g_Cfg.Calc_SpellManaCost(this, pSpellDef, pSrc); ushort uiTithingUse = g_Cfg.Calc_SpellTithingCost(this, pSpellDef, pSrc); - CScriptTriggerArgs Args( spellRef, uiManaUse, pSrc ); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(spellRef, uiManaUse, 0, pSrc); if ( fTest ) - Args.m_iN3 |= 0x0001; + pScriptArgs->m_iN3 |= 0x0001; if ( fFailMsg ) - Args.m_iN3 |= 0x0002; - Args.m_VarsLocal.SetNum("TithingUse",uiTithingUse); + pScriptArgs->m_iN3 |= 0x0002; + pScriptArgs->m_VarsLocal.SetNum("TithingUse",uiTithingUse); if ( IsTrigUsed(TRIGGER_SELECT) ) { - TRIGRET_TYPE iRet = Spell_OnTrigger( spellRef, SPTRIG_SELECT, this, &Args ); + TRIGRET_TYPE iRet = Spell_OnTrigger( spellRef, SPTRIG_SELECT, pScriptArgs, this ); if ( iRet == TRIGRET_RET_TRUE ) return false; @@ -2369,7 +2387,7 @@ bool CChar::Spell_CanCast( SPELL_TYPE &spellRef, bool fTest, CObjBase * pSrc, bo if ( IsTrigUsed(TRIGGER_SPELLSELECT) ) { - TRIGRET_TYPE iRet = OnTrigger(CTRIG_SpellSelect, this, &Args ); + TRIGRET_TYPE iRet = OnTrigger(CTRIG_SpellSelect, pScriptArgs, this ); if ( iRet == TRIGRET_RET_TRUE ) return false; @@ -2377,15 +2395,15 @@ bool CChar::Spell_CanCast( SPELL_TYPE &spellRef, bool fTest, CObjBase * pSrc, bo return true; } - if ( spellRef != Args.m_iN1 ) + if ( spellRef != pScriptArgs->m_iN1 ) { pSpellDef = g_Cfg.GetSpellDef(spellRef); if ( pSpellDef == nullptr ) return false; - spellRef = (SPELL_TYPE)(Args.m_iN1); + spellRef = (SPELL_TYPE)(pScriptArgs->m_iN1); } - uiManaUse = (ushort)(Args.m_iN2); - uiTithingUse = (ushort)(Args.m_VarsLocal.GetKeyNum("TithingUse")); + uiManaUse = (ushort)(pScriptArgs->m_iN2); + uiTithingUse = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("TithingUse")); if ( !pSrc->IsChar() )// Looking for non-character sources { @@ -2907,18 +2925,19 @@ bool CChar::Spell_CastDone() uint uiFieldGauge = 0; uint uiAreaRadius = 0; - CScriptTriggerArgs Args(spell, iSkillLevel, pObjSrc); - Args.m_VarsLocal.SetNum("Duration", GetSpellDuration(spell, iSkillLevel, this), true); // tenths of second + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(spell, iSkillLevel, 0, pObjSrc); + pScriptArgs->m_VarsLocal.SetNum("Duration", GetSpellDuration(spell, iSkillLevel, this), true); // tenths of second if (fIsSpellArea) { - Args.m_VarsLocal.SetNum("AreaRadius", 0); + pScriptArgs->m_VarsLocal.SetNum("AreaRadius", 0); } if (fIsSpellField) { - Args.m_VarsLocal.SetNum("FieldWidth", 0); - Args.m_VarsLocal.SetNum("FieldGauge", 0); + pScriptArgs->m_VarsLocal.SetNum("FieldWidth", 0); + pScriptArgs->m_VarsLocal.SetNum("FieldGauge", 0); switch (spell) // Only setting ids and locals for field spells { @@ -2930,28 +2949,28 @@ bool CChar::Spell_CastDone() default: break; } - Args.m_VarsLocal.SetNum("CreateObject1", uiCreatedItemID_1, false); - Args.m_VarsLocal.SetNum("CreateObject2", uiCreatedItemID_2, false); + pScriptArgs->m_VarsLocal.SetNum("CreateObject1", uiCreatedItemID_1, false); + pScriptArgs->m_VarsLocal.SetNum("CreateObject2", uiCreatedItemID_2, false); } if (fIsSpellSummon) { - Args.m_VarsLocal.SetNum("FollowerSlotsOverride", iFollowerSlotsOverride); + pScriptArgs->m_VarsLocal.SetNum("FollowerSlotsOverride", iFollowerSlotsOverride); } if (IsTrigUsed(TRIGGER_SPELLSUCCESS)) { - if (OnTrigger(CTRIG_SpellSuccess, this, &Args) == TRIGRET_RET_TRUE) + if (OnTrigger(CTRIG_SpellSuccess, pScriptArgs, this) == TRIGRET_RET_TRUE) return false; } if (IsTrigUsed(TRIGGER_SUCCESS)) { - if (Spell_OnTrigger(spell, SPTRIG_SUCCESS, this, &Args) == TRIGRET_RET_TRUE) + if (Spell_OnTrigger(spell, SPTRIG_SUCCESS, pScriptArgs, this) == TRIGRET_RET_TRUE) return false; } - iSkillLevel = (int)(Args.m_iN2); + iSkillLevel = (int)(pScriptArgs->m_iN2); ITEMID_TYPE it1test = ITEMID_NOTHING; ITEMID_TYPE it2test = ITEMID_NOTHING; @@ -2959,20 +2978,20 @@ bool CChar::Spell_CastDone() if (fIsSpellField) { //Setting new IDs as another variables to pass as different arguments to the field function. - it1test = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject1"))); - it2test = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject2"))); - uiFieldWidth = (uint)Args.m_VarsLocal.GetKeyNum("FieldWidth"); - uiFieldGauge = (uint)Args.m_VarsLocal.GetKeyNum("FieldGauge"); + it1test = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("CreateObject1"))); + it2test = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("CreateObject2"))); + uiFieldWidth = (uint)pScriptArgs->m_VarsLocal.GetKeyNum("FieldWidth"); + uiFieldGauge = (uint)pScriptArgs->m_VarsLocal.GetKeyNum("FieldGauge"); } - uiSummonedCreatureID = (CREID_TYPE)(Args.m_VarsLocal.GetKeyNum("CreateObject1") & 0xFFFF); - uiAreaRadius = (uint)Args.m_VarsLocal.GetKeyNum("AreaRadius"); - int iDuration = (int)(std::max((int64)0, Args.m_VarsLocal.GetKeyNum("Duration"))); - uiColor = (HUE_TYPE)(Args.m_VarsLocal.GetKeyNum("EffectColor")); + uiSummonedCreatureID = (CREID_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("CreateObject1") & 0xFFFF); + uiAreaRadius = (uint)pScriptArgs->m_VarsLocal.GetKeyNum("AreaRadius"); + int iDuration = (int)(std::max((int64)0, pScriptArgs->m_VarsLocal.GetKeyNum("Duration"))); + uiColor = (HUE_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("EffectColor")); if (fIsSpellSummon) { - iFollowerSlotsOverride = n64_narrow_n16(Args.m_VarsLocal.GetKeyNum("FollowerSlotsOverride")); + iFollowerSlotsOverride = n64_narrow_n16(pScriptArgs->m_VarsLocal.GetKeyNum("FollowerSlotsOverride")); if (!pSpellDef->IsSpellType(SPELLFLAG_TARG_OBJ | SPELLFLAG_TARG_XYZ)) m_Act_p = GetTopPoint(); @@ -3321,29 +3340,30 @@ void CChar::Spell_CastFail(bool fAbort) iTithingLoss = g_Cfg.Calc_SpellTithingCost(this, pSpell, m_Act_Prv_UID.ObjFind()); } - CScriptTriggerArgs Args( m_atMagery.m_iSpell, iManaLoss, m_Act_Prv_UID.ObjFind() ); - Args.m_VarsLocal.SetNum("CreateObject1",iT1); - Args.m_VarsLocal.SetNum("TithingLoss", iTithingLoss); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(m_atMagery.m_iSpell, iManaLoss, 0, m_Act_Prv_UID.ObjFind()); + pScriptArgs->m_VarsLocal.SetNum("CreateObject1",iT1); + pScriptArgs->m_VarsLocal.SetNum("TithingLoss", iTithingLoss); if ( IsTrigUsed(TRIGGER_SPELLFAIL) ) { - if ( OnTrigger( CTRIG_SpellFail, this, &Args ) == TRIGRET_RET_TRUE ) + if ( OnTrigger( CTRIG_SpellFail, pScriptArgs, this ) == TRIGRET_RET_TRUE ) return; } if ( IsTrigUsed(TRIGGER_FAIL) ) { - if ( Spell_OnTrigger( m_atMagery.m_iSpell, SPTRIG_FAIL, this, &Args ) == TRIGRET_RET_TRUE ) + if ( Spell_OnTrigger( m_atMagery.m_iSpell, SPTRIG_FAIL, pScriptArgs, this ) == TRIGRET_RET_TRUE ) return; } - iManaLoss = (ushort)Args.m_iN2; - iTithingLoss = (ushort)Args.m_VarsLocal.GetKeyNum("TithingLoss"); + iManaLoss = (ushort)pScriptArgs->m_iN2; + iTithingLoss = (ushort)pScriptArgs->m_VarsLocal.GetKeyNum("TithingLoss"); - HUE_TYPE iColor = (HUE_TYPE)(Args.m_VarsLocal.GetKeyNum("EffectColor")); - dword dwRender = (dword)Args.m_VarsLocal.GetKeyNum("EffectRender"); + HUE_TYPE iColor = (HUE_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("EffectColor")); + dword dwRender = (dword)pScriptArgs->m_VarsLocal.GetKeyNum("EffectRender"); - iT1 = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject1"))); + iT1 = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("CreateObject1"))); if (iT1) Effect(EFFECT_OBJ, iT1, this, 1, 30, false, iColor, dwRender); Sound( SOUND_SPELL_FIZZLE ); @@ -3390,7 +3410,6 @@ void CChar::Spell_CastFail(bool fAbort) } - } int CChar::Spell_CastStart() @@ -3485,9 +3504,10 @@ int CChar::Spell_CastStart() if ( iWaitTime < 1 ) iWaitTime = 1; - CScriptTriggerArgs Args((int)m_atMagery.m_iSpell, iDifficulty, pItem); - Args.m_iN3 = iWaitTime; - Args.m_VarsLocal.SetNum("WOP", fWOP); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init((int)m_atMagery.m_iSpell, iDifficulty, 0, pItem); + pScriptArgs->m_iN3 = iWaitTime; + pScriptArgs->m_VarsLocal.SetNum("WOP", fWOP); int64 WOPFont = g_Cfg.m_iWordsOfPowerFont; int64 WOPColor; TALKMODE_TYPE WOPTalkMode = g_Cfg.m_iWordsOfPowerTalkMode ? g_Cfg.m_iWordsOfPowerTalkMode : TALKMODE_SPELL; @@ -3504,19 +3524,19 @@ int CChar::Spell_CastStart() else WOPColor = HUE_TEXT_DEF; - Args.m_VarsLocal.SetNum("WOPColor", WOPColor, true); - Args.m_VarsLocal.SetNum("WOPFont", WOPFont, true); - Args.m_VarsLocal.SetNum("WOPTalkMode", WOPTalkMode, true); + pScriptArgs->m_VarsLocal.SetNum("WOPColor", WOPColor, true); + pScriptArgs->m_VarsLocal.SetNum("WOPFont", WOPFont, true); + pScriptArgs->m_VarsLocal.SetNum("WOPTalkMode", WOPTalkMode, true); if ( IsTrigUsed(TRIGGER_SPELLCAST) ) { - if ( OnTrigger(CTRIG_SpellCast, this, &Args) == TRIGRET_RET_TRUE ) + if ( OnTrigger(CTRIG_SpellCast, pScriptArgs, this) == TRIGRET_RET_TRUE ) return -1; } if ( IsTrigUsed(TRIGGER_START) ) { - if ( Spell_OnTrigger((SPELL_TYPE)(Args.m_iN1), SPTRIG_START, this, &Args) == TRIGRET_RET_TRUE ) + if ( Spell_OnTrigger((SPELL_TYPE)(pScriptArgs->m_iN1), SPTRIG_START, pScriptArgs, this) == TRIGRET_RET_TRUE ) return -1; } @@ -3529,9 +3549,9 @@ int CChar::Spell_CastStart() return -1; } - m_atMagery.m_iSpell = (SPELL_TYPE)Args.m_iN1; - iDifficulty = (int)Args.m_iN2; - iWaitTime = Args.m_iN3; + m_atMagery.m_iSpell = (SPELL_TYPE)pScriptArgs->m_iN1; + iDifficulty = (int)pScriptArgs->m_iN2; + iWaitTime = pScriptArgs->m_iN3; pSpellDef = g_Cfg.GetSpellDef(m_atMagery.m_iSpell); if ( !pSpellDef ) @@ -3546,12 +3566,12 @@ int CChar::Spell_CastStart() if ( !pSpellDef->IsSpellType(SPELLFLAG_NO_CASTANIM) && !IsSetMagicFlags(MAGICF_NOANIM) ) UpdateAnimate(pSpellDef->IsSpellType(SPELLFLAG_DIR_ANIM) ? ANIM_CAST_DIR : ANIM_CAST_AREA); - fWOP = Args.m_VarsLocal.GetKeyNum("WOP") > 0 ? true : false; + fWOP = pScriptArgs->m_VarsLocal.GetKeyNum("WOP") > 0 ? true : false; if ( fWOP ) { - WOPColor = Args.m_VarsLocal.GetKeyNum("WOPColor"); - WOPFont = Args.m_VarsLocal.GetKeyNum("WOPFont"); - WOPTalkMode = (TALKMODE_TYPE)Args.m_VarsLocal.GetKeyNum("WOPTalkMode"); + WOPColor = pScriptArgs->m_VarsLocal.GetKeyNum("WOPColor"); + WOPFont = pScriptArgs->m_VarsLocal.GetKeyNum("WOPFont"); + WOPTalkMode = (TALKMODE_TYPE)pScriptArgs->m_VarsLocal.GetKeyNum("WOPTalkMode"); // Correct talk mode for spells WOP is TALKMODE_SPELL, but sphere doesn't have any delay between spell casts this can allow WOP flood on screen. if ( pSpellDef->m_sRunes[0] == '.' ) @@ -3679,18 +3699,19 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, } } - CScriptTriggerArgs Args((int)(spell), iSkillLevel, pSourceItem); - Args.m_VarsLocal.SetNum("DamageType", 0); - Args.m_VarsLocal.SetNum("CreateObject1", pSpellDef->m_idEffect); - Args.m_VarsLocal.SetNum("Explode", fExplode); - Args.m_VarsLocal.SetNum("Sound", iSound); - Args.m_VarsLocal.SetNum("Effect", iEffect); - Args.m_VarsLocal.SetNum("Resist", uiResist); - Args.m_VarsLocal.SetNum("Duration", iDuration); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init((int)spell, iSkillLevel, 0, pSourceItem); + pScriptArgs->m_VarsLocal.SetNum("DamageType", 0); + pScriptArgs->m_VarsLocal.SetNum("CreateObject1", pSpellDef->m_idEffect); + pScriptArgs->m_VarsLocal.SetNum("Explode", fExplode); + pScriptArgs->m_VarsLocal.SetNum("Sound", iSound); + pScriptArgs->m_VarsLocal.SetNum("Effect", iEffect); + pScriptArgs->m_VarsLocal.SetNum("Resist", uiResist); + pScriptArgs->m_VarsLocal.SetNum("Duration", iDuration); if ( IsTrigUsed(TRIGGER_SPELLEFFECT) ) { - switch ( OnTrigger(CTRIG_SpellEffect, pCharSrc ? pCharSrc : this, &Args) ) + switch ( OnTrigger(CTRIG_SpellEffect, pScriptArgs, pCharSrc ? pCharSrc : this) ) { case TRIGRET_RET_TRUE: return false; case TRIGRET_RET_FALSE: if ( pSpellDef->IsSpellType(SPELLFLAG_SCRIPTED) ) return true; @@ -3700,7 +3721,7 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, if ( IsTrigUsed(TRIGGER_EFFECT) ) { - switch ( Spell_OnTrigger(spell, SPTRIG_EFFECT, pCharSrc ? pCharSrc : this, &Args) ) + switch ( Spell_OnTrigger(spell, SPTRIG_EFFECT, pScriptArgs, pCharSrc ? pCharSrc : this) ) { case TRIGRET_RET_TRUE: return false; case TRIGRET_RET_FALSE: if ( pSpellDef->IsSpellType(SPELLFLAG_SCRIPTED) ) return true; @@ -3708,18 +3729,18 @@ bool CChar::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, } } - spell = (SPELL_TYPE)(Args.m_iN1); - iSkillLevel = (int)(Args.m_iN2); // remember that effect/duration is calculated before triggers - DAMAGE_TYPE iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("DamageType"))); - ITEMID_TYPE iEffectID = (ITEMID_TYPE)(ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum("CreateObject1"))); - fExplode = Args.m_VarsLocal.GetKeyNum("EffectExplode") > 0 ? true : false; - iSound = (SOUND_TYPE)(Args.m_VarsLocal.GetKeyNum("Sound")); - iEffect = (int)(Args.m_VarsLocal.GetKeyNum("Effect")); - uiResist = (ushort)(Args.m_VarsLocal.GetKeyNum("Resist")); - iDuration = (int)(Args.m_VarsLocal.GetKeyNum("Duration")); - - HUE_TYPE iColor = (HUE_TYPE)Args.m_VarsLocal.GetKeyNum("EffectColor"); - dword dwRender = (dword)Args.m_VarsLocal.GetKeyNum("EffectRender"); + spell = (SPELL_TYPE)(pScriptArgs->m_iN1); + iSkillLevel = (int)(pScriptArgs->m_iN2); // remember that effect/duration is calculated before triggers + DAMAGE_TYPE iDmgType = (DAMAGE_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("DamageType"))); + ITEMID_TYPE iEffectID = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum("CreateObject1"))); + fExplode = pScriptArgs->m_VarsLocal.GetKeyNum("EffectExplode") > 0 ? true : false; + iSound = (SOUND_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("Sound")); + iEffect = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("Effect")); + uiResist = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("Resist")); + iDuration = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("Duration")); + + HUE_TYPE iColor = (HUE_TYPE)pScriptArgs->m_VarsLocal.GetKeyNum("EffectColor"); + dword dwRender = (dword)pScriptArgs->m_VarsLocal.GetKeyNum("EffectRender"); if ( iEffectID > ITEMID_QTY ) iEffectID = pSpellDef->m_idEffect; diff --git a/src/game/chars/CCharStat.cpp b/src/game/chars/CCharStat.cpp index 7e7df141c..bcc75c720 100644 --- a/src/game/chars/CCharStat.cpp +++ b/src/game/chars/CCharStat.cpp @@ -1,9 +1,9 @@ // CChar is either an NPC or a Player. #include "../../common/sphere_library/CSRand.h" #include "../../common/resource/sections/CSkillClassDef.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" -#include "../../common/CScriptTriggerArgs.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../items/CItem.h" #include "../triggers.h" #include "../CServer.h" @@ -33,15 +33,15 @@ void CChar::Stat_SetMod( STAT_TYPE i, int iVal ) { if ( i >= STAT_STR && i <= STAT_DEX ) { - CScriptTriggerArgs args; - args.m_iN1 = i + 8LL; // shift by 8 to indicate modSTR, modINT, modDEX - args.m_iN2 = iStatVal; - args.m_iN3 = iVal; - if ( OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = i + 8LL; // shift by 8 to indicate modSTR, modINT, modDEX + pScriptArgs->m_iN2 = iStatVal; + pScriptArgs->m_iN3 = iVal; + if ( OnTrigger(CTRIG_StatChange, pScriptArgs, this) == TRIGRET_RET_TRUE ) return; // do not restore argn1 to i, bad things will happen! leave i untouched. (matex) - iVal = (int)(args.m_iN3); + iVal = (int)(pScriptArgs->m_iN3); } } @@ -89,14 +89,14 @@ void CChar::Stat_SetMaxMod( STAT_TYPE i, int iVal ) { if ( (i >= STAT_STR) && (i <= STAT_DEX) ) { - CScriptTriggerArgs args; - args.m_iN1 = i + 12LL; // shift by 12 to indicate modMaxHits, modMaxMana, modMaxStam - args.m_iN2 = iStatVal; - args.m_iN3 = iVal; - if ( OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = i + 12LL; // shift by 12 to indicate modMaxHits, modMaxMana, modMaxStam + pScriptArgs->m_iN2 = iStatVal; + pScriptArgs->m_iN3 = iVal; + if ( OnTrigger(CTRIG_StatChange, pScriptArgs, this) == TRIGRET_RET_TRUE ) return; // do not restore argn1 to i, bad things will happen! leave i untouched. (matex) - iVal = (int)(args.m_iN3); + iVal = (int)(pScriptArgs->m_iN3); } } @@ -241,14 +241,14 @@ void CChar::Stat_SetMax( STAT_TYPE i, ushort uiVal ) { if ( i > STAT_NONE && i < STAT_QTY ) // only STR, DEX, INT, FOOD fire MaxHits, MaxMana, MaxStam, MaxFood for @StatChange { - CScriptTriggerArgs args; - args.m_iN1 = i + 4LL; // shift by 4 to indicate MaxHits, etc.. - args.m_iN2 = Stat_GetMax(i); - args.m_iN3 = uiVal; - if ( OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = i + 4LL; // shift by 4 to indicate MaxHits, etc.. + pScriptArgs->m_iN2 = Stat_GetMax(i); + pScriptArgs->m_iN3 = uiVal; + if ( OnTrigger(CTRIG_StatChange, pScriptArgs, this) == TRIGRET_RET_TRUE ) return; // do not restore argn1 to i, bad things will happen! leave it untouched. (matex) - uiVal = (ushort)(args.m_iN3); + uiVal = (ushort)(pScriptArgs->m_iN3); } } m_Stat[i].m_max = uiVal; @@ -343,21 +343,21 @@ void CChar::Stat_SetBase( STAT_TYPE i, ushort uiVal ) ASSERT(i >= 0 && i < STAT_QTY); ushort uiStatVal = Stat_GetBase(i); - if (IsTrigUsed(TRIGGER_STATCHANGE) && !g_Serv.IsLoading() && !IsTriggerActive("CREATE")) + if (IsTrigUsed(TRIGGER_STATCHANGE) && !g_Serv.IsLoadingGeneric() && !IsTriggerActive("CREATE")) { // Only Str, Dex, Int, Food fire @StatChange here if (i >= STAT_STR && i <= STAT_FOOD) { - CScriptTriggerArgs args; - args.m_iN1 = i; - args.m_iN2 = uiStatVal; - args.m_iN3 = uiVal; - if (OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = i; + pScriptArgs->m_iN2 = uiStatVal; + pScriptArgs->m_iN3 = uiVal; + if (OnTrigger(CTRIG_StatChange, pScriptArgs, this ) == TRIGRET_RET_TRUE) return; // do not restore argn1 to i, bad things will happen! leave i untouched. (matex) - int64 iPrevVal = args.m_iN3; + int64 iPrevVal = pScriptArgs->m_iN3; int64 iVal = std::clamp(iPrevVal, int64(-UINT16_MAX), int64(UINT16_MAX)); if (iVal != iPrevVal) { @@ -369,15 +369,15 @@ void CChar::Stat_SetBase( STAT_TYPE i, ushort uiVal ) // MaxFood cannot depend on something, otherwise if the Stat depends on STR, INT, DEX, fire MaxHits, MaxMana, MaxStam if (i != STAT_FOOD && m_Stat[i].m_max < 1) { - args.m_iN1 = i + 4LL; // Shift by 4 to indicate MaxHits, MaxMana, MaxStam - args.m_iN2 = uiStatVal; - args.m_iN3 = uiVal; - if (OnTrigger(CTRIG_StatChange, this, &args) == TRIGRET_RET_TRUE) + pScriptArgs->m_iN1 = i + 4LL; // Shift by 4 to indicate MaxHits, MaxMana, MaxStam + pScriptArgs->m_iN2 = uiStatVal; + pScriptArgs->m_iN3 = uiVal; + if (OnTrigger(CTRIG_StatChange, pScriptArgs, this ) == TRIGRET_RET_TRUE) return; // do not restore argn1 to i, bad things will happen! leave i untouched. (matex) - iPrevVal = args.m_iN3; + iPrevVal = pScriptArgs->m_iN3; iVal = std::clamp(iPrevVal, int64(-UINT16_MAX), int64(UINT16_MAX)); if (iVal != iPrevVal) { @@ -536,18 +536,18 @@ bool CChar::Stats_Regen() if (IsTrigUsed(TRIGGER_REGENSTAT)) { - CScriptTriggerArgs Args; - Args.m_VarsLocal.SetNum("StatID", i, true); - Args.m_VarsLocal.SetNum("Value", iMod, true); - Args.m_VarsLocal.SetNum("StatLimit", uiStatLimit, true); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetNum("StatID", i, true); + pScriptArgs->m_VarsLocal.SetNum("Value", iMod, true); + pScriptArgs->m_VarsLocal.SetNum("StatLimit", uiStatLimit, true); if (i == STAT_DEX || i == STAT_INT) - Args.m_VarsLocal.SetNum("FocusValue", iFocusGain); + pScriptArgs->m_VarsLocal.SetNum("FocusValue", iFocusGain); if (i == STAT_FOOD) - Args.m_VarsLocal.SetNum("HitsHungerLoss", iHitsHungerLoss); + pScriptArgs->m_VarsLocal.SetNum("HitsHungerLoss", iHitsHungerLoss); - if (OnTrigger(CTRIG_RegenStat, this, &Args) == TRIGRET_RET_TRUE) + if (OnTrigger(CTRIG_RegenStat, pScriptArgs, this ) == TRIGRET_RET_TRUE) { // Setting the last regen time to 0 will make this trigger be called over and over and over, without a delay, which // can suck quite a bit cpu. @@ -555,22 +555,22 @@ bool CChar::Stats_Regen() continue; } - i = (STAT_TYPE)(Args.m_VarsLocal.GetKeyNum("StatID")); + i = (STAT_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("StatID")); if (i < STAT_STR) i = STAT_STR; else if (i > STAT_FOOD) i = STAT_FOOD; - iMod = (int)(Args.m_VarsLocal.GetKeyNum("Value")); - uiStatLimit = (ushort)(Args.m_VarsLocal.GetKeyNum("StatLimit")); + iMod = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("Value")); + uiStatLimit = (ushort)(pScriptArgs->m_VarsLocal.GetKeyNum("StatLimit")); if (i == STAT_DEX || i == STAT_INT) { - iFocusGain = (int)(Args.m_VarsLocal.GetKeyNum("FocusValue")); + iFocusGain = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("FocusValue")); iMod += iFocusGain; } if (i == STAT_FOOD) - iHitsHungerLoss = (int)(Args.m_VarsLocal.GetKeyNum("HitsHungerLoss")); + iHitsHungerLoss = (int)(pScriptArgs->m_VarsLocal.GetKeyNum("HitsHungerLoss")); } if (iMod == 0) continue; @@ -673,18 +673,18 @@ void CChar::SetKarma(short iNewKarma, CChar* pNPC) if (IsTrigUsed(TRIGGER_KARMACHANGE)) { - CScriptTriggerArgs Args(iKarmaChange, iOldKarma); - Args.m_pO1 = pNPC; - TRIGRET_TYPE retType = OnTrigger(CTRIG_KarmaChange, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iKarmaChange, iOldKarma, 0, pNPC); + TRIGRET_TYPE retType = OnTrigger(CTRIG_KarmaChange, pScriptArgs, this); if (retType == TRIGRET_RET_TRUE) return; - iKarmaChange = (short)Args.m_iN1; + iKarmaChange = (short)pScriptArgs->m_iN1; iNewKarma = (short)(maximum(g_Cfg.m_iMinKarma, minimum(g_Cfg.m_iMaxKarma, iOldKarma + iKarmaChange))); } m_iKarma = (short)(maximum(g_Cfg.m_iMinKarma, minimum(g_Cfg.m_iMaxKarma, iNewKarma))); - if ( !g_Serv.IsLoading() ) + if ( !g_Serv.IsLoadingGeneric() ) NotoSave_Update(); } @@ -711,12 +711,12 @@ void CChar::SetFame(ushort uiNewFame, CChar* pNPC) if (IsTrigUsed(TRIGGER_FAMECHANGE)) { - CScriptTriggerArgs Args(iFameChange, iOldFame); - Args.m_pO1 = pNPC; - TRIGRET_TYPE retType = OnTrigger(CTRIG_FameChange, this, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iFameChange, iOldFame, 0, pNPC); + TRIGRET_TYPE retType = OnTrigger(CTRIG_FameChange, pScriptArgs, this); if (retType == TRIGRET_RET_TRUE) return; - iFameChange = (short)Args.m_iN1; + iFameChange = (short)pScriptArgs->m_iN1; uiNewFame = (short)(maximum(0, minimum(g_Cfg.m_iMaxFame, iOldFame + iFameChange))); } diff --git a/src/game/chars/CCharStatus.cpp b/src/game/chars/CCharStatus.cpp index 53d6ab8d6..382bda43b 100644 --- a/src/game/chars/CCharStatus.cpp +++ b/src/game/chars/CCharStatus.cpp @@ -1,6 +1,7 @@ // CChar is either an NPC or a Player. -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../components/CCPropsItemEquippable.h" #include "../components/CCPropsItemWeapon.h" @@ -150,13 +151,13 @@ CItemContainer *CChar::GetBank( LAYER_TYPE layer ) layer = LAYER_BANKBOX; break; } - + CItem *pItemTest = LayerFind(layer); CItemContainer *pBankBox = dynamic_cast(pItemTest); if ( pBankBox ) return pBankBox; - if ( !g_Serv.IsLoading() ) + if ( !g_Serv.IsLoadingGeneric() ) { if ( pItemTest ) { @@ -208,7 +209,7 @@ CItem *CChar::LayerFind( LAYER_TYPE layer ) const return nullptr; } -TRIGRET_TYPE CChar::OnCharTrigForLayerLoop( CScript &s, CTextConsole *pSrc, CScriptTriggerArgs *pArgs, CSString *pResult, LAYER_TYPE layer ) +TRIGRET_TYPE CChar::OnCharTrigForLayerLoop( CScript &s, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole *pSrc, CSString *pResult, LAYER_TYPE layer ) { ADDTOCALLSTACK("CChar::OnCharTrigForLayerLoop"); const CScriptLineContext StartContext = s.GetContext(); @@ -219,7 +220,7 @@ TRIGRET_TYPE CChar::OnCharTrigForLayerLoop( CScript &s, CTextConsole *pSrc, CScr CItem* pItem = static_cast(pObjRec); if ( pItem->GetEquipLayer() == layer ) { - TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = pItem->OnTriggerRun(s, TRIGRUN_SECTION_TRUE, pScriptArgs, pSrc, pResult); if ( iRet == TRIGRET_BREAK ) { EndContext = StartContext; @@ -237,7 +238,7 @@ TRIGRET_TYPE CChar::OnCharTrigForLayerLoop( CScript &s, CTextConsole *pSrc, CScr if ( EndContext.m_iOffset <= StartContext.m_iOffset ) { // just skip to the end. - TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pSrc, pArgs, pResult); + TRIGRET_TYPE iRet = OnTriggerRun(s, TRIGRUN_SECTION_FALSE, pScriptArgs, pSrc, pResult); if ( iRet != TRIGRET_ENDIF ) return iRet; } @@ -564,7 +565,7 @@ NPCBRAIN_TYPE CChar::GetNPCBrainAuto() const noexcept //ADDTOCALLSTACK("CChar::GetNPCBrainAuto"); // Auto-detect the brain const CREID_TYPE id = GetDispID(); - + switch (id) { //TODO: add other dragons @@ -685,7 +686,7 @@ byte CChar::GetModeFlag( const CClient *pViewer ) const mode |= CHARMODE_WAR; uint64 iFlags = STATF_SLEEPING; - + //When you want change the color of character anim, you must evitate to send CHARMODE_INVIS because the anim will automaticly be grey //Here we check if it's define on the ini that you need override the color if ( !g_Cfg.m_iColorInvis ) //Serv.ColorInvis @@ -694,7 +695,7 @@ byte CChar::GetModeFlag( const CClient *pViewer ) const iFlags |= STATF_HIDDEN; if ( !g_Cfg.m_iColorInvisSpell ) //serv.ColorInvisSpell iFlags |= STATF_INVISIBLE; - + if ( IsStatFlag(iFlags) ) // Checking if I have any of these settings enabled on the ini and I have any of them, if so ... CHARMODE_INVIS is set and color applied. mode |= CHARMODE_INVIS; //When sending CHARMODE_INVIS state to client, your character anim are grey @@ -919,6 +920,9 @@ CChar * CChar::GetOwner() const bool CChar::CanDress(const CChar* pChar) const { + // Self dressing always allowed + if (pChar == this) + return true; if (IsPriv(PRIV_GM) && (GetPrivLevel() > pChar->GetPrivLevel() || GetPrivLevel() == PLEVEL_Owner)) return true; else if (g_Cfg.m_fCanUndressPets && pChar->IsOwnedBy(this)) @@ -962,74 +966,25 @@ lpctstr CChar::GetTradeTitle() const // Paperdoll title for character p (2) { if ( !IsIndividualName() ) return ""; // same as type anyhow. - lpctstr ptcArticle = pCharDef->IsFemale() ? g_Cfg.GetDefaultMsg(DEFMSG_TRADETITLE_ARTICLE_FEMALE) : g_Cfg.GetDefaultMsg(DEFMSG_TRADETITLE_ARTICLE_MALE); - snprintf(pTemp, Str_TempLength(), "%s %s", ptcArticle, pCharDef->GetTradeName()); - return pTemp; - } + + //auto gReader = g_ExprGlobals.mtEngineLockedReader(); + lpctstr ptcArticle = g_Cfg.GetDefaultMsg( + pCharDef->IsFemale() + ? DEFMSG_TRADETITLE_ARTICLE_FEMALE + : DEFMSG_TRADETITLE_ARTICLE_MALE); + snprintf(pTemp, Str_TempLength(), "%s %s", ptcArticle, pCharDef->GetTradeName()); + return pTemp; + } // Only players can have skill titles if ( !m_pPlayer ) return pTemp; int len; - SKILL_TYPE skill = Skill_GetBest(); - if ( skill == SKILL_NINJITSU ) - { - static const CValStr sm_SkillTitles[] = - { - { "", INT32_MIN }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_NEOPHYTE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_NEOPHYTE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_NOVICE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_NOVICE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_APPRENTICE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_APPRENTICE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_JOURNEYMAN), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_JOURNEYMAN")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_EXPERT), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_EXPERT")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_ADEPT), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_ADEPT")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_MASTER), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_MASTER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_GRANDMASTER), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_GRANDMASTER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_ELDER_NINJITSU), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_ELDER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_LEGENDARY_NINJITSU), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_LEGENDARY")) }, - { nullptr, INT32_MAX } - }; - len = snprintf(pTemp, Str_TempLength(), "%s ", sm_SkillTitles->FindName(Skill_GetBase(skill))); - } - else if ( skill == SKILL_BUSHIDO ) - { - static const CValStr sm_SkillTitles[] = - { - { "", INT32_MIN }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_NEOPHYTE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_NEOPHYTE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_NOVICE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_NOVICE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_APPRENTICE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_APPRENTICE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_JOURNEYMAN), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_JOURNEYMAN")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_EXPERT), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_EXPERT")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_ADEPT), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_ADEPT")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_MASTER), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_MASTER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_GRANDMASTER), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_GRANDMASTER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_ELDER_BUSHIDO), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_ELDER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_LEGENDARY_BUSHIDO), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_LEGENDARY")) }, - { nullptr, INT32_MAX } - }; - len = snprintf(pTemp, Str_TempLength(), "%s ", sm_SkillTitles->FindName(Skill_GetBase(skill))); - } - else - { - static const CValStr sm_SkillTitles[] = - { - { "", INT32_MIN }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_NEOPHYTE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_NEOPHYTE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_NOVICE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_NOVICE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_APPRENTICE), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_APPRENTICE")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_JOURNEYMAN), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_JOURNEYMAN")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_EXPERT), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_EXPERT")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_ADEPT), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_ADEPT")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_MASTER), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_MASTER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_GRANDMASTER),(int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_GRANDMASTER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_ELDER), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_ELDER")) }, - { g_Cfg.GetDefaultMsg(DEFMSG_SKILLTITLE_LEGENDARY), (int)(g_Exp.m_VarDefs.GetKeyNum("SKILLTITLE_LEGENDARY")) }, - { nullptr, INT32_MAX } - }; - len = snprintf(pTemp, Str_TempLength(), "%s ", sm_SkillTitles->FindName(Skill_GetBase(skill))); - } + const SKILL_TYPE skill = Skill_GetBest(); + const uint uiSkVal = Skill_GetBase(skill); + len = snprintf(pTemp, Str_TempLength(), + "%s ", g_ExprGlobals.mtEngineLockedReader()->SkillTitle(skill, uiSkVal)); snprintf(pTemp + len, Str_TempLength() - len, "%s", g_Cfg.GetSkillDef(skill)->m_sTitle.GetBuffer()); return pTemp; @@ -1115,6 +1070,7 @@ bool CChar::CanSeeInContainer( const CItemContainer *pContItem ) const return true; } +// TODO: restore some const safety... remove const_cast and make this function non-const bool CChar::CanSee( const CObjBaseTemplate *pObj ) const //true = client can see the invisble target { @@ -1238,12 +1194,12 @@ bool CChar::CanSee( const CObjBaseTemplate *pObj ) const { if ( IsTrigUsed( TRIGGER_SEEHIDDEN ) ) { - CScriptTriggerArgs Args; - Args.m_iN1 = (plevelMe <= plevelChar); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = (plevelMe <= plevelChar); CChar *pChar2 = const_cast< CChar* >( pChar ); CChar *this2 = const_cast< CChar* >( this ); - this2->OnTrigger( CTRIG_SeeHidden, pChar2, &Args ); - return ( Args.m_iN1 != 1 ); + this2->OnTrigger( CTRIG_SeeHidden, pScriptArgs, pChar2 ); + return ( pScriptArgs->m_iN1 != 1 ); } } //Here we analyse how GM can see other player/GM when they are hide. @@ -1394,7 +1350,7 @@ bool CChar::CanTouch( const CObjBase *pObj ) default: break; } - + if ( !fDeathImmune && IsStatFlag(STATF_FREEZE) ) { if ( !fFreezeImmune && !pItem->IsAttr(ATTR_CANUSE_PARALYZED) ) @@ -1541,7 +1497,7 @@ bool CChar::CanHear( const CObjBaseTemplate *pSrc, TALKMODE_TYPE mode ) const else { pSrcRegion = dynamic_cast(pSrc->GetTopPoint().GetRegion(REGION_TYPE_MULTI|REGION_TYPE_AREA)); - } + } if ( !pSrcRegion || !m_pArea ) // should not happen really. return false; @@ -1608,7 +1564,7 @@ bool CChar::CanHear( const CObjBaseTemplate *pSrc, TALKMODE_TYPE mode ) const if ( m_pArea == pSrcRegion )// same region is always ok. return true; - // Different region (which can be a multi or an areadef). + // Different region (which can be a multi or an areadef). if ( IsSetOF(OF_NoHouseMuteSpeech) ) return true; @@ -1981,7 +1937,7 @@ bool CChar::CanStandAt(CPointMap *ptDest, const CRegion* pArea, uint64 uiMyMovem } else if (uiMapPointMovementFlags & CAN_I_CLIMB) { - // If dwBlockFlags & CAN_I_CLIMB, then it's a "climbable" item (and i can climb it, + // If dwBlockFlags & CAN_I_CLIMB, then it's a "climbable" item (and i can climb it, // since i don't have CAN_I_CLIMB in uiBlockedBy) } else diff --git a/src/game/chars/CCharUse.cpp b/src/game/chars/CCharUse.cpp index bdbdf3964..e9a1861b9 100644 --- a/src/game/chars/CCharUse.cpp +++ b/src/game/chars/CCharUse.cpp @@ -1,6 +1,7 @@ // CChar is either an NPC or a Player. #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../clients/CClient.h" #include "../items/CItem.h" #include "../items/CItemCorpse.h" @@ -75,11 +76,11 @@ void CChar::Use_CarveCorpse( CItemCorpse * pCorpse, CItem * pItemCarving ) word iResourceQty = 0; size_t iResourceTotalQty = pCorpseDef->m_BaseResources.size(); - CScriptTriggerArgs Args(iResourceTotalQty,0,pItemCarving); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iResourceTotalQty, 0, 0, pItemCarving); for (size_t i = 0; i < iResourceTotalQty; ++i) { - const CResourceID& rid = pCorpseDef->m_BaseResources[i].GetResourceID(); if (rid.GetResType() != RES_ITEMDEF) continue; @@ -90,15 +91,15 @@ void CChar::Use_CarveCorpse( CItemCorpse * pCorpse, CItem * pItemCarving ) tchar* pszTmp = Str_GetTemp(); snprintf(pszTmp, Str_TempLength(), "resource.%u.ID", (int)i); - Args.m_VarsLocal.SetNum(pszTmp, (int64)id); + pScriptArgs->m_VarsLocal.SetNum(pszTmp, (int64)id); iResourceQty = (word)pCorpseDef->m_BaseResources[i].GetResQty(); snprintf(pszTmp, Str_TempLength(), "resource.%u.amount", (int)i); - Args.m_VarsLocal.SetNum(pszTmp, iResourceQty); + pScriptArgs->m_VarsLocal.SetNum(pszTmp, iResourceQty); } if (IsTrigUsed(TRIGGER_CARVECORPSE) || IsTrigUsed(TRIGGER_ITEMCARVECORPSE)) { - switch (static_cast(pCorpse)->OnTrigger(ITRIG_CarveCorpse, this, &Args)) + switch (static_cast(pCorpse)->OnTrigger(ITRIG_CarveCorpse, pScriptArgs, this)) { case TRIGRET_RET_TRUE: return; default: break; @@ -119,12 +120,12 @@ void CChar::Use_CarveCorpse( CItemCorpse * pCorpse, CItem * pItemCarving ) tchar* pszTmp = Str_GetTemp(); snprintf(pszTmp, Str_TempLength(), "resource.%u.ID", (int)i); - ITEMID_TYPE id = (ITEMID_TYPE)ResGetIndex((dword)Args.m_VarsLocal.GetKeyNum(pszTmp)); + ITEMID_TYPE id = (ITEMID_TYPE)ResGetIndex((dword)pScriptArgs->m_VarsLocal.GetKeyNum(pszTmp)); if (id == ITEMID_NOTHING) break; snprintf(pszTmp, Str_TempLength(), "resource.%u.amount", (int)i); - iResourceQty =(word)Args.m_VarsLocal.GetKeyNum(pszTmp); + iResourceQty =(word)pScriptArgs->m_VarsLocal.GetKeyNum(pszTmp); ++ iItems; CItem *pPart = CItem::CreateTemplate(id, nullptr, this); @@ -1002,13 +1003,15 @@ void CChar::Use_Drink( CItem * pItem ) if (IsTrigUsed(TRIGGER_DRINK)) { - CScriptTriggerArgs args(dwDelay, wConsume); - args.m_pO1 = pItem; - args.m_VarsLocal.SetNumNew("BottleId", idbottle); - TRIGRET_TYPE iRet = OnTrigger(CTRIG_Drink, this, &args); - idbottle = (ITEMID_TYPE)args.m_VarsLocal.GetKeyNum("BottleId"); - dwDelay = (dword)(args.m_iN1 > 0 ? args.m_iN1 : 1); //0 causes stays memory infinitely. - wConsume = (word)args.m_iN2; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(dwDelay, wConsume, 0, pItem); + pScriptArgs->m_VarsLocal.SetNumNew("BottleId", idbottle); + + TRIGRET_TYPE iRet = OnTrigger(CTRIG_Drink, pScriptArgs, this); + + idbottle = (ITEMID_TYPE)pScriptArgs->m_VarsLocal.GetKeyNum("BottleId"); + dwDelay = (dword)(pScriptArgs->m_iN1 > 0 ? pScriptArgs->m_iN1 : 1); //0 causes stays memory infinitely. + wConsume = (word)pScriptArgs->m_iN2; wBottleAmount = wConsume; if (iRet == TRIGRET_RET_TRUE) @@ -1239,14 +1242,14 @@ bool CChar::FollowersUpdate(CChar * pCharPet, short iPetFollowerSlots, bool fChe // Arguments should be read only. Otherwise we have to call this trigger also if fCheckOnly == true and // everyone scripts have to be changed to recognize this scenario. - CScriptTriggerArgs Args; - Args.m_iN1 = (iPetFollowerSlots >= 0) ? 0 : 1; - Args.m_iN2 = abs(iPetFollowerSlots); - //Args.m_iN3 = fCheckOnly; - if (OnTrigger(CTRIG_FollowersUpdate, pCharPet, &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = (iPetFollowerSlots >= 0) ? 0 : 1; + pScriptArgs->m_iN2 = abs(iPetFollowerSlots); + //pScriptArgs->m_iN3 = fCheckOnly; + if (OnTrigger(CTRIG_FollowersUpdate, pScriptArgs, pCharPet) == TRIGRET_RET_TRUE) return false; - //iPetFollowerSlots = n64_narrow_n16(Args.m_iN2) * ((Args.m_iN1 == 1) ? -1 : 1); + //iPetFollowerSlots = n64_narrow_n16(pScriptArgs->m_iN2) * ((pScriptArgs->m_iN1 == 1) ? -1 : 1); } const short iMaxFollower = n64_narrow_n16(GetDefNum("MAXFOLLOWER", true)); @@ -1577,7 +1580,7 @@ int CChar::Do_Use_Item(CItem *pItem, bool fLink) if (m_pNPC && (IsTrigUsed(TRIGGER_DCLICK) || IsTrigUsed(TRIGGER_ITEMDCLICK))) // for players, DClick was called before this function { - if (pItem->OnTrigger(ITRIG_DCLICK, this) == TRIGRET_RET_TRUE) + if (pItem->OnTrigger(ITRIG_DCLICK, CScriptParserBufs::GetCScriptTriggerArgsPtr(), this) == TRIGRET_RET_TRUE) return false; } @@ -1981,8 +1984,12 @@ bool CChar::Use_Obj( CObjBase * pObj, bool fTestTouch, bool fScript ) return false; if ( IsClientActive() ) return GetClientActive()->Event_DoubleClick(pObj->GetUID(), false, fTestTouch, fScript); - else - return Use_Item(dynamic_cast(pObj), fTestTouch); + + CItem *pItem = dynamic_cast(pObj); + if (!pItem) + return false; + + return Use_Item(pItem, fTestTouch); } bool CChar::ItemEquipArmor( bool fForce ) diff --git a/src/game/chars/CStoneMember.cpp b/src/game/chars/CStoneMember.cpp index 0407dd224..5a22e4a14 100644 --- a/src/game/chars/CStoneMember.cpp +++ b/src/game/chars/CStoneMember.cpp @@ -1,5 +1,5 @@ -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "../../common/CLog.h" #include "../chars/CChar.h" #include "../items/CItemStone.h" @@ -236,7 +236,7 @@ bool CStoneMember::r_LoadVal( CScript & s ) // Load an item Script default: return false; } - } + } else if ( GetLinkUID().IsItem() ) { switch ( iIndex ) @@ -381,7 +381,7 @@ CStoneMember::CStoneMember( CItemStone * pStone, CUID uid, STONEPRIV_TYPE iType, m_Member.m_iAccountGold = nAccountGold; - if ( ! g_Serv.IsLoading() && pStone->GetMemoryType()) + if ( ! g_Serv.IsLoadingGeneric() && pStone->GetMemoryType()) { CChar * pChar = uid.CharFind(); if ( pChar != nullptr ) @@ -441,12 +441,12 @@ lpctstr CStoneMember::GetPrivName() const TemporaryString tsDefname; snprintf(tsDefname.buffer(), tsDefname.capacity(), "STONECONFIG_PRIVNAME_PRIVID-%d", (int)iPriv); - CVarDefCont * pResult = g_Exp.m_VarDefs.GetKey(tsDefname); + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + CVarDefCont * pResult = gReader->m_VarDefs.GetKey(tsDefname); if (pResult) return pResult->GetValStr(); - else - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_PRIVNAME_PRIVUNK"); + pResult = gReader->m_VarDefs.GetKey("STONECONFIG_PRIVNAME_PRIVUNK"); return ( pResult == nullptr ) ? "" : pResult->GetValStr(); } diff --git a/src/game/clients/CAccount.cpp b/src/game/clients/CAccount.cpp index 6e292947e..c125c3100 100644 --- a/src/game/clients/CAccount.cpp +++ b/src/game/clients/CAccount.cpp @@ -1,8 +1,9 @@ #include "../../common/crypto/CMD5.h" #include "../../common/sphere_library/CSRand.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/CClientIterator.h" #include "../chars/CChar.h" #include "../CServer.h" @@ -213,10 +214,10 @@ bool CAccounts::Account_Delete( CAccount * pAccount ) ADDTOCALLSTACK("CAccounts::Account_Delete"); ASSERT(pAccount != nullptr); - CScriptTriggerArgs Args; - Args.Init(pAccount->GetName()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pAccount->GetName()); enum TRIGRET_TYPE tr = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onaccount_delete", &g_Serv, &Args, nullptr, &tr); + g_Serv.r_Call("f_onaccount_delete", pScriptArgs, &g_Serv, nullptr, &tr); if ( tr == TRIGRET_RET_TRUE ) { return false; @@ -231,13 +232,13 @@ void CAccounts::Account_Add( CAccount * pAccount ) { ADDTOCALLSTACK("CAccounts::Account_Add"); ASSERT(pAccount != nullptr); - if ( !g_Serv.IsLoading() ) - { - CScriptTriggerArgs Args; - Args.Init(pAccount->GetName()); + if ( !g_Serv.IsLoadingGeneric() ) + { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pAccount->GetName()); //Accounts are 'created' in server startup so we don't fire the function. TRIGRET_TYPE tRet = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onaccount_create", &g_Serv, &Args, nullptr, &tRet); + g_Serv.r_Call("f_onaccount_create", pScriptArgs, &g_Serv, nullptr, &tRet); if ( tRet == TRIGRET_RET_TRUE ) { g_Log.Event(LOGL_ERROR|LOGM_INIT, "Account '%s': Creation blocked via script\n", pAccount->GetName()); @@ -393,24 +394,24 @@ bool CAccounts::Cmd_ListUnused(CTextConsole * pSrc, lpctstr pszDays, lpctstr psz void CAccount::SetBlockStatus(bool fNewStatus) { - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (IsPriv(PRIV_BLOCKED) && fNewStatus == false) { - CScriptTriggerArgs Args; - Args.Init(GetName()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(GetName()); TRIGRET_TYPE iRet = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onaccount_unblock", &g_Serv, &Args, nullptr, &iRet); + g_Serv.r_Call("f_onaccount_unblock", pScriptArgs, &g_Serv, nullptr, &iRet); if (iRet == TRIGRET_RET_TRUE) return; ClearPrivFlags(PRIV_BLOCKED); } else if (!IsPriv(PRIV_BLOCKED) && fNewStatus == true) { - CScriptTriggerArgs Args; - Args.Init(GetName()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(GetName()); TRIGRET_TYPE iRet = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onaccount_block", &g_Serv, &Args, nullptr, &iRet); + g_Serv.r_Call("f_onaccount_block", pScriptArgs, &g_Serv, nullptr, &iRet); if (iRet == TRIGRET_RET_TRUE) return; SetPrivFlags(PRIV_BLOCKED); @@ -513,13 +514,13 @@ bool CAccounts::Account_OnCmd( tchar * pszArgs, CTextConsole * pSrc ) { CSString cmdArgs; if (ppCmd[4] && ppCmd[4][0]) - cmdArgs.Format("%s %s %s", ppCmd[2], ppCmd[3], ppCmd[4]); + cmdArgs.Format("%s %s %s", ppCmd[2], ppCmd[3], ppCmd[4]); else if (ppCmd[3] && ppCmd[3][0]) - cmdArgs.Format("%s %s", ppCmd[2], ppCmd[3]); + cmdArgs.Format("%s %s", ppCmd[2], ppCmd[3]); else if (ppCmd[2] && ppCmd[2][0]) - cmdArgs.Format("%s", ppCmd[2]); + cmdArgs.Format("%s", ppCmd[2]); - CScript script( ppCmd[1], cmdArgs.GetBuffer() ); + CScript script( ppCmd[1], cmdArgs.GetBuffer() ); return pAccount->r_Verb( script, pSrc ); } @@ -616,7 +617,7 @@ void CAccount::DeleteChars() } // Now track down all my disconnected chars ! - if ( ! g_Serv.IsLoading()) + if ( ! g_Serv.IsLoadingGeneric()) { size_t i = m_Chars.GetCharCount(); while (i > 0) @@ -944,11 +945,11 @@ bool CAccount::CheckPassword( lpctstr pszPassword ) return false; } - CScriptTriggerArgs Args; - Args.m_VarsLocal.SetStrNew("Account",GetName()); - Args.m_VarsLocal.SetStrNew("Password",pszPassword); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetStrNew("Account",GetName()); + pScriptArgs->m_VarsLocal.SetStrNew("Password",pszPassword); TRIGRET_TYPE tr = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onaccount_connect", &g_Serv, &Args, nullptr, &tr); + g_Serv.r_Call("f_onaccount_connect", pScriptArgs, &g_Serv, nullptr, &tr); if ( tr == TRIGRET_RET_TRUE ) return false; if ( tr == TRIGRET_RET_HALFBAKED) @@ -986,20 +987,20 @@ bool CAccount::SetPassword( lpctstr pszPassword, bool isMD5Hash ) { ADDTOCALLSTACK("CAccount::SetPassword"); - if ( Str_Check( pszPassword ) ) // Prevents exploits + if ( Str_Untrusted_InvalidTermination( pszPassword ) ) // Prevents exploits return false; bool useMD5 = g_Cfg.m_fMd5Passwords; //Accounts are 'created' in server startup so we don't fire the function. - if ( !g_Serv.IsLoading() ) + if ( !g_Serv.IsLoadingGeneric() ) { - CScriptTriggerArgs Args; - Args.Init(GetName()); - Args.m_VarsLocal.SetStrNew("password",pszPassword); - Args.m_VarsLocal.SetStrNew("oldPassword",m_sCurPassword.GetBuffer()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(GetName()); + pScriptArgs->m_VarsLocal.SetStrNew("password",pszPassword); + pScriptArgs->m_VarsLocal.SetStrNew("oldPassword",m_sCurPassword.GetBuffer()); TRIGRET_TYPE tRet = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onaccount_pwchange", &g_Serv, &Args, nullptr, &tRet); + g_Serv.r_Call("f_onaccount_pwchange", pScriptArgs, &g_Serv, nullptr, &tRet); if ( tRet == TRIGRET_RET_TRUE ) { return false; @@ -1392,7 +1393,7 @@ bool CAccount::r_LoadVal( CScript & s ) break; case AC_CHARUID: // just ignore this ? chars are loaded later ! - if ( ! g_Serv.IsLoading()) + if ( ! g_Serv.IsLoadingGeneric()) { const CUID uid( s.GetArgVal()); CChar * pChar = uid.CharFind(); @@ -1657,8 +1658,9 @@ bool CAccount::r_Verb( CScript &s, CTextConsole * pSrc ) { // RES_FUNCTION call CSString sVal; - CScriptTriggerArgs Args( s.GetArgRaw() ); - fLoad = r_Call( ptcKey, pSrc, &Args, &sVal ); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(s.GetArgRaw()); + fLoad = r_Call( ptcKey, pScriptArgs, pSrc, &sVal ); } return fLoad; } diff --git a/src/game/clients/CAccount.h b/src/game/clients/CAccount.h index 85ac42d09..54f7587af 100644 --- a/src/game/clients/CAccount.h +++ b/src/game/clients/CAccount.h @@ -1,5 +1,5 @@ /** -* @file CAcount.h +* @file CAccount.h */ #ifndef _INC_CACCOUNT_H @@ -236,7 +236,6 @@ class CAccount : public CScriptObj static PLEVEL_TYPE GetPrivLevelText( lpctstr pszFlags ); /** * @brief Gets the CAccount PLEVEL. - * @param pszFlags TODOC. * @return TODOC. */ PLEVEL_TYPE GetPrivLevel() const { return( m_PrivLevel ); } @@ -314,7 +313,7 @@ class CAccount : public CScriptObj bool IsMyAccountChar( const CChar * pChar ) const; /** * @brief Unlink the CChar from this CAccount. - * @param CChar to detach. + * @param pChar to detach. * @return TODOC. */ size_t DetachChar( CChar * pChar ); @@ -442,13 +441,13 @@ class CAccounts * If there is not an CAccount with the providded name, AutoAccount is enabled in sphere.ini and the name is a valid account name, a CAcount is created and a CAccount * of the returned. * Otherwise, nullptr is returned. * @param pszName name of the account. - * @param fAutoCreate try to create the account if not exists. + * @param fCreate try to create the account if not exists. * @return CAccount * if account exists or created, nullptr otherwise. */ CAccount * Account_FindCreate( lpctstr pszName, bool fCreate = false ); /** * @brief Check if a chat name is already used. - * @param pszChatName string containing the name. + * @param pszName string containing the name. * @return CAccount * if the name is already used, nullptr otherwise. */ CAccount * Account_FindChat( lpctstr pszName ); diff --git a/src/game/clients/CClient.cpp b/src/game/clients/CClient.cpp index 6b398f771..45637dc8e 100644 --- a/src/game/clients/CClient.cpp +++ b/src/game/clients/CClient.cpp @@ -1,12 +1,14 @@ #include "../../common/resource/sections/CResourceNamedDef.h" #include "../../common/sphere_library/CSRand.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CUOClientVersion.h" #include "../../network/CClientIterator.h" #include "../../network/CNetworkManager.h" #include "../../network/CIPHistoryManager.h" +#include "../../network/send.h" #include "../chars/CChar.h" #include "../components/CCSpawn.h" #include "../items/CItemMultiCustom.h" @@ -92,7 +94,7 @@ CClient::~CClient() noexcept HistoryIP& history = g_NetworkManager.getIPHistoryManager().getHistoryForIP(GetPeer()); if ( GetConnectType() != CONNECT_GAME ) { - EXC_TRYSUB("m_iPendingConnectionRequests") + EXC_TRYSUB("m_iPendingConnectionRequests"); ASSERT(history.m_iPendingConnectionRequests > 0); -- history.m_iPendingConnectionRequests; @@ -137,7 +139,7 @@ CClient::~CClient() noexcept bool CClient::CanInstantLogOut() const { ADDTOCALLSTACK("CClient::CanInstantLogOut"); - if ( g_Serv.IsLoading()) // or exiting. + if ( g_Serv.IsLoadingGeneric()) // or exiting. return true; if ( ! g_Cfg.m_iClientLingerTime ) return true; @@ -190,10 +192,13 @@ void CClient::CharDisconnect() if ( IsTrigUsed(TRIGGER_LOGOUT) ) { - CScriptTriggerArgs Args(iLingerTime, fCanInstaLogOut); - m_pChar->OnTrigger(CTRIG_LogOut, m_pChar, &Args); - iLingerTime = (int)(Args.m_iN1); - fCanInstaLogOut = (Args.m_iN2 != 0); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iLingerTime, fCanInstaLogOut, 0, nullptr); + + m_pChar->OnTrigger(CTRIG_LogOut, pScriptArgs, m_pChar); + + iLingerTime = (int)(pScriptArgs->m_iN1); + fCanInstaLogOut = (pScriptArgs->m_iN2 != 0); } if ( iLingerTime <= 0 ) @@ -1259,7 +1264,7 @@ bool CClient::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command from pChar = uid.CharFind(); } if ( pChar ) - closeUIWindow(pChar, PacketCloseUIWindow::Paperdoll); + closeUIWindow(pChar, PacketCloseUIWindowType::Paperdoll); } break; @@ -1272,7 +1277,7 @@ bool CClient::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command from pChar = uid.CharFind(); } if ( pChar ) - closeUIWindow(pChar, PacketCloseUIWindow::Profile); + closeUIWindow(pChar, PacketCloseUIWindowType::Profile); } break; @@ -1285,7 +1290,7 @@ bool CClient::r_Verb( CScript & s, CTextConsole * pSrc ) // Execute command from pChar = uid.CharFind(); } if ( pChar ) - closeUIWindow(pChar, PacketCloseUIWindow::Status); + closeUIWindow(pChar, PacketCloseUIWindowType::Status); } break; diff --git a/src/game/clients/CClient.h b/src/game/clients/CClient.h index 1a74d0957..6900fd8e5 100644 --- a/src/game/clients/CClient.h +++ b/src/game/clients/CClient.h @@ -9,7 +9,7 @@ #include "../../common/crypto/CCrypto.h" #include "../../common/CScriptTriggerArgs.h" #include "../../common/CTextConsole.h" -#include "../../network/send.h" +#include "../../network/CNetState.h" #include "../CSectorEnviron.h" #include "../game_enums.h" #include "CAccount.h" @@ -17,12 +17,17 @@ #include "CGlobalChatChanMember.h" #include "CGMPage.h" +class Packet; +class PacketCloseUIWindow; +enum PacketCloseUIWindowType : uint; +class PacketDisplayPopup; class CItemBase; class CItemContainer; class CItemMap; class CItemMultiCustom; class CSObjCont; + struct VendorItem; enum CREID_TYPE : uint32; enum ITEMID_TYPE : uint32; @@ -44,6 +49,8 @@ enum CC_TYPE }; +// TODO: just split CDialogResponseArgs into two objects (CScriptTriggerArgs and a struct for other data?) +// Or just make CScriptTriggerArgs a member of CDialogResponseArgs... Favor composition over inheritance! class CDialogResponseArgs : public CScriptTriggerArgs { // The scriptable return from a gump dialog. @@ -55,7 +62,8 @@ class CDialogResponseArgs : public CScriptTriggerArgs const word m_ID; CSString const m_sText; - TResponseString( word id, lpctstr pszText ) : m_ID( id ), m_sText( pszText ) + TResponseString( word id, lpctstr pszText ) + : m_ID( id ), m_sText( pszText ) { } TResponseString(const TResponseString& copy) = delete; @@ -352,7 +360,7 @@ class CClient : public CSObjListRec, public CScriptObj, public CChatChanMember, bool Event_ExceededNetworkQuota(uchar uiType, int64 iBytes, int64 iQuota); TRIGRET_TYPE Menu_OnSelect( const CResourceID& rid, int iSelect, CObjBase * pObj ); - TRIGRET_TYPE Dialog_OnButton( const CResourceID& rid, dword dwButtonID, CObjBase * pObj, CDialogResponseArgs * pArgs ); + TRIGRET_TYPE Dialog_OnButton(const CResourceID& rid, dword dwButtonID, CObjBase * pObj, std::shared_ptr pScriptArgs ); bool Login_Relay( uint iServer ); // Relay player to a certain IP byte Login_ServerList( const char * pszAccount, const char * pszPassword ); // Initial login (Login on "loginserver", new format) @@ -436,7 +444,7 @@ class CClient : public CSObjListRec, public CScriptObj, public CChatChanMember, void addTime( bool fCurrent = false ) const; void addObjectRemoveCantSee( const CUID& uid, lpctstr pszName = nullptr ) const; void closeContainer( const CObjBase * pObj ) const; - void closeUIWindow( const CObjBase* pObj, PacketCloseUIWindow::UIWindow windowType ) const; + void closeUIWindow( const CObjBase* pObj, PacketCloseUIWindowType windowType ) const; void addObjectRemove( const CUID& uid ) const; void addObjectRemove( const CObjBase * pObj ) const; void addRemoveAll( bool fItems, bool fChars ); @@ -446,7 +454,7 @@ class CClient : public CSObjListRec, public CScriptObj, public CChatChanMember, void addItem_InContainer( const CItem * pItem ); void addItem( CItem * pItem ); - void addBuff( const BUFF_ICONS IconId, const dword ClilocOne, const dword ClilocTwo, const word durationSeconds = 0, lpctstr* pArgs = nullptr, uint uiArgCount = 0) const; + void addBuff(const BUFF_ICONS IconId, const dword ClilocOne, const dword ClilocTwo, const word durationSeconds = 0, lpctstr* pptcArgs = nullptr, uint uiArgCount = 0) const; void removeBuff(const BUFF_ICONS IconId) const; void resendBuffs() const; diff --git a/src/game/clients/CClientDialog.cpp b/src/game/clients/CClientDialog.cpp index 67b859367..43b6fc7db 100644 --- a/src/game/clients/CClientDialog.cpp +++ b/src/game/clients/CClientDialog.cpp @@ -1,7 +1,8 @@ #include "../../common/resource/sections/CDialogDef.h" #include "../../common/resource/CResourceLock.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../../network/receive.h" #include "../../network/send.h" @@ -137,7 +138,7 @@ bool CClient::addGumpDialogProps( const CUID& uid ) return true; } -TRIGRET_TYPE CClient::Dialog_OnButton( const CResourceID& rid, dword dwButtonID, CObjBase * pObj, CDialogResponseArgs * pArgs ) +TRIGRET_TYPE CClient::Dialog_OnButton(const CResourceID& rid, dword dwButtonID, CObjBase * pObj, std::shared_ptr pScriptArgs ) { ADDTOCALLSTACK("CClient::Dialog_OnButton"); // one of the gump dialog buttons was pressed. @@ -177,16 +178,16 @@ TRIGRET_TYPE CClient::Dialog_OnButton( const CResourceID& rid, dword dwButtonID, continue; } - pArgs->m_iN1 = dwButtonID; + pScriptArgs->m_iN1 = dwButtonID; auto stopPrebutton = TRIGRET_RET_FALSE; CResourceLock prebutton; if (g_Cfg.ResourceLock(prebutton, CResourceID(RES_DIALOG, rid.GetResIndex(), RES_DIALOG_PREBUTTON))) - stopPrebutton = pObj->OnTriggerRun(prebutton, TRIGRUN_SECTION_TRUE, m_pChar, pArgs, nullptr); + stopPrebutton = pObj->OnTriggerRun(prebutton, TRIGRUN_SECTION_TRUE, pScriptArgs, m_pChar, nullptr); if (stopPrebutton != TRIGRET_RET_TRUE) - return pObj->OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, m_pChar, pArgs); + return pObj->OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, pScriptArgs, m_pChar); } return TRIGRET_ENDIF; @@ -254,7 +255,7 @@ TRIGRET_TYPE CClient::Menu_OnSelect( const CResourceID& rid, int iSelect, CObjBa if (strnicmp(ptcStr, "@CANCEL", 7 ) ) continue; - return pObj->OnTriggerRunVal( s, TRIGRUN_SECTION_TRUE, m_pChar, nullptr ); + return pObj->OnTriggerRunVal( s, TRIGRUN_SECTION_TRUE, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar); } } else @@ -271,7 +272,7 @@ TRIGRET_TYPE CClient::Menu_OnSelect( const CResourceID& rid, int iSelect, CObjBa if ( i > iSelect ) break; - return pObj->OnTriggerRunVal( s, TRIGRUN_SECTION_TRUE, m_pChar, nullptr ); + return pObj->OnTriggerRunVal( s, TRIGRUN_SECTION_TRUE, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar); } } @@ -320,10 +321,11 @@ bool CMenuItem::ParseLine( tchar * pszArgs, CScriptObj * pObjBase, CTextConsole m_id = 0; } - if ( pObjBase != nullptr ) - pObjBase->ParseScriptText( pszArgs, pSrc ); - else - g_Serv.ParseScriptText( pszArgs, pSrc ); + + CScriptExprContext scpContext{ + ._pScriptObjI = pObjBase ? pObjBase : &g_Serv + }; + CExpression::GetExprParser().ParseScriptText( pszArgs, scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc ); // Parsing @color if ( *pszArgs == '@' ) @@ -378,7 +380,8 @@ void CClient::Menu_Setup( CResourceID rid, CObjBase * pObj ) DEBUG_ERR(("Error getting the menu title.\n")); return; } - pObj->ParseScriptText( s.GetKeyBuffer(), m_pChar ); + CScriptExprContext scpContext{._pScriptObjI = pObj}; + CExpression::GetExprParser().ParseScriptText( s.GetKeyBuffer(), scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar ); CMenuItem item[MAX_MENU_ITEMS]; item[0].m_sText = s.GetKey(); diff --git a/src/game/clients/CClientEvent.cpp b/src/game/clients/CClientEvent.cpp index d0721529c..c323aca24 100644 --- a/src/game/clients/CClientEvent.cpp +++ b/src/game/clients/CClientEvent.cpp @@ -1,6 +1,7 @@ #include "../../common/resource/CResourceLock.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/CClientIterator.h" #include "../../network/receive.h" #include "../../network/send.h" @@ -53,7 +54,7 @@ void CClient::Event_ChatButton(const nachar* pszName) // Client's chat button wa if ( IsTrigUsed(TRIGGER_USERCHATBUTTON) ) { - if (m_pChar && m_pChar->OnTrigger(CTRIG_UserChatButton, m_pChar) == TRIGRET_RET_TRUE) + if (m_pChar && m_pChar->OnTrigger(CTRIG_UserChatButton, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE) return; } GetChar()->SetTriggerActive("UserChatButton"); // dirty fix for SA Classic clients with injection moving a lot when using chat button, we set 'active trigger' to this, so we check it back on the packet to limit the amount of steps to do. @@ -170,7 +171,7 @@ void CClient::Event_Book_Title( CUID uid, lpctstr pszTitle, lpctstr pszAuthor ) if ( !pBook->IsBookWritable() ) return; - if ( Str_Check(pszTitle) || Str_Check(pszAuthor) ) + if ( Str_Untrusted_InvalidTermination(pszTitle) || Str_Untrusted_InvalidTermination(pszAuthor) ) return; pBook->SetName(pszTitle); @@ -417,8 +418,9 @@ void CClient::Event_Item_Drop( CUID uidItem, CPointMap pt, CUID uidOn, uchar gri CObjBase *pOldCont = pItem->GetContainer(); if (( IsTrigUsed(TRIGGER_DROPON_ITEM) ) || ( IsTrigUsed(TRIGGER_ITEMDROPON_ITEM) )) { - CScriptTriggerArgs Args( pObjOn ); - if ( pItem->OnTrigger( ITRIG_DROPON_ITEM, m_pChar, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pObjOn; + if ( pItem->OnTrigger( ITRIG_DROPON_ITEM, pScriptArgs, m_pChar ) == TRIGRET_RET_TRUE ) { Event_Item_Drop_Fail( pItem ); return; @@ -429,11 +431,12 @@ void CClient::Event_Item_Drop( CUID uidItem, CPointMap pt, CUID uidOn, uchar gri return; CItem * pItemOn = dynamic_cast ( pObjOn ); - if (( pItemOn ) && (( IsTrigUsed(TRIGGER_DROPON_SELF) ) || ( IsTrigUsed(TRIGGER_ITEMDROPON_SELF) ))) + if (pItemOn && (IsTrigUsed(TRIGGER_DROPON_SELF) || IsTrigUsed(TRIGGER_ITEMDROPON_SELF))) { CItem* pPrevCont = dynamic_cast(pItem->GetContainer()); - CScriptTriggerArgs Args( pItem ); - if ( pItemOn->OnTrigger( ITRIG_DROPON_SELF, m_pChar, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pItem; + if ( pItemOn->OnTrigger( ITRIG_DROPON_SELF, pScriptArgs, m_pChar ) == TRIGRET_RET_TRUE ) { CItem* pCont = dynamic_cast(pItem->GetContainer()); if (pPrevCont == pCont) @@ -589,7 +592,6 @@ void CClient::Event_Item_Drop( CUID uidItem, CPointMap pt, CUID uidOn, uchar gri } - void CClient::Event_Skill_Use( SKILL_TYPE skill ) // Skill is clicked on the skill list { ADDTOCALLSTACK("CClient::Event_Skill_Use"); @@ -612,7 +614,7 @@ void CClient::Event_Skill_Use( SKILL_TYPE skill ) // Skill is clicked on the ski if ( IsTrigUsed(TRIGGER_SKILLSELECT) ) { - if ( m_pChar->Skill_OnCharTrigger( skill, CTRIG_SkillSelect ) == TRIGRET_RET_TRUE ) + if ( m_pChar->Skill_OnCharTrigger( skill, CTRIG_SkillSelect, CScriptParserBufs::GetCScriptTriggerArgsPtr() ) == TRIGRET_RET_TRUE ) { m_pChar->Skill_Fail( true ); // clean up current skill. return; @@ -621,7 +623,7 @@ void CClient::Event_Skill_Use( SKILL_TYPE skill ) // Skill is clicked on the ski if ( IsTrigUsed(TRIGGER_SELECT) ) { - if ( m_pChar->Skill_OnTrigger( skill, SKTRIG_SELECT ) == TRIGRET_RET_TRUE ) + if ( m_pChar->Skill_OnTrigger( skill, SKTRIG_SELECT, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { m_pChar->Skill_Fail( true ); // clean up current skill. return; @@ -722,7 +724,6 @@ void CClient::Event_Skill_Use( SKILL_TYPE skill ) // Skill is clicked on the ski } - bool CClient::Event_CheckWalkBuffer(byte rawdir) { ADDTOCALLSTACK("CClient::Event_CheckWalkBuffer"); @@ -807,7 +808,7 @@ bool CClient::Event_CheckWalkBuffer(byte rawdir) DEBUG_WARN(("%s (%x): Fast Walk ?\n", GetName(), GetSocketID())); if ( IsTrigUsed(TRIGGER_USEREXWALKLIMIT) ) { - if ( m_pChar->OnTrigger(CTRIG_UserExWalkLimit, m_pChar) != TRIGRET_RET_TRUE ) + if ( m_pChar->OnTrigger(CTRIG_UserExWalkLimit, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) != TRIGRET_RET_TRUE ) return false; } } @@ -819,12 +820,13 @@ bool CClient::Event_ExceededNetworkQuota(uchar uiType, int64 iBytes, int64 iQuot { ADDTOCALLSTACK("CClient::Event_ExceededNetworkQuota"); - CScriptTriggerArgs Args(uiType, iBytes, iQuota); - Args.m_VarsLocal.SetStrNew("Account", GetName()); - Args.m_VarsLocal.SetStrNew("IP", GetPeer().GetAddrStr()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(uiType, iBytes, iQuota, nullptr); + pScriptArgs->m_VarsLocal.SetStrNew("Account", GetName()); + pScriptArgs->m_VarsLocal.SetStrNew("IP", GetPeer().GetAddrStr()); TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; - g_Serv.r_Call("f_onclient_exceed_network_quota", this, &Args, nullptr, &tRet); + g_Serv.r_Call("f_onclient_exceed_network_quota", pScriptArgs, this, nullptr, &tRet); if (tRet == TRIGRET_RET_FALSE) { @@ -972,9 +974,9 @@ void CClient::Event_CombatAbilitySelect(dword dwAbility) if ( IsTrigUsed(TRIGGER_USERSPECIALMOVE) ) { - CScriptTriggerArgs Args; - Args.m_iN1 = dwAbility; - m_pChar->OnTrigger(CTRIG_UserSpecialMove, m_pChar, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = dwAbility; + m_pChar->OnTrigger(CTRIG_UserSpecialMove, pScriptArgs, m_pChar); } } @@ -987,9 +989,10 @@ void CClient::Event_VirtueSelect(dword dwVirtue, CChar *pCharTarg) if ( IsTrigUsed(TRIGGER_USERVIRTUE) ) { - CScriptTriggerArgs Args(pCharTarg); - Args.m_iN1 = dwVirtue; - m_pChar->OnTrigger(CTRIG_UserVirtue, m_pChar, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pCharTarg; + pScriptArgs->m_iN1 = dwVirtue; + m_pChar->OnTrigger(CTRIG_UserVirtue, pScriptArgs, m_pChar ); } } @@ -1005,18 +1008,18 @@ void CClient::Event_CombatMode( bool fWar ) // Only for switching to combat mode if ( IsTrigUsed(TRIGGER_USERWARMODE) ) { - CScriptTriggerArgs Args; - Args.m_iN1 = m_pChar->IsStatFlag(STATF_WAR) ? 1 : 0; - Args.m_iN2 = 1; - Args.m_iN3 = 0; - if (m_pChar->OnTrigger(CTRIG_UserWarmode, m_pChar, &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = m_pChar->IsStatFlag(STATF_WAR) ? 1 : 0; + pScriptArgs->m_iN2 = 1; + pScriptArgs->m_iN3 = 0; + if (m_pChar->OnTrigger(CTRIG_UserWarmode, pScriptArgs, m_pChar) == TRIGRET_RET_TRUE) return; - if ( Args.m_iN2 == 0 ) + if ( pScriptArgs->m_iN2 == 0 ) fCleanSkill = false; - if ( Args.m_iN3 != 0 && Args.m_iN3 < 3) - fWar = (Args.m_iN3 == 1 ? false : true); + if ( pScriptArgs->m_iN3 != 0 && pScriptArgs->m_iN3 < 3) + fWar = (pScriptArgs->m_iN3 == 1 ? false : true); } m_pChar->StatFlag_Mod( STATF_WAR, fWar ); @@ -1042,7 +1045,7 @@ bool CClient::Event_Command(lpctstr pszCommand, TALKMODE_TYPE mode) return false; if ( pszCommand[0] == 0 ) return true; // should not be said - if ( Str_Check(pszCommand) ) + if ( Str_Untrusted_InvalidTermination(pszCommand) ) return true; // should not be said if ( ((m_pChar->GetDispID() == CREID_EQUIP_GM_ROBE) && (pszCommand[0] == '=')) // WTF? In any case, keep using dispid, or it's bugged when you change character's dispid to c_man_gm. || (pszCommand[0] == g_Cfg.m_cCommandPrefix)) @@ -1071,18 +1074,19 @@ bool CClient::Event_Command(lpctstr pszCommand, TALKMODE_TYPE mode) // filter on commands is active - so trigger it if ( !g_Cfg.m_sCommandTrigger.IsEmpty() ) { - CScriptTriggerArgs Args(pszCommand); - Args.m_iN1 = fAllowCommand; - Args.m_iN2 = fAllowSay; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pszCommand); + pScriptArgs->m_iN1 = fAllowCommand; + pScriptArgs->m_iN2 = fAllowSay; enum TRIGRET_TYPE tr; // Call the filtering function - if ( m_pChar->r_Call(g_Cfg.m_sCommandTrigger, m_pChar, &Args, nullptr, &tr) ) + if ( m_pChar->r_Call(g_Cfg.m_sCommandTrigger, pScriptArgs, m_pChar, nullptr, &tr) ) if ( tr == TRIGRET_RET_TRUE ) - return (Args.m_iN2 != 0); + return (pScriptArgs->m_iN2 != 0); - fAllowCommand = ( Args.m_iN1 != 0 ); - fAllowSay = ( Args.m_iN2 != 0 ); + fAllowCommand = ( pScriptArgs->m_iN1 != 0 ); + fAllowSay = ( pScriptArgs->m_iN2 != 0 ); } if ( !fAllowCommand && !fAllowSay ) @@ -1274,17 +1278,18 @@ void CClient::Event_VendorBuy(CChar* pVendor, const VendorItem* items, uint uiIt continue; //We need to continue for loop for other items not break. pItem = dynamic_cast (items[i].m_serial.ItemFind()); - word amount = items[i].m_vcAmount; + word wAmount = items[i].m_vcAmount; if (pItem == nullptr) continue; if ((IsTrigUsed(TRIGGER_BUY)) || (IsTrigUsed(TRIGGER_ITEMBUY))) { - int64 iItemCost = int64(amount) * items[i].m_price; - CScriptTriggerArgs Args( amount, iItemCost, pVendor ); - Args.m_VarsLocal.SetNum( "TOTALCOST", iCostTotal); - if (pItem->OnTrigger(ITRIG_Buy, this->GetChar(), &Args) == TRIGRET_RET_TRUE) + const int64 iItemCost = int64(wAmount) * items[i].m_price; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(wAmount, iItemCost, 0, pVendor); + pScriptArgs->m_VarsLocal.SetNum( "TOTALCOST", iCostTotal); + if (pItem->OnTrigger(ITRIG_Buy, pScriptArgs, this->GetChar()) == TRIGRET_RET_TRUE) { iCostTotal -= iItemCost; //If we are blocking the transaction we should not pay for it!. uiItemBlocked++; @@ -1294,13 +1299,13 @@ void CClient::Event_VendorBuy(CChar* pVendor, const VendorItem* items, uint uiIt if (!fPlayerVendor) //NPC vendors { - pItem->SetAmount(pItem->GetAmount() - amount); + pItem->SetAmount(pItem->GetAmount() - wAmount); switch (pItem->GetType()) { case IT_FIGURINE: { - for ( int f = 0; f < amount; ++f ) + for ( int f = 0; f < wAmount; ++f ) m_pChar->Use_Figurine(pItem); goto do_consume; } @@ -1320,9 +1325,9 @@ void CClient::Event_VendorBuy(CChar* pVendor, const VendorItem* items, uint uiIt break; } - if ((amount > 1) && (!pItem->Item_GetDef()->IsStackableType())) + if ((wAmount > 1) && (!pItem->Item_GetDef()->IsStackableType())) { - while (amount--) + while (wAmount--) { CItem * pItemNew = CItem::CreateDupeItem(pItem); pItemNew->SetAmount(1); @@ -1337,7 +1342,7 @@ void CClient::Event_VendorBuy(CChar* pVendor, const VendorItem* items, uint uiIt else { CItem * pItemNew = CItem::CreateDupeItem(pItem); - pItemNew->SetAmount(amount); + pItemNew->SetAmount(wAmount); pItemNew->m_TagDefs.SetNum("NOSAVE", 0, true); if (!pPack->CanContainerHold(pItemNew, m_pChar) || (!m_pChar->CanCarry(pItemNew))) m_pChar->ItemDrop(pItemNew, m_pChar->GetTopPoint()); @@ -1347,7 +1352,7 @@ void CClient::Event_VendorBuy(CChar* pVendor, const VendorItem* items, uint uiIt } else //Player vendors { - if ( pItem->GetAmount() <= amount ) //Buy the whole item + if ( pItem->GetAmount() <= wAmount ) //Buy the whole item { if ((!pPack->CanContainerHold(pItem, m_pChar)) || (!m_pChar->CanCarry(pItem))) m_pChar->ItemDrop(pItem, m_pChar->GetTopPoint()); @@ -1358,11 +1363,11 @@ void CClient::Event_VendorBuy(CChar* pVendor, const VendorItem* items, uint uiIt } else { - pItem->SetAmount(pItem->GetAmount() - amount); + pItem->SetAmount(pItem->GetAmount() - wAmount); CItem *pItemNew = CItem::CreateDupeItem(pItem); pItemNew->m_TagDefs.SetNum("NOSAVE", 0, true); - pItemNew->SetAmount(amount); + pItemNew->SetAmount(wAmount); if ((!pPack->CanContainerHold(pItemNew, m_pChar)) || (!m_pChar->CanCarry(pItemNew))) m_pChar->ItemDrop(pItemNew, m_pChar->GetTopPoint()); else @@ -1471,13 +1476,13 @@ void CClient::Event_VendorSell(CChar* pVendor, const VendorItem* items, uint uiI if ( pItemSell == nullptr ) continue; - word amount = items[i].m_vcAmount; + word wAmount = items[i].m_vcAmount; // Now how much did i say i wanted to sell ? dword dwPrice = 0; - if ( pItem->GetAmount() < amount ) // Selling more than i have ? + if ( pItem->GetAmount() < wAmount ) // Selling more than i have ? { - amount = pItem->GetAmount(); + wAmount = pItem->GetAmount(); } // If OVERRIDE.VALUE is define on the script and this NPC buy this item at a specific price, we use this price in priority @@ -1485,18 +1490,19 @@ void CClient::Event_VendorSell(CChar* pVendor, const VendorItem* items, uint uiI if (pItemSell->GetKey("OVERRIDE.VALUE", true)) { //Get the price on NPC template - dwPrice = pItemSell->GetVendorPrice(iConvertFactor,1) * amount; //Check the value of item on NPC template or itemdef + dwPrice = pItemSell->GetVendorPrice(iConvertFactor,1) * wAmount; //Check the value of item on NPC template or itemdef } else - { + { //Get the price/Value of the real item in the backpack - dwPrice = pItem->GetVendorPrice(iConvertFactor,1) * amount; //Check the value of the item on the player + dwPrice = pItem->GetVendorPrice(iConvertFactor,1) * wAmount; //Check the value of the item on the player } if (( IsTrigUsed(TRIGGER_SELL) ) || ( IsTrigUsed(TRIGGER_ITEMSELL) )) { - CScriptTriggerArgs Args( amount, dwPrice, pVendor ); - if ( pItem->OnTrigger( ITRIG_Sell, this->GetChar(), &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(wAmount, dwPrice, 0, pVendor); + if ( pItem->OnTrigger( ITRIG_Sell, pScriptArgs, this->GetChar() ) == TRIGRET_RET_TRUE ) continue; } @@ -1513,7 +1519,7 @@ void CClient::Event_VendorSell(CChar* pVendor, const VendorItem* items, uint uiI // Take the items from player. // Put items in vendor inventory. - if ( amount >= pItem->GetAmount()) + if ( wAmount >= pItem->GetAmount()) { pItem->RemoveFromView(); if ( pVendor->IsStatFlag(STATF_PET) && pContExtra ) @@ -1529,13 +1535,13 @@ void CClient::Event_VendorSell(CChar* pVendor, const VendorItem* items, uint uiI if ( pVendor->IsStatFlag(STATF_PET) && pContExtra ) { CItem * pItemNew = CItem::CreateDupeItem(pItem); - pItemNew->SetAmount(amount); + pItemNew->SetAmount(wAmount); pContExtra->ContentAdd(pItemNew); } - pItem->SetAmountUpdate( pItem->GetAmount() - amount ); + pItem->SetAmountUpdate( pItem->GetAmount() - wAmount ); if (IsSetOF(OF_VendorStockLimit)) - pItemSell->ConsumeAmount(amount); + pItemSell->ConsumeAmount(wAmount); } } @@ -1580,7 +1586,7 @@ void CClient::Event_Profile( byte fWriteMode, CUID uid, lpctstr pszProfile, int if ( IsTrigUsed(TRIGGER_PROFILE) ) { - if ( pChar->OnTrigger(CTRIG_Profile, m_pChar) == TRIGRET_RET_TRUE ) + if ( pChar->OnTrigger(CTRIG_Profile, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE ) return; } @@ -1605,7 +1611,6 @@ void CClient::Event_Profile( byte fWriteMode, CUID uid, lpctstr pszProfile, int } - void CClient::Event_MailMsg( CUID uid1, CUID uid2 ) { ADDTOCALLSTACK("CClient::Event_MailMsg"); @@ -1624,7 +1629,7 @@ void CClient::Event_MailMsg( CUID uid1, CUID uid2 ) if ( IsTrigUsed(TRIGGER_USERMAILBAG) ) { - if (pChar->OnTrigger(CTRIG_UserMailBag, m_pChar, nullptr) == TRIGRET_RET_TRUE) + if (pChar->OnTrigger(CTRIG_UserMailBag, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE) return; } @@ -1639,7 +1644,6 @@ void CClient::Event_MailMsg( CUID uid1, CUID uid2 ) } - void CClient::Event_ToolTip( CUID uid ) { ADDTOCALLSTACK("CClient::Event_ToolTip"); @@ -1649,7 +1653,7 @@ void CClient::Event_ToolTip( CUID uid ) if (( IsTrigUsed(TRIGGER_TOOLTIP) ) || (( IsTrigUsed(TRIGGER_ITEMTOOLTIP) )&&(pObj->IsItem()))) { - if ( pObj->OnTrigger("@ToolTip", this) == TRIGRET_RET_TRUE ) // CTRIG_ToolTip, ITRIG_ToolTip + if ( pObj->OnTrigger("@ToolTip", CScriptParserBufs::GetCScriptTriggerArgsPtr(), this) == TRIGRET_RET_TRUE ) // CTRIG_ToolTip, ITRIG_ToolTip return; } @@ -1667,7 +1671,7 @@ void CClient::Event_PromptResp( lpctstr pszText, size_t len, dword context1, dwo // result of addPrompt tchar szText[MAX_TALK_BUFFER]; - if ( Str_Check( pszText ) ) + if ( Str_Untrusted_InvalidTermination( pszText, MAX_TALK_BUFFER - 1) ) return; CLIMODE_TYPE promptMode = m_Prompt_Mode; @@ -1855,8 +1859,13 @@ void CClient::Event_Talk_Common(lpctstr pszText) // PC speech return; // Guards are special - lpctstr pszMsgGuards = g_Exp.m_VarDefs.GetKeyStr("guardcall"); - if ( !strnicmp(pszMsgGuards, "", 0) ) + lpctstr pszMsgGuards; + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + pszMsgGuards = Str_mtEngineGetSafeTemp(gReader->m_VarDefs.GetKeyStr("guardcall")); + } + + if ( !strnicmp(pszMsgGuards, "", 0) ) pszMsgGuards = "GUARD,GUARDS"; if ( FindStrWord(pszText, pszMsgGuards) > 0 ) m_pChar->CallGuards(); @@ -2196,7 +2205,7 @@ bool CClient::Event_SetName( CUID uid, const char * pszCharName ) if (!pChar || !m_pChar) return false; - if ( Str_CheckName(pszCharName) || !strlen(pszCharName) ) + if ( Str_Untrusted_InvalidName(pszCharName) || !strlen(pszCharName) ) return false; // Do we have the right to do this ? @@ -2211,10 +2220,10 @@ bool CClient::Event_SetName( CUID uid, const char * pszCharName ) if ( IsTrigUsed(TRIGGER_RENAME) ) { - CScriptTriggerArgs args; - args.m_pO1 = pChar; - args.m_s1 = pszCharName; - if ( m_pChar->OnTrigger(CTRIG_Rename, this, &args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pChar; + pScriptArgs->m_s1 = pszCharName; + if ( m_pChar->OnTrigger(CTRIG_Rename, pScriptArgs, this) == TRIGRET_RET_TRUE ) return false; } if (pChar->IsOwnedBy(m_pChar)) @@ -2344,7 +2353,7 @@ bool CClient::Event_DoubleClick( CUID uid, bool fMacro, bool fTestTouch, bool fS CChar * pChar = static_cast(pObj); if ( IsTrigUsed(TRIGGER_DCLICK) || IsTrigUsed(TRIGGER_CHARDCLICK) ) { - if ( pChar->OnTrigger(CTRIG_DClick, m_pChar) == TRIGRET_RET_TRUE ) + if ( pChar->OnTrigger(CTRIG_DClick, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE ) return true; } @@ -2412,9 +2421,10 @@ void CClient::Event_SingleClick( CUID uid ) if ( IsTrigUsed(TRIGGER_CLICK) || (IsTrigUsed(TRIGGER_ITEMCLICK) && pObj->IsItem()) || (IsTrigUsed(TRIGGER_CHARCLICK) && pObj->IsChar()) ) { - CScriptTriggerArgs Args(this); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = this; // The "@Click" trigger str should be the same between items and chars... - if ( pObj->OnTrigger(CChar::sm_szTrigName[CTRIG_Click], m_pChar, &Args) == TRIGRET_RET_TRUE ) // CTRIG_Click, ITRIG_Click + if ( pObj->OnTrigger(CChar::sm_szTrigName[CTRIG_Click], pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) // CTRIG_Click, ITRIG_Click return; } @@ -2558,8 +2568,8 @@ void CClient::Event_AOSPopupMenuRequest( dword uid ) //construct packet after a } m_pPopupPacket = new PacketDisplayPopup(this, uObj); - CScriptTriggerArgs Args; - bool fPreparePacket = false; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + bool fPreparePacket = false; CItem *pItem = uObj.ItemFind(); CChar *pChar = uObj.CharFind(); @@ -2567,8 +2577,8 @@ void CClient::Event_AOSPopupMenuRequest( dword uid ) //construct packet after a { if ( IsTrigUsed(TRIGGER_CONTEXTMENUREQUEST) || IsTrigUsed(TRIGGER_ITEMCONTEXTMENUREQUEST) ) { - Args.m_iN1 = 1; - pItem->OnTrigger(ITRIG_ContextMenuRequest, GetChar(), &Args); + pScriptArgs->m_iN1 = 1; + pItem->OnTrigger(ITRIG_ContextMenuRequest, pScriptArgs, GetChar()); fPreparePacket = true; // there's no hardcoded stuff for items } else @@ -2582,8 +2592,8 @@ void CClient::Event_AOSPopupMenuRequest( dword uid ) //construct packet after a { if ( IsTrigUsed(TRIGGER_CONTEXTMENUREQUEST) ) { - Args.m_iN1 = 1; - TRIGRET_TYPE iRet = pChar->OnTrigger(CTRIG_ContextMenuRequest, GetChar(), &Args); + pScriptArgs->m_iN1 = 1; + TRIGRET_TYPE iRet = pChar->OnTrigger(CTRIG_ContextMenuRequest, pScriptArgs, GetChar()); if ( iRet == TRIGRET_RET_TRUE ) fPreparePacket = true; } @@ -2592,7 +2602,7 @@ void CClient::Event_AOSPopupMenuRequest( dword uid ) //construct packet after a { delete m_pPopupPacket; m_pPopupPacket = nullptr; - return; + return; } if ( pChar && !fPreparePacket ) @@ -2714,10 +2724,10 @@ void CClient::Event_AOSPopupMenuRequest( dword uid ) //construct packet after a m_pPopupPacket->addOption(POPUP_TRADE_OPEN, 1077728, POPUPFLAG_COLOR, 0xFFFF); } - if ( (Args.m_iN1 != 1) && (IsTrigUsed(TRIGGER_CONTEXTMENUREQUEST)) ) + if ( (pScriptArgs->m_iN1 != 1) && (IsTrigUsed(TRIGGER_CONTEXTMENUREQUEST)) ) { - Args.m_iN1 = 2; - pChar->OnTrigger(CTRIG_ContextMenuRequest, GetChar(), &Args); + pScriptArgs->m_iN1 = 2; + pChar->OnTrigger(CTRIG_ContextMenuRequest, pScriptArgs, GetChar()); } } @@ -2746,14 +2756,14 @@ void CClient::Event_AOSPopupMenuSelect(dword uid, word EntryTag) //do something if ( !IsSetOF(OF_NoContextMenuLOS) && !m_pChar->CanSeeLOS(pObj) ) return; - CScriptTriggerArgs Args; - CItem *pItem = uObj.ItemFind(); - if ( pItem ) + CItem *pItem = uObj.ItemFind(); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + if ( pItem ) { if ( IsTrigUsed(TRIGGER_CONTEXTMENUSELECT) || IsTrigUsed(TRIGGER_ITEMCONTEXTMENUSELECT) ) { - Args.m_iN1 = EntryTag; - pItem->OnTrigger(ITRIG_ContextMenuSelect, GetChar(), &Args); + pScriptArgs->m_iN1 = EntryTag; + pItem->OnTrigger(ITRIG_ContextMenuSelect, pScriptArgs, GetChar()); } return; // there's no hardcoded stuff for items } @@ -2764,8 +2774,8 @@ void CClient::Event_AOSPopupMenuSelect(dword uid, word EntryTag) //do something if ( IsTrigUsed(TRIGGER_CONTEXTMENUSELECT) ) { - Args.m_iN1 = EntryTag; - if ( pChar->OnTrigger(CTRIG_ContextMenuSelect, GetChar(), &Args) == TRIGRET_RET_TRUE ) + pScriptArgs->m_iN1 = EntryTag; + if ( pChar->OnTrigger(CTRIG_ContextMenuSelect, pScriptArgs, GetChar()) == TRIGRET_RET_TRUE ) return; } @@ -2909,17 +2919,18 @@ void CClient::Event_AOSPopupMenuSelect(dword uid, word EntryTag) //do something void CClient::Event_BugReport( const tchar * pszText, int len, BUGREPORT_TYPE type, CLanguageID lang ) { ADDTOCALLSTACK("CClient::Event_BugReport"); - UnreferencedParameter(len); + UnreferencedParameter(len); if ( !m_pChar ) return; if ( IsTrigUsed(TRIGGER_USERBUGREPORT) ) { - CScriptTriggerArgs Args(type); - Args.m_s1 = pszText; - Args.m_VarsLocal.SetStr("LANG", false, lang.GetStr()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = type; + pScriptArgs->m_s1 = pszText; + pScriptArgs->m_VarsLocal.SetStr("LANG", false, lang.GetStr()); - m_pChar->OnTrigger(CTRIG_UserBugReport, m_pChar, &Args); + m_pChar->OnTrigger(CTRIG_UserBugReport, pScriptArgs, m_pChar); } } @@ -2931,8 +2942,9 @@ void CClient::Event_UseToolbar(byte bType, dword dwArg) if ( IsTrigUsed(TRIGGER_USERKRTOOLBAR) ) { - CScriptTriggerArgs Args( bType, dwArg ); - if ( m_pChar->OnTrigger( CTRIG_UserKRToolbar, m_pChar, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(bType, dwArg, 0, nullptr); + if ( m_pChar->OnTrigger( CTRIG_UserKRToolbar, pScriptArgs, m_pChar ) == TRIGRET_RET_TRUE ) return; } @@ -2978,22 +2990,23 @@ void CClient::Event_ExtCmd( EXTCMD_TYPE type, tchar *pszName ) byte bDoorAutoDist = 1; if ( IsTrigUsed(TRIGGER_USEREXTCMD) ) - { - CScriptTriggerArgs Args(pszName); - Args.m_iN1 = type; + { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pszName); + pScriptArgs->m_iN1 = type; if (type == EXTCMD_DOOR_AUTO) - Args.m_VarsLocal.SetNumNew("DoorAutoDist", bDoorAutoDist); + pScriptArgs->m_VarsLocal.SetNumNew("DoorAutoDist", bDoorAutoDist); - if ( m_pChar->OnTrigger(CTRIG_UserExtCmd, m_pChar, &Args) == TRIGRET_RET_TRUE ) + if ( m_pChar->OnTrigger(CTRIG_UserExtCmd, pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) return; if (type == EXTCMD_DOOR_AUTO) { - bDoorAutoDist = (byte)std::clamp(Args.m_VarsLocal.GetKeyNum("DoorAutoDist"), (int64)0, (int64)UO_MAP_VIEW_SIGHT); + bDoorAutoDist = (byte)std::clamp(pScriptArgs->m_VarsLocal.GetKeyNum("DoorAutoDist"), (int64)0, (int64)UO_MAP_VIEW_SIGHT); } - Str_CopyLimitNull(pszName, Args.m_s1, MAX_TALK_BUFFER); + Str_CopyLimitNull(pszName, pScriptArgs->m_s1, MAX_TALK_BUFFER); } tchar *ppArgs[2]; @@ -3117,9 +3130,10 @@ void CClient::Event_ExtCmd( EXTCMD_TYPE type, tchar *pszName ) return; int iVirtueID = ppArgs[0][0] - '0'; // 0x1=Honor, 0x2=Sacrifice, 0x3=Valor - CScriptTriggerArgs Args(m_pChar); - Args.m_iN1 = iVirtueID; - m_pChar->OnTrigger(CTRIG_UserVirtueInvoke, m_pChar, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = m_pChar; + pScriptArgs->m_iN1 = iVirtueID; + m_pChar->OnTrigger(CTRIG_UserVirtueInvoke, pScriptArgs, m_pChar); return; } @@ -3141,28 +3155,29 @@ bool CClient::xPacketFilter( const byte * pData, uint iLen ) EXC_TRY("packet filter"); if ( iLen > 0 && g_Serv.m_PacketFilter[pData[0]][0] ) { - CScriptTriggerArgs Args(pData[0]); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = pData[0]; enum TRIGRET_TYPE trigReturn; tchar idx[12]; - Args.m_s1 = GetPeerStr(); - Args.m_pO1 = this; // Yay for ARGO.SENDPACKET - Args.m_VarsLocal.SetNum("CONNECTIONTYPE", GetConnectType()); + pScriptArgs->m_s1 = GetPeerStr(); + pScriptArgs->m_pO1 = this; // Yay for ARGO.SENDPACKET + pScriptArgs->m_VarsLocal.SetNum("CONNECTIONTYPE", GetConnectType()); uint bytes = iLen; uint bytestr = minimum(bytes, SCRIPT_MAX_LINE_LEN); tchar *zBuf = Str_GetTemp(); - Args.m_VarsLocal.SetNum("NUM", bytes); + pScriptArgs->m_VarsLocal.SetNum("NUM", bytes); memcpy(zBuf, &(pData[0]), bytestr); zBuf[bytestr] = 0; - Args.m_VarsLocal.SetStr("STR", true, zBuf); + pScriptArgs->m_VarsLocal.SetStr("STR", true, zBuf); if ( m_pAccount ) { - Args.m_VarsLocal.SetStr("ACCOUNT", false, m_pAccount->GetName()); + pScriptArgs->m_VarsLocal.SetStr("ACCOUNT", false, m_pAccount->GetName()); if ( m_pChar ) { - Args.m_VarsLocal.SetNum("CHAR", m_pChar->GetUID().GetObjUID()); + pScriptArgs->m_VarsLocal.SetNum("CHAR", m_pChar->GetUID().GetObjUID()); } } @@ -3170,11 +3185,11 @@ bool CClient::xPacketFilter( const byte * pData, uint iLen ) for ( uint i = 0; i < bytes; ++i ) { snprintf(idx, sizeof(idx), "%u", i); - Args.m_VarsLocal.SetNum(idx, (int)(pData[i])); + pScriptArgs->m_VarsLocal.SetNum(idx, (int)(pData[i])); } // Call the filtering function - if ( g_Serv.r_Call(g_Serv.m_PacketFilter[pData[0]], &g_Serv, &Args, nullptr, &trigReturn) ) + if ( g_Serv.r_Call(g_Serv.m_PacketFilter[pData[0]], pScriptArgs, &g_Serv, nullptr, &trigReturn) ) if ( trigReturn == TRIGRET_RET_TRUE ) return true; // do not cry about errors } @@ -3190,28 +3205,29 @@ bool CClient::xOutPacketFilter( const byte * pData, uint iLen ) EXC_TRY("Outgoing packet filter"); if ( iLen > 0 && g_Serv.m_OutPacketFilter[pData[0]][0] ) { - CScriptTriggerArgs Args(pData[0]); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = pData[0]; enum TRIGRET_TYPE trigReturn; tchar idx[12]; - Args.m_s1 = GetPeerStr(); - Args.m_pO1 = this; - Args.m_VarsLocal.SetNum("CONNECTIONTYPE", GetConnectType()); + pScriptArgs->m_s1 = GetPeerStr(); + pScriptArgs->m_pO1 = this; + pScriptArgs->m_VarsLocal.SetNum("CONNECTIONTYPE", GetConnectType()); size_t bytes = iLen; size_t bytestr = minimum(bytes, SCRIPT_MAX_LINE_LEN); tchar *zBuf = Str_GetTemp(); - Args.m_VarsLocal.SetNum("NUM", bytes); + pScriptArgs->m_VarsLocal.SetNum("NUM", bytes); memcpy(zBuf, &(pData[0]), bytestr); zBuf[bytestr] = 0; - Args.m_VarsLocal.SetStr("STR", true, zBuf); + pScriptArgs->m_VarsLocal.SetStr("STR", true, zBuf); if ( m_pAccount ) { - Args.m_VarsLocal.SetStr("ACCOUNT", false, m_pAccount->GetName()); + pScriptArgs->m_VarsLocal.SetStr("ACCOUNT", false, m_pAccount->GetName()); if ( m_pChar ) { - Args.m_VarsLocal.SetNum("CHAR", m_pChar->GetUID().GetObjUID()); + pScriptArgs->m_VarsLocal.SetNum("CHAR", m_pChar->GetUID().GetObjUID()); } } @@ -3219,11 +3235,11 @@ bool CClient::xOutPacketFilter( const byte * pData, uint iLen ) for ( size_t i = 0; i < bytes; ++i ) { snprintf(idx, sizeof(idx), "%" PRIuSIZE_T, i); - Args.m_VarsLocal.SetNum(idx, (int)(pData[i])); + pScriptArgs->m_VarsLocal.SetNum(idx, (int)(pData[i])); } // Call the filtering function - if ( g_Serv.r_Call(g_Serv.m_OutPacketFilter[pData[0]], &g_Serv, &Args, nullptr, &trigReturn) ) + if ( g_Serv.r_Call(g_Serv.m_OutPacketFilter[pData[0]], pScriptArgs, &g_Serv, nullptr, &trigReturn) ) if ( trigReturn == TRIGRET_RET_TRUE ) return true; } diff --git a/src/game/clients/CClientLog.cpp b/src/game/clients/CClientLog.cpp index f034b0e24..192deb78a 100644 --- a/src/game/clients/CClientLog.cpp +++ b/src/game/clients/CClientLog.cpp @@ -4,8 +4,9 @@ #include "../../common/crypto/CHuffman.h" #include "../../common/sphere_library/CSFileList.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/CIPHistoryManager.h" #include "../../network/CNetworkManager.h" #include "../../network/send.h" @@ -177,8 +178,7 @@ bool CClient::addLoginErr(byte code) break; } - if ( GetNetState()->m_clientVersionNumber || GetNetState()->m_reportedVersionNumber ) // only reply the packet to valid clients - new PacketLoginError(this, static_cast(code)); + new PacketLoginError(this, static_cast(code)); GetNetState()->markReadClosed(); return false; } @@ -477,12 +477,14 @@ bool CClient::OnRxAxis( const byte * pData, uint iLen ) } if (GetPeer().IsValidAddr()) { - CScriptTriggerArgs Args; - Args.m_VarsLocal.SetStrNew("Account",GetName()); - Args.m_VarsLocal.SetStrNew("IP",GetPeer().GetAddrStr()); - TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; - r_Call("f_axis_preload", this, &Args, nullptr, &tRet); - if ( tRet == TRIGRET_RET_FALSE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetStrNew("Account",GetName()); + pScriptArgs->m_VarsLocal.SetStrNew("IP",GetPeer().GetAddrStr()); + + TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; + r_Call("f_axis_preload", pScriptArgs, this, nullptr, &tRet); + + if ( tRet == TRIGRET_RET_FALSE ) return false; if ( tRet == TRIGRET_RET_TRUE ) { diff --git a/src/game/clients/CClientMsg.cpp b/src/game/clients/CClientMsg.cpp index adfafa2e8..80e81ad30 100644 --- a/src/game/clients/CClientMsg.cpp +++ b/src/game/clients/CClientMsg.cpp @@ -3,8 +3,9 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/send.h" #include "../chars/CChar.h" #include "../chars/CCharNPC.h" @@ -218,7 +219,7 @@ void CClient::resendBuffs() const } } -void CClient::addBuff( const BUFF_ICONS IconId, const dword ClilocOne, const dword ClilocTwo, const word durationSeconds, lpctstr* pArgs, uint uiArgCount) const +void CClient::addBuff( const BUFF_ICONS IconId, const dword ClilocOne, const dword ClilocTwo, const word durationSeconds, lpctstr* pptcArgs, uint uiArgCount) const { ADDTOCALLSTACK("CClient::addBuff"); if ( !IsSetOF(OF_Buffs) ) @@ -226,7 +227,7 @@ void CClient::addBuff( const BUFF_ICONS IconId, const dword ClilocOne, const dwo if ( PacketBuff::CanSendTo(GetNetState()) == false ) return; - new PacketBuff(this, IconId, ClilocOne, ClilocTwo, durationSeconds, pArgs, uiArgCount); + new PacketBuff(this, IconId, ClilocOne, ClilocTwo, durationSeconds, pptcArgs, uiArgCount); } void CClient::removeBuff(const BUFF_ICONS IconId) const @@ -288,7 +289,7 @@ void CClient::closeContainer( const CObjBase * pObj ) const new PacketCloseContainer(this, pObj); } -void CClient::closeUIWindow( const CObjBase* pObj, PacketCloseUIWindow::UIWindow windowType ) const +void CClient::closeUIWindow(const CObjBase* pObj, PacketCloseUIWindowType windowType ) const { ADDTOCALLSTACK("CClient::closeUIWindow"); new PacketCloseUIWindow(this, pObj, windowType); @@ -573,21 +574,22 @@ void CClient::addArrowQuest( int x, int y, int id ) const { ADDTOCALLSTACK("CClient::addArrowQuest"); - CScriptTriggerArgs args(x, y); - if (this->GetNetState()->isClientVersionNumber(MINCLIVER_HS) || this->GetNetState()->isClientEnhanced()) - args.m_iN3 = id; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(x, y, 0, nullptr); + if (GetNetState()->isClientVersionNumber(MINCLIVER_HS) || GetNetState()->isClientEnhanced()) + pScriptArgs->m_iN3 = id; else - args.m_iN3 = 0; + pScriptArgs->m_iN3 = 0; if (x > 0) { if (IsTrigUsed(TRIGGER_ARROWQUEST_ADD)) - m_pChar->OnTrigger(CTRIG_ArrowQuest_Add, m_pChar, &args); + m_pChar->OnTrigger(CTRIG_ArrowQuest_Add, pScriptArgs, m_pChar); } else { if (IsTrigUsed(TRIGGER_ARROWQUEST_CLOSE)) - m_pChar->OnTrigger(CTRIG_ArrowQuest_Close, m_pChar, &args); + m_pChar->OnTrigger(CTRIG_ArrowQuest_Close, pScriptArgs, m_pChar); } new PacketArrowQuest(this, x, y, id); @@ -709,7 +711,7 @@ void CClient::addBarkLocalizedEx( int iClilocId, const CObjBaseTemplate * pSrc, new PacketMessageLocalisedEx(this, iClilocId, pSrc, wHue, mode, font, affix, pAffix, pArgs); } -void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, bool fUnicode, lpctstr name) const +void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_TYPE wHue, TALKMODE_TYPE mode, FONT_TYPE font, bool fUnicode, lpctstr ptcName) const { ADDTOCALLSTACK("CClient::addBarkParse"); if ( !pszText ) @@ -718,62 +720,78 @@ void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_ HUE_TYPE defaultHue = HUE_TEXT_DEF; FONT_TYPE defaultFont = FONT_NORMAL; bool defaultUnicode = false; + bool fUseSpeechHueOverride = false; bool fUseEmoteHueOverride = false; - const CChar * pSrcChar = nullptr; + + const CChar * pSrcChar = nullptr; if (pSrc && pSrc->IsChar()) pSrcChar = static_cast(pSrc); - switch ( mode ) + static constexpr uint kiManagedTalkmodes = 5; + static constexpr lpctstr s_ptcTalkmodesDefsColor[kiManagedTalkmodes] = + { + "SMSG_DEF_COLOR", + "EMOTE_DEF_COLOR", + "SAY_DEF_COLOR", + "CMSG_DEF_COLOR", + "IMSG_DEF_COLOR", + }; + static constexpr lpctstr s_ptcTalkmodesDefsFont[kiManagedTalkmodes] = + { + "SMSG_DEF_FONT", + "EMOTE_DEF_FONT", + "SAY_DEF_FONT", + "CMSG_DEF_FONT", + "IMSG_DEF_FONT", + }; + static constexpr lpctstr s_ptcTalkmodesDefsUnicode[kiManagedTalkmodes] = + { + "SMSG_DEF_UNICODE", + "EMOTE_DEF_UNICODE", + "SAY_DEF_UNICODE", + "CMSG_DEF_UNICODE", + "IMSG_DEF_UNICODE", + }; + + ushort iTalkmodeHue, iTalkmodeFont, iTalkmodeUnicode; + iTalkmodeHue = iTalkmodeFont = iTalkmodeUnicode = UINT16_MAX; + switch ( mode ) { case TALKMODE_SYSTEM: - { talkmode_system: - defaultHue = (HUE_TYPE)(g_Exp.m_VarDefs.GetKeyNum("SMSG_DEF_COLOR")); - defaultFont = (FONT_TYPE)(g_Exp.m_VarDefs.GetKeyNum("SMSG_DEF_FONT")); - defaultUnicode = g_Exp.m_VarDefs.GetKeyNum("SMSG_DEF_UNICODE") > 0 ? true : false; + iTalkmodeHue = iTalkmodeFont = iTalkmodeUnicode = 0; break; - } case TALKMODE_EMOTE: - { fUseEmoteHueOverride = true; - defaultHue = (HUE_TYPE)(g_Exp.m_VarDefs.GetKeyNum("EMOTE_DEF_COLOR")); - defaultFont = (FONT_TYPE)(g_Exp.m_VarDefs.GetKeyNum("EMOTE_DEF_FONT")); - defaultUnicode = g_Exp.m_VarDefs.GetKeyNum("EMOTE_DEF_UNICODE") > 0 ? true : false; + iTalkmodeHue = iTalkmodeFont = iTalkmodeUnicode = 1; break; - } case TALKMODE_SAY: - { if (pSrc == nullptr) - { - // It's a SYSMESSAGE - goto talkmode_system; - } + goto talkmode_system; // It's a SYSMESSAGE fUseSpeechHueOverride = true; - defaultHue = (HUE_TYPE)(g_Exp.m_VarDefs.GetKeyNum("SAY_DEF_COLOR")); - defaultFont = (FONT_TYPE)(g_Exp.m_VarDefs.GetKeyNum("SAY_DEF_FONT")); - defaultUnicode = g_Exp.m_VarDefs.GetKeyNum("SAY_DEF_UNICODE") > 0 ? true : false; + iTalkmodeHue = iTalkmodeFont = iTalkmodeUnicode = 2; break; - } case TALKMODE_ITEM: - { if ( pSrc && pSrc->IsChar() ) - { - defaultFont = (FONT_TYPE)(g_Exp.m_VarDefs.GetKeyNum("CMSG_DEF_FONT")); - defaultUnicode = g_Exp.m_VarDefs.GetKeyNum("CMSG_DEF_UNICODE") > 0 ? true : false; - } + iTalkmodeHue = iTalkmodeUnicode = 3; else - { - defaultHue = (HUE_TYPE)(g_Exp.m_VarDefs.GetKeyNum("IMSG_DEF_COLOR")); - defaultFont = (FONT_TYPE)(g_Exp.m_VarDefs.GetKeyNum("IMSG_DEF_FONT")); - defaultUnicode = g_Exp.m_VarDefs.GetKeyNum("IMSG_DEF_UNICODE") > 0 ? true : false; - } - } + iTalkmodeHue = iTalkmodeFont = iTalkmodeUnicode = 4; break; default: break; } + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + if (iTalkmodeHue != UINT16_MAX) + defaultHue = (HUE_TYPE)(gReader->m_VarDefs.GetKeyNum(s_ptcTalkmodesDefsColor[iTalkmodeHue])); + if (iTalkmodeFont != UINT16_MAX) + defaultFont = (FONT_TYPE)(gReader->m_VarDefs.GetKeyNum(s_ptcTalkmodesDefsFont[iTalkmodeFont])); + if (iTalkmodeUnicode != UINT16_MAX) + defaultUnicode = gReader->m_VarDefs.GetKeyNum(s_ptcTalkmodesDefsUnicode[iTalkmodeUnicode]) > 0 ? true : false; + } + word Args[] = { (word)wHue, (word)font, (word)fUnicode }; lptstr ptcBarkBuffer = Str_GetTemp(); // Be sure to init this before the goto instruction @@ -843,7 +861,7 @@ void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_ if ( Args[2] == 0 ) Args[2] = (word)defaultUnicode; - Str_CopyLimitNull( ptcBarkBuffer, name, Str_TempLength()); + Str_CopyLimitNull( ptcBarkBuffer, ptcName, Str_TempLength()); Str_ConcatLimitNull(ptcBarkBuffer, pszText, Str_TempLength()); if (mode == TALKMODE_SPELL) //Set TALKMODE_SPELL to TALKMODE_SAY after every color check completed to block spell flood. @@ -858,14 +876,15 @@ void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_ int iClilocId = Exp_GetVal( pszText ); //pszText holds the cliloc number, we can't use ppArgs[0] because if the string name exists it will contain the speaker name along with the cliloc number. int iAffixType = Exp_GetVal( ppArgs[1] ); CSString CArgs; + for (int i = 3; i < iQty; ++i ) { - if ( CArgs.GetLength() ) + if ( CArgs.GetLength() ) CArgs += "\t"; CArgs += ( !strcmp(ppArgs[i], "NULL") ? " " : ppArgs[i] ); } - addBarkLocalizedEx( iClilocId, pSrc, (HUE_TYPE)(Args[0]), mode, (FONT_TYPE)(Args[1]), (AFFIX_TYPE)(iAffixType), ppArgs[2], CArgs.GetBuffer()); + addBarkLocalizedEx( iClilocId, pSrc, (HUE_TYPE)(Args[0]), mode, (FONT_TYPE)(Args[1]), (AFFIX_TYPE)(iAffixType), ppArgs[2], CArgs.GetBuffer()); break; } @@ -877,12 +896,12 @@ void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_ CSString CArgs; for ( int i = 1; i < iQty; ++i ) { - if ( CArgs.GetLength() ) + if ( CArgs.GetLength() ) CArgs += "\t"; CArgs += ( !strcmp(ppArgs[i], "NULL") ? " " : ppArgs[i] ); } - addBarkLocalized( iClilocId, pSrc, (HUE_TYPE)(Args[0]), mode, (FONT_TYPE)(Args[1]), CArgs.GetBuffer()); + addBarkLocalized( iClilocId, pSrc, (HUE_TYPE)(Args[0]), mode, (FONT_TYPE)(Args[1]), CArgs.GetBuffer()); break; } @@ -900,7 +919,7 @@ void CClient::addBarkParse( lpctstr pszText, const CObjBaseTemplate * pSrc, HUE_ bark_default: if (ptcBarkBuffer[0] == '\0') { - Str_CopyLimitNull(ptcBarkBuffer, name, Str_TempLength()); + Str_CopyLimitNull(ptcBarkBuffer, ptcName, Str_TempLength()); Str_ConcatLimitNull(ptcBarkBuffer, pszText, Str_TempLength()); } @@ -1015,8 +1034,13 @@ void CClient::GetAdjustedItemID( const CChar * pChar, const CItem * pItem, ITEMI { tchar * sMountDefname = Str_GetTemp(); sprintf(sMountDefname, "mount_0x%x", idHorse); - ITEMID_TYPE idMountItem = (ITEMID_TYPE)(g_Exp.m_VarDefs.GetKeyNum(sMountDefname)); - if ( idMountItem > ITEMID_NOTHING ) + + ITEMID_TYPE idMountItem; + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + idMountItem = (ITEMID_TYPE)(gReader->m_VarDefs.GetKeyNum(sMountDefname)); + } + if ( idMountItem > ITEMID_NOTHING ) { id = idMountItem; pItemDef = CItemBase::FindItemBase(id); @@ -1291,21 +1315,22 @@ void CClient::addItemName( CItem * pItem ) if (( IsTrigUsed(TRIGGER_AFTERCLICK) ) || ( IsTrigUsed(TRIGGER_ITEMAFTERCLICK) )) { - CScriptTriggerArgs Args( this ); - Args.m_VarsLocal.SetStrNew("ClickMsgText", &szName[0]); - Args.m_VarsLocal.SetNumNew("ClickMsgHue", (int64)(wHue)); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(0, 0, 0, this); + pScriptArgs->m_VarsLocal.SetStrNew("ClickMsgText", &szName[0]); + pScriptArgs->m_VarsLocal.SetNumNew("ClickMsgHue", (int64)(wHue)); - TRIGRET_TYPE ret = pItem->OnTrigger( "@AfterClick", m_pChar, &Args ); // CTRIG_AfterClick, ITRIG_AfterClick + TRIGRET_TYPE ret = pItem->OnTrigger( "@AfterClick", pScriptArgs, m_pChar ); // CTRIG_AfterClick, ITRIG_AfterClick if ( ret == TRIGRET_RET_TRUE ) return; - lpctstr pNewStr = Args.m_VarsLocal.GetKeyStr("ClickMsgText"); + lpctstr pNewStr = pScriptArgs->m_VarsLocal.GetKeyStr("ClickMsgText"); if ( pNewStr != nullptr ) Str_CopyLimitNull(szName, pNewStr, ARRAY_COUNT(szName)); - wHue = (HUE_TYPE)(Args.m_VarsLocal.GetKeyNum("ClickMsgHue")); + wHue = (HUE_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("ClickMsgHue")); } addObjMessage( szName, pItem, wHue, TALKMODE_ITEM ); @@ -1415,21 +1440,23 @@ void CClient::addCharName( const CChar * pChar ) // Singleclick text for a chara if ( IsTrigUsed(TRIGGER_AFTERCLICK) ) { - CScriptTriggerArgs Args( this ); - Args.m_VarsLocal.SetStrNew("ClickMsgText", pszTemp); - Args.m_VarsLocal.SetNumNew("ClickMsgHue", (int64)(wHue)); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(0, 0, 0, this); + pScriptArgs->m_VarsLocal.SetStrNew("ClickMsgText", pszTemp); + pScriptArgs->m_VarsLocal.SetNumNew("ClickMsgHue", (int64)(wHue)); - TRIGRET_TYPE ret = const_cast(pChar)->OnTrigger( "@AfterClick", m_pChar, &Args ); // CTRIG_AfterClick, ITRIG_AfterClick + // TODO: const correctness... + TRIGRET_TYPE ret = const_cast(pChar)->OnTrigger( "@AfterClick", pScriptArgs, m_pChar ); // CTRIG_AfterClick, ITRIG_AfterClick if ( ret == TRIGRET_RET_TRUE ) return; - lpctstr pNewStr = Args.m_VarsLocal.GetKeyStr("ClickMsgText"); + lpctstr pNewStr = pScriptArgs->m_VarsLocal.GetKeyStr("ClickMsgText"); if ( pNewStr != nullptr ) Str_CopyLimitNull(pszTemp, pNewStr, Str_TempLength()); - wHue = (HUE_TYPE)(Args.m_VarsLocal.GetKeyNum("ClickMsgHue")); + wHue = (HUE_TYPE)(pScriptArgs->m_VarsLocal.GetKeyNum("ClickMsgHue")); } addObjMessage( pszTemp, pChar, wHue, TALKMODE_ITEM ); @@ -1567,7 +1594,7 @@ uint CClient::Setup_FillCharList(Packet* pPacket, const CChar * pCharFirst) { m_tmSetupCharList[0] = pCharFirst->GetUID(); - pPacket->writeStringFixedASCII(pCharFirst->GetName(), MAX_NAME_SIZE); + pPacket->writeStringFixedASCII(pCharFirst->GetNameWithoutIncognito(), MAX_NAME_SIZE); pPacket->writeStringFixedASCII("", MAX_NAME_SIZE); ++count; @@ -1593,7 +1620,7 @@ uint CClient::Setup_FillCharList(Packet* pPacket, const CChar * pCharFirst) m_tmSetupCharList[count] = uid; - pPacket->writeStringFixedASCII(pChar->GetName(), MAX_NAME_SIZE); + pPacket->writeStringFixedASCII(pChar->GetNameWithoutIncognito(), MAX_NAME_SIZE); pPacket->writeStringFixedASCII("", MAX_NAME_SIZE); ++count; @@ -1632,9 +1659,9 @@ void CClient::SetTargMode( CLIMODE_TYPE targmode, lpctstr pPrompt, int64 iTimeou { if ( IsTrigUsed(TRIGGER_TARGON_CANCEL) ) { - CScriptTriggerArgs Args; - Args.m_s1 = m_Targ_Text; - if (pCharThis->OnTrigger( CTRIG_Targon_Cancel, pCharThis, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_s1 = m_Targ_Text; + if (pCharThis->OnTrigger( CTRIG_Targon_Cancel, pScriptArgs, pCharThis ) == TRIGRET_RET_TRUE ) fSuppressCancelMessage = true; } } break; @@ -1643,7 +1670,7 @@ void CClient::SetTargMode( CLIMODE_TYPE targmode, lpctstr pPrompt, int64 iTimeou CItem * pItemUse = m_Targ_UID.ItemFind(); if (pItemUse && (IsTrigUsed(TRIGGER_TARGON_CANCEL) || IsTrigUsed(TRIGGER_ITEMTARGON_CANCEL))) { - if ( pItemUse->OnTrigger( ITRIG_TARGON_CANCEL, pCharThis) == TRIGRET_RET_TRUE ) + if ( pItemUse->OnTrigger( ITRIG_TARGON_CANCEL, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharThis) == TRIGRET_RET_TRUE ) fSuppressCancelMessage = true; } } break; @@ -1653,11 +1680,12 @@ void CClient::SetTargMode( CLIMODE_TYPE targmode, lpctstr pPrompt, int64 iTimeou const CSpellDef* pSpellDef = g_Cfg.GetSpellDef(m_tmSkillMagery.m_iSpell); if (pSpellDef) { - CScriptTriggerArgs Args(m_tmSkillMagery.m_iSpell, 0, m_Targ_Prv_UID.ObjFind()); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(m_tmSkillMagery.m_iSpell, 0, 0, m_Targ_Prv_UID.ObjFind()); if ( IsTrigUsed(TRIGGER_SPELLTARGETCANCEL) ) { - if (pCharThis->OnTrigger( CTRIG_SpellTargetCancel, this, &Args ) == TRIGRET_RET_TRUE ) + if (pCharThis->OnTrigger( CTRIG_SpellTargetCancel, pScriptArgs, this ) == TRIGRET_RET_TRUE ) { fSuppressCancelMessage = true; break; @@ -1666,7 +1694,7 @@ void CClient::SetTargMode( CLIMODE_TYPE targmode, lpctstr pPrompt, int64 iTimeou if ( IsTrigUsed(TRIGGER_TARGETCANCEL) ) { - if (pCharThis->Spell_OnTrigger( m_tmSkillMagery.m_iSpell, SPTRIG_TARGETCANCEL, pCharThis, &Args ) == TRIGRET_RET_TRUE ) + if (pCharThis->Spell_OnTrigger( m_tmSkillMagery.m_iSpell, SPTRIG_TARGETCANCEL, pScriptArgs, pCharThis ) == TRIGRET_RET_TRUE ) fSuppressCancelMessage = true; } } @@ -1700,7 +1728,7 @@ void CClient::SetTargMode( CLIMODE_TYPE targmode, lpctstr pPrompt, int64 iTimeou { if ( IsTrigUsed(TRIGGER_SKILLTARGETCANCEL) ) { - if (pCharThis->Skill_OnCharTrigger(action, CTRIG_SkillTargetCancel) == TRIGRET_RET_TRUE ) + if (pCharThis->Skill_OnCharTrigger(action, CTRIG_SkillTargetCancel, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) { fSuppressCancelMessage = true; break; @@ -1708,7 +1736,7 @@ void CClient::SetTargMode( CLIMODE_TYPE targmode, lpctstr pPrompt, int64 iTimeou } if ( IsTrigUsed(TRIGGER_TARGETCANCEL) ) { - if (pCharThis->Skill_OnTrigger(action, SKTRIG_TARGETCANCEL) == TRIGRET_RET_TRUE ) + if (pCharThis->Skill_OnTrigger(action, SKTRIG_TARGETCANCEL, CScriptParserBufs::GetCScriptTriggerArgsPtr()) == TRIGRET_RET_TRUE ) fSuppressCancelMessage = true; } } @@ -1906,8 +1934,9 @@ void CClient::addSkillWindow(SKILL_TYPE skill, bool fFromInfo) const // Opens th if ( IsTrigUsed(TRIGGER_USERSKILLS) ) { - CScriptTriggerArgs Args(fAllSkills? -1 : skill, fFromInfo); - if (m_pChar->OnTrigger(CTRIG_UserSkills, pChar, &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init((fAllSkills? -1 : skill), fFromInfo, 0, nullptr); + if (m_pChar->OnTrigger(CTRIG_UserSkills, pScriptArgs, pChar) == TRIGRET_RET_TRUE) return; } @@ -2182,9 +2211,9 @@ void CClient::addStatusWindow( CObjBase *pObj, bool fRequested ) // Opens the st if ( IsTrigUsed(TRIGGER_USERSTATS) ) { - CScriptTriggerArgs Args(0, 0, pObj); - Args.m_iN3 = fRequested; - if ( m_pChar->OnTrigger(CTRIG_UserStats, dynamic_cast(pObj), &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(0, 0, fRequested, pObj); + if ( m_pChar->OnTrigger(CTRIG_UserStats, pScriptArgs, dynamic_cast(pObj)) == TRIGRET_RET_TRUE ) return; } @@ -2273,8 +2302,9 @@ void CClient::addSpellbookOpen( CItem * pBook ) if ( IsTrigUsed(TRIGGER_SPELLBOOK) ) { - CScriptTriggerArgs Args( 0, 0, pBook ); - if ( m_pChar->OnTrigger( CTRIG_SpellBook, m_pChar, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init( 0, 0, 0, pBook ); + if ( m_pChar->OnTrigger( CTRIG_SpellBook, pScriptArgs, m_pChar ) == TRIGRET_RET_TRUE ) return; } @@ -2616,7 +2646,7 @@ void CClient::addGlobalChatStatusToggle() } tchar* pszXML = Str_GetTemp(); - sprintf(pszXML, "", + sprintf(pszXML, "", CGlobalChatChanMember::GetJID(), static_cast(CSTime::GetCurrentTime().GetTime()), m_pChar->GetName(), iShow); CGlobalChatChanMember::SetVisible(static_cast(iShow)); @@ -2660,7 +2690,7 @@ void CClient::addCharPaperdoll( CChar * pChar ) if (IsTrigUsed(TRIGGER_SENDPAPERDOLL)) { - pChar->OnTrigger(CTRIG_SendPaperdoll, m_pChar); + pChar->OnTrigger(CTRIG_SendPaperdoll, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar); } new PacketPaperdoll(this, pChar); @@ -2790,15 +2820,16 @@ byte CClient::Setup_Start( CChar * pChar ) // Send character startup stuff to pl bool fQuickLogIn = pChar->LayerFind(LAYER_FLAG_ClientLinger); if ( IsTrigUsed(TRIGGER_LOGIN) ) { - CScriptTriggerArgs Args( fNoMessages, fQuickLogIn ); - if ( pChar->OnTrigger( CTRIG_LogIn, pChar, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(fNoMessages, fQuickLogIn, 0, nullptr); + if ( pChar->OnTrigger( CTRIG_LogIn, pScriptArgs, pChar ) == TRIGRET_RET_TRUE ) { m_pChar->ClientDetach(); pChar->SetDisconnected(); return PacketLoginError::Blocked; } - fNoMessages = (Args.m_iN1 != 0); - fQuickLogIn = (Args.m_iN2 != 0); + fNoMessages = (pScriptArgs->m_iN1 != 0); + fQuickLogIn = (pScriptArgs->m_iN2 != 0); } tchar *z = Str_GetTemp(); @@ -2850,7 +2881,7 @@ byte CClient::Setup_Start( CChar * pChar ) // Send character startup stuff to pl /* * // If ever we want to change how timers are suspended... - * + * if (m_pChar->IsPlayer()) { // When a character logs out, its timer and its contents' timers has to freeze. @@ -2937,7 +2968,7 @@ byte CClient::Setup_Delete( dword iSlot ) // Deletion of character } } - + if (pChar->Delete()) // Do the scripts allow to delete the char? { @@ -2952,7 +2983,7 @@ byte CClient::Setup_Delete( dword iSlot ) // Deletion of character { return PacketDeleteError::InvalidRequest; } - + } byte CClient::Setup_ListReq( const char * pszAccName, const char * pszPassword, bool fTest ) @@ -3108,12 +3139,14 @@ byte CClient::LogIn( CAccount * pAccount, CSString & sMsg ) // Do the scripts allow to login this account? pAccount->m_Last_IP.SetAddrIP(GetPeer().GetAddrIP()); - CScriptTriggerArgs Args; - Args.Init(pAccount->GetName()); - Args.m_iN1 = GetConnectType(); - Args.m_pO1 = this; - TRIGRET_TYPE tr = TRIGRET_RET_DEFAULT; - g_Serv.r_Call("f_onaccount_login", &g_Serv, &Args, nullptr, &tr); + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pAccount->GetName()); + pScriptArgs->m_iN1 = GetConnectType(); + pScriptArgs->m_pO1 = this; + + TRIGRET_TYPE tr = TRIGRET_RET_DEFAULT; + g_Serv.r_Call("f_onaccount_login", pScriptArgs, &g_Serv, nullptr, &tr); if ( tr == TRIGRET_RET_TRUE ) { sMsg = g_Cfg.GetDefaultMsg( DEFMSG_MSG_ACC_DENIED ); @@ -3155,9 +3188,9 @@ byte CClient::LogIn( lpctstr ptcAccName, lpctstr ptcPassword, CSString & sMsg ) tchar ptcName[ MAX_ACCOUNT_NAME_SIZE ]; - if ( !CAccount::NameStrip(ptcName, ptcAccName) || Str_Check(ptcAccName) ) + if ( !CAccount::NameStrip(ptcName, ptcAccName) || Str_Untrusted_InvalidTermination(ptcAccName) ) return( PacketLoginError::BadAccount ); - else if ( Str_Check(ptcPassword) ) + else if ( Str_Untrusted_InvalidTermination(ptcPassword) ) return( PacketLoginError::BadPassword ); const bool fGuestAccount = ! strnicmp( ptcAccName, "GUEST", 5 ); diff --git a/src/game/clients/CClientMsg_AOSTooltip.cpp b/src/game/clients/CClientMsg_AOSTooltip.cpp index 9b513f8eb..66cd80508 100644 --- a/src/game/clients/CClientMsg_AOSTooltip.cpp +++ b/src/game/clients/CClientMsg_AOSTooltip.cpp @@ -1,4 +1,6 @@ -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h +#include "../../network/send.h" #include "../chars/CChar.h" #include "../chars/CCharNPC.h" #include "../clients/CClientTooltip.h" @@ -96,9 +98,10 @@ bool CClient::addAOSTooltip(CObjBase * pObj, bool fRequested, bool fShop) if (IsTrigUsed(TRIGGER_CLIENTTOOLTIP) || (pItem && IsTrigUsed(TRIGGER_ITEMCLIENTTOOLTIP)) || (pChar && IsTrigUsed(TRIGGER_CHARCLIENTTOOLTIP))) { - CScriptTriggerArgs args(pObj); - args.m_iN1 = fRequested; - iRet = pObj->OnTrigger("@ClientTooltip", this->GetChar(), &args); //ITRIG_CLIENTTOOLTIP , CTRIG_ClientTooltip + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pObj; + pScriptArgs->m_iN1 = fRequested; + iRet = pObj->OnTrigger("@ClientTooltip", pScriptArgs, this->GetChar()); //ITRIG_CLIENTTOOLTIP , CTRIG_ClientTooltip } if (iRet != TRIGRET_RET_TRUE) @@ -117,9 +120,10 @@ bool CClient::addAOSTooltip(CObjBase * pObj, bool fRequested, bool fShop) if (IsTrigUsed(TRIGGER_CLIENTTOOLTIP_AFTERDEFAULT) || (pItem && IsTrigUsed(TRIGGER_ITEMCLIENTTOOLTIP_AFTERDEFAULT)) || (pChar && IsTrigUsed(TRIGGER_CHARCLIENTTOOLTIP_AFTERDEFAULT))) { - CScriptTriggerArgs args(pObj); - args.m_iN1 = fRequested; - iRet = pObj->OnTrigger("@ClientTooltip_AfterDefault", this->GetChar(), &args); //Save to return on iRet to make sure return value doesn't stuck the boolean. + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pObj; + pScriptArgs->m_iN1 = fRequested; + iRet = pObj->OnTrigger("@ClientTooltip_AfterDefault", pScriptArgs, this->GetChar()); //Save to return on iRet to make sure return value doesn't stuck the boolean. } } diff --git a/src/game/clients/CClientTarg.cpp b/src/game/clients/CClientTarg.cpp index f0b3f4b32..54547dc51 100644 --- a/src/game/clients/CClientTarg.cpp +++ b/src/game/clients/CClientTarg.cpp @@ -3,7 +3,8 @@ #include "../../network/send.h" #include "../../common/resource/sections/CItemTypeDef.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../chars/CChar.h" #include "../items/CItemCorpse.h" @@ -88,18 +89,19 @@ bool CClient::OnTarg_Obj_Set( CObjBase * pObj ) bool CClient::OnTarg_Obj_Function( CObjBase * pObj, const CPointMap & pt, ITEMID_TYPE id ) { ADDTOCALLSTACK("CClient::OnTarg_Obj_Function"); - m_Targ_p = pt; - lpctstr pSpace = strchr( m_Targ_Text, ' ' ); + m_Targ_p = pt; + lpctstr pSpace = strchr( m_Targ_Text, ' ' ); if ( !pSpace ) pSpace = strchr( m_Targ_Text, '\t' ); if ( pSpace ) GETNONWHITESPACE( pSpace ); - CScriptTriggerArgs Args( pSpace ? pSpace : "" ); - Args.m_VarsLocal.SetNum( "ID", id, true ); - Args.m_pO1 = pObj; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(pSpace ? pSpace : ""); + pScriptArgs->m_VarsLocal.SetNum( "ID", id, true ); + pScriptArgs->m_pO1 = pObj; CSString sVal; - m_pChar->r_Call( static_cast(m_Targ_Text), this, &Args, &sVal ); + m_pChar->r_Call(m_Targ_Text.GetBuffer(), pScriptArgs, this, &sVal ); return true; } @@ -283,7 +285,7 @@ bool CClient::OnTarg_UnExtract( CObjBase * pObj, const CPointMap & pt ) // result of the MULTI command. // Break a multi out of the multi.txt files and turn it into items. - if ( !pt.GetRegion(REGION_TYPE_AREA) ) + if ( !pt.GetRegion(REGION_TYPE_AREA) ) //TODO: add err message return false; CScript s; // It is not really a valid script type file. @@ -330,7 +332,7 @@ bool CClient::OnTarg_Char_Add( CObjBase * pObj, const CPointMap & pt ) ASSERT(m_pChar); if ( !pt.GetRegion(REGION_TYPE_AREA) ) - return false; + return false; //TODO: add err message if ( pObj && pObj->IsItemInContainer() ) return false; @@ -356,7 +358,7 @@ bool CClient::OnTarg_Item_Add( CObjBase * pObj, CPointMap & pt ) // m_tmAdd.m_id = item id ASSERT(m_pChar); - if ( !pt.GetRegion(REGION_TYPE_AREA) ) + if ( !pt.GetRegion(REGION_TYPE_AREA) ) //TODO: add err message return false; if ( pObj && pObj->IsItemInContainer() ) return false; @@ -1656,7 +1658,7 @@ CItem * CClient::OnTarg_Use_Multi(const CItemBase * pItemDef, CPointMap & pt, CI ADDTOCALLSTACK("CClient::OnTarg_Use_Multi"); // Might be a IT_MULTI or it might not. place it anyhow. - if ((pItemDef == nullptr) || !pt.GetRegion(REGION_TYPE_AREA)) + if ((pItemDef == nullptr) || !pt.GetRegion(REGION_TYPE_AREA)) //TODO: add err message return nullptr; return CItemMulti::Multi_Create(GetChar(), pItemDef, pt, pDeed); @@ -1714,8 +1716,9 @@ bool CClient::OnTarg_Use_Item( CObjBase * pObjTarg, CPointMap & pt, ITEMID_TYPE if (( IsTrigUsed(CItem::sm_szTrigName[trigtype]) ) || ( IsTrigUsed(CChar::sm_szTrigName[(CTRIG_itemAfterClick - 1) + trigtype]) )) //ITRIG_TARGON_GROUND, ITRIG_TARGON_CHAR, ITRIG_TARGON_ITEM { - CScriptTriggerArgs Args( id, 0, pObjTarg ); - if ( pItemUse->OnTrigger( trigtype, m_pChar, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(id, 0, 0, pObjTarg); + if ( pItemUse->OnTrigger( trigtype, pScriptArgs, m_pChar ) == TRIGRET_RET_TRUE ) return true; } @@ -2464,8 +2467,7 @@ bool CClient::OnTarg_Party_Add( CChar * pChar ) if ( IsTrigUsed(TRIGGER_PARTYINVITE) ) { - CScriptTriggerArgs args; - if ( pChar->OnTrigger(CTRIG_PartyInvite, m_pChar, &args) == TRIGRET_RET_TRUE ) + if ( pChar->OnTrigger(CTRIG_PartyInvite, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE ) return false; } diff --git a/src/game/clients/CClientUse.cpp b/src/game/clients/CClientUse.cpp index 4f6914afa..4a8ce4a2d 100644 --- a/src/game/clients/CClientUse.cpp +++ b/src/game/clients/CClientUse.cpp @@ -1,7 +1,8 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../../network/send.h" #include "../chars/CChar.h" @@ -103,7 +104,7 @@ bool CClient::Cmd_Use_Item( CItem *pItem, bool fTestTouch, bool fScript ) if ( IsTrigUsed(TRIGGER_DCLICK) || IsTrigUsed(TRIGGER_ITEMDCLICK) ) { - if ( pItem->OnTrigger(ITRIG_DCLICK, m_pChar) == TRIGRET_RET_TRUE ) + if ( pItem->OnTrigger(ITRIG_DCLICK, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE ) return true; } @@ -644,16 +645,16 @@ bool CClient::Skill_Menu(SKILL_TYPE skill, lpctstr skillmenu, ITEMID_TYPE itemus // Default menu is d_craft_menu // Open in page 0, args is skill used. // LPCTSTR dSkillMenu = "d_CraftingMenu"; - CScriptTriggerArgs Args; - Args.m_VarsLocal.SetStrNew("SkillMenu", skillmenu); - Args.m_VarsLocal.SetNumNew("Skill", skill); - Args.m_VarsLocal.SetNumNew("ItemUsed", itemused); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetStrNew("SkillMenu", skillmenu); + pScriptArgs->m_VarsLocal.SetNumNew("Skill", skill); + pScriptArgs->m_VarsLocal.SetNumNew("ItemUsed", itemused); if (IsTrigUsed(TRIGGER_SKILLMENU)) { - if (m_pChar->OnTrigger(CTRIG_SkillMenu, m_pChar, &Args) == TRIGRET_RET_TRUE ) + if (m_pChar->OnTrigger(CTRIG_SkillMenu, pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) return true; - skillmenu = Args.m_VarsLocal.GetKeyStr("Skillmenu", false); + skillmenu = pScriptArgs->m_VarsLocal.GetKeyStr("Skillmenu", false); } lpctstr SkillUsed = g_Cfg.GetSkillKey(skill); @@ -793,7 +794,7 @@ int CClient::Cmd_Skill_Menu_Build( const CResourceID& rid, int iSelect, CMenuIte if ( strcmpi(s.GetArgStr(), "@Cancel") ) continue; - if ( m_pChar->OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, m_pChar, nullptr) == TRIGRET_RET_TRUE ) + if ( m_pChar->OnTriggerRunVal(s, TRIGRUN_SECTION_TRUE, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar) == TRIGRET_RET_TRUE ) return 0; break; @@ -811,8 +812,7 @@ int CClient::Cmd_Skill_Menu_Build( const CResourceID& rid, int iSelect, CMenuIte bool fSkip = false; // skip this if we lack resources or skill. bool fSkipNeedCleanup = false; int iOnCount = 0; - int iShowCount = 0; - CScriptTriggerArgs Args; + int iShowCount = 0; while ( s.ReadKeyParse() ) { @@ -889,7 +889,8 @@ int CClient::Cmd_Skill_Menu_Build( const CResourceID& rid, int iSelect, CMenuIte // Check for a skill / non-consumables required. if ( s.IsKey("TEST") ) { - m_pChar->ParseScriptText(s.GetArgRaw(), m_pChar); + CScriptExprContext scpContext{._pScriptObjI = m_pChar}; + CExpression::GetExprParser().ParseScriptText(s.GetArgRaw(), scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar); CResourceQtyArray skills(s.GetArgStr()); if ( !skills.IsResourceMatchAll(m_pChar) ) { @@ -900,7 +901,8 @@ int CClient::Cmd_Skill_Menu_Build( const CResourceID& rid, int iSelect, CMenuIte if ( s.IsKey("TESTIF") ) { - m_pChar->ParseScriptText(s.GetArgRaw(), m_pChar); + CScriptExprContext scpContext{._pScriptObjI = m_pChar}; + CExpression::GetExprParser().ParseScriptText(s.GetArgRaw(), scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar); if ( !s.GetArgVal() ) { fSkipNeedCleanup = true; @@ -912,7 +914,7 @@ int CClient::Cmd_Skill_Menu_Build( const CResourceID& rid, int iSelect, CMenuIte if ( iOnCount == iSelect ) { // Execute command from script - TRIGRET_TYPE tRet = m_pChar->OnTriggerRunVal(s, TRIGRUN_SINGLE_EXEC, m_pChar, &Args); + TRIGRET_TYPE tRet = m_pChar->OnTriggerRunVal(s, TRIGRUN_SINGLE_EXEC, CScriptParserBufs::GetCScriptTriggerArgsPtr(), m_pChar); if ( tRet != TRIGRET_RET_DEFAULT ) return (tRet == TRIGRET_RET_TRUE) ? 0 : 1; @@ -1012,9 +1014,10 @@ bool CClient::Cmd_Skill_Magery( SPELL_TYPE iSpell, CObjBase *pSrc ) case SPELL_Polymorph: { if ( IsTrigUsed(TRIGGER_SKILLMENU) ) - { - CScriptTriggerArgs args("sm_polymorph"); - if ( m_pChar->OnTrigger("@SkillMenu", m_pChar, &args) == TRIGRET_RET_TRUE ) + { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init("sm_polymorph"); + if ( m_pChar->OnTrigger("@SkillMenu", pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) return true; } return Cmd_Skill_Menu(g_Cfg.ResourceGetIDType(RES_SKILLMENU, "sm_polymorph")); @@ -1023,9 +1026,10 @@ bool CClient::Cmd_Skill_Magery( SPELL_TYPE iSpell, CObjBase *pSrc ) case SPELL_Summon: { if ( IsTrigUsed(TRIGGER_SKILLMENU) ) - { - CScriptTriggerArgs args("sm_summon"); - if ( m_pChar->OnTrigger("@SkillMenu", m_pChar, &args) == TRIGRET_RET_TRUE ) + { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init("sm_summon"); + if ( m_pChar->OnTrigger("@SkillMenu", pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) return true; } return Cmd_Skill_Menu(g_Cfg.ResourceGetIDType(RES_SKILLMENU, "sm_summon")); @@ -1034,9 +1038,10 @@ bool CClient::Cmd_Skill_Magery( SPELL_TYPE iSpell, CObjBase *pSrc ) case SPELL_Summon_Familiar: { if ( IsTrigUsed(TRIGGER_SKILLMENU) ) - { - CScriptTriggerArgs args("sm_summon_familiar"); - if ( m_pChar->OnTrigger("@SkillMenu", m_pChar, &args) == TRIGRET_RET_TRUE ) + { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init("sm_summon_familiar"); + if ( m_pChar->OnTrigger("@SkillMenu", pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) return true; } return Cmd_Skill_Menu(g_Cfg.ResourceGetIDType(RES_SKILLMENU, "sm_summon_familiar")); @@ -1321,8 +1326,9 @@ bool CClient::Cmd_SecureTrade( CChar *pChar, CItem *pItem ) if ( pItem && (IsTrigUsed(TRIGGER_DROPON_CHAR) || IsTrigUsed(TRIGGER_ITEMDROPON_CHAR)) ) { - CScriptTriggerArgs Args(pChar); - if ( pItem->OnTrigger(ITRIG_DROPON_CHAR, m_pChar, &Args) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pChar; + if ( pItem->OnTrigger(ITRIG_DROPON_CHAR, pScriptArgs, m_pChar) == TRIGRET_RET_TRUE ) return false; } @@ -1356,8 +1362,9 @@ bool CClient::Cmd_SecureTrade( CChar *pChar, CItem *pItem ) { if ( IsTrigUsed(TRIGGER_DROPON_TRADE) ) { - CScriptTriggerArgs Args1(pChar); - if ( pItem->OnTrigger(ITRIG_DROPON_TRADE, this, &Args1) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs1 = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs1->m_pO1 = pChar; + if ( pItem->OnTrigger(ITRIG_DROPON_TRADE, pScriptArgs1, this) == TRIGRET_RET_TRUE ) return false; } CItemContainer *pCont = dynamic_cast(pItemCont); @@ -1370,15 +1377,18 @@ bool CClient::Cmd_SecureTrade( CChar *pChar, CItem *pItem ) // Open new trade window if ( IsTrigUsed(TRIGGER_TRADECREATE) ) { - CScriptTriggerArgs Args(pItem); - if ( (m_pChar->OnTrigger(CTRIG_TradeCreate, pChar, &Args) == TRIGRET_RET_TRUE) || (pChar->OnTrigger(CTRIG_TradeCreate, m_pChar, &Args) == TRIGRET_RET_TRUE) ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pItem; + if ( (m_pChar->OnTrigger(CTRIG_TradeCreate, pScriptArgs, pChar) == TRIGRET_RET_TRUE) + || (pChar->OnTrigger(CTRIG_TradeCreate, pScriptArgs, m_pChar) == TRIGRET_RET_TRUE) ) return false; } if ( IsTrigUsed(TRIGGER_DROPON_TRADE) && pItem ) { - CScriptTriggerArgs Args1(pChar); - if ( pItem->OnTrigger(ITRIG_DROPON_TRADE, this, &Args1) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pChar; + if ( pItem->OnTrigger(ITRIG_DROPON_TRADE, pScriptArgs, this) == TRIGRET_RET_TRUE ) return false; } @@ -1437,8 +1447,9 @@ bool CClient::Cmd_SecureTrade( CChar *pChar, CItem *pItem ) { if ( IsTrigUsed(TRIGGER_DROPON_TRADE) ) { - CScriptTriggerArgs Args1(pChar); - if ( pItem->OnTrigger(ITRIG_DROPON_TRADE, this, &Args1) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pChar; + if ( pItem->OnTrigger(ITRIG_DROPON_TRADE, pScriptArgs, this) == TRIGRET_RET_TRUE ) { pCont1->Delete(); pCont2->Delete(); diff --git a/src/game/clients/CGMPage.cpp b/src/game/clients/CGMPage.cpp index 984ce8518..46412e580 100644 --- a/src/game/clients/CGMPage.cpp +++ b/src/game/clients/CGMPage.cpp @@ -1,5 +1,5 @@ -#include "../../common/CException.h" +//#include "../../common/CException.h" // included in the precompiled header #include "../chars/CChar.h" #include "../CWorld.h" #include "../CWorldGameTime.h" diff --git a/src/game/clients/CParty.cpp b/src/game/clients/CParty.cpp index 047eeece0..c912c1cbf 100644 --- a/src/game/clients/CParty.cpp +++ b/src/game/clients/CParty.cpp @@ -1,6 +1,7 @@ -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../../common/CScriptObj.h" #include "../../network/send.h" @@ -241,12 +242,9 @@ bool CPartyDef::MessageEvent( CUID uidDst, CUID uidSrc, const nachar *pText, int if ( uidDst.IsValidUID() && !IsInParty(uidDst.CharFind()) ) return false; - CChar *pFrom = uidSrc.CharFind(); - CChar *pTo = nullptr; - if ( uidDst != (dword)0 ) - pTo = uidDst.CharFind(); + CChar *pFrom = uidSrc.CharFind(); ASSERT(pFrom); - ASSERT(pTo); + CChar *pTo = (uidDst == (dword)0) ? nullptr : uidDst.CharFind(); tchar *szText = Str_GetTemp(); CvtNETUTF16ToSystem(szText, MAX_TALK_BUFFER, pText, MAX_TALK_BUFFER); @@ -254,13 +252,13 @@ bool CPartyDef::MessageEvent( CUID uidDst, CUID uidSrc, const nachar *pText, int if ( !m_pSpeechFunction.IsEmpty() ) { TRIGRET_TYPE tr = TRIGRET_RET_FALSE; - CScriptTriggerArgs Args; - Args.m_iN1 = uidSrc.GetObjUID(); - Args.m_iN2 = uidDst.GetObjUID(); - Args.m_s1 = szText; - Args.m_s1_buf_vec = szText; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = uidSrc.GetObjUID(); + pScriptArgs->m_iN2 = uidDst.GetObjUID(); + pScriptArgs->m_s1 = szText; + pScriptArgs->m_s1_buf_vec = szText; - if ( r_Call(m_pSpeechFunction, &g_Serv, &Args, nullptr, &tr) ) + if ( r_Call(m_pSpeechFunction, pScriptArgs, &g_Serv, nullptr, &tr) ) { if ( tr == TRIGRET_RET_TRUE ) return false; @@ -268,7 +266,10 @@ bool CPartyDef::MessageEvent( CUID uidDst, CUID uidSrc, const nachar *pText, int } if ( g_Log.IsLoggedMask(LOGM_PLAYER_SPEAK) ) - g_Log.Event(LOGM_PLAYER_SPEAK|LOGM_NOCONTEXT, "%x:'%s' Says '%s' in party to '%s'\n", pFrom->GetClientActive()->GetSocketID(), pFrom->GetName(), szText, pTo ? pTo->GetName() : "all"); + g_Log.Event(LOGM_PLAYER_SPEAK|LOGM_NOCONTEXT, + "%x:'%s' Says '%s' in party to '%s'\n", + pFrom->GetClientActive()->GetSocketID(), pFrom->GetName(), szText, + (pTo ? pTo->GetName() : "all")); PacketPartyChat cmd(pFrom, pText); @@ -292,44 +293,60 @@ void CPartyDef::AcceptMember( CChar *pChar ) SendAddList(nullptr); } -bool CPartyDef::RemoveMember( CUID uidRemove, CUID uidCommand ) +bool CPartyDef::RemoveMember(const CUID& uidRemove, const CUID &uidCommand, const bool fDisband) { ADDTOCALLSTACK("CPartyDef::RemoveMember"); - // ARGS: - // uidRemove = Who is being removed. - // uidCommand = who removed this person (only the master or self can remove) - // - // NOTE: remove of the master will cause the party to disband. - if ( m_Chars.GetCharCount() <= 0 ) + if (m_Chars.GetCharCount() <= 0) return false; - CUID uidMaster(GetMaster()); - if ( (uidRemove != uidCommand) && (uidCommand != uidMaster) ) + const CUID uidMaster(GetMaster()); + if (uidRemove != uidCommand && uidCommand != uidMaster) return false; CChar *pCharRemove(uidRemove.CharFind()); - if ( !pCharRemove ) + if (!pCharRemove) return false; - if ( !IsInParty(pCharRemove) ) + if (!IsInParty(pCharRemove)) return false; - if ( uidRemove == uidMaster ) + if (fDisband && uidRemove == uidMaster) return Disband(uidMaster); CChar *pSrc = uidCommand.CharFind(); - if ( pSrc && IsTrigUsed(TRIGGER_PARTYREMOVE) ) + if (pSrc && IsTrigUsed(TRIGGER_PARTYREMOVE)) { - CScriptTriggerArgs args; - if ( pCharRemove->OnTrigger(CTRIG_PartyRemove, pSrc, &args) == TRIGRET_RET_TRUE ) + if (pCharRemove->OnTrigger(CTRIG_PartyRemove, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc) == TRIGRET_RET_TRUE) return false; } - if ( IsTrigUsed(TRIGGER_PARTYLEAVE) ) + if (IsTrigUsed(TRIGGER_PARTYLEAVE)) { - if ( pCharRemove->OnTrigger(CTRIG_PartyLeave, pCharRemove, nullptr) == TRIGRET_RET_TRUE ) + if (pCharRemove->OnTrigger(CTRIG_PartyLeave, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharRemove) == TRIGRET_RET_TRUE) return false; } - // Remove it from the party + // If the party leader left, try to promote new character to be the leader. + tchar *pszChangeLeader = Str_GetTemp(); + if (uidRemove == uidMaster) + { + // No valid character on second position in the party, disband it. + if (!m_Chars.IsValidIndex(1)) + return Disband(uidMaster); + + const CUID newMaster(m_Chars.GetChar(1)); + CChar *pCharNewMaster(newMaster.CharFind()); + + // Cannot find new leader's character, disband it. + if (!pCharNewMaster) + return Disband(uidMaster); + + // If new master wasn't appointed, disband the party. + if (!SetMaster(pCharNewMaster)) + return Disband(uidMaster); + + snprintf(pszChangeLeader, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_PARTY_CHANGE_LEADER), pCharNewMaster->GetName()); + } + + // Remove the character from the party. SendRemoveList(pCharRemove, true); DetachChar(pCharRemove); pCharRemove->SysMessageDefault(DEFMSG_PARTY_LEAVE_2); @@ -338,13 +355,20 @@ bool CPartyDef::RemoveMember( CUID uidRemove, CUID uidCommand ) snprintf(pszMsg, Str_TempLength(), g_Cfg.GetDefaultMsg(DEFMSG_PARTY_LEAVE_1), pCharRemove->GetName()); SysMessageAll(pszMsg); - if ( m_Chars.GetCharCount() <= 1 ) + // Disband the party if I'm alone. + if (m_Chars.GetCharCount() <= 1) { - // Disband the party SysMessageAll(g_Cfg.GetDefaultMsg(DEFMSG_PARTY_LEAVE_LAST_PERSON)); return Disband(uidMaster); } + // Notify about change in party leadership (we need to do it here, so it doesn't get sent to the previous leader). + if (uidRemove == uidMaster) + SysMessageAll(pszChangeLeader); + + // We still have a party, notify other members about removal. + SendRemoveList(pCharRemove, false); + return true; } @@ -360,8 +384,7 @@ bool CPartyDef::Disband( CUID uidMaster ) CChar *pMaster = GetMaster().CharFind(); if ( pMaster && IsTrigUsed(TRIGGER_PARTYDISBAND) ) { - CScriptTriggerArgs args; - if ( pMaster->OnTrigger(CTRIG_PartyDisband, pMaster, &args) == TRIGRET_RET_TRUE ) + if ( pMaster->OnTrigger(CTRIG_PartyDisband, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pMaster) == TRIGRET_RET_TRUE ) return false; } @@ -378,9 +401,9 @@ bool CPartyDef::Disband( CUID uidMaster ) if ( IsTrigUsed(TRIGGER_PARTYREMOVE) ) { - CScriptTriggerArgs args; - args.m_iN1 = 1; - pChar->OnTrigger(CTRIG_PartyRemove, pSrc, &args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = 1; + pChar->OnTrigger(CTRIG_PartyRemove, pScriptArgs, pSrc); } SendRemoveList(pChar, true); @@ -454,11 +477,10 @@ bool CPartyDef::AcceptEvent( CChar *pCharAccept, CUID uidInviter, bool bForced, else return false; } - + if (IsTrigUsed(TRIGGER_PARTYADD)) - { - CScriptTriggerArgs Args; - if ( pCharAccept->OnTrigger(CTRIG_PartyAdd, pCharInviter, &Args) == TRIGRET_RET_TRUE ) + { + if ( pCharAccept->OnTrigger(CTRIG_PartyAdd, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pCharInviter) == TRIGRET_RET_TRUE ) return false; } @@ -555,7 +577,7 @@ bool CPartyDef::r_GetRef( lpctstr &ptcKey, CScriptObj *&pRef ) } bool CPartyDef::r_LoadVal( CScript &s ) -{ +{ ADDTOCALLSTACK("CPartyDef::r_LoadVal"); EXC_TRY("LoadVal"); lpctstr ptcKey = s.GetKey(); @@ -589,7 +611,7 @@ bool CPartyDef::r_LoadVal( CScript &s ) lpctstr ptcArg = s.GetArgStr(&fQuoted); m_TagDefs.SetStr(ptcKey, fQuoted, ptcArg, fZero); } break; - + default: return false; } @@ -754,7 +776,7 @@ bool CPartyDef::r_Verb( CScript &s, CTextConsole *pSrc ) if ( pCharMaster && !fForced ) pCharMaster->SetKeyNum("PARTY_LASTINVITE", toAdd.GetObjUID()); - + return CPartyDef::AcceptEvent(pCharAdd, GetMaster(), fForced); } break; @@ -886,10 +908,10 @@ bool CPartyDef::r_Verb( CScript &s, CTextConsole *pSrc ) } bool CPartyDef::r_Load( CScript &s ) -{ +{ ADDTOCALLSTACK("CPartyDef::r_Load"); UnreferencedParameter(s); - return false; + return false; } lpctstr CPartyDef::GetDefStr( lpctstr ptcKey, bool fZero ) const diff --git a/src/game/clients/CParty.h b/src/game/clients/CParty.h index 5182f0a4a..18aab6805 100644 --- a/src/game/clients/CParty.h +++ b/src/game/clients/CParty.h @@ -78,7 +78,20 @@ class CPartyDef : public CSObjListRec, public CScriptObj // Commands bool Disband( CUID uidMaster ); - bool RemoveMember( CUID uidRemove, CUID uidCommand ); + + /** + * Removes a member from party. If leader is removed, it tries to pass leader to another character. + * + * @note We need to switch characters at leader position before we remove him, because original client doesn't like removing leader and thinks, the party + * was disbanded (Classic UO doesn't care though). + * + * @param uidRemove Character being removed. + * @param uidCommand Character, who issued the removal (leader or self). + * @param fDisband If set to false, we try to change leader instead of disbanding the party. + * + * @return True if successfully removed, false otherwise. + */ + bool RemoveMember(const CUID& uidRemove, const CUID &uidCommand, bool fDisband = true); void AcceptMember( CChar * pChar ); void SetLootFlag( CChar * pChar, bool fSet ); bool GetLootFlag( const CChar * pChar ); diff --git a/src/game/components/CCChampion.cpp b/src/game/components/CCChampion.cpp index 98266c28a..3c8286ea6 100644 --- a/src/game/components/CCChampion.cpp +++ b/src/game/components/CCChampion.cpp @@ -13,7 +13,8 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../../common/CScript.h" #include "../chars/CChar.h" @@ -21,7 +22,7 @@ #include "../CWorldGameTime.h" #include "CCChampion.h" // predef header. #include -#include +#include #define CANDLESNEXTRED 4 #define MAXSPAWN 2400 @@ -76,6 +77,8 @@ CCChampion::CCChampion(CItem* pLink) : CComponent(COMP_CHAMPION) _idChampion = CREID_INVALID; _iLastActivationTime = 0; Init(); + _iSpawnsNextRed = 0; + _iSpawnsNextWhite = 0; } void CCChampion::Copy(const CComponent* target) @@ -95,6 +98,7 @@ CCChampion::~CCChampion() CItem* CCChampion::GetLink() const { + ASSERT(_pLink != nullptr); return _pLink; } @@ -171,8 +175,7 @@ void CCChampion::Start(CChar *pChar) if (pChar && IsTrigUsed(TRIGGER_START)) { // TODO: add source? - // DONE - if (OnTrigger(ITRIG_Start, pChar, nullptr) == TRIGRET_RET_TRUE) + if (OnTrigger(ITRIG_Start, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar) == TRIGRET_RET_TRUE) return; } @@ -188,7 +191,7 @@ void CCChampion::Stop(CChar* pChar) { if (IsTrigUsed(TRIGGER_STOP)) { - if (OnTrigger(ITRIG_STOP, pChar, nullptr) == TRIGRET_RET_TRUE) + if (OnTrigger(ITRIG_STOP, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar) == TRIGRET_RET_TRUE) return; } } @@ -219,11 +222,10 @@ void CCChampion::Complete() Stop(); // TODO: add new trigger - // DONE // TODO: Add attacker list in trigger. if (IsTrigUsed(TRIGGER_COMPLETE)) { - OnTrigger(ITRIG_COMPLETE, &g_Serv, nullptr); + OnTrigger(ITRIG_COMPLETE, CScriptParserBufs::GetCScriptTriggerArgsPtr(), &g_Serv); } // TODO: add rewards, titles, etc? } @@ -397,12 +399,13 @@ void CCChampion::AddWhiteCandle(const CUID& uid) } pCandle->SetTopPoint(pt); - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (IsTrigUsed(TRIGGER_ADDWHITECANDLE)) { - CScriptTriggerArgs args(pCandle); - if (OnTrigger(ITRIG_ADDWHITECANDLE, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pCandle; + if (OnTrigger(ITRIG_ADDWHITECANDLE, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) { pCandle->Delete(); return; @@ -441,7 +444,7 @@ void CCChampion::AddRedCandle(const CUID& uid) return; _iSpawnsNextWhite = _iSpawnsNextRed / (CANDLESNEXTRED + 1); - if (!g_Serv.IsLoading()) // Do not remove white candles, while server is loading them from save. + if (!g_Serv.IsLoadingGeneric()) // Do not remove white candles, while server is loading them from save. { ClearWhiteCandles(); } @@ -518,12 +521,13 @@ void CCChampion::AddRedCandle(const CUID& uid) default: break; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (IsTrigUsed(TRIGGER_ADDREDCANDLE)) { - CScriptTriggerArgs args(pCandle); - if (OnTrigger(ITRIG_ADDREDCANDLE, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pCandle; + if (OnTrigger(ITRIG_ADDREDCANDLE, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) { pCandle->Delete(); return; @@ -546,7 +550,7 @@ void CCChampion::AddRedCandle(const CUID& uid) void CCChampion::SetLevel(byte iLevel) { ADDTOCALLSTACK("CCChampion::SetLevel"); - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) return; _iLevel = iLevel; @@ -559,8 +563,9 @@ void CCChampion::SetLevel(byte iLevel) // DONE if (IsTrigUsed(TRIGGER_LEVEL)) { - CScriptTriggerArgs args(_iLevel, iLevelMonsters, _iCandlesNextLevel); - OnTrigger(ITRIG_LEVEL, &g_Serv, &args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(_iLevel, iLevelMonsters, _iCandlesNextLevel, nullptr); + OnTrigger(ITRIG_LEVEL, pScriptArgs, &g_Serv); } if (_iLevel >= _iLevelMax) // Start boss fight when level maxed. @@ -673,9 +678,10 @@ void CCChampion::DelWhiteCandle(CANDLEDELREASON_TYPE reason) // DONE if (IsTrigUsed(TRIGGER_DELWHITECANDLE)) { - CScriptTriggerArgs args(reason); - args.m_pO1 = pCandle; - if (OnTrigger(ITRIG_DELWHITECANDLE, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = reason; + pScriptArgs->m_pO1 = pCandle; + if (OnTrigger(ITRIG_DELWHITECANDLE, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) return; } @@ -699,9 +705,10 @@ void CCChampion::DelRedCandle(CANDLEDELREASON_TYPE reason) // DONE if (IsTrigUsed(TRIGGER_DELREDCANDLE)) { - CScriptTriggerArgs args(reason); - args.m_pO1 = pCandle; - if (OnTrigger(ITRIG_DELREDCANDLE, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = reason; + pScriptArgs->m_pO1 = pCandle; + if (OnTrigger(ITRIG_DELREDCANDLE, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) return; } @@ -993,7 +1000,7 @@ bool CCChampion::r_LoadVal(CScript& s) { case ICHMPL_ACTIVE: { - if (g_Serv.IsLoading() == true) //Only when the server is loading. + if (g_Serv.IsLoadingGeneric() == true) //Only when the server is loading. { _fActive = (bool)s.GetArgBVal(); } @@ -1191,7 +1198,7 @@ bool CCChampion::r_Verb(CScript & s, CTextConsole * pSrc) } -TRIGRET_TYPE CCChampion::OnTrigger(ITRIG_TYPE trig, CTextConsole* pSrc, CScriptTriggerArgs* pArgs) +TRIGRET_TYPE CCChampion::OnTrigger(ITRIG_TYPE trig, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole* pSrc) { lpctstr pszTrigName = CItem::sm_szTrigName[trig]; @@ -1205,12 +1212,12 @@ TRIGRET_TYPE CCChampion::OnTrigger(ITRIG_TYPE trig, CTextConsole* pSrc, CScriptT CResourceLock s; if (pResourceLink->ResourceLock(s)) { - iRet = GetLink()->OnTriggerScript(s, pszTrigName, pSrc, pArgs); + iRet = GetLink()->OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); } } if (iRet == TRIGRET_RET_DEFAULT) { - iRet = GetLink()->OnTrigger(trig, pSrc, pArgs); + iRet = GetLink()->OnTrigger(trig, pScriptArgs, pSrc); } return iRet; } diff --git a/src/game/components/CCChampion.h b/src/game/components/CCChampion.h index 9a4982570..61aad2360 100644 --- a/src/game/components/CCChampion.h +++ b/src/game/components/CCChampion.h @@ -220,19 +220,19 @@ class CCChampion : public CComponent * otherwise it calls AddRedCandle and stop execution. * Create a pCandle pointer and set it's properties, color, and places it or sets the pointer to the item given in the param 'uid'. * Store in m_WhiteCandle[m_WhiteCandles] the CItem stored in pCandle. - * @param uid, if given (mostly during world loading) instead of creating a new candle, only the uid will be set to hold the already existing candle and do not have duplicates. + * @param uid if given (mostly during world loading) instead of creating a new candle, only the uid will be set to hold the already existing candle and do not have duplicates. */ void AddWhiteCandle(const CUID& uid = CUID()); /** * @brief Red candles check, add new one or I am in boss phase? * - * @param uid, if given (mostly during world loading) instead of creating a new candle, only the uid will be set to hold the already existing candle and do not have duplicates. + * @param uid if given (mostly during world loading) instead of creating a new candle, only the uid will be set to hold the already existing candle and do not have duplicates. */ void AddRedCandle(const CUID& uid = CUID()); /** * @brief Deleting last added White Candle. */ - void DelWhiteCandle(CANDLEDELREASON_TYPE reason = CANDLEDELREASON_CLEAR); + void DelWhiteCandle(CANDLEDELREASON_TYPE iReason = CANDLEDELREASON_CLEAR); /** * @brief Deleting last added Red Candle.d. */ @@ -265,7 +265,6 @@ class CCChampion : public CComponent /** * @brief How many monsters do we need to kill to gain a White Candle. * - * @param iMonsters total amount of monsters of the champion. * @return amount of killed monsters needed to reach the next level. */ uint16 GetMonstersCount(); @@ -273,7 +272,6 @@ class CCChampion : public CComponent /** * @brief Retrieves how much Red Candles are needed to reach next Champion's Level. * - * @param iLevel the level to check for (0 = reach for next Level). * More detailed description ... * Called from SetLevel() to set the amount of candles. * Value stored in morex (m_itNormal.m_morep.m_x). @@ -304,7 +302,7 @@ class CCChampion : public CComponent virtual bool r_LoadVal(CScript & s) override; virtual bool r_Verb(CScript & s, CTextConsole * pSrc) override; // Execute command from script virtual void Copy(const CComponent *target) override; - TRIGRET_TYPE OnTrigger(ITRIG_TYPE trig, CTextConsole *pSrc, CScriptTriggerArgs *args); + TRIGRET_TYPE OnTrigger(ITRIG_TYPE trig, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole *pSrc); /************************************************************************ * CItem related section. @@ -328,7 +326,6 @@ class CCChampion : public CComponent }; - /** * @brief Storing information from scripted [CHAMPION ] resource */ diff --git a/src/game/components/CCItemDamageable.cpp b/src/game/components/CCItemDamageable.cpp index 639a63e24..d941417b7 100644 --- a/src/game/components/CCItemDamageable.cpp +++ b/src/game/components/CCItemDamageable.cpp @@ -20,6 +20,7 @@ CCItemDamageable::CCItemDamageable(CItem * pLink) : CComponent(COMP_ITEMDAMAGEAB CItem * CCItemDamageable::GetLink() const noexcept { + ASSERT(_pLink != nullptr); return _pLink; } @@ -30,7 +31,7 @@ bool CCItemDamageable::CanSubscribe(const CItem* pItem) noexcept // static void CCItemDamageable::SetCurHits(word iCurHits) { - if (!g_Serv.IsLoading() && (_iCurHits != iCurHits)) + if (!g_Serv.IsLoadingGeneric() && (_iCurHits != iCurHits)) { _fNeedUpdate = true; } @@ -40,7 +41,7 @@ void CCItemDamageable::SetCurHits(word iCurHits) void CCItemDamageable::SetMaxHits(word iMaxHits) { - if (!g_Serv.IsLoading() && (_iMaxHits != iMaxHits)) + if (!g_Serv.IsLoadingGeneric() && (_iMaxHits != iMaxHits)) { _fNeedUpdate = true; } @@ -69,28 +70,28 @@ void CCItemDamageable::OnTickStatsUpdate() if (_iTimeLastUpdate + g_Cfg._iItemHitpointsUpdate < iCurtime) { + CItem *pItem = GetLink(); + const CPointMap pt = pItem->GetTopPoint(); + + // Check, whether item has already been placed in the world, because settings hits/maxhits in @create trigger calls this method. + if (_iTimeLastUpdate == 0 && !pt.IsValidXY()) + return; + _iTimeLastUpdate = iCurtime; - CItem *pItem = static_cast(GetLink()); - auto AreaChars = CWorldSearchHolder::GetInstance(pItem->GetTopPoint(), g_Cfg.m_iMapViewSize); + auto AreaChars = CWorldSearchHolder::GetInstance(pt, g_Cfg.m_iMapViewSize); AreaChars->SetSearchSquare(true); CChar *pChar = nullptr; for (;;) { pChar = AreaChars->GetChar(); if (!pChar) - { break; - } + CClient *pClient = pChar->GetClientActive(); - if (!pClient) - { + if (!pClient || !pClient->CanSee(pItem)) continue; - } - if (!pClient->CanSee(pItem)) - { - continue; - } + pClient->addStatusWindow(pItem); } } diff --git a/src/game/components/CCMultiMovable.cpp b/src/game/components/CCMultiMovable.cpp index 95c8efc01..859edd6c9 100644 --- a/src/game/components/CCMultiMovable.cpp +++ b/src/game/components/CCMultiMovable.cpp @@ -1,5 +1,6 @@ #include "../../common/sphere_library/CSRand.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/CClientIterator.h" #include "../../network/send.h" #include "../clients/CClient.h" @@ -430,7 +431,7 @@ bool CCMultiMovable::MoveToRegion(CRegionWorld * pRegionOld, CRegionWorld *pRegi { return false; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { // Leaving region trigger. (may not be allowed to leave ?) if (pRegionOld) @@ -445,8 +446,9 @@ bool CCMultiMovable::MoveToRegion(CRegionWorld * pRegionOld, CRegionWorld *pRegi if (IsTrigUsed(TRIGGER_REGIONLEAVE)) { - CScriptTriggerArgs Args(pRegionOld); - if (pMulti->OnTrigger(ITRIG_RegionLeave, pMulti->GetCaptain(), &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pRegionOld; + if (pMulti->OnTrigger(ITRIG_RegionLeave, pScriptArgs, pMulti->GetCaptain()) == TRIGRET_RET_TRUE) { return false; } @@ -464,8 +466,9 @@ bool CCMultiMovable::MoveToRegion(CRegionWorld * pRegionOld, CRegionWorld *pRegi if (IsTrigUsed(TRIGGER_REGIONENTER)) { - CScriptTriggerArgs Args(pRegionNew); - if (pMulti->OnTrigger(ITRIG_RegionEnter, pMulti->GetCaptain(), &Args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pRegionNew; + if (pMulti->OnTrigger(ITRIG_RegionEnter, pScriptArgs, pMulti->GetCaptain()) == TRIGRET_RET_TRUE) { return false; } @@ -624,8 +627,9 @@ bool CCMultiMovable::Face(DIR_TYPE dir) if (IsTrigUsed(TRIGGER_SHIP_TURN)) { - CScriptTriggerArgs Args(dir, sm_FaceDir[iFaceOffset]); - pItem->OnTrigger(ITRIG_Ship_Turn, &g_Serv, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(dir, sm_FaceDir[iFaceOffset], 0, nullptr); + pItem->OnTrigger(ITRIG_Ship_Turn, pScriptArgs, &g_Serv); } } else if (pObj->IsChar()) @@ -666,6 +670,7 @@ bool CCMultiMovable::Move(DIR_TYPE dir, int distance) CPointMap ptDelta; ptDelta.ZeroPoint(); + ptDelta.m_map = pItemThis->GetTopMap(); CPointMap ptFore(pMultiRegion->GetRegionCorner(dir)); CPointMap ptBack(pMultiRegion->GetRegionCorner(GetDirTurn(dir, 4))); @@ -857,8 +862,9 @@ bool CCMultiMovable::Move(DIR_TYPE dir, int distance) if (IsTrigUsed(TRIGGER_SHIP_MOVE)) { - CScriptTriggerArgs Args(dir, fStopped); - pItemThis->OnTrigger(ITRIG_Ship_Move, &g_Serv, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(dir, fStopped, 0, nullptr); + pItemThis->OnTrigger(ITRIG_Ship_Move, pScriptArgs, &g_Serv); } return true; @@ -909,8 +915,9 @@ void CCMultiMovable::Stop() if (IsTrigUsed(TRIGGER_SHIP_STOP)) { - CScriptTriggerArgs Args(pItemThis); - pItemThis->OnTrigger(ITRIG_Ship_Stop, &g_Serv, &Args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pItemThis; + pItemThis->OnTrigger(ITRIG_Ship_Stop, pScriptArgs, &g_Serv); } _pCaptain = nullptr; @@ -1260,7 +1267,10 @@ bool CCMultiMovable::r_Verb(CScript & s, CTextConsole * pSrc) // Execute command tchar szText[MAX_TALK_BUFFER]; Str_CopyLimitNull(szText, pszSpeak, MAX_TALK_BUFFER); - pChar->ParseScriptText(szText, &g_Serv); + + CScriptExprContext scpContext{._pScriptObjI = pChar}; + CExpression::GetExprParser().ParseScriptText(szText, scpContext, CScriptParserBufs::GetCScriptTriggerArgsPtr(), &g_Serv); + pTiller->Speak(szText, HUE_TEXT_DEF, TALKMODE_SAY, FONT_NORMAL); } return true; diff --git a/src/game/components/CCPropsChar.cpp b/src/game/components/CCPropsChar.cpp index 7c34a1664..723febb19 100644 --- a/src/game/components/CCPropsChar.cpp +++ b/src/game/components/CCPropsChar.cpp @@ -62,6 +62,8 @@ bool CCPropsChar::IgnoreElementalProperty(PropertyIndex_t iPropIndex) // static case PROPCH_RESFIRE: case PROPCH_RESFIREMAX: return true; + default: + break; } return false; } diff --git a/src/game/components/CCSpawn.cpp b/src/game/components/CCSpawn.cpp index 12ddc0cda..317a8636c 100644 --- a/src/game/components/CCSpawn.cpp +++ b/src/game/components/CCSpawn.cpp @@ -1,8 +1,9 @@ #include "../../common/resource/sections/CRandGroupDef.h" #include "../../common/sphere_library/CSRand.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../chars/CChar.h" #include "../chars/CCharNPC.h" #include "../CObjBase.h" @@ -25,7 +26,7 @@ void CCSpawn::AddBadSpawn() { return; } - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); if (std::find(_vBadSpawns.cbegin(), _vBadSpawns.cend(), this) == _vBadSpawns.cend()) { _vBadSpawns.emplace_back(this); //only if it's not inserted already. @@ -36,7 +37,7 @@ void CCSpawn::AddBadSpawn() void CCSpawn::DelBadSpawn() { ADDTOCALLSTACK("CCSpawn::DelBadSpawn"); - MT_ENGINE_UNIQUE_LOCK_SET; + MT_ENGINE_UNIQUE_LOCK_SET(this); if (!_vBadSpawns.empty()) { _vBadSpawns.erase(std::find(_vBadSpawns.begin(), _vBadSpawns.end(), this)); @@ -80,6 +81,7 @@ CCSpawn::~CCSpawn() CItem * CCSpawn::GetLink() const { + ASSERT(_pLink != nullptr); return _pLink; } @@ -309,12 +311,13 @@ void CCSpawn::GenerateItem() if (IsTrigUsed(TRIGGER_PRESPAWN)) { - CScriptTriggerArgs args(rid.GetResIndex()); - if (pSpawnItem->OnTrigger(ITRIG_PreSpawn, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(rid.GetResIndex(), 0, 0, nullptr); + if (pSpawnItem->OnTrigger(ITRIG_PreSpawn, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) { return; } - rid = CResourceIDBase(RES_ITEMDEF, (int)args.m_iN1); + rid = CResourceIDBase(RES_ITEMDEF, (int)pScriptArgs->m_iN1); } const ITEMID_TYPE id = (ITEMID_TYPE)(rid.GetResIndex()); CItem *pItem = CItem::CreateTemplate(id); @@ -339,9 +342,9 @@ void CCSpawn::GenerateItem() //pItem->SetDecayTime(g_Cfg.m_iDecay_Item); // it will decay eventually to be replaced later if (IsTrigUsed(TRIGGER_SPAWN)) { - CScriptTriggerArgs args; - args.m_pO1 = pItem; - if (pSpawnItem->OnTrigger(ITRIG_Spawn, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pItem; + if (pSpawnItem->OnTrigger(ITRIG_Spawn, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) { pItem->Delete(); return; @@ -383,12 +386,13 @@ CChar* CCSpawn::GenerateChar(CResourceIDBase rid) } if (IsTrigUsed(TRIGGER_PRESPAWN)) { - CScriptTriggerArgs args(rid.GetResIndex()); - if (pSpawnItem->OnTrigger(ITRIG_PreSpawn, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = rid.GetResIndex(); + if (pSpawnItem->OnTrigger(ITRIG_PreSpawn, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) { return nullptr; } - rid = CResourceIDBase(RES_CHARDEF, (int)args.m_iN1); + rid = CResourceIDBase(RES_CHARDEF, (int)pScriptArgs->m_iN1); } RES_TYPE iRidType = rid.GetResType(); @@ -413,9 +417,9 @@ CChar* CCSpawn::GenerateChar(CResourceIDBase rid) if (IsTrigUsed(TRIGGER_SPAWN)) { - CScriptTriggerArgs args; - args.m_pO1 = pChar; - if (pSpawnItem->OnTrigger(ITRIG_Spawn, &g_Serv, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pChar; + if (pSpawnItem->OnTrigger(ITRIG_Spawn, pScriptArgs, &g_Serv) == TRIGRET_RET_TRUE) { pChar->Delete(); return nullptr; @@ -566,11 +570,11 @@ void CCSpawn::DelObj(const CUID& uid) if (IsTrigUsed(TRIGGER_DELOBJ)) { - CScriptTriggerArgs args; - args.m_pO1 = pSpawnItem; - args.m_iN1 = pSpawnItem->_GetTimerAdjusted() / MSECS_PER_SEC; - pSpawnItem->OnTrigger(ITRIG_DELOBJ, &g_Serv, &args); - pSpawnItem->_SetTimeoutS(args.m_iN1); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pSpawnItem; + pScriptArgs->m_iN1 = pSpawnItem->_GetTimerAdjusted() / MSECS_PER_SEC; + pSpawnItem->OnTrigger(ITRIG_DELOBJ, pScriptArgs, &g_Serv); + pSpawnItem->_SetTimeoutS(pScriptArgs->m_iN1); } pSpawnItem->UpdatePropertyFlag(); @@ -594,7 +598,7 @@ void CCSpawn::AddObj(const CUID& uid) bool fIsSpawnChar = (pSpawnItem->IsType(IT_SPAWN_CHAR) || pSpawnItem->IsType(IT_SPAWN_CHAMPION)); bool fIsSpawnChampion = pSpawnItem->IsType(IT_SPAWN_CHAMPION); - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { // Only checking UIDs when server is running because some of them may not yet exist when loading worldsave. if (!uid.IsValidUID()) @@ -643,12 +647,12 @@ void CCSpawn::AddObj(const CUID& uid) if (IsTrigUsed(TRIGGER_ADDOBJ)) { - CScriptTriggerArgs args; - args.m_pO1 = pSpawnedObj; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pSpawnedObj; const int64 iTimer= pSpawnItem->_GetTimerAdjusted(); - args.m_iN1 = (iTimer < 0) ? -1 : iTimer/MSECS_PER_SEC; - pSpawnItem->OnTrigger(ITRIG_ADDOBJ, &g_Serv, &args); - pSpawnItem->_SetTimeoutS(args.m_iN1); + pScriptArgs->m_iN1 = (iTimer < 0) ? -1 : iTimer/MSECS_PER_SEC; + pSpawnItem->OnTrigger(ITRIG_ADDOBJ, pScriptArgs, &g_Serv); + pSpawnItem->_SetTimeoutS(pScriptArgs->m_iN1); } pSpawnItem->UpdatePropertyFlag(); } @@ -1017,7 +1021,7 @@ bool CCSpawn::r_LoadVal(CScript & s) return false; } } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { FixDef(); SetTrackID(); diff --git a/src/game/components/CCSpawn.h b/src/game/components/CCSpawn.h index bea92a9c1..9725064ed 100644 --- a/src/game/components/CCSpawn.h +++ b/src/game/components/CCSpawn.h @@ -154,13 +154,13 @@ class CCSpawn : public CComponent /** * @brief Removing one UID in Spawn's m_obj[]. - * @param UID of the obj to remove. + * @param uid of the obj to remove. */ void DelObj(const CUID& uid); /** * @brief Storing one UID in Spawn's m_obj[]. - * @param UID of the obj to add. + * @param uid of the obj to add. */ void AddObj(const CUID& uid); diff --git a/src/game/game_enums.h b/src/game/game_enums.h index 637174639..3aaea7e3d 100644 --- a/src/game/game_enums.h +++ b/src/game/game_enums.h @@ -143,7 +143,6 @@ enum CLIMODE_TYPE // What mode is the client to server connection in ? (waiting }; - ////////////////////////////////////////////////////////////////////////// // Buff Icons diff --git a/src/game/items/CItem.cpp b/src/game/items/CItem.cpp index 2f21b4740..09d90e718 100644 --- a/src/game/items/CItem.cpp +++ b/src/game/items/CItem.cpp @@ -1,7 +1,8 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../network/CClientIterator.h" #include "../../network/send.h" #include "../components/CCChampion.h" @@ -243,7 +244,7 @@ bool CItem::NotifyDelete() ADDTOCALLSTACK("CItem::NotifyDelete"); if ((IsTrigUsed(TRIGGER_DESTROY)) || (IsTrigUsed(TRIGGER_ITEMDESTROY))) { - if (CItem::OnTrigger(ITRIG_DESTROY, &g_Serv) == TRIGRET_RET_TRUE) + if (CItem::OnTrigger(ITRIG_DESTROY, CScriptParserBufs::GetCScriptTriggerArgsPtr(), &g_Serv) == TRIGRET_RET_TRUE) return false; } @@ -452,7 +453,7 @@ CItem * CItem::GenerateScript( CChar * pSrc) CResourceLock s; if ( pItemDef->ResourceLock(s)) { - OnTrigger(ITRIG_Create, pSrc ? static_cast(pSrc) : static_cast(&g_Serv), nullptr); + OnTrigger(ITRIG_Create, CScriptParserBufs::GetCScriptTriggerArgsPtr(), pSrc ? static_cast(pSrc) : static_cast(&g_Serv)); } return this; } @@ -650,15 +651,15 @@ CItem * CItem::ReadTemplate( CResourceLock & s, CObjBase * pCont ) // static continue; { lptstr ptcFunctionName = s.GetArgRaw(); - std::unique_ptr pScriptArgs; - // Locate arguments for the called function + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + // Locate arguments for the called function tchar* ptcArgs = strchr(ptcFunctionName, ' '); if (ptcArgs) { *ptcArgs = 0; ++ptcArgs; GETNONWHITESPACE(ptcArgs); - pScriptArgs = std::make_unique(ptcArgs); + pScriptArgs->Init(ptcArgs); } // use pCont is exist, if not use g_Serv @@ -666,11 +667,11 @@ CItem * CItem::ReadTemplate( CResourceLock & s, CObjBase * pCont ) // static { CObjBaseTemplate* pContObjBaseT = pCont->GetTopLevelObj(); ASSERT(pContObjBaseT); - pItem->r_Call(ptcFunctionName, dynamic_cast(pContObjBaseT), pScriptArgs.get()); + pItem->r_Call(ptcFunctionName, pScriptArgs, dynamic_cast(pContObjBaseT)); } else { - pItem->r_Call(ptcFunctionName, &g_Serv, pScriptArgs.get()); + pItem->r_Call(ptcFunctionName, pScriptArgs, &g_Serv); } if (pItem->IsDeleted()) @@ -862,7 +863,7 @@ int CItem::FixWeirdness() } else { - DEBUG_ERR(("'%s' Bad Link to 0%x\n", GetName(), (dword)(m_uidLink))); + g_Log.EventError("GC: Object '%s' has Bad Link to UID 0%" PRIx32 ".\n", GetName(), m_uidLink.GetObjUID()); m_uidLink.InitUID(); iResultCode = 0x2205; return iResultCode; // get rid of it. @@ -1565,7 +1566,7 @@ bool CItem::MoveTo(const CPointMap& pt, bool fForceFix) // Put item on the groun pSector->MoveItemToSector(this); // This also awakes the item // Is this area too complex ? - if ( ! g_Serv.IsLoading()) + if ( ! g_Serv.IsLoadingGeneric()) { if (pSector->CheckItemComplexity()) { @@ -1624,19 +1625,19 @@ bool CItem::MoveToCheck( const CPointMap & pt, CChar * pCharMover ) TRIGRET_TYPE ttResult = TRIGRET_RET_DEFAULT; if (IsTrigUsed(TRIGGER_DROPON_GROUND) || IsTrigUsed(TRIGGER_ITEMDROPON_GROUND)) { - CScriptTriggerArgs args; - args.m_iN1 = iDecayTime / MSECS_PER_TENTH; // ARGN1 = Decay time for the dropped item (in tenths of second) - //args.m_iN2 = 0; - args.m_s1 = ptNewPlace.WriteUsed(); - ttResult = OnTrigger(ITRIG_DROPON_GROUND, pCharMover, &args); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = iDecayTime / MSECS_PER_TENTH; // ARGN1 = Decay time for the dropped item (in tenths of second) + //args->m_iN2 = 0; + pScriptArgs->m_s1 = ptNewPlace.WriteUsed(); + ttResult = OnTrigger(ITRIG_DROPON_GROUND, pScriptArgs, pCharMover); if (IsDeleted()) return false; - iDecayTime = args.m_iN1 * MSECS_PER_TENTH; + iDecayTime = pScriptArgs->m_iN1 * MSECS_PER_TENTH; // Warning: here we ignore the read-onlyness of CSString's buffer only because we know that CPointMap constructor won't write past the end, but only replace some characters with '\0'. It's not worth it to build another string just for that. - tchar* ptcArgs = const_cast(args.m_s1.GetBuffer()); + tchar* ptcArgs = const_cast(pScriptArgs->m_s1.GetBuffer()); const CPointMap ptChanged(ptcArgs); if (!ptChanged.IsValidPoint()) g_Log.EventError("Trying to override item drop P with an invalid P. Using the original one.\n"); @@ -1866,13 +1867,13 @@ lpctstr CItem::GetNameFull( bool fIdentified ) const len += Str_CopyLimitNull( pTemp+len, g_Cfg.GetDefaultMsg( DEFMSG_ITEMTITLE_BLANK ), Str_TempLength() - len); break; case IT_RUNE: - if ( ! m_itRune.m_ptMark.IsCharValid()) + if ( ! m_itRune.m_ptMark.IsCharValid()) len += Str_CopyLimitNull( pTemp+len, g_Cfg.GetDefaultMsg( DEFMSG_ITEMTITLE_BLANK ), Str_TempLength() - len); else if ( ! m_itRune.m_Strength ) len += Str_CopyLimitNull( pTemp+len, g_Cfg.GetDefaultMsg( DEFMSG_ITEMTITLE_FADED ), Str_TempLength() - len); break; case IT_TELEPAD: - if ( ! m_itTelepad.m_ptMark.IsValidPoint()) + if ( ! m_itTelepad.m_ptMark.IsCharValid()) len += Str_CopyLimitNull( pTemp+len, g_Cfg.GetDefaultMsg( DEFMSG_ITEMTITLE_BLANK ), Str_TempLength() - len); break; default: @@ -2053,14 +2054,16 @@ height_t CItem::GetHeight() const return tmpHeight; } - char heightDef[24]{"itemheight_"}; + auto reader = g_ExprGlobals.mtEngineLockedReader(); + + char heightDef[24]{"itemheight_"}; Str_FromUI(uint(uiDispID), heightDef + 11, sizeof(heightDef) - 11, 16); - tmpHeight = static_cast(g_Exp.m_VarDefs.GetKeyNum(heightDef)); + tmpHeight = static_cast(reader->m_VarDefs.GetKeyNum(heightDef)); if ( tmpHeight ) //set by a defname ([DEFNAME charheight] height_0a) return tmpHeight; Str_FromUI(uint(uiDispID), heightDef + 11, sizeof(heightDef) - 11, 10); - tmpHeight = static_cast(g_Exp.m_VarDefs.GetKeyNum(heightDef)); + tmpHeight = static_cast(reader->m_VarDefs.GetKeyNum(heightDef)); if ( tmpHeight ) //set by a defname ([DEFNAME charheight] height_10) return tmpHeight; @@ -2371,7 +2374,7 @@ void CItem::r_WriteMore1(CSString & sVal) if (ptcErr) { - g_Log.EventError("Invalid MORE1 for item 0%" PRIx32 ": %s.\n", + g_Log.EventError("Invalid MORE1 for item 0%" PRIx32 ": %s", GetUID().GetObjUID(), ptcErr); } } @@ -2431,7 +2434,7 @@ void CItem::r_WriteMore2( CSString & sVal ) if (ptcErr) { - g_Log.EventError("Invalid MORE2 for item 0%" PRIx32 ": %s.\n", + g_Log.EventError("Invalid MORE2 for item 0%" PRIx32 ": %s", GetUID().GetObjUID(), ptcErr); } } @@ -3000,7 +3003,7 @@ void CItem::r_LoadMore1(dword dwVal) case IT_SPAWN_CHAR: case IT_SPAWN_ITEM: case IT_SPAWN_CHAMPION: - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { CCSpawn* pSpawn = GetSpawn(); if (pSpawn) @@ -3270,8 +3273,8 @@ bool CItem::r_LoadVal( CScript & s ) // Load an item Script bool normcont = LoadSetContainer(CUID(s.GetArgDWVal()), (LAYER_TYPE)GetUnkZ()); if (!normcont) { - SERVMODE_TYPE iModeCode = g_Serv.GetServerMode(); - if ((iModeCode == SERVMODE_Loading) || (iModeCode == SERVMODE_GarbageCollection)) + ServMode iModeCode = g_Serv.GetServerMode(); + if ((iModeCode == ServMode::StartupLoadingSaves) || (iModeCode == ServMode::GarbageCollection)) Delete(); // since the item is no longer in container, it should be deleted } return normcont; @@ -3476,12 +3479,13 @@ bool CItem::r_LoadVal( CScript & s ) // Load an item Script m_itNormal.m_morep.m_z = s.GetArgCVal(); break; case IC_P: - // Loading or import ONLY ! others use the r_Verb + // Loading or import ONLY ! others use CObjBase::r_Verb if ( ! IsDisconnected() && ! IsItemInContainer() ) return false; else { - // Will be placed in the world later. + // Will be placed in the world later (in CItem::r_Load): + // since we are loading the world, the parent region might not be created/"realized" yet. CPointMap pt; pt.Read( s.GetArgStr()); if (pt.IsValidPoint()) @@ -3517,13 +3521,16 @@ bool CItem::r_LoadVal( CScript & s ) // Load an item Script bool CItem::r_Load( CScript & s ) // Load an item from script { ADDTOCALLSTACK("CItem::r_Load"); - CScriptObj::r_Load( s ); - if ( GetContainer() == nullptr ) - { + CScriptObj::r_Load( s ); + + if ( GetContainer() == nullptr ) + { // Actually place the item into the world. - if ( GetTopPoint().IsCharValid()) + if ( GetTopPoint().IsCharValid()) + { MoveToUpdate( GetTopPoint()); + } } int iResultCode = CObjBase::IsWeird(); @@ -3719,13 +3726,14 @@ void CItem::SetTriggerActive(lpctstr trig) _iRunningTriggerId = -1; } -TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) { ADDTOCALLSTACK("CItem::OnTrigger"); if (IsTriggerActive(pszTrigName)) //This should protect any item trigger from infinite loop return TRIGRET_RET_ABORTED; + ASSERT(pScriptArgs); if ( !pSrc ) pSrc = &g_Serv; @@ -3760,7 +3768,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript EXC_SET_BLOCK("chardef"); const CUID uidOldAct = pChar->m_Act_UID; pChar->m_Act_UID = GetUID(); - iRet = pChar->OnTrigger(ptcCharTrigName, pSrc, pArgs); + iRet = pChar->OnTrigger(ptcCharTrigName, pScriptArgs, pSrc); pChar->m_Act_UID = uidOldAct; if (iRet == TRIGRET_RET_TRUE) goto stopandret; // Block further action. @@ -3785,7 +3793,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript if (!pLink->ResourceLock(s)) continue; - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if (iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT) goto stopandret; @@ -3809,7 +3817,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript CResourceLock s; if ( !pLink->ResourceLock(s) ) continue; - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if ( iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT ) goto stopandret; } @@ -3824,7 +3832,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript CResourceLock s; if ( !pLink->ResourceLock(s) ) continue; - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); if ( iRet != TRIGRET_RET_FALSE && iRet != TRIGRET_RET_DEFAULT ) goto stopandret; } @@ -3838,7 +3846,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript { const CChar* pChar = pSrc->GetChar(); if ( pChar ) - g_Log.EventError( "0%x '%s' has unhandled [TYPEDEF %d] for 0%x '%s'\n", (dword) GetUID(), GetName(), GetType(), (dword) pChar->GetUID(), pChar->GetName()); + g_Log.EventError( "0%x '%s' has unhandled [TYPEDEF %d] for 0%x '%s'\n", (dword) GetUID(), GetName(), GetType(), (dword) pChar->GetUID(), pChar->GetName()); else g_Log.EventError( "0%x '%s' has unhandled [TYPEDEF %d]\n", (dword) GetUID(), GetName(), GetType() ); SetType(Item_GetDef()->GetType()); @@ -3851,7 +3859,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript CResourceLock s; if ( pResourceLink->ResourceLock(s)) { - iRet = CScriptObj::OnTriggerScript( s, pszTrigName, pSrc, pArgs ); + iRet = CScriptObj::OnTriggerScript( s, pszTrigName, pScriptArgs, pSrc ); if ( iRet == TRIGRET_RET_TRUE ) goto stopandret; } @@ -3869,7 +3877,7 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript { CResourceLock s; if (pResourceLink->ResourceLock(s)) - iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pSrc, pArgs); + iRet = CScriptObj::OnTriggerScript(s, pszTrigName, pScriptArgs, pSrc); } // If i'm running the @Create trigger, i jumped here first, but i need to go back and try to run the trigger from the other sources @@ -3884,15 +3892,15 @@ TRIGRET_TYPE CItem::OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScript EXC_CATCH; EXC_DEBUG_START; - g_Log.EventDebug("trigger '%s' action '%d' char '0%x' [0%x]\n", pszTrigName, iAction, (pSrc && pSrc->GetChar()) ? (dword)pSrc->GetChar()->GetUID() : 0, (dword)GetUID()); + g_Log.EventDebug("trigger '%s' action '%d' char '0%x' [0%x]\n", pszTrigName, iAction, ((pSrc && pSrc->GetChar()) ? (dword)pSrc->GetChar()->GetUID() : 0), (dword)GetUID()); EXC_DEBUG_END; return iRet; } -TRIGRET_TYPE CItem::OnTrigger( ITRIG_TYPE trigger, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) +TRIGRET_TYPE CItem::OnTrigger( ITRIG_TYPE trigger, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) { ASSERT((trigger >= 0) && (trigger < ITRIG_QTY)); - return OnTrigger( CItem::sm_szTrigName[trigger], pSrc, pArgs ); + return OnTrigger( CItem::sm_szTrigName[trigger], pScriptArgs, pSrc ); } // Item type specific stuff. @@ -5579,21 +5587,22 @@ bool CItem::SetMagicLock( CChar * pCharSrc, int iSkillLevel ) return true; } -bool CItem::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, CItem * pSourceItem, bool bReflecting, int64 iDuration) +bool CItem::OnSpellEffect(SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, CItem * pSourceItem, bool fReflecting, int64 iDuration) { ADDTOCALLSTACK("CItem::OnSpellEffect"); - UnreferencedParameter(bReflecting); // items are not affected by Magic Reflection + UnreferencedParameter(fReflecting); // items are not affected by Magic Reflection UnreferencedParameter(iDuration); // A spell is cast on this item. // ARGS: // iSkillLevel = 0-1000 = difficulty. may be slightly larger . how advanced is this spell (might be from a wand) const CSpellDef * pSpellDef = g_Cfg.GetSpellDef(spell); - CScriptTriggerArgs Args( spell, iSkillLevel, pSourceItem ); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(spell, iSkillLevel, 0, pSourceItem); if ( IsTrigUsed(TRIGGER_SPELLEFFECT) || IsTrigUsed(TRIGGER_ITEMSPELL) ) { - switch ( OnTrigger(ITRIG_SPELLEFFECT, pCharSrc, &Args) ) + switch ( OnTrigger(ITRIG_SPELLEFFECT, pScriptArgs, pCharSrc) ) { case TRIGRET_RET_TRUE: return false; @@ -5608,7 +5617,7 @@ bool CItem::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, if ( IsTrigUsed(TRIGGER_EFFECT) ) { - switch (Spell_OnTrigger(spell, SPTRIG_EFFECT, pCharSrc, &Args)) + switch (Spell_OnTrigger(spell, SPTRIG_EFFECT, pScriptArgs, pCharSrc)) { case TRIGRET_RET_TRUE: return false; @@ -5621,8 +5630,8 @@ bool CItem::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, } } - spell = (SPELL_TYPE)(Args.m_iN1); - iSkillLevel = (int)(Args.m_iN2); + spell = (SPELL_TYPE)(pScriptArgs->m_iN1); + iSkillLevel = (int)(pScriptArgs->m_iN2); pSpellDef = g_Cfg.GetSpellDef( spell ); ASSERT(pSpellDef); @@ -5742,8 +5751,8 @@ bool CItem::OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, if ( (iEffectID > ITEMID_NOTHING) && (iEffectID < ITEMID_QTY) ) { bool fExplode = (pSpellDef->IsSpellType(SPELLFLAG_FX_BOLT) && !pSpellDef->IsSpellType(SPELLFLAG_GOOD)); // bolt (chasing) spells have explode = 1 by default (if not good spell) - dword dwColor = (dword)(Args.m_VarsLocal.GetKeyNum("EffectColor")); - dword dwRender = (dword)(Args.m_VarsLocal.GetKeyNum("EffectRender")); + dword dwColor = (dword)(pScriptArgs->m_VarsLocal.GetKeyNum("EffectColor")); + dword dwRender = (dword)(pScriptArgs->m_VarsLocal.GetKeyNum("EffectRender")); if ( pSpellDef->IsSpellType(SPELLFLAG_FX_BOLT) ) Effect(EFFECT_BOLT, iEffectID, pCharSrc, 5, 1, fExplode, dwColor, dwRender); @@ -5816,8 +5825,9 @@ int CItem::OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uType ) if ( IsTrigUsed(TRIGGER_DAMAGE) || IsTrigUsed(TRIGGER_ITEMDAMAGE) ) { - CScriptTriggerArgs Args(iDmg, (int)(uType)); - if ( OnTrigger( ITRIG_DAMAGE, pSrc, &Args ) == TRIGRET_RET_TRUE ) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iDmg, (int)uType, 0, nullptr); + if ( OnTrigger( ITRIG_DAMAGE, pScriptArgs, pSrc ) == TRIGRET_RET_TRUE ) return 0; } @@ -6148,10 +6158,10 @@ bool CItem::_CanHoldTimer() const return true; } -bool CItem::_CanTick() const +bool CItem::_TickableState() const { - //ADDTOCALLSTACK_DEBUG("CItem::_CanTick"); - EXC_TRY("Can tick?"); + //ADDTOCALLSTACK_DEBUG("CItem::_TickableState"); + EXC_TRY("Able to tick?"); const CObjBase* pCont = GetContainer(); const bool fIgnoreCont = (HAS_FLAGS_STRICT(g_Cfg.m_uiItemTimers, ITEM_CANTIMER_IN_CONTAINER) || Can(CAN_I_TIMER_CONTAINED)); @@ -6171,23 +6181,23 @@ bool CItem::_CanTick() const return false; } - return CObjBase::_CanTick(); + return CObjBase::_TickableState(); } if (IsAttr(ATTR_DECAY) && !pCont) { // If pCont is not a CObjBase, it will most probably be a CSector. Decaying items won't go to sleep. - return CObjBase::_CanTick(); + return CObjBase::_TickableState(); } const bool fCharCont = pCont && pCont->IsChar(); - if (fCharCont && !pCont->CanTick()) + if (fCharCont && !pCont->TickableState()) { // Is it equipped on a Char? return false; } - return CObjBase::_CanTick(); + return CObjBase::_TickableState(); EXC_CATCH; @@ -6207,7 +6217,7 @@ bool CItem::_OnTick() if (!_IsSleeping()) { - if (!_CanTick()) + if (!_TickableState()) { const CSector* pSector = GetTopSector(); // It prints an error if it belongs to an invalid sector. if (pSector && pSector->IsSleeping()) @@ -6225,7 +6235,8 @@ bool CItem::_OnTick() if (( IsTrigUsed(TRIGGER_TIMER) ) || ( IsTrigUsed(TRIGGER_ITEMTIMER) )) { - iRet = OnTrigger( ITRIG_TIMER, &g_Serv ); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + iRet = OnTrigger( ITRIG_TIMER, pScriptArgs, &g_Serv ); if (iRet == TRIGRET_RET_TRUE) { return true; diff --git a/src/game/items/CItem.h b/src/game/items/CItem.h index 4b71a2d3b..34ebe0448 100644 --- a/src/game/items/CItem.h +++ b/src/game/items/CItem.h @@ -608,7 +608,7 @@ class CItem : public CObjBase public: virtual bool _OnTick() override; - virtual bool _CanTick() const override; + virtual bool _TickableState() const override; bool _CanHoldTimer() const; virtual void DupeCopy( const CObjBase * pItem ) override; @@ -726,7 +726,9 @@ class CItem : public CObjBase bool IsTopLevelMultiLocked() const; bool IsMovableType() const; bool IsMovable() const; - virtual int GetVisualRange() const override; + + [[nodiscard]] + virtual int GetVisualRange() const override; bool IsStackableException() const; bool IsStackable( const CItem * pItem ) const; @@ -821,8 +823,8 @@ protected: virtual void _SetTimeout(int64 iMsecs) override final; */ void SetTriggerActive(lpctstr trig = nullptr); - virtual TRIGRET_TYPE OnTrigger( lpctstr pszTrigName, CTextConsole * pSrc, CScriptTriggerArgs * pArgs ) override; - TRIGRET_TYPE OnTrigger( ITRIG_TYPE trigger, CTextConsole * pSrc, CScriptTriggerArgs * pArgs = nullptr ); + virtual TRIGRET_TYPE OnTrigger( lpctstr pszTrigName, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ) override; + TRIGRET_TYPE OnTrigger( ITRIG_TYPE trigger, CScriptTriggerArgsPtr const& pScriptArgs, CTextConsole * pSrc ); // Item type specific stuff. inline bool IsType(IT_TYPE type) const noexcept { @@ -883,7 +885,7 @@ protected: virtual void _SetTimeout(int64 iMsecs) override final; bool IsBookSystem() const; void OnExplosion(); - virtual bool OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, CItem * pSourceItem, bool bReflecting = false, int64 iDuration = 0) override; + virtual bool OnSpellEffect( SPELL_TYPE spell, CChar * pCharSrc, int iSkillLevel, CItem * pSourceItem, bool fReflecting = false, int64 iDuration = 0) override; int OnTakeDamage( int iDmg, CChar * pSrc, DAMAGE_TYPE uType = DAMAGE_HIT_BLUNT ); int Armor_GetRepairPercent() const; diff --git a/src/game/items/CItemBase.cpp b/src/game/items/CItemBase.cpp index 1d888e94a..9b1f150f0 100644 --- a/src/game/items/CItemBase.cpp +++ b/src/game/items/CItemBase.cpp @@ -4,8 +4,8 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/CUOInstall.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "../../sphere/ProfileTask.h" #include "../../sphere/threads.h" #include "../components/CCPropsItem.h" diff --git a/src/game/items/CItemCommCrystal.cpp b/src/game/items/CItemCommCrystal.cpp index ce01f2d59..c9e1ad389 100644 --- a/src/game/items/CItemCommCrystal.cpp +++ b/src/game/items/CItemCommCrystal.cpp @@ -1,5 +1,5 @@ #include "../../common/resource/CResourceLock.h" -//#include "../../common/CException.h" +//#include "../../common/CException.h" // included in the precompiled header #include "../CSector.h" #include "CItemVendable.h" #include "CItemCommCrystal.h" diff --git a/src/game/items/CItemContainer.cpp b/src/game/items/CItemContainer.cpp index 58e0fbc52..2c13b964a 100644 --- a/src/game/items/CItemContainer.cpp +++ b/src/game/items/CItemContainer.cpp @@ -1,6 +1,7 @@ #include "../../common/sphere_library/CSRand.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" #include "../../network/send.h" #include "../chars/CChar.h" @@ -157,29 +158,34 @@ void CItemContainer::Trade_Status( bool bCheck ) if ( IsTrigUsed(TRIGGER_TRADEACCEPTED) || IsTrigUsed(TRIGGER_CHARTRADEACCEPTED) ) { - CScriptTriggerArgs Args1(pChar1); + CScriptTriggerArgsPtr pScriptArgsPlayer1 = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgsPlayer1->Init(pChar1); + + CScriptTriggerArgsPtr pScriptArgsPlayer2 = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgsPlayer2->Init(pChar2); + ushort i = 1; for (CSObjContRec* pObjRec : *pPartner) { CItem* pItem = static_cast(pObjRec); - Args1.m_VarObjs.Insert(i, pItem, true); + pScriptArgsPlayer1->m_VarObjs.Insert(i, pItem, true); ++i; } - Args1.m_iN1 = --i; + pScriptArgsPlayer1->m_iN1 = --i; - CScriptTriggerArgs Args2(pChar2); i = 1; for (CSObjContRec * pObjRec : *this) { CItem* pItem = static_cast(pObjRec); - Args2.m_VarObjs.Insert(i, pItem, true); + pScriptArgsPlayer2->m_VarObjs.Insert(i, pItem, true); ++i; } - Args2.m_iN1 = --i; + pScriptArgsPlayer2->m_iN1 = --i; - Args1.m_iN2 = Args2.m_iN1; - Args2.m_iN2 = Args1.m_iN1; - if ( (pChar1->OnTrigger(CTRIG_TradeAccepted, pChar2, &Args1) == TRIGRET_RET_TRUE) || (pChar2->OnTrigger(CTRIG_TradeAccepted, pChar1, &Args2) == TRIGRET_RET_TRUE) ) + pScriptArgsPlayer1->m_iN2 = pScriptArgsPlayer2->m_iN1; + pScriptArgsPlayer2->m_iN2 = pScriptArgsPlayer1->m_iN1; + if ((pChar1->OnTrigger(CTRIG_TradeAccepted, pScriptArgsPlayer1, pChar2) == TRIGRET_RET_TRUE) + || (pChar2->OnTrigger(CTRIG_TradeAccepted, pScriptArgsPlayer2, pChar1) == TRIGRET_RET_TRUE) ) return; } @@ -314,11 +320,15 @@ bool CItemContainer::Trade_Delete() if ( IsTrigUsed(TRIGGER_TRADECLOSE) ) { - CChar *pChar2 = dynamic_cast(pPartner->GetParent()); - CScriptTriggerArgs Args(pChar2); - pChar->OnTrigger(CTRIG_TradeClose, pChar, &Args); - CScriptTriggerArgs Args2(pChar); - pChar2->OnTrigger(CTRIG_TradeClose, pChar, &Args2); + CScriptTriggerArgsPtr pScriptArgsPlayer1 = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgsPlayer1->Init(pChar); + + CChar *pChar2 = dynamic_cast(pPartner->GetParent()); + CScriptTriggerArgsPtr pScriptArgsPlayer2 = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgsPlayer2->Init(pChar2); + + pChar->OnTrigger(CTRIG_TradeClose, pScriptArgsPlayer1, pChar); + pChar2->OnTrigger(CTRIG_TradeClose, pScriptArgsPlayer2, pChar); } m_uidLink.InitUID(); // unlink. @@ -461,48 +471,72 @@ CPointMap CItemContainer::GetRandContainerLoc() const { GUMP_CHEST_METAL2, 18, 105, 162, 178 } }; - // Get a random location in the container. + // Prepare fallback values, in case we don't have hardcoded one and user didn't define TDATA3/4. + short minValX = 0; + short minValY = 0; + short maxValX = 512; + short maxValY = 512; const CItemBase *pItemDef = Item_GetDef(); - GUMP_TYPE gump = pItemDef->m_ttContainer.m_idGump; // Get the TDATA2 + // Get gump visual from item TDATA2. + const GUMP_TYPE gump = pItemDef->m_ttContainer.m_idGump; + // Prepare a random location in the container. + const int iRandOnce = CSRand::GetValFast(UINT16_MAX); - // check for custom values in TDATA3/TDATA4 - if ( pItemDef->m_ttContainer.m_dwMinXY || pItemDef->m_ttContainer.m_dwMaxXY ) + // Use custom values in TDATA3/TDATA4, if they are defined. + if ( pItemDef->m_ttContainer.m_dwMinXY && pItemDef->m_ttContainer.m_dwMaxXY ) { - int tmp_MinX = pItemDef->m_ttContainer.m_dwMinXY >> 16; - int tmp_MinY = (pItemDef->m_ttContainer.m_dwMinXY & 0x0000FFFF); - int tmp_MaxX = pItemDef->m_ttContainer.m_dwMaxXY >> 16; - int tmp_MaxY = (pItemDef->m_ttContainer.m_dwMaxXY & 0x0000FFFF); - //DEBUG_WARN(("Custom container gump id %d for 0%x\n", gump, GetDispID())); - return CPointMap( - (word)(tmp_MinX + g_Rand.GetValFast(tmp_MaxX - tmp_MinX)), - (word)(tmp_MinY + g_Rand.GetValFast(tmp_MaxY - tmp_MinY)), - 0); + minValX = pItemDef->m_ttContainer.m_dwMinXY >> 16; + minValY = (pItemDef->m_ttContainer.m_dwMinXY & 0x0000FFFF); + maxValX = pItemDef->m_ttContainer.m_dwMaxXY >> 16; + maxValY = (pItemDef->m_ttContainer.m_dwMaxXY & 0x0000FFFF); + + // Make sure this value aren't same, we can't divide by zero. + if (maxValX == minValX) + maxValX += 1; + if (maxValY == minValY) + maxValY += 1; + + return { + static_cast(minValX + (iRandOnce % (maxValX - minValX))), + static_cast(minValY + (iRandOnce % (maxValY - minValY))), + 0 }; } - // No TDATA3 or no TDATA4: check if we have hardcoded in sm_ContSize the size of the gump indicated by TDATA2 + // We may want a keyring with no gump, so no need to show the warning. + if (IsType(IT_KEYRING)) + { + return { + static_cast(minValX + (iRandOnce % (maxValX - minValX))), + static_cast(minValY + (iRandOnce % (maxValY - minValY))), + 0 }; + } + // No TDATA3 and TDATA4: check if we have hardcoded in sm_ContSize the size of the gump indicated by TDATA2. uint i = 0; - // We may want a keyring with no gump, so no need to show the warning. - if (!IsType(IT_KEYRING)) + for (; ; ++i) { - for (; ; ++i) + // We didn't find anything in hardcoded list. + if (i >= std::size(sm_ContSize)) { - if (i >= ARRAY_COUNT(sm_ContSize)) - { - i = 0; // set to default - g_Log.EventWarn("Unknown container gump id %d for 0%x\n", gump, GetDispID()); - break; - } - if (sm_ContSize[i].m_gump == gump) - break; + g_Log.EventWarn("Container 0%x with gump %d doesn't have TDATA3/4 defined.\n", GetDispID(), gump); + break; + } + // We got a hardoded gump sizes, use them. + if (sm_ContSize[i].m_gump == gump) + { + minValX = sm_ContSize[i].m_minx; + minValY = sm_ContSize[i].m_miny; + maxValX = sm_ContSize[i].m_maxx; + maxValY = sm_ContSize[i].m_maxy; + + break; } } - const int iRandOnce = g_Rand.GetValFast(UINT16_MAX); return { - (short)(sm_ContSize[i].m_minx + (iRandOnce % (sm_ContSize[i].m_maxx - sm_ContSize[i].m_minx))), - (short)(sm_ContSize[i].m_miny + (iRandOnce % (sm_ContSize[i].m_maxy - sm_ContSize[i].m_miny))), + static_cast(minValX + (iRandOnce % (maxValX - minValX))), + static_cast(minValY + (iRandOnce % (maxValY - minValY))), 0 }; } @@ -515,7 +549,7 @@ void CItemContainer::ContentAdd( CItem *pItem, CPointMap pt, bool bForceNoStack, if ( pItem == this ) return; // infinite loop. - if ( !g_Serv.IsLoading() ) + if ( !g_Serv.IsLoadingGeneric() ) { switch (GetType()) { @@ -549,27 +583,43 @@ void CItemContainer::ContentAdd( CItem *pItem, CPointMap pt, bool bForceNoStack, // check for custom values in TDATA3/TDATA4 CItemBase *pContDef = Item_GetDef(); - if (pContDef->m_ttContainer.m_dwMinXY || pContDef->m_ttContainer.m_dwMaxXY) + + // Default rectangle defining size of container (best to define them in scripts as tdata3/4 to avoid visual stripping). + short minValX = 0; + short minValY = 0; + short maxValX = 512; + short maxValY = 512; + + if (pContDef->m_ttContainer.m_dwMinXY && pContDef->m_ttContainer.m_dwMaxXY) { const short tmp_MinX = (short)( pContDef->m_ttContainer.m_dwMinXY >> 16 ); const short tmp_MinY = (short)( (pContDef->m_ttContainer.m_dwMinXY & 0x0000FFFF) ); const short tmp_MaxX = (short)( pContDef->m_ttContainer.m_dwMaxXY >> 16 ); const short tmp_MaxY = (short)( (pContDef->m_ttContainer.m_dwMaxXY & 0x0000FFFF) ); - if (pt.m_x < tmp_MinX) - pt.m_x = tmp_MinX; - if (pt.m_x > tmp_MaxX) - pt.m_x = tmp_MaxX; - if (pt.m_y < tmp_MinY) - pt.m_y = tmp_MinY; - if (pt.m_y > tmp_MaxY) - pt.m_y = tmp_MaxY; + + // Rewrite default size. + if (minValX < tmp_MinX) + minValX = tmp_MinX; + if (minValY < tmp_MinY) + minValY = tmp_MinY; + + if (tmp_MaxX > tmp_MinX) + maxValX = tmp_MaxX; + else + g_Log.EventWarn("Container 0%x has invalid max X size (upper 4 bytes) in TDATA4 (lower than TDATA3)\n", pContDef->GetDispID()); + if (tmp_MaxY > tmp_MinY) + maxValY = tmp_MaxY; + else + g_Log.EventWarn("Container 0%x has invalid max Y size (lower 4 bytes) in TDATA4 (lower than TDATA3)\n", pContDef->GetDispID()); } bool fStackInsert = false; - if ( pt.m_x <= 0 || pt.m_y <= 0 || pt.m_x > 512 || pt.m_y > 512 ) // invalid container location ? + + // We are dropping item onto container. + if (pt.m_x < 0 && pt.m_y < 0) { // Try to stack it. - if ( !g_Serv.IsLoading() && pItem->Item_GetDef()->IsStackableType() && !bForceNoStack ) + if ( !g_Serv.IsLoadingGeneric() && pItem->Item_GetDef()->IsStackableType() && !bForceNoStack ) { for (CSObjContRec* pObjRec : *this) { @@ -582,9 +632,16 @@ void CItemContainer::ContentAdd( CItem *pItem, CPointMap pt, bool bForceNoStack, } } } - if ( !fStackInsert) + // If we are not stacking it, get random location in container. + if (!fStackInsert) pt = GetRandContainerLoc(); } + else + { + // We might be placing item out of container bounds, clamp it to the side if so. + pt.m_x = std::clamp(pt.m_x, minValX, maxValX); + pt.m_y = std::clamp(pt.m_y, minValY, maxValY); + } // Try drop it on given container grid index (if not available, drop it on next free index) { @@ -683,7 +740,7 @@ void CItemContainer::ContentAdd( CItem *pItem, bool bForceNoStack ) return; // already here. CPointMap pt; // invalid point. - if ( g_Serv.IsLoading() ) + if ( g_Serv.IsLoadingGeneric() ) pt = pItem->GetUnkPoint(); ContentAdd(pItem, pt, bForceNoStack); diff --git a/src/game/items/CItemContainer.h b/src/game/items/CItemContainer.h index 61d50c64c..51513a958 100644 --- a/src/game/items/CItemContainer.h +++ b/src/game/items/CItemContainer.h @@ -73,7 +73,10 @@ class CItemContainer : public CItemVendable, public CContainer virtual void DupeCopy( const CObjBase * pItem ) override; // overriding CItem::DupeCopy - CPointMap GetRandContainerLoc() const; + /** + * Gets the random location in container based on sizes defined in tdata3/4. + */ + CPointMap GetRandContainerLoc() const; void OnOpenEvent( CChar * pCharOpener, const CObjBaseTemplate * pObjTop ); }; diff --git a/src/game/items/CItemCorpse.cpp b/src/game/items/CItemCorpse.cpp index d74f94c20..4a435e5d3 100644 --- a/src/game/items/CItemCorpse.cpp +++ b/src/game/items/CItemCorpse.cpp @@ -1,5 +1,5 @@ -//#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "../../common/CLog.h" #include "../../common/sphereproto.h" #include "../chars/CChar.h" @@ -38,7 +38,7 @@ bool CItemCorpse::IsCorpseResurrectable(CChar * pCharHealer, CChar * pCharGhost) { return false; } - + //Check if the ghost is visible when targetting the corpse. if (pCharGhost->IsStatFlag(STATF_INSUBSTANTIAL)) { @@ -217,7 +217,7 @@ CItemCorpse * CChar::MakeCorpse( bool fFrontFall ) if ( !(uiFlags & DEATH_NOLOOTDROP) ) // move non-newbie contents of the pack to corpse DropAll( pCorpse ); - + if (iDecayTimer != -1) { pCorpse->MoveToDecay(GetTopPoint(), iDecayTimer); diff --git a/src/game/items/CItemMap.cpp b/src/game/items/CItemMap.cpp index 07d7a6735..c4b2601e9 100644 --- a/src/game/items/CItemMap.cpp +++ b/src/game/items/CItemMap.cpp @@ -1,6 +1,6 @@ -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "CItemMap.h" ///////////////////////////////////////////////////////////////////////////// diff --git a/src/game/items/CItemMessage.cpp b/src/game/items/CItemMessage.cpp index d93e66b09..09788eeb0 100644 --- a/src/game/items/CItemMessage.cpp +++ b/src/game/items/CItemMessage.cpp @@ -1,6 +1,6 @@ -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "CItemMessage.h" #include "CItemVendable.h" diff --git a/src/game/items/CItemMulti.cpp b/src/game/items/CItemMulti.cpp index 64753406c..43235c1d9 100644 --- a/src/game/items/CItemMulti.cpp +++ b/src/game/items/CItemMulti.cpp @@ -1,7 +1,8 @@ #include "../../common/resource/CResourceLock.h" #include "../../common/sphere_library/CSRand.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/sphereproto.h" #include "../chars/CChar.h" #include "../clients/CClient.h" @@ -106,6 +107,7 @@ CItemMulti::~CItemMulti() } if (!_lAddons.empty()) { + // TODOC: add a comment... why aren't we deleting addons!? /* for (const CUID& itemUID : _lAddons) { @@ -138,9 +140,9 @@ CItemMulti::~CItemMulti() bool CItemMulti::Delete(bool fForce) { - + RemoveAllComponents(); - + const CChar* pOwner = GetOwner().CharFind(); if (pOwner && pOwner->m_pPlayer) // If pOwner is null it means we are redeeming the multi or we manually added a multi. In the first case DelMulti is already called in the Redeed multi method. { @@ -148,7 +150,7 @@ bool CItemMulti::Delete(bool fForce) if (pMultiStorage) pMultiStorage->DelMulti(GetUID()); } - + //return CObjBase::Delete(fForce); return CItem::Delete(fForce); } @@ -248,8 +250,8 @@ bool CItemMulti::MultiRealizeRegion() m_pRegion->SetName(pszTemp); m_pRegion->_pMultiLink = this; - //We have to update the Characters if not moving around like Player Vendors. - //Otherwise, when you reboot server, the region.name of the characters returns as Region name instead of multis. + // We have to update the Characters if not moving around like Player Vendors. + // Otherwise, when you reboot server, the region.name of the characters returns as Region name instead of multis. auto Area = CWorldSearchHolder::GetInstance(m_pRegion->m_pt, Multi_GetDistanceMax()); Area->SetSearchSquare(true); for (;;) @@ -266,6 +268,9 @@ bool CItemMulti::MultiRealizeRegion() pChar->MoveToRegion(m_pRegion, false); //Move the character to house region. } + //g_Log.EventError("Realizing MULTI region at P=%hd,%hd,%hhd,%hhu. Multi uid=0%x. Multi rect: %d,%d, %d,%d.\n", + // pt.m_x, pt.m_y, pt.m_z, pt.m_map, (dword)GetUID(), + // rect.m_left, rect.m_top, rect.m_right, rect.m_bottom); return m_pRegion->RealizeRegion(); } @@ -278,6 +283,16 @@ void CItemMulti::MultiUnRealizeRegion() } m_pRegion->_pMultiLink = nullptr; + const CItemBaseMulti * pMultiDef = Multi_GetDef(); + ASSERT(pMultiDef); + + CRectMap rect = pMultiDef->m_rect; + + //auto pt = m_pRegion->m_pt; + //g_Log.EventError("UN-Realizing MULTI region at P=%hd,%hd,%hhd,%hhu. Multi uid=0%x. Multi rect: %d,%d, %d,%d.\n", + // pt.m_x, pt.m_y, pt.m_z, pt.m_map, (dword)GetUID(), + // rect.m_left, rect.m_top, rect.m_right, rect.m_bottom); + m_pRegion->UnRealizeRegion(); // find all creatures in the region and remove this from them. @@ -406,7 +421,7 @@ void CItemMulti::Multi_Setup(CChar *pChar, dword dwKeyCode) } } } - pChar->r_Call("f_multi_setup", pChar, nullptr, nullptr, nullptr); + pChar->r_Call("f_multi_setup", CScriptParserBufs::GetCScriptTriggerArgsPtr(), pChar, nullptr, nullptr); } bool CItemMulti::Multi_IsPartOf(const CItem * pItem) const @@ -482,7 +497,7 @@ CItem * CItemMulti::Multi_FindItemType(IT_TYPE type) const } if (pItem->IsType(type)) { - return(pItem); + return pItem; } } } @@ -521,6 +536,7 @@ void CItemMulti::OnMoveFrom() bool CItemMulti::MoveTo(const CPointMap& pt, bool fForceFix) // Put item on the ground here. { ADDTOCALLSTACK("CItemMulti::MoveTo"); + // Move this item to it's point in the world. (ground/top level) if (!CItem::MoveTo(pt, fForceFix)) { @@ -561,6 +577,7 @@ CItem * CItemMulti::Multi_GetSign() { ADDTOCALLSTACK("CItemMulti::Multi_GetSign"); // Get my sign or tiller link. + CItem * pTiller = m_uidLink.ItemFind(); if (pTiller == nullptr) { @@ -694,6 +711,7 @@ CUID CItemMulti::GetOwner() const void CItemMulti::SetGuild(const CUID& uidGuild) { ADDTOCALLSTACK("CItemMulti::SetGuild"); + CItemStone *pGuildStone = static_cast(GetGuildStone().ItemFind()); _uidGuild.InitUID(); if (pGuildStone) // Old Guild may not exist, was it removed...? @@ -736,7 +754,7 @@ void CItemMulti::AddCoowner(const CUID& uidCoowner) { return; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetCoownerIndex(uidCoowner) >= 0) { @@ -814,7 +832,7 @@ void CItemMulti::AddFriend(const CUID& uidFriend) { return; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetFriendIndex(uidFriend) >= 0) { @@ -891,7 +909,7 @@ void CItemMulti::AddBan(const CUID& uidBan) { return; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetBanIndex(uidBan) >= 0) { @@ -966,7 +984,7 @@ void CItemMulti::AddAccess(const CUID& uidAccess) { return; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetAccessIndex(uidAccess) >= 0) { @@ -1203,21 +1221,23 @@ void CItemMulti::Redeed(bool fDisplayMsg, bool fMoveToBank, CUID uidRedeedingCha pMulti->DeleteAddon(GetUID()); } } - CScriptTriggerArgs args(pDeed); - args.m_iN1 = itDeed; - args.m_iN2 = 1; // Transfer / Redeed all items to the moving crate. - args.m_iN3 = fMoveToBank; // Transfer the Moving Crate to the owner's bank. + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = pDeed; + pScriptArgs->m_iN1 = itDeed; + pScriptArgs->m_iN2 = 1; // Transfer / Redeed all items to the moving crate. + pScriptArgs->m_iN3 = fMoveToBank; // Transfer the Moving Crate to the owner's bank. if (IsTrigUsed(TRIGGER_REDEED)) { - tRet = OnTrigger(ITRIG_Redeed, uidRedeedingChar.CharFind(), &args); - if (args.m_iN2 == 0) + tRet = OnTrigger(ITRIG_Redeed, pScriptArgs, uidRedeedingChar.CharFind()); + if (pScriptArgs->m_iN2 == 0) { fMoveToBank = false; } else { fTransferAll = true; - fMoveToBank = args.m_iN3 ? true : false; + fMoveToBank = pScriptArgs->m_iN3 ? true : false; } } RemoveAllComponents(); @@ -1535,7 +1555,7 @@ void CItemMulti::AddAddon(const CUID& uidAddon) { return; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (!pAddon->IsType(IT_MULTI_ADDON)) { @@ -1591,7 +1611,7 @@ void CItemMulti::AddComponent(const CUID& uidComponent) { return; } - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetComponentIndex(uidComponent) >= 0) { @@ -1748,7 +1768,7 @@ void CItemMulti::LockItem(const CUID& uidItem) } CItem *pItem = uidItem.ItemFind(); ASSERT(pItem); - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetLockedItemIndex(uidItem) >= 0 || GetSecuredContainerIndex(uidItem) >= 0) { @@ -1825,7 +1845,7 @@ void CItemMulti::Secure(const CUID& uidContainer) } CItemContainer *pContainer = static_cast(uidContainer.ItemFind()); ASSERT(pContainer); - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (GetSecuredContainerIndex(uidContainer) >= 0) { @@ -1908,7 +1928,7 @@ void CItemMulti::AddVendor(const CUID& uidVendor) { return; } - if ((g_Serv.IsLoading() == false) && (GetVendorIndex(uidVendor) >= 0)) + if ((g_Serv.IsLoadingGeneric() == false) && (GetVendorIndex(uidVendor) >= 0)) { return; } @@ -3245,13 +3265,14 @@ CItem *CItemMulti::Multi_Create(CChar *pChar, const CItemBase * pItemDef, CPoint return nullptr; } } - CScriptTriggerArgs args; - args.m_VarsLocal.SetStrNew("check_blockradius", "-1, -1, 1, 1"); // Values are West, Norht, East, South - args.m_VarsLocal.SetStrNew("check_multiradius", "0, -5, 0, 5"); - args.m_VarsLocal.SetStrNew("id", g_Cfg.ResourceGetName(CResourceID(RES_ITEMDEF, pItemDef->GetID()))); - args.m_VarsLocal.SetStrNew("p", pt.WriteUsed()); + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_VarsLocal.SetStrNew("check_blockradius", "-1, -1, 1, 1"); // Values are West, Norht, East, South + pScriptArgs->m_VarsLocal.SetStrNew("check_multiradius", "0, -5, 0, 5"); + pScriptArgs->m_VarsLocal.SetStrNew("id", g_Cfg.ResourceGetName(CResourceID(RES_ITEMDEF, pItemDef->GetID()))); + pScriptArgs->m_VarsLocal.SetStrNew("p", pt.WriteUsed()); TRIGRET_TYPE tRet; - pChar->r_Call("f_multi_onplacement_check", pChar, &args, nullptr, &tRet); + pChar->r_Call("f_multi_onplacement_check", pScriptArgs, pChar, nullptr, &tRet); if (tRet == TRIGRET_RET_TRUE) { return nullptr; @@ -3270,10 +3291,10 @@ CItem *CItemMulti::Multi_Create(CChar *pChar, const CItemBase * pItemDef, CPoint if (!pChar->IsPriv(PRIV_GM) && tRet != TRIGRET_RET_HALFBAKED) { CRect rectBlockRadius; - rectBlockRadius.Read(args.m_VarsLocal.GetKeyStr("check_blockradius")); + rectBlockRadius.Read(pScriptArgs->m_VarsLocal.GetKeyStr("check_blockradius")); CRect rectMultiRadius; - rectMultiRadius.Read(args.m_VarsLocal.GetKeyStr("check_multiradius")); + rectMultiRadius.Read(pScriptArgs->m_VarsLocal.GetKeyStr("check_multiradius")); if (!pDeed->IsAttr(ATTR_MAGIC)) { @@ -3602,29 +3623,28 @@ void CMultiStorage::AddHouse(const CUID& uidHouse, HOUSE_PRIV ePriv) { return; } + const CItemMulti *pMulti = static_cast(uidHouse.ItemFind()); - CScriptTriggerArgs args; TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; - args.m_iN1 = pMulti->GetMultiCount(); - args.m_iN2 = ePriv; - if (ePriv == HOUSE_PRIV::HP_OWNER) - { - args.m_iN3 = 1; - } + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = pMulti->GetMultiCount(); + pScriptArgs->m_iN2 = ePriv; + pScriptArgs->m_iN3 = (ePriv == HOUSE_PRIV::HP_OWNER) ? 1 : 0; if (IsTrigUsed(TRIGGER_ADDMULTI)) { CChar *pChar = _uidSrc.CharFind(); if (pChar) { - tRet = pChar->OnTrigger(CTRIG_AddMulti, pChar, &args); + tRet = pChar->OnTrigger(CTRIG_AddMulti, pScriptArgs, pChar); } } if (tRet != TRIGRET_RET_TRUE) { - if (args.m_iN3 == 1) + if (pScriptArgs->m_iN3 == 1) { - _iHousesTotal += static_cast(args.m_iN1); + _iHousesTotal += static_cast(pScriptArgs->m_iN1); } _lHouses[uidHouse] = ePriv; } @@ -3637,31 +3657,29 @@ void CMultiStorage::DelHouse(const CUID& uidHouse) { return; } - + if (_lHouses.find(uidHouse) != _lHouses.end()) { CItemMulti *pMulti = static_cast(uidHouse.ItemFind()); HOUSE_PRIV ePriv = GetPriv( uidHouse ); - CScriptTriggerArgs args; - args.m_iN1 = pMulti->GetMultiCount(); - args.m_iN2 = ePriv; - args.m_pO1 = pMulti; - if (ePriv == HOUSE_PRIV::HP_OWNER) - { - args.m_iN3 = 1; - } + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = pMulti->GetMultiCount(); + pScriptArgs->m_iN2 = ePriv; + pScriptArgs->m_pO1 = pMulti; + pScriptArgs->m_iN3 = (ePriv == HOUSE_PRIV::HP_OWNER) ? 1 : 0; if (IsTrigUsed(TRIGGER_DELMULTI)) { CChar* pChar = _uidSrc.CharFind(); if (pChar) { - pChar->OnTrigger(CTRIG_DelMulti, pChar, &args); + pChar->OnTrigger(CTRIG_DelMulti, pScriptArgs, pChar); } } - if (args.m_iN3 == 1) + if (pScriptArgs->m_iN3 == 1) { - _iHousesTotal -= static_cast(args.m_iN1); + _iHousesTotal -= static_cast(pScriptArgs->m_iN1); } _lHouses.erase(uidHouse); return; @@ -3779,29 +3797,28 @@ void CMultiStorage::AddShip(const CUID& uidShip, HOUSE_PRIV ePriv) { return; } + const CItemShip* pShip = static_cast(uidShip.ItemFind()); - CScriptTriggerArgs args; TRIGRET_TYPE tRet = TRIGRET_RET_DEFAULT; - args.m_iN1 = pShip->GetMultiCount(); - args.m_iN2 = ePriv; - if (ePriv == HOUSE_PRIV::HP_OWNER) - { - args.m_iN3 = 1; - } + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = pShip->GetMultiCount(); + pScriptArgs->m_iN2 = ePriv; + pScriptArgs->m_iN3 = (ePriv == HOUSE_PRIV::HP_OWNER) ? 1 : 0; if (IsTrigUsed(TRIGGER_ADDMULTI)) { CChar* pChar = _uidSrc.CharFind(); if (pChar) { - tRet = pChar->OnTrigger(CTRIG_AddMulti, pChar, &args); + tRet = pChar->OnTrigger(CTRIG_AddMulti, pScriptArgs, pChar); } } if (tRet != TRIGRET_RET_TRUE) { - if (args.m_iN3 == 1) + if (pScriptArgs->m_iN3 == 1) { - _iShipsTotal += static_cast(args.m_iN1); + _iShipsTotal += static_cast(pScriptArgs->m_iN1); } _lShips[uidShip] = ePriv; } @@ -3819,13 +3836,14 @@ void CMultiStorage::DelShip(const CUID& uidShip) { CItemMulti* pMulti = static_cast(uidShip.ItemFind()); HOUSE_PRIV ePriv = GetPriv(uidShip); - CScriptTriggerArgs args; - args.m_iN1 = pMulti->GetMultiCount(); - args.m_iN2 = ePriv; - args.m_pO1 = pMulti; + + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = pMulti->GetMultiCount(); + pScriptArgs->m_iN2 = ePriv; + pScriptArgs->m_pO1 = pMulti; if (ePriv == HOUSE_PRIV::HP_OWNER) { - args.m_iN3 = 1; + pScriptArgs->m_iN3 = 1; } if (IsTrigUsed(TRIGGER_DELMULTI)) @@ -3833,12 +3851,12 @@ void CMultiStorage::DelShip(const CUID& uidShip) CChar* pChar = _uidSrc.CharFind(); if (pChar) { - pChar->OnTrigger(CTRIG_DelMulti, pChar, &args); + pChar->OnTrigger(CTRIG_DelMulti, pScriptArgs, pChar); } } - if (args.m_iN3 == 1) + if (pScriptArgs->m_iN3 == 1) { - _iShipsTotal -= static_cast(args.m_iN1); + _iShipsTotal -= static_cast(pScriptArgs->m_iN1); } _lShips.erase(uidShip); return; diff --git a/src/game/items/CItemMulti.h b/src/game/items/CItemMulti.h index e384468e4..41695861f 100644 --- a/src/game/items/CItemMulti.h +++ b/src/game/items/CItemMulti.h @@ -114,7 +114,6 @@ class CItemMulti : public CItem, public CCMultiMovable * @param dy p.y * @param dz p.z * @param dwKeyCode the lock hash to apply on the item (if is lockable). - * @param fIsAddon true when the created component is an addon. * @return true if the component need a key (wether the key is created or not is not checked here). */ bool Multi_CreateComponent(ITEMID_TYPE id, short dx, short dy, char dz, dword dwKeyCode); @@ -157,18 +156,18 @@ class CItemMulti : public CItem, public CCMultiMovable /** * @brief Revokes any privileges from this house. - * @param pSrc the object whom privileges to revoke. + * @param uidSrc the object whom privileges to revoke. */ void RevokePrivs(const CUID& uidSrc); //Owner /** * @brief Set's the owner. - * @param pOwner the new owner. + * @param uidOwner the new owner. */ void SetOwner(const CUID& uidOwner); /** * @brief Checks if the given CChar in the param is the owner. - * @param pTarget the char to check. + * @param uidTarget the char to check. * @return true if true. */ bool IsOwner(const CUID& uidTarget) const; @@ -181,12 +180,12 @@ class CItemMulti : public CItem, public CCMultiMovable // Guilds /** * @brief Links this multi to a guild. - * @param pGuild the guild on which to link. + * @param uidGuild the guild on which to link. */ void SetGuild(const CUID& uidGuild); /** * @brief Checks if the given guild is the one linked to this multi. - * @param pGuild the guild to check on. + * @param uidGuild the guild to check on. * @return true if match. */ bool IsGuild(const CUID& uidGuild) const; @@ -199,12 +198,13 @@ class CItemMulti : public CItem, public CCMultiMovable // Coowner /** * @brief Adds a coowner to the _lCoowners list. - * @param pCoowner the coowner + * @param uidCoowner the coowner */ void AddCoowner(const CUID& uidCoowner); /** * @brief Deletes a coowner to the _lCoowners list. - * @param pCoowner the coowner + * @param uidCoowner the coowner + * @param fRemoveFromList Whether owner should be deleted from list. */ void DeleteCoowner(const CUID& uidCoowner, bool fRemoveFromList); /** @@ -214,7 +214,7 @@ class CItemMulti : public CItem, public CCMultiMovable size_t GetCoownerCount() const; /** * @brief Get's the position on the _lCoowners list of the given char. - * @param pTarget the coowner. + * @param uidTarget the coowner. * @return the position. */ int GetCoownerIndex(const CUID& uidTarget) const; @@ -222,12 +222,13 @@ class CItemMulti : public CItem, public CCMultiMovable // Friend /** * @brief Adds a friend to the _lFriends list. - * @param pFriend the friend + * @param uidFriend the friend */ void AddFriend(const CUID& uidFriend); /** * @brief Deletes a friend to the _lFriends list. - * @param pFriend the friend + * @param uidFriend the friend + * @param fRemoveFromList Whether friend should be deleted from list. */ void DeleteFriend(const CUID& uidFriend, bool fRemoveFromList); /** @@ -237,7 +238,7 @@ class CItemMulti : public CItem, public CCMultiMovable size_t GetFriendCount() const; /** * @brief Get's the position on the _lFriends list of the given char. - * @param pTarget the friend. + * @param uidTarget the friend. * @return the position. */ int GetFriendIndex(const CUID& uidTarget) const; @@ -245,12 +246,13 @@ class CItemMulti : public CItem, public CCMultiMovable // Ban /** * @brief Adds a char to the _lBans list. - * @param pBan the char to ban. + * @param uidBan the char to ban. */ void AddBan(const CUID& uidBan); /** * @brief Deletes a char from the _lBans list. - * @param pBan the char. + * @param uidBan the char. + * @param fRemoveFromList Whether character should be deleted from list. */ void DeleteBan(const CUID& uidBan, bool fRemoveFromList); /** @@ -260,7 +262,7 @@ class CItemMulti : public CItem, public CCMultiMovable size_t GetBanCount() const; /** * @brief Returns the position on the _lBans list of the given char. - * @param pBan the char. + * @param uidBan the char. * @return the pos. */ int GetBanIndex(const CUID& uidBan) const; @@ -268,14 +270,15 @@ class CItemMulti : public CItem, public CCMultiMovable // Access /** * @brief Adds access to this house to the given char. - * @param pAccess the char. + * @param uidAccess the char. */ void AddAccess(const CUID& uidAccess); /** * @brief Revokes the access to this house to the given char. * * Note: This removes the char from the list, but won't prevent it from enter like a Ban. - * @param pAccess the char. + * @param uidAccess the char. + * @param fRemoveFromList Whether character should be deleted from list. */ void DeleteAccess(const CUID& uidAccess, bool fRemoveFromList); /** @@ -285,18 +288,18 @@ class CItemMulti : public CItem, public CCMultiMovable size_t GetAccessCount() const; /** * @brief Returns the position on the _lAccess list of the given char. - * @param pAccess the char. + * @param uidAccess the char. * @return the position. */ int GetAccessIndex(const CUID& uidAccess) const; /** * @brief Ejects a char from inside the house to the House Sign. - * @param pChar the char. + * @param uidChar the char. */ void Eject(const CUID& uidChar); /** * @brief Ejects all chars from this house - * @param pChar this char will not be ejected (eg: the owner kicking all people) + * @param uidChar this char will not be ejected (eg: the owner kicking all people) */ void EjectAll(CUID uidChar = CUID()); ///@} @@ -309,14 +312,14 @@ class CItemMulti : public CItem, public CCMultiMovable // Keys: /** * @brief Creates a key for the given character. - * @param pTarget the target that receives the key. + * @param uidTarget the target that receives the key. * @param fDupeOnBank creates a copy on the bank if true. * @return the Key. */ CItem *GenerateKey(const CUID& uidTarget, bool fDupeOnBank = false); /** * @brief Removes all the keys of this house from the given char. - * @param pTarget the char. + * @param uidTarget the char. */ void RemoveKeys(const CUID& uidTarget); /** @@ -339,14 +342,14 @@ class CItemMulti : public CItem, public CCMultiMovable * @brief Redeeds this multi * @param fDisplayMsg Display the bounce message on the player. * @param fMoveToBank Places the deed on the bank. - * @param pChar the char doing the redeed (if any). + * @param uidChar the char doing the redeed (if any). */ void Redeed(bool fDisplayMsg = true, bool fMoveToBank = true, CUID uidChar = CUID()); //Moving Crate /** * @brief Sets the Moving Crate to the target. - * @param pCrate the new Moving Crate. + * @param uidCrate the new Moving Crate. */ void SetMovingCrate(const CUID& uidCrate); /** @@ -376,17 +379,17 @@ class CItemMulti : public CItem, public CCMultiMovable // AddOns /** * @brief Adds an Addon to the addons list. - * @param pAddon the Addon + * @param uidAddon the Addon */ void AddAddon(const CUID& uidAddon); /** * @brief Removes an Addon from the addons list. - * @param pAddon the Addon + * @param uidAddon the Addon */ void DeleteAddon(const CUID& uidAddon); /** * @brief Returns the position of a given Addon. - * @param pAddon the Addon + * @param uidAddon the Addon * @return the position */ int GetAddonIndex(const CUID& uidAddon) const; @@ -403,17 +406,18 @@ class CItemMulti : public CItem, public CCMultiMovable // Components /** * @brief Adds a CMultiComponent to the components list. - * @param pComponent the component. + * @param uidComponent the component. */ void AddComponent(const CUID& uidComponent); /** * @brief Removes a CMultiComponent from the components list. - * @param pComponent the component. + * @param uidComponent the component. + * @param fRemoveFromList Whether component should be deleted from list. */ virtual void DeleteComponent(const CUID& uidComponent, bool fRemoveFromList); /** * @brief Returns the position of a given CMultiComponent. - * @param pComponent the component + * @param uidComponent the component * @return the position */ int GetComponentIndex(const CUID& uidComponent) const; @@ -428,7 +432,7 @@ class CItemMulti : public CItem, public CCMultiMovable void RemoveAllComponents(); /** * @brief Generates the base components of the multi. - * @param fNeedKey wether a key is required or not. + * @param pfNeedKey wether a key is required or not. * @param dwKeyCode the code hash for keys. */ void GenerateBaseComponents(bool *pfNeedKey, dword dwKeyCode); @@ -449,7 +453,7 @@ class CItemMulti : public CItem, public CCMultiMovable uint16 GetBaseStorage() const; /** * @brief Sets the modifier of Base Storage. - * @param iIncrease the modifier. + * @param uiIncrease the modifier. */ void SetIncreasedStorage(uint16 uiIncrease); /** @@ -476,7 +480,6 @@ class CItemMulti : public CItem, public CCMultiMovable void SetBaseVendors(uint8 iLimit); /** * @brief Returns the Base Vendors. - * @param the value. */ uint8 GetBaseVendors() const; /** @@ -503,12 +506,13 @@ class CItemMulti : public CItem, public CCMultiMovable // -Lockdowns /** * @brief Locks an item and add it to the Lockdowns list. - * @param pItem the item. + * @param uidItem the item. */ void LockItem(const CUID& uidItem); /** * @brief Unlocks an item and remove it from the Lockdowns list. - * @param pItem the item. + * @param uidItem the item. + * @param fRemoveFromList Whether item should be deleted from list. */ void UnlockItem(const CUID& uidItem, bool fRemoveFromList); void UnlockAllItems(); @@ -525,17 +529,18 @@ class CItemMulti : public CItem, public CCMultiMovable // -Secured storage (Containers + items). /** * @brief Secures a container and adds it to the containers list. - * @param pContainer the container. + * @param uidContainer the container. */ void Secure(const CUID& uidContainer); /** * @brief Releases a container and removes it from the containers list. - * @param pContainer the container. + * @param uidContainer the container. + * @param fRemoveFromList Whether container should be deleted from list. */ void Release(const CUID& uidContainer, bool fRemoveFromList); /** * @brief Returns the position of the given container - * @param pContainer the container + * @param uidContainer the container * @return the pos. */ int GetSecuredContainerIndex(const CUID& uidContainer) const; @@ -552,17 +557,18 @@ class CItemMulti : public CItem, public CCMultiMovable // -Vendors /** * @brief Adds a char to the vendors list. - * @param pVendor the vendor. + * @param uidVendor the vendor. */ void AddVendor(const CUID& uidVendor); /** * @brief Removes a char from the vendors list. - * @param pVendor the vendor + * @param uidVendor the vendor + * @param fRemoveFromList Whether vendor should be deleted from list. */ void DeleteVendor(const CUID& uidVendor, bool fRemoveFromList); /** * @brief Returns the position of the given char. - * @param pVendor the char + * @param uidVendor the char * @return the pos. */ int GetVendorIndex(const CUID& uidVendor) const; @@ -682,12 +688,13 @@ class CMultiStorage /** * @brief Adds a multi - * @param pMulti the multi + * @param uidMulti the multi + * @param ePriv Access type for the multi (like ownership, ban etc.) */ void AddMulti(const CUID& uidMulti, HOUSE_PRIV ePriv); /** * @brief Removes a multi - * @param pMulti the multi + * @param uidMulti the multi */ void DelMulti(const CUID& uidMulti); /** @@ -698,17 +705,18 @@ class CMultiStorage /** * @brief Adds a house multi. - * @param pHouse the house. + * @param uidHouse the house. + * @param ePriv Access type for the house (like ownership, ban etc.) */ void AddHouse(const CUID& uidHouse, HOUSE_PRIV ePriv); /** * @brief Removes a house multi. - * @param pHouse the house. + * @param uidHouse the house. */ void DelHouse(const CUID& uidHouse); /** * @brief Checks if the char can place a house. - * @param pChar the char. + * @param uidChar the char. * @param iHouseCount the MultiCount of the house. * @return true if the char has space for the house. */ @@ -745,24 +753,25 @@ class CMultiStorage /** * @brief Adds a ship. - * @param pShip the ship. + * @param uidShip the ship. + * @param ePriv Access type for the ship (like ownership, ban etc.) */ void AddShip(const CUID& uidShip, HOUSE_PRIV ePriv); /** * @brief Removes a ship. - * @param pShip the ship. + * @param uidShip the ship. */ void DelShip(const CUID& uidShip); /** * @brief Checks if the char can place a ship. - * @param pChar the char. - * @param iHouseCount the MultiCount of the ship. + * @param uidChar the char. + * @param iShipCount the MultiCount of the ship. * @return true if the char has space for the ship. */ bool CanAddShip(const CUID& uidChar, int16 iShipCount); /** * @brief Returns the position of the given ship. - * @param pShip the ship. + * @param uidShip the ship. * @return the position. */ int16 GetShipPos(const CUID& uidShip); diff --git a/src/game/items/CItemMultiCustom.cpp b/src/game/items/CItemMultiCustom.cpp index d8e0986d8..636f171b1 100644 --- a/src/game/items/CItemMultiCustom.cpp +++ b/src/game/items/CItemMultiCustom.cpp @@ -4,8 +4,9 @@ #include "../../common/resource/sections/CDialogDef.h" #include "../../common/CLog.h" -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CUOInstall.h" #include "../../network/send.h" #include "../chars/CChar.h" @@ -32,7 +33,7 @@ CItemMultiCustom::CItemMultiCustom(ITEMID_TYPE id, CItemBase * pItemDef) : m_rectDesignArea.SetRectEmpty(); _iMaxPlane = -1; - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { ResetStructure(); CommitChanges(); @@ -120,31 +121,31 @@ void CItemMultiCustom::BeginCustomize(CClient* pClientSrc, bool continueCustomiz if (IsTrigUsed(TRIGGER_HOUSEDESIGNBEGIN)) { - CScriptTriggerArgs args; - args.m_pO1 = this; - args.m_iN1 = 1; // Redeed AddOns - args.m_iN2 = 0; // Transfer Lockdowns and Secured containers to Moving Crate. - args.m_iN3 = 2; // Eject everyone from house. - if (pChar->OnTrigger(CTRIG_HouseDesignBegin, pChar, &args) == TRIGRET_RET_TRUE) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = this; + pScriptArgs->m_iN1 = 1; // Redeed AddOns + pScriptArgs->m_iN2 = 0; // Transfer Lockdowns and Secured containers to Moving Crate. + pScriptArgs->m_iN3 = 2; // Eject everyone from house. + if (pChar->OnTrigger(CTRIG_HouseDesignBegin, pScriptArgs, pChar) == TRIGRET_RET_TRUE) { EndCustomize(true); return; } - if (args.m_iN1 == 1) + if (pScriptArgs->m_iN1 == 1) { RedeedAddons(); } - if (args.m_iN2 == 1) + if (pScriptArgs->m_iN2 == 1) { TransferSecuredToMovingCrate(); TransferLockdownsToMovingCrate(); } - if (args.m_iN3 == 1) + if (pScriptArgs->m_iN3 == 1) { EjectAll(pChar->GetUID()); } - else if (args.m_iN3 == 2) + else if (pScriptArgs->m_iN3 == 2) { EjectAll(); } @@ -207,9 +208,9 @@ void CItemMultiCustom::EndCustomize(bool fForce) { if (IsTrigUsed(TRIGGER_HOUSEDESIGNEXIT)) { - CScriptTriggerArgs Args(this); - Args.m_iN1 = fForce; - if (pChar->OnTrigger(CTRIG_HouseDesignExit, pChar, &Args) == TRIGRET_RET_TRUE && !fForce) + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_iN1 = fForce; + if (pChar->OnTrigger(CTRIG_HouseDesignExit, pScriptArgs, pChar) == TRIGRET_RET_TRUE && !fForce) { BeginCustomize(pClient); return; @@ -279,8 +280,8 @@ void CItemMultiCustom::CommitChanges(CClient * pClientSrc) CChar* pCharClient = pClientSrc ? pClientSrc->GetChar() : nullptr; if (pCharClient) { + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); const bool fSendFullTrigger = IsTrigUsed(TRIGGER_HOUSEDESIGNCOMMITITEM); - CScriptTriggerArgs Args; short iMaxZ = 0; for (auto it = m_designWorking.m_vectorComponents.begin(); it != m_designWorking.m_vectorComponents.end();) @@ -288,15 +289,15 @@ void CItemMultiCustom::CommitChanges(CClient * pClientSrc) const CMultiComponent* pComp = *it; if (fSendFullTrigger) { - Args.Clear(); - Args.m_VarsLocal.SetNum("ID", pComp->m_item.m_wTileID); - Args.m_VarsLocal.SetNum("P.X", pComp->m_item.m_dx); - Args.m_VarsLocal.SetNum("P.Y", pComp->m_item.m_dy); - Args.m_VarsLocal.SetNum("P.Z", pComp->m_item.m_dz); - Args.m_VarsLocal.SetNum("VISIBLE", pComp->m_item.m_visible); - Args.m_pO1 = this; - - const TRIGRET_TYPE iRet = pCharClient->OnTrigger(CTRIG_HouseDesignCommitItem, pCharClient, &Args); + pScriptArgs->Clear(); + pScriptArgs->m_VarsLocal.SetNum("ID", pComp->m_item.m_wTileID); + pScriptArgs->m_VarsLocal.SetNum("P.X", pComp->m_item.m_dx); + pScriptArgs->m_VarsLocal.SetNum("P.Y", pComp->m_item.m_dy); + pScriptArgs->m_VarsLocal.SetNum("P.Z", pComp->m_item.m_dz); + pScriptArgs->m_VarsLocal.SetNum("VISIBLE", pComp->m_item.m_visible); + pScriptArgs->m_pO1 = this; + + const TRIGRET_TYPE iRet = pCharClient->OnTrigger(CTRIG_HouseDesignCommitItem, pScriptArgs, pCharClient); if (iRet == TRIGRET_RET_FALSE) { it = m_designWorking.m_vectorComponents.erase(it); @@ -312,16 +313,16 @@ void CItemMultiCustom::CommitChanges(CClient * pClientSrc) } if (IsTrigUsed(TRIGGER_HOUSEDESIGNCOMMIT)) { - Args.Clear(); - Args.m_iN1 = m_designMain.m_vectorComponents.size(); - Args.m_iN2 = m_designWorking.m_vectorComponents.size(); - Args.m_iN3 = m_designWorking.m_iRevision; - Args.m_pO1 = this; - Args.m_VarsLocal.SetNum("FIXTURES.OLD", GetFixtureCount(&m_designMain)); - Args.m_VarsLocal.SetNum("FIXTURES.NEW", GetFixtureCount(&m_designWorking)); - Args.m_VarsLocal.SetNum("MAXZ", iMaxZ); + pScriptArgs->Clear(); + pScriptArgs->m_iN1 = m_designMain.m_vectorComponents.size(); + pScriptArgs->m_iN2 = m_designWorking.m_vectorComponents.size(); + pScriptArgs->m_iN3 = m_designWorking.m_iRevision; + pScriptArgs->m_pO1 = this; + pScriptArgs->m_VarsLocal.SetNum("FIXTURES.OLD", GetFixtureCount(&m_designMain)); + pScriptArgs->m_VarsLocal.SetNum("FIXTURES.NEW", GetFixtureCount(&m_designWorking)); + pScriptArgs->m_VarsLocal.SetNum("MAXZ", iMaxZ); - if (pCharClient->OnTrigger(CTRIG_HouseDesignCommit, pCharClient, &Args) == TRIGRET_RET_TRUE) + if (pCharClient->OnTrigger(CTRIG_HouseDesignCommit, pScriptArgs, pCharClient) == TRIGRET_RET_TRUE) return; } } @@ -337,7 +338,7 @@ void CItemMultiCustom::CommitChanges(CClient * pClientSrc) CopyDesign(&m_designWorking, &m_designMain); const CPointMap ptMe = GetTopPoint(); - if (g_Serv.IsLoading() || !ptMe.IsValidPoint()) + if (g_Serv.IsLoadingGeneric() || !ptMe.IsValidPoint()) return; // remove all existing dynamic item fixtures @@ -460,7 +461,7 @@ void CItemMultiCustom::AddItem(CClient * pClientSrc, ITEMID_TYPE id, int16 x, in // fixtures are items that should be replaced by dynamic items const bool fFixture = (pItemBase->IsType(IT_DOOR) || pItemBase->IsType(IT_DOOR_LOCKED) || pItemBase->IsID_Door(id) || pItemBase->IsType(IT_TELEPAD)); - if (!g_Serv.IsLoading()) + if (!g_Serv.IsLoadingGeneric()) { if (!IsValidItem(id, pClientSrc, false)) { @@ -551,7 +552,7 @@ void CItemMultiCustom::AddItem(CClient * pClientSrc, ITEMID_TYPE id, int16 x, in m_designWorking.m_vectorComponents.emplace_back(pComponent); ++m_designWorking.m_iRevision; - if (!g_Serv.IsLoading()) // quick fix, change it to execute only on customize mode + if (!g_Serv.IsLoadingGeneric()) // quick fix, change it to execute only on customize mode { CItemContainer *pMovingCrate = static_cast(GetMovingCrate(true).ItemFind()); ASSERT(pMovingCrate); @@ -962,6 +963,7 @@ void CItemMultiCustom::SendStructureTo(CClient * pClientSrc) int iItemCount = 0; int iMaxIndex = 0; + memset(wPlaneBuffer, 0, sizeof(wPlaneBuffer)); for (const CMultiComponent* pComp : pDesign->m_vectorComponents) { const uchar uiCompPlane = GetPlane(pComp); @@ -1840,7 +1842,7 @@ bool CItemMultiCustom::r_LoadVal(CScript & s) ADDTOCALLSTACK("CItemMultiCustom::r_LoadVal"); EXC_TRY("LoadVal"); - if (g_Serv.IsLoading()) + if (g_Serv.IsLoadingGeneric()) { if (s.IsKey("COMP")) { diff --git a/src/game/items/CItemMultiCustom.h b/src/game/items/CItemMultiCustom.h index 5839f3542..b7e83cfcd 100644 --- a/src/game/items/CItemMultiCustom.h +++ b/src/game/items/CItemMultiCustom.h @@ -68,7 +68,8 @@ class CItemMultiCustom : public CItemMulti /** * @brief Removes a CMultiComponent from the components list. - * @param pComponent the component. + * @param uidComponent the component. + * @param fRemoveFromList Whether component should be deleted from list. */ virtual void DeleteComponent(const CUID& uidComponent, bool fRemoveFromList) override final; diff --git a/src/game/items/CItemPlant.cpp b/src/game/items/CItemPlant.cpp index 235f95e83..21b5321bc 100644 --- a/src/game/items/CItemPlant.cpp +++ b/src/game/items/CItemPlant.cpp @@ -1,7 +1,6 @@ - -#include "../../common/CScriptTriggerArgs.h" #include "../../common/resource/CResourceID.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../chars/CChar.h" #include "../triggers.h" #include "../CServer.h" @@ -36,15 +35,19 @@ bool CItem::Plant_Use(CChar *pChar) if (!Can(CAN_I_SCRIPTEDMORE)) iFruitIDOverride = (ITEMID_TYPE)m_itCrop.m_ridFruitOverride.GetResIndex(); word iAmount = std::max(m_itCrop.m_ridAmount, (word)1); + if (IsTrigUsed(TRIGGER_RESOURCETEST)) { - CScriptTriggerArgs args(iGrowID, iFruitID, iFruitIDOverride); - TRIGRET_TYPE iRet = OnTrigger(ITRIG_ResourceTest, pChar, &args); - iGrowID = (ITEMID_TYPE)(ResGetIndex((dword)args.m_iN1)); - iFruitID = (ITEMID_TYPE)(ResGetIndex((dword)args.m_iN2)); - iFruitIDOverride = (ITEMID_TYPE)(ResGetIndex((dword)args.m_iN3)); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iGrowID, iFruitID, iFruitIDOverride, nullptr); + TRIGRET_TYPE iRet = OnTrigger(ITRIG_ResourceTest, pScriptArgs, pChar); + if (iRet == TRIGRET_RET_TRUE) return true; + + iGrowID = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_iN1)); + iFruitID = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_iN2)); + iFruitIDOverride = (ITEMID_TYPE)(ResGetIndex((dword)pScriptArgs->m_iN3)); } if (iGrowID != ITEMID_NOTHING) // If we set an override, we can reap this at every stage @@ -67,10 +70,12 @@ bool CItem::Plant_Use(CChar *pChar) { if (IsTrigUsed(TRIGGER_RESOURCEGATHER)) { - CScriptTriggerArgs args(iAmount); - args.m_pO1 = pItemFruit; - TRIGRET_TYPE iRet = OnTrigger(ITRIG_ResourceGather, pChar, &args); - iAmount = (word)(args.m_iN1 > 0 ? args.m_iN1 : 1); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(iAmount, 0, 0, pItemFruit); + + TRIGRET_TYPE iRet = OnTrigger(ITRIG_ResourceGather, pScriptArgs, pChar); + + iAmount = (word)((pScriptArgs->m_iN1 > 0) ? pScriptArgs->m_iN1 : 1); if (iRet == TRIGRET_RET_TRUE) { pItemFruit->Delete(true); @@ -163,7 +168,7 @@ bool CItem::Plant_OnTick() bool CItem::Plant_SetID(ITEMID_TYPE id) { bool iRet = SetID(id); - OnTrigger(ITRIG_Create, &g_Serv, nullptr); + OnTrigger(ITRIG_Create, CScriptParserBufs::GetCScriptTriggerArgsPtr(), &g_Serv); return iRet; } diff --git a/src/game/items/CItemShip.cpp b/src/game/items/CItemShip.cpp index 549c78307..a4da6c6e2 100644 --- a/src/game/items/CItemShip.cpp +++ b/src/game/items/CItemShip.cpp @@ -2,8 +2,8 @@ // CItemShip.cpp // -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header #include "../chars/CChar.h" #include "../CServer.h" #include "../CWorldSearch.h" @@ -201,7 +201,7 @@ bool CItemShip::r_LoadVal(CScript & s) EXC_TRY("LoadVal"); lpctstr ptcKey = s.GetKey(); IMCS_TYPE index = (IMCS_TYPE)FindTableHeadSorted(ptcKey, sm_szLoadKeys, ARRAY_COUNT(sm_szLoadKeys) - 1); - if (index >= 0 && g_Serv.IsLoading()) + if (index >= 0 && g_Serv.IsLoadingGeneric()) { switch (index) { diff --git a/src/game/items/CItemStone.cpp b/src/game/items/CItemStone.cpp index 52eaf30ad..aacbe0af1 100644 --- a/src/game/items/CItemStone.cpp +++ b/src/game/items/CItemStone.cpp @@ -1,8 +1,8 @@ -#include "../../common/CException.h" -#include "../../common/CExpression.h" +//#include "../../common/CException.h" // included in the precompiled header +//#include "../../common/CExpression.h" // included in the precompiled header +//#include "../../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../../common/CLog.h" -#include "../../common/CScriptTriggerArgs.h" #include "../chars/CChar.h" #include "../CServer.h" #include "../CWorld.h" @@ -100,20 +100,23 @@ lpctstr CItemStone::GetTypeName() const { ADDTOCALLSTACK("CItemStone::GetTypeName"); CVarDefCont * pResult = nullptr; - switch ( GetType() ) - { - case IT_STONE_GUILD: - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_TYPENAME_GUILD"); - break; - case IT_STONE_TOWN: - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_TYPENAME_TOWN"); - break; - default: - break; - } + { + auto gReader = g_ExprGlobals.mtEngineLockedReader(); + switch ( GetType() ) + { + case IT_STONE_GUILD: + pResult = gReader->m_VarDefs.GetKey("STONECONFIG_TYPENAME_GUILD"); + break; + case IT_STONE_TOWN: + pResult = gReader->m_VarDefs.GetKey("STONECONFIG_TYPENAME_TOWN"); + break; + default: + break; + } - if ( pResult == nullptr ) - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_TYPENAME_UNK"); + if ( pResult == nullptr ) + pResult = gReader->m_VarDefs.GetKey("STONECONFIG_TYPENAME_UNK"); + } return ( pResult == nullptr ) ? "" : pResult->GetValStr(); } @@ -172,7 +175,7 @@ lpctstr CItemStone::GetAlignName() const else return ""; - lpctstr sRes = g_Exp.m_VarDefs.GetKeyStr(tsDefname); + lpctstr sRes = g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKeyStr(tsDefname); return ( sRes == nullptr ) ? "" : sRes; } @@ -228,7 +231,7 @@ bool CItemStone::r_GetRef( lpctstr & ptcKey, CScriptObj * & pRef ) if ( nNumber == i ) { - pRef = pMember; + pRef = pMember; return true; } @@ -250,7 +253,7 @@ bool CItemStone::r_GetRef( lpctstr & ptcKey, CScriptObj * & pRef ) CStoneMember * pMemberGuild = GetMember( pMemberChar ); if ( pMemberGuild ) { - pRef = pMemberGuild; + pRef = pMemberGuild; return true; } } @@ -268,7 +271,7 @@ bool CItemStone::r_GetRef( lpctstr & ptcKey, CScriptObj * & pRef ) for ( int i = 0; pMember != nullptr; pMember = pMember->GetNext() ) { - if ( pMember->GetLinkUID().IsChar() ) + if ( pMember->GetLinkUID().IsChar() ) continue; if ( nNumber == i ) @@ -295,7 +298,7 @@ bool CItemStone::r_GetRef( lpctstr & ptcKey, CScriptObj * & pRef ) CStoneMember * pGuild = GetMember( pMemberGuild ); if ( pGuild ) { - pRef = pGuild; + pRef = pGuild; return true; } } @@ -436,7 +439,7 @@ bool CItemStone::r_LoadVal( CScript & s ) // Load an item Script m_sCharter[i] = s.GetArgStr(); return true; } - + return CItem::r_LoadVal(s); EXC_CATCH; @@ -484,7 +487,7 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr { for (; pMember != nullptr; pMember = pMember->GetNext()) { - if (!pMember->GetLinkUID().IsChar()) + if (!pMember->GetLinkUID().IsChar()) continue; ++i; @@ -502,12 +505,12 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr for ( int i = 0 ; pMember != nullptr; pMember = pMember->GetNext() ) { - if (!pMember->GetLinkUID().IsChar()) + if (!pMember->GetLinkUID().IsChar()) continue; - + if ( nNumber == i ) { - if (!pszCmd[0]) + if (!pszCmd[0]) return true; return pMember->r_WriteVal(pszCmd, sVal, pSrc); @@ -572,7 +575,7 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr { for (; pMember != nullptr; pMember = pMember->GetNext()) { - if (pMember->GetLinkUID().IsChar()) + if (pMember->GetLinkUID().IsChar()) continue; i++; @@ -590,12 +593,12 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr for ( int i = 0 ; pMember != nullptr; pMember = pMember->GetNext() ) { - if (pMember->GetLinkUID().IsChar()) + if (pMember->GetLinkUID().IsChar()) continue; - + if ( nNumber == i ) { - if (!pszCmd[0]) + if (!pszCmd[0]) return true; return pMember->r_WriteVal(pszCmd, sVal, pSrc); @@ -672,14 +675,16 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr CStoneMember * pMember = GetMember(pCharSrc); CVarDefCont * pResult = nullptr; + auto gReader = g_ExprGlobals.mtEngineLockedReader(); if ( pMember == nullptr ) { - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_NONMEMBER"); + pResult = gReader->m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_NONMEMBER"); } else { - pResult = pMember->IsAbbrevOn() ? g_Exp.m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_ABBREVON") : - g_Exp.m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_ABBREVOFF"); + pResult = pMember->IsAbbrevOn() + ? gReader->m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_ABBREVON") + : gReader->m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_ABBREVOFF"); } sVal = pResult ? pResult->GetValStr() : ""; @@ -696,14 +701,14 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr if ( pMember == nullptr ) { - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_NONMEMBER"); + pResult = g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_NONMEMBER"); } else { CChar * pLOYALTO = pMember->GetLoyalToUID().CharFind(); if ((pLOYALTO == nullptr) || (pLOYALTO == pCharSrc )) { - pResult = g_Exp.m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_YOURSELF"); + pResult = g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKey("STONECONFIG_VARIOUSNAME_YOURSELF"); } else { @@ -715,33 +720,36 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr sVal = pResult ? pResult->GetValStr() : ""; } return true; - + case STC_MASTER: { CChar * pMaster = GetMaster(); - sVal = (pMaster) ? pMaster->GetName() : g_Exp.m_VarDefs.GetKeyStr("STONECONFIG_VARIOUSNAME_PENDVOTE"); + sVal = (pMaster) + ? pMaster->GetName() + : g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKeyStr("STONECONFIG_VARIOUSNAME_PENDVOTE"); } return true; - + case STC_MASTERGENDERTITLE: { CChar * pMaster = GetMaster(); if ( pMaster == nullptr ) sVal.Clear(); // If no master (vote pending) - else if ( pMaster->Char_GetDef()->IsFemale()) - sVal = g_Exp.m_VarDefs.GetKeyStr("STONECONFIG_VARIOUSNAME_MASTERGENDERFEMALE"); - else - sVal = g_Exp.m_VarDefs.GetKeyStr("STONECONFIG_VARIOUSNAME_MASTERGENDERMALE"); - } + else + sVal = g_ExprGlobals.mtEngineLockedReader()->m_VarDefs.GetKeyStr( + pMaster->Char_GetDef()->IsFemale() + ? "STONECONFIG_VARIOUSNAME_MASTERGENDERFEMALE" + : "STONECONFIG_VARIOUSNAME_MASTERGENDERMALE"); + } return true; - + case STC_MASTERTITLE: { CStoneMember * pMember = GetMasterMember(); sVal = (pMember) ? pMember->GetTitle() : ""; } return true; - + case STC_MASTERUID: { CChar * pMaster = GetMaster(); @@ -751,7 +759,7 @@ bool CItemStone::r_WriteVal( lpctstr ptcKey, CSString & sVal, CTextConsole * pSr sVal.FormatHex( (dword) 0 ); } return true; - + default: return (fNoCallParent ? false : CItem::r_WriteVal( ptcKey, sVal, pSrc )); } @@ -1285,7 +1293,7 @@ bool CItemStone::CheckValidMember( CStoneMember * pMember ) } // just delete this member. (it is mislinked) - DEBUG_ERR(( "Stone UID=0%x has mislinked member uid=0%x\n", + DEBUG_ERR(( "Stone UID=0%x has mislinked member uid=0%x\n", (dword) GetUID(), (dword) pMember->GetLinkUID())); return false; } @@ -1333,11 +1341,12 @@ bool CItemStone::IsAlliedWith( const CItemStone * pStone) const if ( pStone == nullptr ) return false; - CScriptTriggerArgs Args; - Args.m_pO1 = const_cast(pStone); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = const_cast(pStone); enum TRIGRET_TYPE tr = TRIGRET_RET_DEFAULT; - if ( const_cast(this)->r_Call("f_stonesys_internal_isalliedwith", &g_Serv, &Args, nullptr, &tr) ) + // TODO: no const_cast please... we'll have to remove const from this method + if ( const_cast(this)->r_Call("f_stonesys_internal_isalliedwith", pScriptArgs, &g_Serv, nullptr, &tr) ) { if ( tr == TRIGRET_RET_FALSE ) return false; @@ -1370,11 +1379,11 @@ bool CItemStone::IsAtWarWith( const CItemStone * pEnemyStone ) const if ( pEnemyStone == nullptr ) return false; - CScriptTriggerArgs Args; - Args.m_pO1 = const_cast(pEnemyStone); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->m_pO1 = const_cast(pEnemyStone); enum TRIGRET_TYPE tr = TRIGRET_RET_DEFAULT; - if ( const_cast(this)->r_Call("f_stonesys_internal_isatwarwith", &g_Serv, &Args, nullptr, &tr) ) + if ( const_cast(this)->r_Call("f_stonesys_internal_isatwarwith", pScriptArgs, &g_Serv, nullptr, &tr) ) { if ( tr == TRIGRET_RET_FALSE ) return false; diff --git a/src/game/items/CItemVendable.cpp b/src/game/items/CItemVendable.cpp index 4d6a06ad6..11c02d8cd 100644 --- a/src/game/items/CItemVendable.cpp +++ b/src/game/items/CItemVendable.cpp @@ -1,5 +1,5 @@ -#include "../../common/CException.h" +//#include "../../common/CException.h" // included in the precompiled header #include "CItemVendable.h" CItemVendable::CItemVendable( ITEMID_TYPE id, CItemBase * pDef ) : @@ -201,7 +201,7 @@ dword CItemVendable::GetVendorPrice( int iConvertFactor , bool forselling ) if ( llPrice <= 0 ) // No price/overrride.value set, we use the value of item. { - + if ( IsType(IT_DEED) ) { // Deeds just represent the item they are deeding. @@ -212,9 +212,9 @@ dword CItemVendable::GetVendorPrice( int iConvertFactor , bool forselling ) else pItemDef = Item_GetDef(); - llPrice = pItemDef->GetMakeValue(GetQuality()); //If value is a range(ex:10,20), value change depending quality + llPrice = pItemDef->GetMakeValue(GetQuality()); //If value is a range(ex:10,20), value change depending quality } - + llPrice += IMulDivLL(llPrice, maximum(iConvertFactor, -100), 100); if ( llPrice > UINT32_MAX ) return UINT32_MAX; diff --git a/src/game/items/CItemVendable.h b/src/game/items/CItemVendable.h index babb652ab..2d46d10dd 100644 --- a/src/game/items/CItemVendable.h +++ b/src/game/items/CItemVendable.h @@ -31,7 +31,7 @@ class CItemVendable : public CItem void SetPlayerVendorPrice( dword dwVal ); dword GetBasePrice() const; - dword GetVendorPrice( int iConvertFactor , bool forselling); + dword GetVendorPrice( int iConvertFactor , bool forselling); bool IsValidSaleItem( bool fBuyFromVendor ) const; bool IsValidNPCSaleItem() const; diff --git a/src/game/spheresvr.cpp b/src/game/spheresvr.cpp index ccb0410ca..b98e6d81b 100644 --- a/src/game/spheresvr.cpp +++ b/src/game/spheresvr.cpp @@ -1,5 +1,4 @@ #ifdef _WIN32 - #include "../sphere/ntservice.h" // g_Service #include "../sphere/ntwindow.h" #include // getpid() #else @@ -15,113 +14,41 @@ #include "../network/linuxev.h" #endif -#include "../common/sphere_library/CSRand.h" #include "../common/CLog.h" -#include "../common/CException.h" -#include "../common/CExpression.h" +#include "../common/CScriptParserBufs.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CUOInstall.h" -#include "../common/sphereversion.h" #include "../network/CNetworkManager.h" #include "../network/PingServer.h" #include "../sphere/asyncdb.h" +#include "../sphere/GlobalInitializer.h" +#include "../sphere/StartupMonitorThread.h" +#include "../sphere/MainThread.h" #include "clients/CAccount.h" -#include "CObjBase.h" #include "CScriptProfiler.h" #include "CServer.h" #include "CWorld.h" #include "spheresvr.h" -#include #include - -// Dynamic allocation of some global stuff -std::string g_sServerDescription; - -// Dynamic initialization of some static members of other classes, which are used very soon after the server starts -dword CObjBase::sm_iCount = 0; // UID table. -#ifdef _WIN32 -llong CSTime::_kllTimeProfileFrequency = 1; // Default value. +#ifdef UNIT_TESTING +# define DOCTEST_CONFIG_IMPLEMENT +# include #endif - -// This method MUST be the first code running when the application starts! -GlobalInitializer::GlobalInitializer() -{ - // The order of the instructions is important! - - std::stringstream ssServerDescription; - ssServerDescription << SPHERE_TITLE << " Version " << SPHERE_BUILD_INFO_STR; - ssServerDescription << " [" << get_target_os_str() << '-' << get_target_arch_str() << "]"; - ssServerDescription << " by www.spherecommunity.net"; - g_sServerDescription = ssServerDescription.str(); - -//-- Time - -/* -#ifdef _WIN32 - // Ensure it's ACTUALLY necessary, before resorting to this. - timeBeginPeriod(1); // from timeapi.h, we need also to link against Winmm.lib... -#endif -*/ - PeriodicSyncTimeConstants(); - -//--- Sphere threading system - - DummySphereThread::createInstance(); - -//--- Exception handling - -#ifdef WINDOWS_SPHERE_SHOULD_HANDLE_STRUCTURED_EXCEPTIONS - SetWindowsStructuredExceptionTranslator(); -#endif - - // Set function to handle the invalid case where a pure virtual function is called. - SetPurecallHandler(); - -//--- Pre-startup sanity checks - - constexpr const char* m_sClassName = "GlobalInitializer"; - EXC_TRY("Pre-startup Init"); - - static_assert(MAX_BUFFER >= sizeof(CCommand)); - static_assert(MAX_BUFFER >= sizeof(CEvent)); - static_assert(sizeof(int) == sizeof(dword)); // make this assumption often. - static_assert(sizeof(ITEMID_TYPE) == sizeof(dword)); - static_assert(sizeof(word) == 2); - static_assert(sizeof(dword) == 4); - static_assert(sizeof(nword) == 2); - static_assert(sizeof(ndword) == 4); - static_assert(sizeof(wchar) == 2); // 16 bits - static_assert(sizeof(CUOItemTypeRec) == 37); // is byte packing working ? - - EXC_CATCH; -} - -void GlobalInitializer::InitRuntimeDefaultValues() // static -{ - CPointBase::InitRuntimeDefaultValues(); -} - -void GlobalInitializer::PeriodicSyncTimeConstants() // static -{ - // TODO: actually call it periodically! - -#ifdef _WIN32 - // Needed to get precise system time. - LARGE_INTEGER liProfFreq; - if (QueryPerformanceFrequency(&liProfFreq)) - { - CSTime::_kllTimeProfileFrequency = liProfFreq.QuadPart; - } -#endif // _WIN32 -} - - /* Start global declarations */ +DummySphereThread* DummySphereThread::_instance = nullptr; +std::string g_sServerDescription; + // NOLINTNEXTLINE(clazy-non-pod-global-static) static GlobalInitializer g_GlobalInitializer; +extern CScriptParserBufs g_ScriptParserBuffers; +CScriptParserBufs g_ScriptParserBuffers; + + #ifdef _WIN32 CNTWindow g_NTWindow; #else @@ -154,15 +81,22 @@ CWorld g_World; // the world. (we save this stuff) // Again, game servers stuff. CUOInstall g_Install; CVerDataMul g_VerData; -CSRand g_Rand; // TODO: remove this, since now all the members are static. -CExpression g_Exp; // Global script variables. +sl::GuardedAccess // TODO: put inside GuardedAccess also g_Cfg, and slowly also the other stuff? + g_ExprGlobals; // Global script variables. CSStringList g_AutoComplete; // auto-complete list CScriptProfiler g_profiler; // script profiler CUOMapList g_MapList; // global maps information +//-- Threads. + +// Startup/Monitor Sphere context bound to the bootstrap thread. +// NOLINTNEXTLINE(clazy-non-pod-global-static) +static StartupMonitorThread g_StartupMonitor; + // NOLINTNEXTLINE(clazy-non-pod-global-static) static MainThread g_Main; + // NOLINTNEXTLINE(clazy-non-pod-global-static) static PingServer g_PingServer; @@ -170,45 +104,30 @@ CDataBaseAsyncHelper g_asyncHdb; //******************************************************************* -// Main server loop -MainThread::MainThread() - : AbstractSphereThread("T_Main", ThreadPriority::RealTime) -{ - m_profile.EnableProfile(PROFILE_NETWORK_RX); - m_profile.EnableProfile(PROFILE_CLIENTS); - //m_profile.EnableProfile(PROFILE_NETWORK_TX); - m_profile.EnableProfile(PROFILE_CHARS); - m_profile.EnableProfile(PROFILE_ITEMS); - m_profile.EnableProfile(PROFILE_MAP); - m_profile.EnableProfile(PROFILE_MULTIS); - m_profile.EnableProfile(PROFILE_NPC_AI); - m_profile.EnableProfile(PROFILE_SCRIPTS); - m_profile.EnableProfile(PROFILE_SHIPS); - m_profile.EnableProfile(PROFILE_TIMEDFUNCTIONS); - m_profile.EnableProfile(PROFILE_TIMERS); -} - -void MainThread::onStart() +#ifdef _WIN32 +// Expose bootstrap-thread binding helpers for other TUs (e.g., WinMain/service). +#include "../sphere/StartupMonitorAPI.h" +extern "C" { - AbstractSphereThread::onStart(); -} + void Sphere_AttachBootstrapContext() + { + g_StartupMonitor.attachToCurrentThread("T_SphereStartup"); + } -void MainThread::tick() -{ - Sphere_OnTick(); -} + void Sphere_RenameBootstrapToMonitor() + { + g_StartupMonitor.renameAsMonitor(); + } -bool MainThread::shouldExit() noexcept -{ - if (g_Serv.GetExitFlag() != 0) - return true; - return AbstractSphereThread::shouldExit(); + void Sphere_RunMonitorLoop() + { + g_StartupMonitor.runMonitorLoop(); + } } +#endif -//******************************************************************* - static bool WritePidFile(int iMode = 0) { lpctstr fileName = SPHERE_FILE ".pid"; @@ -243,16 +162,15 @@ static bool WritePidFile(int iMode = 0) } } - int Sphere_InitServer( int argc, char *argv[] ) { constexpr const char *m_sClassName = "SphereInit"; - EXC_TRY("Init Server"); + EXC_TRY("Init Server"); GlobalInitializer::InitRuntimeDefaultValues(); EXC_SET_BLOCK("loading ini and scripts"); if ( !g_Serv.Load() ) - return -3; + return -3; if ( argc > 1 ) { @@ -266,11 +184,13 @@ int Sphere_InitServer( int argc, char *argv[] ) EXC_SET_BLOCK("sockets init"); if ( !g_Serv.SocketsInit() ) return -9; + EXC_SET_BLOCK("load world"); + g_Serv.SetServerMode(ServMode::StartupLoadingSaves); if ( !g_World.LoadAll() ) return -8; - // load auto-complete dictionary + // load auto-complete dictionary // TODO: might as well be removed...? EXC_SET_BLOCK("auto-complete"); { CSFileText dict; @@ -307,7 +227,9 @@ int Sphere_InitServer( int argc, char *argv[] ) g_Cfg.PrintEFOFFlags(); EXC_SET_BLOCK("finalizing"); - g_Serv.SetServerMode(SERVMODE_Run); // ready to go. + g_Serv.SetServerMode(ServMode::Run); // ready to go. + // Enter Run mode: inform ThreadHolder to disable startup fallback for current() + ThreadHolder::markServEnteredRunMode(); g_Log.Event(LOGM_INIT, "%s", g_Serv.GetStatusString(0x24)); g_Log.Event(LOGM_INIT, "\nStartup complete (items=%" PRIuSIZE_T ", chars=%" PRIuSIZE_T ", Accounts = %" PRIuSIZE_T ")\n", g_Serv.StatGet(SERV_STAT_ITEMS), g_Serv.StatGet(SERV_STAT_CHARS), g_Serv.StatGet(SERV_STAT_ACCOUNTS)); @@ -323,7 +245,7 @@ int Sphere_InitServer( int argc, char *argv[] ) // Trigger server start - g_Serv.r_Call("f_onserver_start", &g_Serv, nullptr); + g_Serv.r_Call("f_onserver_start", CScriptParserBufs::GetCScriptTriggerArgsPtr(), &g_Serv); return g_Serv.GetExitFlag(); EXC_CATCH; @@ -337,9 +259,9 @@ int Sphere_InitServer( int argc, char *argv[] ) void Sphere_ExitServer() { // Trigger server quit - g_Serv.r_Call("f_onserver_exit", &g_Serv, nullptr); + g_Serv.r_Call("f_onserver_exit", CScriptParserBufs::GetCScriptTriggerArgsPtr(), &g_Serv); - g_Serv.SetServerMode(SERVMODE_Exiting); + g_Serv.SetServerMode(ServMode::Exiting); g_NetworkManager.stop(); g_Main.waitForClose(); @@ -390,88 +312,32 @@ void Sphere_ExitServer() } -int Sphere_OnTick() -{ - // Give the world (CMainTask) a single tick. RETURN: 0 = everything is fine. - constexpr const char *m_sClassName = "SphereTick"; - EXC_TRY("Tick"); -#ifdef _WIN32 - EXC_SET_BLOCK("service"); - g_NTService._OnTick(); -#endif - - EXC_SET_BLOCK("world"); - g_World._OnTick(); - - // process incoming data - EXC_SET_BLOCK("network-in"); - g_NetworkManager.processAllInput(); - - EXC_SET_BLOCK("server"); - g_Serv._OnTick(); - - // push outgoing data - EXC_SET_BLOCK("network-out"); - g_NetworkManager.processAllOutput(); - - // don't put the network-tick between in.tick and out.tick, otherwise it will clean the out queue! - EXC_SET_BLOCK("network-tick"); - g_NetworkManager.tick(); // then this thread has to call the network tick - - EXC_CATCH; - return g_Serv.GetExitFlag(); -} - - //***************************************************** -static void Sphere_MainMonitorLoop() +// Provide the monitor stuck-check wrapper referenced by StartupMonitorThread +bool Sphere_CheckMainStuckAndRestart() { - constexpr const char *m_sClassName = "SphereMonitor"; - // Just make sure the main loop is alive every so often. - // This should be the parent thread. try to restart it if it is not. - while ( !g_Serv.GetExitFlag() ) - { - EXC_TRY("MainMonitorLoop"); - - if ( g_Cfg.m_iFreezeRestartTime <= 0 ) - { - DEBUG_ERR(("Freeze Restart Time cannot be cleared at run time\n")); - g_Cfg.m_iFreezeRestartTime = 10; - } - - EXC_SET_BLOCK("Sleep"); - // only sleep 1 second at a time, to avoid getting stuck here when closing - // down with large m_iFreezeRestartTime values set - for (int i = 0; i < g_Cfg.m_iFreezeRestartTime; ++i) - { - if ( g_Serv.GetExitFlag() ) - break; - - SLEEP(1000); - } - - EXC_SET_BLOCK("Checks"); - // Don't look for freezing when doing certain things. - if ( g_Serv.IsLoading() || ! g_Cfg.m_fSecure || g_Serv.IsValidBusy() ) - continue; - -#ifndef _DEBUG - EXC_SET_BLOCK("Check Stuck"); - if (g_Main.checkStuck() == true) - g_Log.Event(LOGL_CRIT, "'%s' thread hang, restarting...\n", g_Main.getName()); -#endif - EXC_CATCH; - } - + return g_Main.checkStuck(); } - -void atexit_handler() +static void atexit_handler() { ThreadHolder::get().markThreadsClosing(); } +#ifdef UNIT_TESTING +static int DocTestMain() +{ + doctest::Context context; + + int res = context.run(); // run + + if(context.shouldExit()) // important - query flags (and --exit) rely on the user doing this + return res; // propagate the result of the tests + + return res; +} +#endif #ifdef _WIN32 int Sphere_MainEntryPoint( int argc, char *argv[] ) @@ -479,13 +345,9 @@ int Sphere_MainEntryPoint( int argc, char *argv[] ) int main( int argc, char * argv[] ) #endif { - { - // Ensure i have this to have context for ADDTOCALLSTACK and other operations. - const AbstractThread* curthread = ThreadHolder::get().current(); - ASSERT(curthread != nullptr); - ASSERT(dynamic_cast(curthread)); - UnreferencedParameter(curthread); - } +#ifdef UNIT_TESTING + return DocTestMain(); +#endif static constexpr lpctstr m_sClassName = "main"; EXC_TRY("MAIN"); @@ -498,7 +360,10 @@ int main( int argc, char * argv[] ) } #ifndef _WIN32 - AbstractThread::setThreadName("T_SphereStartup"); + // Bind the bootstrap OS thread as a proper Sphere thread during startup. + g_StartupMonitor.attachToCurrentThread("T_SphereStartup"); + + // Start UnixTerminal in its own thread. g_UnixTerminal.start(); #endif @@ -516,7 +381,7 @@ int main( int argc, char * argv[] ) #ifndef _WIN32 // We need to find out the log files folder... look it up in the .ini file (on Windows it's done in WinMain function). - g_Serv.SetServerMode(SERVMODE_PreLoadingINI); + g_Serv.SetServerMode(ServMode::StartupPreLoadingIni); // Parse command line arguments. for (int argn = 1; argn < argc; ++argn) @@ -540,8 +405,8 @@ int main( int argc, char * argv[] ) g_Cfg.LoadIni(false); #endif - g_Serv.SetServerMode(SERVMODE_Loading); - g_Serv.SetExitFlag( Sphere_InitServer( argc, argv )); + g_Serv.SetServerMode(ServMode::StartupLoadingScripts); + g_Serv.SetExitFlag( Sphere_InitServer( argc, argv )); if ( ! g_Serv.GetExitFlag() ) { WritePidFile(); @@ -559,21 +424,25 @@ int main( int argc, char * argv[] ) // an instance of CNetworkInput nad CNetworkOutput, which support working in a multi threaded way (declarations and definitions in network_multithreaded.h/.cpp) g_NetworkManager.start(); - const bool fShouldCoreRunInSeparateThread = ( g_Cfg.m_iFreezeRestartTime > 0 ); - if (fShouldCoreRunInSeparateThread) - { - g_Main.start(); // Starts another thread to do all the work (it does Sphere_OnTick()) - AbstractThread::setThreadName("T_Monitor"); - Sphere_MainMonitorLoop(); // Use this thread to monitor if the others are stuck - } - else - { - while ( !g_Serv.GetExitFlag() ) - { - g_Main.tick(); // Use this thread to do all the work, without monitoring the other threads state - } - } - } + const bool fShouldCoreRunInSeparateThread = (g_Cfg.m_iFreezeRestartTime != 0); + if (fShouldCoreRunInSeparateThread) + { + // Core runs on a separate OS thread. + g_Main.start(); + // Switch the bootstrap thread from startup duties to monitoring duties. + g_StartupMonitor.renameAsMonitor(); + // Blocking monitor loop on the bootstrap thread. + g_StartupMonitor.runMonitorLoop(); + } + else + { + // Inline core on the bootstrap thread: release the startup context and bind g_Main. + g_StartupMonitor.detachFromCurrentThread(); + AbstractThread::ThreadBindingScope bind(g_Main, "T_Main"); + while (!g_Serv.GetExitFlag()) + g_Main.tick(); + } + } exit_server: Sphere_ExitServer(); diff --git a/src/game/spheresvr.h b/src/game/spheresvr.h index c71ce3c20..a4ff904d4 100644 --- a/src/game/spheresvr.h +++ b/src/game/spheresvr.h @@ -7,49 +7,13 @@ #define _INC_SPHERESVR_H #include "../common/sphere_library/CSAssoc.h" -#include "../sphere/threads.h" - - -class GlobalInitializer -{ -public: - GlobalInitializer(); - static void InitRuntimeDefaultValues(); - static void PeriodicSyncTimeConstants(); -}; - -//////////////////////////////////////////////////////////////////////////////////// - -class MainThread : public AbstractSphereThread -{ -public: - MainThread(); - virtual ~MainThread() { }; - -private: - MainThread(const MainThread& copy); - MainThread& operator=(const MainThread& other); - -public: - // we increase the access level from protected to public in order to allow manual execution when - // configuration disables using threads - // TODO: in the future, such simulated functionality should lie in AbstractThread inself instead of hacks - virtual void tick() override; - -protected: - virtual void onStart() override; - virtual bool shouldExit() noexcept override; -}; - -////////////////////////////////////////////////////////////// extern std::string g_sServerDescription; extern CSStringList g_AutoComplete; -extern int Sphere_InitServer( int argc, char *argv[] ); -extern int Sphere_OnTick(); -extern void Sphere_ExitServer(); -extern int Sphere_MainEntryPoint( int argc, char *argv[] ); - +int Sphere_MainEntryPoint( int argc, char *argv[] ); +int Sphere_InitServer( int argc, char *argv[] ); +void Sphere_ExitServer(); +bool Sphere_CheckMainStuckAndRestart(); #endif // _INC_SPHERESVR_H diff --git a/src/game/triggers.cpp b/src/game/triggers.cpp index 8dd92fbcf..74271dd5a 100644 --- a/src/game/triggers.cpp +++ b/src/game/triggers.cpp @@ -26,7 +26,7 @@ static std::vector sm_vTriggersId; bool IsTrigUsed(E_TRIGGERS id) { - if ( g_Serv.IsLoading() == true) + if ( g_Serv.IsLoadingGeneric() == true) return false; return (( (uint)id < sm_vTriggersId.size() ) && sm_vTriggersId[id].m_used ); @@ -34,7 +34,7 @@ bool IsTrigUsed(E_TRIGGERS id) bool IsTrigUsed(const char *name) { - if ( g_Serv.IsLoading() == true) + if ( g_Serv.IsLoadingGeneric() == true) return false; const int index = FindTableSorted(name, kOrderedTrigsNames, ARRAY_COUNT(kOrderedTrigsNames)); diff --git a/src/game/uo_files/CUOMapList.cpp b/src/game/uo_files/CUOMapList.cpp index fd0f08c01..922bfd3df 100644 --- a/src/game/uo_files/CUOMapList.cpp +++ b/src/game/uo_files/CUOMapList.cpp @@ -1,3 +1,5 @@ +//#include "../../common/sphere_library/sstring.h" +//#include "../../common/CExpression.h" #include "../../common/CLog.h" #include "../../common/CServerMap.h" #include "../../common/CUOInstall.h" @@ -17,7 +19,7 @@ void CUOMapList::MapGeoDataHolder::clear() noexcept } -// CUOMapList:: Constructors, Destructor, Asign operator. +// CUOMapList:: Constructors, Destructor, Assign operator. CUOMapList::CUOMapList() noexcept { @@ -37,15 +39,18 @@ void CUOMapList::Clear() noexcept //ResetMap(i, -1, -1, -1, i, i); } -void CUOMapList::ResetMap(int map, int maxx, int maxy, int sectorsize, int realmapnum, int mapid) +// Unused +/* +void CUOMapList::ResetMap(uint map, ushort maxx, ushort maxy, ushort iSectorSize, ushort realmapnum, ushort mapid) { MapGeoData& map_data = m_mapGeoData.maps[map]; - map_data.sizex = maxx; - map_data.sizey = maxy; - map_data.sectorsize = sectorsize; - map_data.num = realmapnum; + map_data.uiSizeX = maxx; + map_data.uiSizeY = maxy; + map_data.iSectorSize = iSectorSize; + map_data.iNum = realmapnum; map_data.id = mapid; } +*/ void CUOMapList::Init() { @@ -58,13 +63,13 @@ void CUOMapList::Init() for ( int i = 0; i < MAP_SUPPORTED_QTY; ++i ) { MapGeoData& map_data = m_mapGeoData.maps[i]; - if ( map_data.enabled ) // map marked as available. check whatever it's possible + if ( map_data.fEnabled ) // map marked as available. check whatever it's possible { // check coordinates first - if ( map_data.num == -1 ) - map_data.enabled = false; - else if ( map_data.sizex <= 0 || map_data.sizey <= 0 || map_data.sectorsize <= 0 ) - map_data.enabled = DetectMapSize(i); + if ( map_data.iNum == -1 ) + map_data.fEnabled = false; + else if ( map_data.uiSizeX <= 0 || map_data.uiSizeY <= 0 || map_data.iSectorSize <= 0 ) + map_data.fEnabled = DetectMapSize(i); } } } @@ -78,57 +83,63 @@ bool CUOMapList::Load(int map, char *args) } MapGeoData& map_data = m_mapGeoData.maps[map]; - if ( false == map_data.initialized ) // disable double intialization + if ( false == map_data.fInitialized ) // disable double intialization { tchar * ppCmd[5]; // maxx,maxy,sectorsize,mapnum[like 0 for map0/statics0/staidx0],mapid size_t iCount = Str_ParseCmds(args, ppCmd, ARRAY_COUNT(ppCmd), ","); if ( iCount <= 0 ) // simple MAPX= same as disabling the map { - map_data.enabled = false; + map_data.fEnabled = false; + map_data.fInitialized = true; + return true; } - else + + int maxx = 0, maxy = 0, sectorsize = 0, realmapnum = 0, mapid = -1; + if ( ppCmd[0] ) maxx = atoi(ppCmd[0]); + if ( ppCmd[1] ) maxy = atoi(ppCmd[1]); + if ( ppCmd[2] ) sectorsize = atoi(ppCmd[2]); + if ( ppCmd[3] ) realmapnum = atoi(ppCmd[3]); + if ( ppCmd[4] ) mapid = atoi(ppCmd[4]); + + // zero settings of anything except the real map num means + if ( maxx ) // skipping the argument { - int maxx = 0, maxy = 0, sectorsize = 0, realmapnum = 0, mapid = -1; - if ( ppCmd[0] ) maxx = atoi(ppCmd[0]); - if ( ppCmd[1] ) maxy = atoi(ppCmd[1]); - if ( ppCmd[2] ) sectorsize = atoi(ppCmd[2]); - if ( ppCmd[3] ) realmapnum = atoi(ppCmd[3]); - if ( ppCmd[4] ) mapid = atoi(ppCmd[4]); - - // zero settings of anything except the real map num means - if ( maxx ) // skipping the argument + if (( maxx < 8 ) || ( maxx % 8 )) { - if (( maxx < 8 ) || ( maxx % 8 )) - { - g_Log.EventError("MAP%d: X coord must be multiple of 8 (%d is invalid, %d is still effective).\n", - map, maxx, map_data.sizex); - } - else - map_data.sizex = maxx; + g_Log.EventError("MAP%d: X coord must be multiple of 8 (%d is invalid, %d is still effective).\n", + map, maxx, map_data.uiSizeX); } - if ( maxy ) + else + map_data.uiSizeX = (ushort)std::clamp(maxx, 0, (int)UINT16_MAX); + } + if ( maxy ) + { + if (( maxy < 8 ) || ( maxy % 8 )) { - if (( maxy < 8 ) || ( maxy % 8 )) - { - g_Log.EventError("MAP%d: Y coord must be multiple of 8 (%d is invalid, %d is still effective).\n", - map, maxy, map_data.sizey); - } - else - map_data.sizey = maxy; + g_Log.EventError("MAP%d: Y coord must be multiple of 8 (%d is invalid, %d is still effective).\n", + map, maxy, map_data.uiSizeY); } - if ( sectorsize > 0 ) - map_data.sectorsize = sectorsize; - if ( realmapnum >= 0 ) - map_data.num = realmapnum; - if ( mapid >= 0 ) - map_data.id = mapid; else - map_data.id = map; + map_data.uiSizeY = (ushort)std::clamp(maxy, 0, (int)UINT16_MAX); } - - map_data.initialized = true; + if ( sectorsize > 0 ) + { + if (!IsPowerOfTwo((ushort)sectorsize)) + { + g_Log.EventError("MAP%d: Invalid SectorSize (%d) is not a power of 2.\n", + map, sectorsize); + } + else + map_data.iSectorSize = (int16)sectorsize; + } + if ( realmapnum >= 0 ) + map_data.iNum = (int16)realmapnum; + if ( mapid >= 0 ) + map_data.iId = (int16)mapid; } + + map_data.fInitialized = true; return true; } @@ -137,10 +148,10 @@ bool CUOMapList::Load(int map, char *args) bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, if not specified in the ini (<= 0) { - if (m_mapGeoData.maps[map].initialized == false ) + if (m_mapGeoData.maps[map].fInitialized == false ) return false; - const int index = m_mapGeoData.maps[map].num; + const int index = m_mapGeoData.maps[map].iNum; if ( index < 0 ) return false; @@ -167,58 +178,58 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, //g_Install.m_Maps[index].GetLength() == 89923808) // (UOP packed) { // ML+ map - if (map_data.sizex <= 0) - map_data.sizex = 7168; - if (map_data.sizey <= 0) - map_data.sizey = 4096; + if (map_data.uiSizeX <= 0) + map_data.uiSizeX = 7168; + if (map_data.uiSizeY <= 0) + map_data.uiSizeY = 4096; } else { // Pre ML map - if (map_data.sizex <= 0) - map_data.sizex = 6144; - if (map_data.sizey <= 0) - map_data.sizey = 4096; + if (map_data.uiSizeX <= 0) + map_data.uiSizeX = 6144; + if (map_data.uiSizeY <= 0) + map_data.uiSizeY = 4096; } - if (map_data.sectorsize <= 0) - map_data.sectorsize = SECTORSIZE_DEFAULT; + if (map_data.iSectorSize <= 0) + map_data.iSectorSize = SECTORSIZE_DEFAULT; break; case 2: // map2.mul - if (map_data.sizex <= 0) - map_data.sizex = 2304; - if (map_data.sizey <= 0) - map_data.sizey = 1600; - if (map_data.sectorsize <= 0) - map_data.sectorsize = SECTORSIZE_DEFAULT; + if (map_data.uiSizeX <= 0) + map_data.uiSizeX = 2304; + if (map_data.uiSizeY <= 0) + map_data.uiSizeY = 1600; + if (map_data.iSectorSize <= 0) + map_data.iSectorSize = SECTORSIZE_DEFAULT; break; case 3: // map3.mul - if (map_data.sizex <= 0) - map_data.sizex = 2560; - if (map_data.sizey <= 0) - map_data.sizey = 2048; - if (map_data.sectorsize <= 0) - map_data.sectorsize = SECTORSIZE_DEFAULT; + if (map_data.uiSizeX <= 0) + map_data.uiSizeX = 2560; + if (map_data.uiSizeY <= 0) + map_data.uiSizeY = 2048; + if (map_data.iSectorSize <= 0) + map_data.iSectorSize = SECTORSIZE_DEFAULT; break; case 4: // map4.mul - if (map_data.sizex <= 0) - map_data.sizex = 1448; - if (map_data.sizey <= 0) - map_data.sizey = 1448; - if (map_data.sectorsize <= 0) - map_data.sectorsize = SECTORSIZE_DEFAULT; + if (map_data.uiSizeX <= 0) + map_data.uiSizeX = 1448; + if (map_data.uiSizeY <= 0) + map_data.uiSizeY = 1448; + if (map_data.iSectorSize <= 0) + map_data.iSectorSize = SECTORSIZE_DEFAULT; break; case 5: // map5.mul - if (map_data.sizex <= 0) - map_data.sizex = 1280; - if (map_data.sizey <= 0) - map_data.sizey = 4096; - if (map_data.sectorsize <= 0) - map_data.sectorsize = SECTORSIZE_DEFAULT; + if (map_data.uiSizeX <= 0) + map_data.uiSizeX = 1280; + if (map_data.uiSizeY <= 0) + map_data.uiSizeY = 4096; + if (map_data.iSectorSize <= 0) + map_data.iSectorSize = SECTORSIZE_DEFAULT; break; default: @@ -227,27 +238,25 @@ bool CUOMapList::DetectMapSize(int map) // it sets also the default sector size, break; } - return (map_data.sizex > 0 && map_data.sizey > 0 && map_data.sectorsize > 0); + return (map_data.uiSizeX > 0 && map_data.uiSizeY > 0 && map_data.iSectorSize > 0); } bool CUOMapList::IsMapSupported(int map) const noexcept { - if (( map < 0 ) || ( map >= MAP_SUPPORTED_QTY)) - return false; - return m_mapGeoData.maps[map].enabled; + return ((map >= 0) && (map < MAP_SUPPORTED_QTY) && m_mapGeoData.maps[map].fEnabled); } bool CUOMapList::IsInitialized(int map) const { ASSERT(IsMapSupported(map)); - return m_mapGeoData.maps[map].initialized; + return m_mapGeoData.maps[map].fInitialized; } int CUOMapList::GetSectorSize(int map) const { ASSERT(IsMapSupported(map)); - ASSERT(m_mapGeoData.maps[map].sectorsize > 0); - return m_mapGeoData.maps[map].sectorsize; + ASSERT(m_mapGeoData.maps[map].iSectorSize > 0); + return m_mapGeoData.maps[map].iSectorSize; } int CUOMapList::CalcSectorQty(int map) const @@ -258,7 +267,7 @@ int CUOMapList::CalcSectorQty(int map) const int CUOMapList::CalcSectorCols(int map) const { ASSERT(IsMapSupported(map)); - const int a = m_mapGeoData.maps[map].sizex; + const int a = m_mapGeoData.maps[map].uiSizeX; const int b = GetSectorSize(map); // ceil division: some maps may not have x or y size perfectly dividable by 64 (default sector size), // still we need to make room even for sectors with a smaller number of usable tiles @@ -268,7 +277,7 @@ int CUOMapList::CalcSectorCols(int map) const int CUOMapList::CalcSectorRows(int map) const { ASSERT(IsMapSupported(map)); - const int a = m_mapGeoData.maps[map].sizey; + const int a = m_mapGeoData.maps[map].uiSizeY; const int b = GetSectorSize(map); // ceil division: some maps may not have x or y size perfectly dividable by 64 (default sector size), // still we need to make room even for sectors with a smaller number of usable tiles @@ -278,27 +287,27 @@ int CUOMapList::CalcSectorRows(int map) const int CUOMapList::GetMapCenterX(int map) const { ASSERT(IsMapSupported(map)); - ASSERT(m_mapGeoData.maps[map].sizex != -1); - return (m_mapGeoData.maps[map].sizex / 2); + ASSERT(m_mapGeoData.maps[map].uiSizeX != (ushort)-1); + return (m_mapGeoData.maps[map].uiSizeX / 2); } int CUOMapList::GetMapCenterY(int map) const { ASSERT(IsMapSupported(map)); - ASSERT(m_mapGeoData.maps[map].sizey != -1); - return (m_mapGeoData.maps[map].sizey / 2); + ASSERT(m_mapGeoData.maps[map].uiSizeY != (ushort)-1); + return (m_mapGeoData.maps[map].uiSizeY / 2); } int CUOMapList::GetMapFileNum(int map) const { ASSERT(IsMapSupported(map)); - ASSERT(m_mapGeoData.maps[map].num != -1); - return m_mapGeoData.maps[map].num; + ASSERT(m_mapGeoData.maps[map].iNum != -1); + return m_mapGeoData.maps[map].iNum; } int CUOMapList::GetMapID(int map) const { ASSERT(IsMapSupported(map)); - ASSERT(m_mapGeoData.maps[map].id != -1); - return m_mapGeoData.maps[map].id; + ASSERT(m_mapGeoData.maps[map].iId != -1); + return m_mapGeoData.maps[map].iId; } diff --git a/src/game/uo_files/CUOMapList.h b/src/game/uo_files/CUOMapList.h index d4e7c5623..6dfe5090a 100644 --- a/src/game/uo_files/CUOMapList.h +++ b/src/game/uo_files/CUOMapList.h @@ -6,6 +6,7 @@ #ifndef _INC_CUOMAPLIST_H #define _INC_CUOMAPLIST_H +#include "../../common/common.h" class CServerMapDiffCollection; @@ -22,24 +23,25 @@ extern class CUOMapList protected: struct MapGeoData { - bool enabled; // supported map? - bool initialized; - int num; // real map number (0 for 0 and 1, 2 for 2, and so on) - file name - int id; // map id used by the client - int sizex; - int sizey; - int sectorsize; - + int16 iNum; // real map number (0 for 0 and 1, 2 for 2, and so on) - file name + int16 iId; // map id used by the client + uint16 uiSizeX; + uint16 uiSizeY; + int16 iSectorSize; + bool fEnabled; // supported map? + bool fInitialized; + + [[nodiscard]] static constexpr MapGeoData invalid() noexcept { return { - .enabled = true, - .initialized = false, - .num = -1, - .id = -1, - .sizex = -1, - .sizey = -1, - .sectorsize = -1 + .iNum = -1, + .iId = -1, + .uiSizeX = (uint16)-1, + .uiSizeY = (uint16)-1, + .iSectorSize = -1, + .fEnabled = true, + .fInitialized = false }; } }; @@ -55,7 +57,7 @@ extern class CUOMapList CServerMapDiffCollection * m_pMapDiffCollection; public: - /** @name Constructors, Destructor, Asign operator: + /** @name Constructors, Destructor, Assign operator: */ ///@{ CUOMapList() noexcept; @@ -69,7 +71,7 @@ extern class CUOMapList */ ///@{ void Clear() noexcept; - void ResetMap(int map, int maxx, int maxy, int sectorsize, int realmapnum, int mapid); + //void ResetMap(uint map, ushort maxx, ushort maxy, ushort sectorsize, ushort realmapnum, ushort mapid); void Init(); bool Load(int map, char *args); ///@} @@ -84,37 +86,39 @@ extern class CUOMapList public: bool IsMapSupported(int map) const noexcept; bool IsInitialized(int map) const; + int CalcSectorQty(int map) const; // Use it only when initializing the map sectors! (because it's slower than the Get* method) int CalcSectorCols(int map) const; // Use it only when initializing the map sectors! (because it's slower than the Get* method) int CalcSectorRows(int map) const; // Use it only when initializing the map sectors! (because it's slower than the Get* method) - inline int GetMapSizeX(int map) const noexcept; - inline int GetMapSizeY(int map) const noexcept; + + inline uint16 GetMapSizeX(int map) const noexcept; + inline uint16 GetMapSizeY(int map) const noexcept; int GetMapCenterX(int map) const; int GetMapCenterY(int map) const; int GetMapFileNum(int map) const; int GetMapID(int map) const; - + ///@} } g_MapList; // Inline methods definition -inline int CUOMapList::GetMapSizeX(int map) const noexcept +inline uint16 CUOMapList::GetMapSizeX(int map) const noexcept { - // Used by CPointBase::IsValidXY(), which is called a LOT + // Used by CPointBase::IsValidXY() and IsValidPoint(), which is called a LOT //ASSERT(IsMapSupported(map)); //ASSERT(m_sizex[map] != -1); - return m_mapGeoData.maps[map].sizex; + return m_mapGeoData.maps[map].uiSizeX; } -inline int CUOMapList::GetMapSizeY(int map) const noexcept +inline uint16 CUOMapList::GetMapSizeY(int map) const noexcept { - // Used by CPointBase::IsValidXY(), which is called a LOT + // Used by CPointBase::IsValidXY() and IsValidPoint(), which is called a LOT //ASSERT(IsMapSupported(map)); //ASSERT(m_sizey[map] != -1); - return m_mapGeoData.maps[map].sizey; + return m_mapGeoData.maps[map].uiSizeY; } #endif //_INC_CUOMAPLIST_H diff --git a/src/game/uo_files/CUOMobtypes.cpp b/src/game/uo_files/CUOMobtypes.cpp index 131f4d720..1054dafe8 100644 --- a/src/game/uo_files/CUOMobtypes.cpp +++ b/src/game/uo_files/CUOMobtypes.cpp @@ -4,7 +4,7 @@ */ #include "../../sphere/threads.h" -#include "../../common/CExpression.h" +//#include "../../common/CExpression.h" // included in the precompiled header #include "../../common/CLog.h" #include "../../common/CUOInstall.h" #include "../../game/uo_files/uofiles_enums_creid.h" diff --git a/src/game/uo_files/CUOMobtypes.h b/src/game/uo_files/CUOMobtypes.h index d4514bddf..19449d2ff 100644 --- a/src/game/uo_files/CUOMobtypes.h +++ b/src/game/uo_files/CUOMobtypes.h @@ -46,7 +46,4 @@ class CUOMobTypes }; - - - #endif // _INC_CUOMOBTYPES_H diff --git a/src/game/uo_files/CUOTiledata.cpp b/src/game/uo_files/CUOTiledata.cpp index d3af4a5ee..bc8de9aeb 100644 --- a/src/game/uo_files/CUOTiledata.cpp +++ b/src/game/uo_files/CUOTiledata.cpp @@ -4,7 +4,7 @@ */ #include "../../sphere/threads.h" -#include "../../common/CException.h" +//#include "../../common/CException.h" // included in the precompiled header #include "../../common/CLog.h" #include "../../common/CUOInstall.h" #include "CUOTerrainTypeRec.h" diff --git a/src/game/uo_files/uofiles_enums.h b/src/game/uo_files/uofiles_enums.h index 6889838d9..31738052f 100644 --- a/src/game/uo_files/uofiles_enums.h +++ b/src/game/uo_files/uofiles_enums.h @@ -456,7 +456,7 @@ enum STAT_TYPE // MaxFood (7) }; -enum SKILL_TYPE // List of skill numbers (things that can be done at a given time) +enum SKILL_TYPE : int // List of skill numbers (things that can be done at a given time) { SKILL_NONE = -1, diff --git a/src/network/CIPHistoryManager.cpp b/src/network/CIPHistoryManager.cpp index 4c037776a..c6328b4ed 100644 --- a/src/network/CIPHistoryManager.cpp +++ b/src/network/CIPHistoryManager.cpp @@ -1,4 +1,4 @@ -#include "../common/CScriptTriggerArgs.h" +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../game/CServer.h" #include "../game/CServerConfig.h" #include "../game/CWorldGameTime.h" @@ -34,10 +34,11 @@ void HistoryIP::setBlocked(bool isBlocked, int64 timeoutSeconds) ADDTOCALLSTACK("HistoryIP:setBlocked"); if (isBlocked == true) { - CScriptTriggerArgs args(m_ip.GetAddrStr()); - args.m_iN1 = timeoutSeconds; - g_Serv.r_Call("f_onserver_blockip", &g_Serv, &args); - timeoutSeconds = args.m_iN1; + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(m_ip.GetAddrStr()); + pScriptArgs->m_iN1 = timeoutSeconds; + g_Serv.r_Call("f_onserver_blockip", pScriptArgs, &g_Serv ); + timeoutSeconds = pScriptArgs->m_iN1; } m_fBlocked = isBlocked; diff --git a/src/network/CIPHistoryManager.h b/src/network/CIPHistoryManager.h index 0658a2a78..a12d22834 100644 --- a/src/network/CIPHistoryManager.h +++ b/src/network/CIPHistoryManager.h @@ -38,7 +38,6 @@ struct HistoryIP typedef std::deque IPHistoryList; - /*************************************************************************** * * diff --git a/src/network/CNetState.cpp b/src/network/CNetState.cpp index eec21d7fb..21b7b167f 100644 --- a/src/network/CNetState.cpp +++ b/src/network/CNetState.cpp @@ -200,7 +200,7 @@ void CNetState::init(SOCKET socket, CSocketAddress addr) // Disable NAGLE algorythm for data compression/coalescing. // Send as fast as we can. we handle packing ourselves. - + int iSockFlag = 1; iSockRet = m_socket.SetSockOpt(TCP_NODELAY, &iSockFlag, sizeof(iSockFlag), IPPROTO_TCP); CheckReportNetAPIErr(iSockRet, "NetState::init.TCP_NODELAY"); diff --git a/src/network/CNetworkInput.cpp b/src/network/CNetworkInput.cpp index 70f050b37..57443969e 100644 --- a/src/network/CNetworkInput.cpp +++ b/src/network/CNetworkInput.cpp @@ -204,7 +204,7 @@ void CNetworkInput::processData() delete packet; } - if (g_Serv.IsLoading() == false) + if (g_Serv.IsLoadingGeneric() == false) { EXC_SET_BLOCK("start client profile"); const ProfileTask clientTask(PROFILE_CLIENTS); diff --git a/src/network/CNetworkManager.cpp b/src/network/CNetworkManager.cpp index ca558b289..aee45b5af 100644 --- a/src/network/CNetworkManager.cpp +++ b/src/network/CNetworkManager.cpp @@ -1,3 +1,4 @@ +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../game/clients/CClient.h" #include "../game/CServer.h" #include "../game/CServerConfig.h" @@ -180,16 +181,18 @@ void CNetworkManager::acceptNewConnection(void) // Call this special scripted function. - CScriptTriggerArgs fargs_ex(client_addr.GetAddrStr()); - fargs_ex.m_VarsLocal.SetNumNew("TIME_CUR_CONNECTED_MS", ip.m_iTimeLastConnectedMs); - fargs_ex.m_VarsLocal.SetNumNew("TIME_LAST_CONNECTED_MS", iIpPrevConnectionTime); - fargs_ex.m_VarsLocal.SetNumNew("PINGS", ip.m_iPings); - fargs_ex.m_VarsLocal.SetNumNew("CONNECTION_REQUESTS", ip.m_iConnectionRequests); - fargs_ex.m_VarsLocal.SetNumNew("ALIVE_CONNECTIONS", ip.m_iAliveSuccessfulConnections); - fargs_ex.m_VarsLocal.SetNumNew("PENDING_CONNECTING", ip.m_iPendingConnectionRequests); - fargs_ex.m_VarsLocal.SetNumNew("BAN_TIMEOUT", 5ll * 60); + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(client_addr.GetAddrStr()); + pScriptArgs->m_VarsLocal.SetNumNew("TIME_CUR_CONNECTED_MS", ip.m_iTimeLastConnectedMs); + pScriptArgs->m_VarsLocal.SetNumNew("TIME_LAST_CONNECTED_MS", iIpPrevConnectionTime); + pScriptArgs->m_VarsLocal.SetNumNew("PINGS", ip.m_iPings); + pScriptArgs->m_VarsLocal.SetNumNew("CONNECTION_REQUESTS", ip.m_iConnectionRequests); + pScriptArgs->m_VarsLocal.SetNumNew("ALIVE_CONNECTIONS", ip.m_iAliveSuccessfulConnections); + pScriptArgs->m_VarsLocal.SetNumNew("PENDING_CONNECTING", ip.m_iPendingConnectionRequests); + pScriptArgs->m_VarsLocal.SetNumNew("BAN_TIMEOUT", 5ll * 60); + TRIGRET_TYPE fret = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onserver_connectreq_ex", &g_Serv, &fargs_ex, nullptr, &fret); + g_Serv.r_Call("f_onserver_connectreq_ex", pScriptArgs, &g_Serv, nullptr, &fret); if (fret == -1) { // RETURN -1: do not block. @@ -208,7 +211,7 @@ void CNetworkManager::acceptNewConnection(void) { // RETURN 2 (TRIGRET_RET_DEFAULT) or other: block IP CLOSESOCKET(h); - ip.setBlocked(true, fargs_ex.m_VarsLocal.GetKeyNum("BAN_TIMEOUT")); + ip.setBlocked(true, pScriptArgs->m_VarsLocal.GetKeyNum("BAN_TIMEOUT")); g_Log.Event(LOGM_CLIENTS_LOG | LOGL_ERROR, "Outcome (default): requested kick + IP block allowed by script 'f_onserver_connectreq_ex'.\n"); } @@ -269,11 +272,13 @@ void CNetworkManager::acceptNewConnection(void) */ // Call this special scripted function. - CScriptTriggerArgs fargs_acquired(client_addr.GetAddrStr()); - fargs_acquired.m_iN1 = iIpPrevConnectionTime; - fargs_acquired.m_iN2 = ip.m_iTimeLastConnectedMs; // Current connection time. + CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + pScriptArgs->Init(client_addr.GetAddrStr()); + pScriptArgs->m_iN1 = iIpPrevConnectionTime; + pScriptArgs->m_iN2 = ip.m_iTimeLastConnectedMs; // Current connection time. + TRIGRET_TYPE fret = TRIGRET_RET_FALSE; - g_Serv.r_Call("f_onserver_connection_acquired", &g_Serv, &fargs_acquired, nullptr, &fret); + g_Serv.r_Call("f_onserver_connection_acquired", pScriptArgs, &g_Serv, nullptr, &fret); // select an empty slot EXC_SET_BLOCK("detecting slot"); @@ -364,7 +369,7 @@ void CNetworkManager::start(void) m_isThreaded = g_Cfg._uiNetworkThreads > 0; if (isThreaded()) { - // start network threads + // Start/spawn network threads for (NetworkThreadList::iterator it = m_threads.begin(), end = m_threads.end(); it != end; ++it) { // The thread structure (class) was created via createNetworkThreads, now spawn a new thread and do the work inside there. @@ -377,7 +382,9 @@ void CNetworkManager::start(void) } else { - // initialise network threads (if g_Cfg._uiNetworkThreads is == 0 then we'll have only 1 CNetworkThread) + // In the non-threaded configuration, T_Net #0 is just a worker object; its init()/tick()/processInput()/processOutput() run on the T_Main thread. + + // Initialise network threads (if g_Cfg._uiNetworkThreads is == 0 then we'll have only 1 CNetworkThread) size_t ntCount = m_threads.size(); UnreferencedParameter(ntCount); for (NetworkThreadList::iterator it = m_threads.begin(), end = m_threads.end(); it != end; ++it) @@ -397,7 +404,7 @@ void CNetworkManager::start(void) } */ - pThread->onStart(); // the thread structure (class) was created via createNetworkThreads, but we execute the worker method of that class in this thread + pThread->init(); // the thread structure (class) was created via createNetworkThreads, but we execute the worker method of that class in this thread } } } diff --git a/src/network/CNetworkOutput.cpp b/src/network/CNetworkOutput.cpp index 445c24df1..67a908bb3 100644 --- a/src/network/CNetworkOutput.cpp +++ b/src/network/CNetworkOutput.cpp @@ -48,7 +48,6 @@ static void CALLBACK SendCompleted_Winsock(DWORD dwError, DWORD cbTransferred, L #endif - CNetworkOutput::CNetworkOutput() : m_thread(nullptr) { m_encryptBuffer = new byte[MAX_BUFFER]; diff --git a/src/network/CNetworkOutput.h b/src/network/CNetworkOutput.h index c4cabbfe7..217b44c63 100644 --- a/src/network/CNetworkOutput.h +++ b/src/network/CNetworkOutput.h @@ -56,5 +56,4 @@ class CNetworkOutput }; - #endif // _INC_NETWORKOUTPUT_H diff --git a/src/network/CNetworkThread.cpp b/src/network/CNetworkThread.cpp index 0ebb6db11..f7e9ca87f 100644 --- a/src/network/CNetworkThread.cpp +++ b/src/network/CNetworkThread.cpp @@ -16,6 +16,9 @@ static const char* GenerateNetworkThreadName(size_t id) return name; } +// A CNetworkThread is network worker abstraction that owns client state lists, queues, and APIs +// (processInput/processOutput/flush/assignNetworkState), so the manager can use the same code path in both modes; +// in non-threaded mode the object runs inline on T_Main, and in threaded mode the exact same object is started with its own OS thread. CNetworkThread::CNetworkThread(CNetworkManager* manager, size_t id) : AbstractSphereThread(GenerateNetworkThreadName(id), ThreadPriority::Disabled), @@ -92,9 +95,8 @@ void CNetworkThread::dropInvalidStates(void) } } -void CNetworkThread::onStart(void) +void CNetworkThread::init() { - AbstractSphereThread::onStart(); m_input.setOwner(this); m_output.setOwner(this); m_profile.EnableProfile(PROFILE_NETWORK_RX); @@ -103,6 +105,12 @@ void CNetworkThread::onStart(void) m_profile.EnableProfile(PROFILE_DATA_TX); } +void CNetworkThread::onStart(void) +{ + init(); + AbstractSphereThread::onStart(); +} + void CNetworkThread::tick(void) { // process periodic actions @@ -124,9 +132,9 @@ void CNetworkThread::tick(void) // we're active, take priority setPriority(static_cast(g_Cfg._iNetworkThreadPriority)); - static constexpr int64 kiStateDataCheckPeriod = 10 * 1000; // 10 seconds, expressed in milliseconds + static constexpr int64 kiStateDataCheckPeriodMilli = 10 * 1000; // 10 seconds, expressed in milliseconds const int64 iTimeCur = CSTime::GetMonotonicSysTimeMilli(); - if (iTimeCur - _iTimeLastStateDataCheck > kiStateDataCheckPeriod) + if (iTimeCur - _iTimeLastStateDataCheck > kiStateDataCheckPeriodMilli) { _iTimeLastStateDataCheck = iTimeCur; diff --git a/src/network/CNetworkThread.h b/src/network/CNetworkThread.h index 69ee3a58c..091cd6100 100644 --- a/src/network/CNetworkThread.h +++ b/src/network/CNetworkThread.h @@ -29,7 +29,7 @@ class CNetworkThread : public AbstractSphereThread ThreadSafeQueue m_assignQueue; // queue of states waiting to be taken by this thread CNetworkInput m_input; // handles data input - CNetworkOutput m_output; // handles data output + CNetworkOutput m_output; // handles data output public: size_t id(void) const { return m_id; } // network thread # @@ -77,6 +77,7 @@ class CNetworkThread : public AbstractSphereThread virtual void onStart(void); virtual void tick(void); + void init(); void flushAllClients(void); // flush all output private: diff --git a/src/network/CSocket.cpp b/src/network/CSocket.cpp index 9014f36bc..69dfd641f 100644 --- a/src/network/CSocket.cpp +++ b/src/network/CSocket.cpp @@ -1,6 +1,10 @@ #include "CSocket.h" #ifndef _WIN32 - #include +# include +# include +# include +# include +# include #endif #include "../common/sphere_library/sstring.h" #include "../common/CLog.h" @@ -366,7 +370,7 @@ SOCKET CSocket::Accept( struct sockaddr_in * pSockAddrIn ) const SOCKET CSocket::Accept( CSocketAddress & SockAddr ) const { - // RETURN: Error = hSocketClient < 0 || hSocketClient == INVALID_SOCKET + // RETURN: Error = hSocketClient < 0 || hSocketClient == INVALID_SOCKET struct sockaddr_in SockAddrIn; SOCKET hSocket = Accept( &SockAddrIn ); SockAddr.SetAddrPort( SockAddrIn ); @@ -452,7 +456,7 @@ int CSocket::GetSockOpt( int nOptionName, void * optval, int * poptlen, int nLev // RETURN: length sent return( WSASend( m_hSocket, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine )); } - + void CSocket::ClearAsync() { // TO BE CALLED IN CClient destructor !!! diff --git a/src/network/CSocket.h b/src/network/CSocket.h index 1dc7f9c6e..2f0350bcb 100644 --- a/src/network/CSocket.h +++ b/src/network/CSocket.h @@ -1,39 +1,37 @@ /** * @file CSocket.h -* +* */ #ifndef _INC_CSOCKET_H #define _INC_CSOCKET_H +// TODO: In the different headers and classes, store a pointer to CSocketAddress instead of the class itself. This avoids +// to include CSocket.h. + #include "../common/common.h" #ifdef _WIN32 #include // this needs to be included after common.h, which sets some defines and then includes windows.h, since winsock2.h needs windows.h typedef int socklen_t; -#else +#else // else assume LINUX - #include - #include - #include - #include - #include - #include + #include // needed for hostent struct + //#include // included by netdb.h // Compatibility stuff. #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #define SOCKET int #define TCP_NODELAY 0x0001 - #endif // _WIN32 - #ifdef _WIN32 # define CLOSESOCKET(_x_) { shutdown(_x_, 2); closesocket(_x_); } #else # define CLOSESOCKET(_x_) { shutdown(_x_, 2); close(_x_); } #endif + void AddSocketToSet(fd_set& fds, SOCKET socket, int& count); void CheckReportNetAPIErr(int retval, lpctstr ptcOperation); @@ -85,7 +83,7 @@ struct CSocketAddress : public CSocketAddressIP explicit CSocketAddress(const sockaddr_in & SockAddrIn); explicit CSocketAddress(const CSocketAddressIP&) = delete; explicit CSocketAddress(CSocketAddressIP&) = delete; - + bool operator==( const CSocketAddress & SockAddr ) const; bool operator==( const struct sockaddr_in & SockAddrIn ) const; CSocketAddress& operator = (const struct sockaddr_in& SockAddrIn); @@ -107,9 +105,9 @@ class CSocket { private: SOCKET m_hSocket; // socket connect handle - + void Clear(); - + public: static const char *m_sClassName; diff --git a/src/network/PingServer.cpp b/src/network/PingServer.cpp index 2627c1227..63ef2975a 100644 --- a/src/network/PingServer.cpp +++ b/src/network/PingServer.cpp @@ -3,10 +3,8 @@ #include "PingServer.h" -static PingServer s_PingServer; - // run the thread in RealTime as we need pings to be responded to ASAP -PingServer::PingServer() : AbstractSphereThread("PingServer", ThreadPriority::RealTime) +PingServer::PingServer() : AbstractSphereThread("T_PingServer", ThreadPriority::RealTime) { m_profile.EnableProfile(PROFILE_NETWORK_RX); m_profile.EnableProfile(PROFILE_NETWORK_TX); diff --git a/src/network/linuxev.cpp b/src/network/linuxev.cpp index c946fcfe5..cb0f70f99 100644 --- a/src/network/linuxev.cpp +++ b/src/network/linuxev.cpp @@ -11,7 +11,7 @@ /* { ev_io_stop(loop, w); - + if ( !g_Serv.IsLoading() ) { if ( revents & EV_READ ) @@ -21,7 +21,7 @@ g_NetworkManager.acceptNewConnection(); } } - + ev_io_start(loop, w); } */ @@ -33,18 +33,18 @@ static void socketslave_cb(struct ev_loop *loop, struct ev_io *watcher, int reve // libev could call this function aliasing ev_io as a ev_watcher. // ev_watcher is a "parent" struct of ev_io, they share the first member variables. // it's a evil trick, but does the job since C doesn't have struct inheritance - + ev_io_stop(loop, watcher); CNetState* state = reinterpret_cast( watcher->data ); - + if ( !g_Serv.IsLoading() ) { if ( revents & EV_READ ) - { + { // This happens when the client socket is readable (i can try to retrieve data), this does NOT mean // that i have data to read. It might also mean that i have done writing to the socket? // g_NetworkOut.onAsyncSendComplete(state); - } + } else if ( revents & EV_WRITE ) { CNetworkThread* thread = state->getParentThread(); @@ -52,7 +52,7 @@ static void socketslave_cb(struct ev_loop *loop, struct ev_io *watcher, int reve thread->onAsyncSendComplete(state, true); // we can send (again) data } } - + if (state->isSendingAsync()) { ev_io_start(loop, watcher); @@ -93,7 +93,7 @@ static void periodic_cb(struct ev_loop* /*loop*/, ev_periodic* /*w*/, int /*reve } void LinuxEv::tick() -{ +{ /* A flags value of EVRUN_NOWAIT will look for new events, will handle those events and any already outstanding ones, @@ -111,7 +111,7 @@ void LinuxEv::tick() // This periodic timer keeps awake the event loop. We could have used ev_ref but it had its problems... // Don't ask me why (maybe i don't get how this actually should work, and this is only a workaround), // but if we rely on ev_ref to increase the event loop reference counter to keep it alive without this periodic timer/callback, - // the loop will ignore the io_collect_interval. Moreover, it will make the polling backend in use (like most frequently epoll) wait the maximum + // the loop will ignore the io_collect_interval. Moreover, it will make the polling backend in use (like most frequently epoll) wait the maximum // time (MAX_BLOCKTIME in ev.c, circa 60 seconds) to collect incoming data, only then the callback will be called. So each batch of packets // would be processed every 60 seconds... struct ev_periodic periodic_check; @@ -139,7 +139,7 @@ void LinuxEv::registerClient(CNetState * state, EventsID eventCheck) ADDTOCALLSTACK("LinuxEv::registerClient"); ASSERT(state != nullptr); - + memset(state->iocb(), 0, sizeof(ev_io)); // Right now we support only async writing to the socket. @@ -159,7 +159,7 @@ void LinuxEv::registerClient(CNetState * state, EventsID eventCheck) state->setSendingAsync(true); // set it true: when the socket will be again writable (so we have finished writing data in it) // socketslave_cb will be called (even immediately after this function if we aren't actually sending data) // and it will report that we are not sending async data anymore. - + ev_io_start(m_eventLoop, state->iocb()); } @@ -169,7 +169,7 @@ void LinuxEv::unregisterClient(CNetState * state) ASSERT(state != nullptr); state->setSendingAsync(false); - + ev_io_stop(m_eventLoop, state->iocb()); } @@ -203,7 +203,7 @@ void LinuxEv::registerMainsocket() #else ev_io_init(&m_watchMainsock, socketmain_cb, g_Serv.m_SocketMain.GetSocket(), EV_READ); #endif - ev_io_start(m_eventLoop, &m_watchMainsock); + ev_io_start(m_eventLoop, &m_watchMainsock); */ } diff --git a/src/network/linuxev.h b/src/network/linuxev.h index bdc1cbcd5..746673b4b 100644 --- a/src/network/linuxev.h +++ b/src/network/linuxev.h @@ -11,10 +11,10 @@ #include #include "../common/sphere_library/smutex.h" #include "../sphere/threads.h" - + class CClient; class CNetState; - + class LinuxEv : public AbstractSphereThread { public: @@ -24,34 +24,34 @@ None = 0, Read = 1, Write = 2, - + Error = 0x80000000 }; - + private: struct ev_loop * m_eventLoop; // struct ev_io m_watchMainsock; // Watcher for Sphere's socket, to accept incoming connections (async read). - + public: LinuxEv(void); virtual ~LinuxEv(void); LinuxEv(const LinuxEv& copy) = delete; LinuxEv& operator=(const LinuxEv& other) = delete; - + public: virtual void onStart() override; virtual void tick() override; virtual void waitForClose() override; - + private: void forceClientevent(CNetState *, EventsID); - + public: void printInitInfo(); void forceClientread(CNetState *); void forceClientwrite(CNetState *); - // -------------------------------------- + // -------------------------------------- void registerClient(CNetState *, EventsID); void unregisterClient(CNetState *); // -------------------------------------- diff --git a/src/network/packet.cpp b/src/network/packet.cpp index f524c263f..066756233 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp @@ -83,7 +83,6 @@ void xRecordPacket(const CClient* client, Packet* packet, lpctstr heading) #endif //defined(_PACKETDUMP) || defined(_DUMPSUPPORT) - Packet::Packet(uint size) : m_buffer(nullptr) { m_expectedLength = size; diff --git a/src/network/packet.h b/src/network/packet.h index 6ba324726..c669098f9 100644 --- a/src/network/packet.h +++ b/src/network/packet.h @@ -25,7 +25,6 @@ class Packet; #endif - class CNetState; class SimplePacketTransaction; class AbstractString; @@ -53,8 +52,7 @@ class Packet Packet(const byte* data, uint size); virtual ~Packet(void); -private: - Packet& operator=(const Packet& other); + Packet& operator=(const Packet& other) = delete; public: bool isValid(void) const; @@ -160,7 +158,7 @@ class PacketSend : public Packet public: explicit PacketSend(byte id, uint len = 0, Priority priority = PRI_NORMAL); PacketSend(const PacketSend* other); - virtual ~PacketSend(); + virtual ~PacketSend() override; private: PacketSend& operator=(const PacketSend& other); @@ -282,7 +280,6 @@ class ExtendedPacketTransaction : public PacketTransaction }; - /*************************************************************************** * * diff --git a/src/network/receive.cpp b/src/network/receive.cpp index 3aa57263a..1106e32a0 100644 --- a/src/network/receive.cpp +++ b/src/network/receive.cpp @@ -1,6 +1,7 @@ #include "../common/resource/sections/CDialogDef.h" -#include "../common/CExpression.h" +//#include "../common/CExpression.h" // included in the precompiled header +//#include "../common/CScriptParserBufs.h" // included in the precompiled header via CExpression.h #include "../common/CLog.h" #include "../common/CUOClientVersion.h" #include "../game/uo_files/uofiles_enums_creid.h" @@ -200,22 +201,25 @@ bool PacketCreate::doCreate(CNetState* net, lpctstr charname, bool fFemale, RACE ASSERT(pChar != nullptr); TRIGRET_TYPE tr = TRIGRET_RET_DEFAULT; - CScriptTriggerArgs createArgs; + + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); // RW - createArgs.m_iN1 = uiFlags; - createArgs.m_iN2 = prProf; - createArgs.m_iN3 = rtRace; + pScriptArgs->m_iN1 = uiFlags; + pScriptArgs->m_iN2 = prProf; + pScriptArgs->m_iN3 = rtRace; // R - createArgs.m_s1 = account->GetName(); - createArgs.m_pO1 = client; + pScriptArgs->m_s1 = account->GetName(); + pScriptArgs->m_pO1 = client; - client->r_Call("f_onchar_create_init", &g_Serv, &createArgs, nullptr, &tr); + client->r_Call("f_onchar_create_init", pScriptArgs, &g_Serv, nullptr, &tr); if (tr == TRIGRET_RET_TRUE) goto block_creation; - //uiFlags = (uint)createArgs.m_iN1; // unused at this point - prProf = (PROFESSION_TYPE)createArgs.m_iN2; - rtRace = (RACE_TYPE)createArgs.m_iN3; + //uiFlags = (uint)pScriptArgs->.m_iN1; // unused at this point + prProf = (PROFESSION_TYPE)pScriptArgs->m_iN2; + rtRace = (RACE_TYPE)pScriptArgs->m_iN3; // Creating the pChar pChar->InitPlayer(client, charname, fFemale, rtRace, wStr, wDex, wInt, @@ -223,9 +227,9 @@ bool PacketCreate::doCreate(CNetState* net, lpctstr charname, bool fFemale, RACE wSkinHue, idHair, wHairHue, idBeard, wBeardHue, wShirtHue, wPantsHue, idFace, iStartLoc); // Calling the function after the char creation, it can't be done before or the function won't have SRC. - // The createArgs are Read-Only for this function. + // The pScriptArgs-> are Read-Only for this function. tr = TRIGRET_RET_DEFAULT; - client->r_Call("f_onchar_create", pChar, &createArgs, nullptr, &tr); + client->r_Call("f_onchar_create", pScriptArgs, pChar, nullptr, &tr); if ( tr == TRIGRET_RET_TRUE ) { @@ -446,7 +450,7 @@ bool PacketItemDropReq::onReceive(CNetState* net) } CUID container(readInt32()); - CPointMap pt(x, y, z, character->GetTopMap()); + CPointMap pt((int16_t)x, (int16_t)y, (int8_t)z, character->GetTopMap()); client->Event_Item_Drop(serial, pt, container, grid); return true; @@ -546,6 +550,17 @@ bool PacketItemEquipReq::onReceive(CNetState* net) CChar* target = targetSerial.CharFind(); bool fSuccess = false; + + // Check if player is sending wrong / forged layer. + const LAYER_TYPE itemRealLayer = target->CanEquipLayer(item, LAYER_QTY, target, true); + if (itemLayer != itemRealLayer) + { +#ifdef _DEBUG + g_Log.EventDebug("Player trying to equip item to invalid layer (sent: %d, should be %d)\n", itemLayer, itemRealLayer); +#endif + itemLayer = itemRealLayer; + } + if (target && (itemLayer < LAYER_HORSE) && source->CanDress(target) && source->CanTouch(item)) { //if (target->CanCarry(item)) //Since Weight behavior rework, we want avoid don't be able to equip an item if overweight @@ -937,8 +952,12 @@ bool PacketCharPlay::onReceive(CNetState* net) ADDTOCALLSTACK("PacketCharPlay::onReceive"); skip(4); // 0xedededed - skip(MAX_NAME_SIZE); // char name - skip(MAX_NAME_SIZE); // char pass + skip(MAX_NAME_SIZE); // Character name. + skip(2); // ? + skip(4); // Client flags (0x3f - Orion, Classic, ClassicUO, 0xff - Enhanced client). + skip(4); // ? (0 - Orion, Classic, ClassicUO, 0xff000000 - Enhanced client). + skip(4); // Login count. + skip(16); // ? uint slot = readInt32(); skip(4); // ip @@ -1035,7 +1054,7 @@ bool PacketBookPageEdit::onReceive(CNetState* net) ASSERT(len > 0); content[--len] = '\0'; - if (Str_Check(content)) + if (Str_Untrusted_InvalidTermination(content, len)) break; // set page content @@ -1222,7 +1241,7 @@ bool PacketBulletinBoardReq::onReceive(CNetState* net) uint lenstr = readByte(); tchar* str = Str_GetTemp(); readStringASCII(str, lenstr, false); - if (Str_Check(str)) + if (Str_Untrusted_InvalidTermination(str, lenstr)) return true; // if @@ -1247,7 +1266,7 @@ bool PacketBulletinBoardReq::onReceive(CNetState* net) { lenstr = readByte(); readStringASCII(str, lenstr, false); - if (Str_Check(str) == false) + if (Str_Untrusted_InvalidTermination(str, lenstr) == false) newMessage->AddPageText(str); } @@ -1440,7 +1459,7 @@ bool PacketServersReq::onReceive(CNetState* net) readStringASCII(acctname, ARRAY_COUNT(acctname)); tchar acctpass[MAX_NAME_SIZE]; readStringASCII(acctpass, ARRAY_COUNT(acctpass)); - skip(1); + skip(1); // "NextLoginKey" value from uo.cfg on client machine. CClient* client = net->getClient(); ASSERT(client); @@ -1670,7 +1689,7 @@ bool PacketCharListReq::onReceive(CNetState* net) { ADDTOCALLSTACK("PacketCharListReq::onReceive"); - skip(4); + skip(4); // Session key sent to the client in packet 0x8c (customerId). tchar acctname[MAX_ACCOUNT_NAME_SIZE]; readStringASCII(acctname, ARRAY_COUNT(acctname)); tchar acctpass[MAX_NAME_SIZE]; @@ -2229,12 +2248,13 @@ bool PacketGumpDialogRet::onReceive(CNetState* net) client->m_mapOpenedGumps.erase(itGumpFound); // package up the gump response info. - CDialogResponseArgs resp; + // TODO: just split CDialogResponseArgs into two objects (CScriptTriggerArgs and a struct for other data?) + // Or just make CScriptTriggerArgs a member of CDialogResponseArgs... Favor composition over inheritance! + auto resp = std::make_shared(); // store the returned checked boxes' ids for possible later use for (uint i = 0; i < checkCount; ++i) - resp.m_CheckArray.push_back(readInt32()); - + resp->m_CheckArray.push_back(readInt32()); dword textCount = readInt32(); textCount = minimum(textCount, THREAD_STRING_LENGTH); @@ -2252,7 +2272,7 @@ bool PacketGumpDialogRet::onReceive(CNetState* net) if ((fix = strchr(text, '\t')) != nullptr) *fix = ' '; - resp.AddText(id, text); + resp->AddText(id, text); } if (net->isClientKR()) @@ -2276,7 +2296,7 @@ bool PacketGumpDialogRet::onReceive(CNetState* net) // // Call the scripted response. Lose all the checks and text. // - client->Dialog_OnButton( ridContext, button, object, &resp ); + client->Dialog_OnButton( ridContext, button, object, resp ); return true; } @@ -2473,7 +2493,7 @@ bool PacketClientVersion::onReceive(CNetState* net) tchar* versionStr = Str_GetTemp(); readStringASCII(versionStr, length, false); - if (Str_Check(versionStr)) + if (Str_Untrusted_InvalidTermination(versionStr, length)) return true; if (strstr(versionStr, "UO:3D") != nullptr) @@ -2635,6 +2655,7 @@ bool PacketPartyMessage::onReceive(CNetState* net) client->addTarget( CLIMODE_TARG_PARTY_ADD, g_Cfg.GetDefaultMsg(DEFMSG_PARTY_TARG_WHO), false, false); break; + // This doesn't happen, since disbanding the party is handled by client sending a packet to remove the party master. case PARTYMSG_Disband: if (character->m_pParty == nullptr) return false; @@ -2728,18 +2749,21 @@ bool PacketArrowClick::onReceive(CNetState* net) if (character == nullptr) return false; - bool rightClick = readBool(); + const bool fRightClick = readBool(); if ( IsTrigUsed(TRIGGER_USERQUESTARROWCLICK) ) { - CScriptTriggerArgs Args; - Args.m_iN1 = (rightClick == true? 1 : 0); + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); + pScriptArgs->m_iN1 = (fRightClick == true? 1 : 0); #ifdef _ALPHASPHERE - Args.m_iN2 = character->GetKeyNum("ARROWQUEST_X", true); - Args.m_iN3 = character->GetKeyNum("ARROWQUEST_Y", true); + pScriptArgs->m_iN2 = character->GetKeyNum("ARROWQUEST_X", true); + pScriptArgs->m_iN3 = character->GetKeyNum("ARROWQUEST_Y", true); #endif - if (character->OnTrigger(CTRIG_UserQuestArrowClick, character, &Args) == TRIGRET_RET_TRUE) + if (character->OnTrigger(CTRIG_UserQuestArrowClick, pScriptArgs, character) == TRIGRET_RET_TRUE) return true; } @@ -2762,7 +2786,10 @@ bool PacketWrestleDisarm::onReceive(CNetState* net) { ADDTOCALLSTACK("PacketWrestleDisarm::onReceive"); - net->getClient()->SysMessageDefault(DEFMSG_MSG_WRESTLING_NOGO); + CClient* client = net->getClient(); + ASSERT(client); + + client->Event_CombatAbilitySelect(0x5); // Disarm return true; } @@ -2782,7 +2809,11 @@ bool PacketWrestleStun::onReceive(CNetState* net) { ADDTOCALLSTACK("PacketWrestleStun::onReceive"); - net->getClient()->SysMessageDefault(DEFMSG_MSG_WRESTLING_NOGO); + CClient* client = net->getClient(); + ASSERT(client); + + client->Event_CombatAbilitySelect(0xB); // Paralyzing Blow + return true; } @@ -3217,8 +3248,13 @@ bool PacketBandageMacro::onReceive(CNetState* net) //Should we simulate the dclick? client->m_Targ_UID = bandage->GetUID(); - CScriptTriggerArgs extArgs(1); // Signal we're from the macro - if (bandage->OnTrigger( ITRIG_DCLICK, character, &extArgs ) == TRIGRET_RET_TRUE) + + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); + pScriptArgs->m_iN1 = 1; // Signal we're from the macro + if (bandage->OnTrigger( ITRIG_DCLICK, pScriptArgs, character) == TRIGRET_RET_TRUE) { return true; } @@ -3309,7 +3345,11 @@ bool PacketGargoyleFly::onReceive(CNetState* net) if ( IsTrigUsed(TRIGGER_TOGGLEFLYING) ) { - if ( character->OnTrigger(CTRIG_ToggleFlying, character, nullptr) == TRIGRET_RET_TRUE ) + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); + if ( character->OnTrigger(CTRIG_ToggleFlying, pScriptArgs, character) == TRIGRET_RET_TRUE ) return false; } @@ -4030,19 +4070,11 @@ bool PacketSpecialMove::onReceive(CNetState* net) CClient* client = net->getClient(); ASSERT(client); - CChar* character = client->GetChar(); - if (character == nullptr) - return false; skip(1); dword ability = readInt32(); - if ( IsTrigUsed(TRIGGER_USERSPECIALMOVE) ) - { - CScriptTriggerArgs args; - args.m_iN1 = ability; - character->OnTrigger(CTRIG_UserSpecialMove, character, &args); - } + client->Event_CombatAbilitySelect(ability); return true; } @@ -4137,7 +4169,13 @@ bool PacketGuildButton::onReceive(CNetState* net) return false; if ( IsTrigUsed(TRIGGER_USERGUILDBUTTON) ) - character->OnTrigger(CTRIG_UserGuildButton, character, nullptr); + { + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); + character->OnTrigger(CTRIG_UserGuildButton, pScriptArgs, character); + } return true; } @@ -4164,8 +4202,14 @@ bool PacketQuestButton::onReceive(CNetState* net) return false; if ( IsTrigUsed(TRIGGER_USERQUESTBUTTON) ) - character->OnTrigger(CTRIG_UserQuestButton, character, nullptr); - return true; + { + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); + character->OnTrigger(CTRIG_UserQuestButton, pScriptArgs, character); + } + return true; } @@ -4727,12 +4771,17 @@ bool PacketUltimaStoreButton::onReceive(CNetState *net) return false; if (IsTrigUsed(TRIGGER_USERULTIMASTOREBUTTON)) - character->OnTrigger(CTRIG_UserUltimaStoreButton, character, nullptr); + { + // Networking might be done in a different thread. Do not use the standard object pool, since it's thread unsafe. + //CScriptTriggerArgsPtr pScriptArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + + CScriptTriggerArgsPtr pScriptArgs = std::make_shared(); + character->OnTrigger(CTRIG_UserUltimaStoreButton, pScriptArgs, character); + } return true; } - /*************************************************************************** * * diff --git a/src/network/receive.h b/src/network/receive.h index f464c8a6e..92b5e1fc4 100644 --- a/src/network/receive.h +++ b/src/network/receive.h @@ -25,7 +25,7 @@ class PacketUnknown : public Packet { public: PacketUnknown(uint size = 0); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -39,9 +39,10 @@ class PacketCreate : public Packet { public: PacketCreate(uint size = 104); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; protected: + // TODO: pack this abomination of arguments list in a struct bool doCreate(CNetState* net, lpctstr charname, bool bFemale, RACE_TYPE rtRace, ushort wStr, ushort wDex, ushort wInt, PROFESSION_TYPE prProf, SKILL_TYPE skSkill1, ushort uiSkillVal1, SKILL_TYPE skSkill2, ushort uiSkillVal2, SKILL_TYPE skSkill3, ushort uiSkillVal3, SKILL_TYPE skSkill4, ushort uiSkillVal4, HUE_TYPE wSkinHue, ITEMID_TYPE idHair, HUE_TYPE wHairHue, ITEMID_TYPE idBeard, HUE_TYPE wBeardHue, HUE_TYPE wShirtHue, HUE_TYPE wPantsHue, ITEMID_TYPE idFace, @@ -59,7 +60,7 @@ class PacketMovementReq : public Packet { public: PacketMovementReq(uint size = 7); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -73,7 +74,7 @@ class PacketSpeakReq : public Packet { public: PacketSpeakReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -87,7 +88,7 @@ class PacketAttackReq : public Packet { public: PacketAttackReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -101,7 +102,7 @@ class PacketDoubleClick : public Packet { public: PacketDoubleClick(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -115,7 +116,7 @@ class PacketItemPickupReq : public Packet { public: PacketItemPickupReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -129,8 +130,8 @@ class PacketItemDropReq : public Packet { public: PacketItemDropReq(); - virtual uint getExpectedLength(CNetState* client, Packet* packet); - virtual bool onReceive(CNetState* net); + virtual uint getExpectedLength(CNetState* client, Packet* packet) override; + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -144,7 +145,7 @@ class PacketSingleClick : public Packet { public: PacketSingleClick(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -158,7 +159,7 @@ class PacketTextCommand : public Packet { public: PacketTextCommand(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -172,7 +173,7 @@ class PacketItemEquipReq : public Packet { public: PacketItemEquipReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -186,7 +187,7 @@ class PacketResynchronize : public Packet { public: PacketResynchronize(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -207,7 +208,7 @@ class PacketDeathStatus : public Packet }; PacketDeathStatus(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -221,7 +222,7 @@ class PacketObjStatusReq : public Packet { public: PacketObjStatusReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -235,7 +236,7 @@ class PacketSkillLockChange : public Packet { public: PacketSkillLockChange(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -256,7 +257,7 @@ class PacketVendorBuyReq : public Packet { public: PacketVendorBuyReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -271,7 +272,7 @@ class PacketStaticUpdate : public Packet { public: PacketStaticUpdate(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -285,7 +286,7 @@ class PacketMapEdit : public Packet { public: PacketMapEdit(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -299,7 +300,7 @@ class PacketCharPlay : public Packet { public: PacketCharPlay(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -313,7 +314,7 @@ class PacketBookPageEdit : public Packet { public: PacketBookPageEdit(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -327,7 +328,7 @@ class PacketTarget : public Packet { public: PacketTarget(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -341,7 +342,7 @@ class PacketSecureTradeReq : public Packet { public: PacketSecureTradeReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -355,7 +356,7 @@ class PacketBulletinBoardReq : public Packet { public: PacketBulletinBoardReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -369,7 +370,7 @@ class PacketWarModeReq : public Packet { public: PacketWarModeReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -383,7 +384,7 @@ class PacketPingReq : public Packet { public: PacketPingReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -397,7 +398,7 @@ class PacketCharRename : public Packet { public: PacketCharRename(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -411,7 +412,7 @@ class PacketMenuChoice : public Packet { public: PacketMenuChoice(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -425,7 +426,7 @@ class PacketServersReq : public Packet { public: PacketServersReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -439,7 +440,7 @@ class PacketCharDelete : public Packet { public: PacketCharDelete(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -453,7 +454,7 @@ class PacketCreateNew : public PacketCreate { public: PacketCreateNew(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -467,7 +468,7 @@ class PacketCharListReq : public Packet { public: PacketCharListReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -481,7 +482,7 @@ class PacketBookHeaderEdit : public Packet { public: PacketBookHeaderEdit(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -495,7 +496,7 @@ class PacketDyeObject : public Packet { public: PacketDyeObject(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -509,7 +510,7 @@ class PacketAllNamesReq : public Packet { public: PacketAllNamesReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -523,7 +524,7 @@ class PacketPromptResponse : public Packet { public: PacketPromptResponse(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -537,7 +538,7 @@ class PacketHelpPageReq : public Packet { public: PacketHelpPageReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -551,7 +552,7 @@ class PacketVendorSellReq : public Packet { public: PacketVendorSellReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -565,7 +566,7 @@ class PacketServerSelect : public Packet { public: PacketServerSelect(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -579,7 +580,7 @@ class PacketSystemInfo : public Packet { public: PacketSystemInfo(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -593,7 +594,7 @@ class PacketTipReq : public Packet { public: PacketTipReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -607,7 +608,7 @@ class PacketGumpValueInputResponse : public Packet { public: PacketGumpValueInputResponse(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -621,7 +622,7 @@ class PacketSpeakReqUNICODE : public Packet { public: PacketSpeakReqUNICODE(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -635,7 +636,7 @@ class PacketGumpDialogRet : public Packet { public: PacketGumpDialogRet(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -649,7 +650,7 @@ class PacketChatCommand : public Packet { public: PacketChatCommand(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -663,7 +664,7 @@ class PacketChatButton : public Packet { public: PacketChatButton(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -677,7 +678,7 @@ class PacketToolTipReq : public Packet { public: PacketToolTipReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -691,7 +692,7 @@ class PacketProfileReq : public Packet { public: PacketProfileReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -705,7 +706,7 @@ class PacketMailMessage : public Packet { public: PacketMailMessage(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -719,7 +720,7 @@ class PacketClientVersion : public Packet { public: PacketClientVersion(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -747,7 +748,7 @@ class PacketExtendedCommand : public Packet { public: PacketExtendedCommand(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -761,7 +762,7 @@ class PacketScreenSize : public Packet { public: PacketScreenSize(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -775,7 +776,7 @@ class PacketPartyMessage : public Packet { public: PacketPartyMessage(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -789,7 +790,7 @@ class PacketArrowClick : public Packet { public: PacketArrowClick(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -803,7 +804,7 @@ class PacketWrestleDisarm : public Packet { public: PacketWrestleDisarm(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -817,7 +818,7 @@ class PacketWrestleStun : public Packet { public: PacketWrestleStun(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -831,7 +832,7 @@ class PacketLanguage : public Packet { public: PacketLanguage(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -845,7 +846,7 @@ class PacketStatusClose : public Packet { public: PacketStatusClose(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -859,7 +860,7 @@ class PacketAnimationReq : public Packet { public: PacketAnimationReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -873,7 +874,7 @@ class PacketClientInfo : public Packet { public: PacketClientInfo(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -887,7 +888,7 @@ class PacketAosTooltipInfo : public Packet { public: PacketAosTooltipInfo(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -901,7 +902,7 @@ class PacketPopupReq : public Packet { public: PacketPopupReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -915,7 +916,7 @@ class PacketPopupSelect : public Packet { public: PacketPopupSelect(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -929,7 +930,7 @@ class PacketChangeStatLock : public Packet { public: PacketChangeStatLock(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -943,7 +944,7 @@ class PacketSpellSelect : public Packet { public: PacketSpellSelect(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -957,7 +958,7 @@ class PacketHouseDesignReq : public Packet { public: PacketHouseDesignReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -971,7 +972,7 @@ class PacketAntiCheat : public Packet { public: PacketAntiCheat(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -985,7 +986,7 @@ class PacketBandageMacro : public Packet { public: PacketBandageMacro(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -999,7 +1000,7 @@ class PacketTargetedSkill : public Packet { public: PacketTargetedSkill(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1013,7 +1014,7 @@ class PacketGargoyleFly : public Packet { public: PacketGargoyleFly(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1027,7 +1028,7 @@ class PacketWheelBoatMove : public Packet { public: PacketWheelBoatMove(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1041,7 +1042,7 @@ class PacketPromptResponseUnicode : public Packet { public: PacketPromptResponseUnicode(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1055,7 +1056,7 @@ class PacketViewRange : public Packet { public: PacketViewRange(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1069,7 +1070,7 @@ class PacketLogout : public Packet { public: PacketLogout(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1083,7 +1084,7 @@ class PacketBookHeaderEditNew : public Packet { public: PacketBookHeaderEditNew(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1097,7 +1098,7 @@ class PacketAOSTooltipReq : public Packet { public: PacketAOSTooltipReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1111,7 +1112,7 @@ class PacketEncodedCommand : public Packet { public: PacketEncodedCommand(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1125,7 +1126,7 @@ class PacketHouseDesignBackup : public Packet { public: PacketHouseDesignBackup(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1139,7 +1140,7 @@ class PacketHouseDesignRestore : public Packet { public: PacketHouseDesignRestore(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1153,7 +1154,7 @@ class PacketHouseDesignCommit : public Packet { public: PacketHouseDesignCommit(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1167,7 +1168,7 @@ class PacketHouseDesignDestroyItem : public Packet { public: PacketHouseDesignDestroyItem(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1181,7 +1182,7 @@ class PacketHouseDesignPlaceItem : public Packet { public: PacketHouseDesignPlaceItem(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1195,7 +1196,7 @@ class PacketHouseDesignExit : public Packet { public: PacketHouseDesignExit(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1209,7 +1210,7 @@ class PacketHouseDesignPlaceStair : public Packet { public: PacketHouseDesignPlaceStair(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1223,7 +1224,7 @@ class PacketHouseDesignSync : public Packet { public: PacketHouseDesignSync(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1237,7 +1238,7 @@ class PacketHouseDesignClear : public Packet { public: PacketHouseDesignClear(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1251,7 +1252,7 @@ class PacketHouseDesignSwitch : public Packet { public: PacketHouseDesignSwitch(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1265,7 +1266,7 @@ class PacketHouseDesignPlaceRoof : public Packet { public: PacketHouseDesignPlaceRoof(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1279,7 +1280,7 @@ class PacketHouseDesignDestroyRoof : public Packet { public: PacketHouseDesignDestroyRoof(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1293,7 +1294,7 @@ class PacketSpecialMove : public Packet { public: PacketSpecialMove(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1307,7 +1308,7 @@ class PacketHouseDesignRevert : public Packet { public: PacketHouseDesignRevert(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1321,7 +1322,7 @@ class PacketEquipLastWeapon : public Packet { public: PacketEquipLastWeapon(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1335,7 +1336,7 @@ class PacketGuildButton : public Packet { public: PacketGuildButton(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1349,7 +1350,7 @@ class PacketQuestButton : public Packet { public: PacketQuestButton(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1363,7 +1364,7 @@ class PacketHardwareInfo : public Packet { public: PacketHardwareInfo(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1377,7 +1378,7 @@ class PacketBugReport : public Packet { public: PacketBugReport(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1391,7 +1392,7 @@ class PacketClientType : public Packet { public: PacketClientType(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1405,7 +1406,7 @@ class PacketRemoveUIHighlight : public Packet { public: PacketRemoveUIHighlight(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1419,7 +1420,7 @@ class PacketUseHotbar : public Packet { public: PacketUseHotbar(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1433,7 +1434,7 @@ class PacketEquipItemMacro : public Packet { public: PacketEquipItemMacro(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1447,7 +1448,7 @@ class PacketUnEquipItemMacro : public Packet { public: PacketUnEquipItemMacro(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1461,7 +1462,7 @@ class PacketMovementReqNew : public Packet { public: PacketMovementReqNew(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1475,7 +1476,7 @@ class PacketTimeSyncRequest : public Packet { public: PacketTimeSyncRequest(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1489,7 +1490,7 @@ class PacketCrashReport : public Packet { public: PacketCrashReport(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1503,7 +1504,7 @@ class PacketCreateHS : public PacketCreate { public: PacketCreateHS(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1517,7 +1518,7 @@ class PacketGlobalChatReq : public Packet { public: PacketGlobalChatReq(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; /*************************************************************************** @@ -1531,7 +1532,7 @@ class PacketUltimaStoreButton : public Packet { public: PacketUltimaStoreButton(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; @@ -1546,7 +1547,7 @@ class PacketPublicHouseContent : public Packet { public: PacketPublicHouseContent(); - virtual bool onReceive(CNetState* net); + virtual bool onReceive(CNetState* net) override; }; #endif // _INC_RECEIVE_H diff --git a/src/network/send.cpp b/src/network/send.cpp index 50856ed42..7086daaae 100644 --- a/src/network/send.cpp +++ b/src/network/send.cpp @@ -1,6 +1,6 @@ #include "../common/resource/CResourceLock.h" -#include "../common/CExpression.h" +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CLog.h" #include "../common/CUOInstall.h" #include "../game/chars/CChar.h" @@ -17,7 +17,9 @@ #include "../game/items/CItemVendable.h" #include "../game/uo_files/uofiles_enums_creid.h" #include "../game/CServer.h" +#include "../game/CServerConfig.h" #include "../game/CWorldGameTime.h" +#include "CNetState.h" #include "CNetworkManager.h" #include "send.h" @@ -119,6 +121,11 @@ PacketCombatDamage::PacketCombatDamage(const CClient* target, word damage, CUID push(target); } +bool PacketCombatDamage::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_NEWDAMAGE); +} + /*************************************************************************** * @@ -406,6 +413,11 @@ bool PacketHealthBarUpdateNew::onSend(const CClient* client) return client->CanSee(m_character.CharFind()); } +bool PacketHealthBarUpdateNew::CanSendTo(const CNetState* state) // static +{ + return state->isClientEnhanced(); +} + /*************************************************************************** * * @@ -439,6 +451,11 @@ bool PacketHealthBarUpdate::onSend(const CClient* client) return client->CanSee(m_character.CharFind()); } +bool PacketHealthBarUpdate::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_SA) || state->isClientKR(); +} + /*************************************************************************** * @@ -616,7 +633,8 @@ PacketPlayerStart::PacketPlayerStart(const CClient* target) : PacketSend(XCMD_St writeInt16((word)(character->GetDispID())); writeInt16(pt.m_x); writeInt16(pt.m_y); - writeInt16(pt.m_z); + writeByte(0); + writeByte(pt.m_z); writeByte(character->GetDirFlag()); writeByte(0); writeInt32(0xffffffff); @@ -1014,6 +1032,11 @@ PacketDropAccepted::PacketDropAccepted(const CClient* target) : PacketSend(XCMD_ push(target); } +bool PacketDropAccepted::CanSendTo(const CNetState* state) // static +{ + return state->isClientKR(); +} + /*************************************************************************** * @@ -2538,6 +2561,11 @@ PacketChangeCharacter::PacketChangeCharacter(CClient* target) : PacketSend(XCMD_ push(target); } +bool PacketChangeCharacter::CanSendTo(const CNetState* state) // static +{ + return !(state->isClientKR() || state->isClientEnhanced()); +} + /*************************************************************************** * @@ -4083,6 +4111,11 @@ bool PacketPropertyListVersionOld::onSend(const CClient* client) return true; } +bool PacketPropertyListVersionOld::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_TOOLTIP); +} + /*************************************************************************** * @@ -4161,7 +4194,7 @@ void PacketDisplayPopup::finalise(void) * * ***************************************************************************/ -PacketCloseUIWindow::PacketCloseUIWindow(const CClient* target, const CObjBase* obj, UIWindow command) : PacketExtended(EXTDATA_CloseUI_Window, 13, PRI_NORMAL) +PacketCloseUIWindow::PacketCloseUIWindow(const CClient* target, const CObjBase* obj, PacketCloseUIWindowType command) : PacketExtended(EXTDATA_CloseUI_Window, 13, PRI_NORMAL) { ADDTOCALLSTACK("PacketCloseUIWindow::PacketCloseUIWindow"); @@ -4313,6 +4346,10 @@ PacketStatLocks::PacketStatLocks(const CClient* target, const CChar* character) push(target); } +bool PacketStatLocks::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_STATLOCKS); +} /*************************************************************************** * @@ -4356,6 +4393,11 @@ PacketSpellbookContent::PacketSpellbookContent(const CClient* target, const CIte push(target); } +bool PacketSpellbookContent::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_SPELLBOOK); +} + /*************************************************************************** * @@ -4396,6 +4438,11 @@ PacketHouseBeginCustomise::PacketHouseBeginCustomise(const CClient* target, cons push(target); } +bool PacketHouseBeginCustomise::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_CUSTOMMULTI) || state->isClientKR() || state->isClientEnhanced(); +} + /*************************************************************************** * @@ -4440,6 +4487,10 @@ PacketCombatDamageOld::PacketCombatDamageOld(const CClient* target, byte damage, push(target); } +bool PacketCombatDamageOld::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_DAMAGE); +} /*************************************************************************** * @@ -4656,6 +4707,10 @@ PacketDisplayBookNew::PacketDisplayBookNew(const CClient* target, CItem* book) : push(target); } +bool PacketDisplayBookNew::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_NEWBOOK) || state->isClientKR() || state->isClientEnhanced(); +} /*************************************************************************** * @@ -4731,6 +4786,11 @@ bool PacketPropertyList::hasExpired(int64 iTimeout) const return (m_time + iTimeout) < CWorldGameTime::GetCurrentTime().GetTimeRaw(); } +bool PacketPropertyList::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_TOOLTIP); +} + /*************************************************************************** * @@ -4902,6 +4962,11 @@ void PacketHouseDesign::finalise(void) seek(endPosition); } +bool PacketHouseDesign::CanSendToClient(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_CUSTOMMULTI) || state->isClientKR() || state->isClientEnhanced(); +} + /*************************************************************************** * @@ -4941,6 +5006,11 @@ bool PacketPropertyListVersion::onSend(const CClient* client) return true; } +bool PacketPropertyListVersion::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_TOOLTIPHASH); +} + /*************************************************************************** * @@ -5026,6 +5096,11 @@ PacketBuff::PacketBuff(const CClient* target, const BUFF_ICONS iconId) : PacketS push(target); } +bool PacketBuff::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_BUFFS); +} + /*************************************************************************** * * @@ -5083,6 +5158,11 @@ PacketWaypointAdd::PacketWaypointAdd(const CClient *target, CObjBase *object, MA push(target); } +bool PacketWaypointAdd::CanSendTo(const CNetState *state) // static +{ + return state->isClientVersionNumber(MINCLIVER_MAPWAYPOINT) || state->isClientKR() || state->isClientEnhanced(); +} + /*************************************************************************** * * @@ -5102,6 +5182,11 @@ PacketWaypointRemove::PacketWaypointRemove(const CClient *target, CObjBase *obje push(target); } +bool PacketWaypointRemove::CanSendTo(const CNetState *state) // static +{ + return state->isClientVersionNumber(MINCLIVER_MAPWAYPOINT) || state->isClientKR() || state->isClientEnhanced(); +} + /*************************************************************************** * * @@ -5118,6 +5203,12 @@ PacketToggleHotbar::PacketToggleHotbar(const CClient* target, bool enable) : Pac push(target); } +bool PacketToggleHotbar::CanSendTo(const CNetState* state) // static +{ + return state->isClientKR(); +} + + /*************************************************************************** * * @@ -5142,6 +5233,11 @@ PacketTimeSyncResponse::PacketTimeSyncResponse(const CClient* target) : PacketSe push(target); } +bool PacketTimeSyncResponse::CanSendTo(const CNetState* state) //static +{ + return state->isClientVersionNumber(MINCLIVER_SA) || state->isClientEnhanced() || state->isClientKR(); +} + /*************************************************************************** * @@ -5243,6 +5339,11 @@ PacketItemWorldNew::PacketItemWorldNew(const CClient* target, const CChar* mobil push(target); } +bool PacketItemWorldNew::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_SA) || state->isClientEnhanced(); +} + /*************************************************************************** * * @@ -5281,6 +5382,11 @@ PacketDisplayMapNew::PacketDisplayMapNew(const CClient* target, const CItemMap* push(target); } +bool PacketDisplayMapNew::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_NEWMAPDISPLAY) || state->isClientEnhanced(); +} + /*************************************************************************** * @@ -5410,6 +5516,12 @@ PacketContainer::PacketContainer(const CClient* target, CObjBase** objects, uint push(target); } +bool PacketContainer::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_HS); +} + + /*************************************************************************** * * @@ -5433,3 +5545,8 @@ PacketGlobalChat::PacketGlobalChat(const CClient* target, byte unknown, byte act trim(); push(target); } + +bool PacketGlobalChat::CanSendTo(const CNetState* state) // static +{ + return state->isClientVersionNumber(MINCLIVER_GLOBALCHAT); +} diff --git a/src/network/send.h b/src/network/send.h index 52f5d41a2..ccab69ba7 100644 --- a/src/network/send.h +++ b/src/network/send.h @@ -10,15 +10,13 @@ #include "../common/CUID.h" #include "../common/CLanguageID.h" #include "../common/sphereproto.h" -#include "../common/CRect.h" #include "../game/game_enums.h" -#include "../game/CServerConfig.h" -#include "CNetState.h" #include "packet.h" -#include struct CMenuItem; +struct CRectMap; +class CNetState; class CItemMap; class CObjBaseTemplate; class CSpellDef; @@ -64,7 +62,6 @@ class PacketGeneric : public PacketSend class PacketTelnet : public PacketSend { public: - PacketTelnet(const CClient* target, lpctstr message, bool fNullTerminated = false); }; @@ -94,11 +91,8 @@ class PacketCombatDamage : public PacketSend public: PacketCombatDamage(const CClient* target, word damage, CUID defender); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_NEWDAMAGE); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -138,13 +132,10 @@ class PacketHealthBarUpdateNew : public PacketSend PacketHealthBarUpdateNew(const CClient* target, const CChar* character); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientEnhanced(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -168,13 +159,10 @@ class PacketHealthBarUpdate : public PacketSend PacketHealthBarUpdate(const CClient* target, const CChar* character); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_SA) || state->isClientKR(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -197,7 +185,7 @@ class PacketItemWorld : public PacketSend static void adjustItemData(const CClient* target, const CItem* item, ITEMID_TYPE &id, HUE_TYPE &hue, word &amount, DIR_TYPE &dir, byte &flags, byte &light); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; }; /*************************************************************************** @@ -290,7 +278,7 @@ class PacketDragAnimation : public PacketSend public: PacketDragAnimation(const CChar* source, const CItem* item, const CObjBase* container, const CPointMap* pt); - virtual bool canSendTo(const CNetState* state) const; + virtual bool canSendTo(const CNetState* state) const override; }; /*************************************************************************** @@ -308,7 +296,7 @@ class PacketContainerOpen : public PacketSend public: PacketContainerOpen(const CClient* target, const CObjBase* container, GUMP_TYPE gump); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; }; /*************************************************************************** @@ -328,7 +316,7 @@ class PacketItemContainer : public PacketSend PacketItemContainer(const CItem* spellbook, const CSpellDef* spell); void completeForTarget(const CClient* target, const CItem* spellbook); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; }; /*************************************************************************** @@ -379,11 +367,8 @@ class PacketDropAccepted : public PacketSend public: PacketDropAccepted(const CClient* target); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientKR(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -475,7 +460,7 @@ class PacketItemContents : public PacketSend PacketItemContents(CClient* target, const CItemContainer* container, bool fIsShop, bool fFilterLayers); // standard content PacketItemContents(const CClient* target, const CItem* spellbook); // spellbook spells PacketItemContents(const CClient* target, const CItemContainer* spellbook); // custom spellbook spells - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; }; /*************************************************************************** @@ -821,7 +806,7 @@ class PacketCharacter : public PacketSend public: PacketCharacter(CClient* target, const CChar* character); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; }; /*************************************************************************** @@ -849,11 +834,8 @@ class PacketChangeCharacter : public PacketSend public: PacketChangeCharacter(CClient* target); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return !(state->isClientKR() || state->isClientEnhanced()); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -965,7 +947,7 @@ class PacketCorpseEquipment : public PacketSend public: PacketCorpseEquipment(CClient* target, const CItemContainer* corpse); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; }; /*************************************************************************** @@ -1450,13 +1432,10 @@ class PacketPropertyListVersionOld : public PacketExtended public: PacketPropertyListVersionOld(const CClient* target, const CObjBase* object, dword version); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_TOOLTIP); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1491,18 +1470,18 @@ class PacketDisplayPopup : public PacketExtended * * ***************************************************************************/ +enum PacketCloseUIWindowType : uint +{ + Paperdoll = 1, + Status = 2, + Profile = 8, + Container = 0xC +}; + class PacketCloseUIWindow : public PacketExtended { public: - enum UIWindow - { - Paperdoll = 1, - Status = 2, - Profile = 8, - Container = 0xC - }; - - PacketCloseUIWindow(const CClient* target, const CObjBase* obj, UIWindow command); + PacketCloseUIWindow(const CClient* target, const CObjBase* obj, PacketCloseUIWindowType command); }; /*************************************************************************** @@ -1569,11 +1548,8 @@ class PacketStatLocks : public PacketExtended public: PacketStatLocks(const CClient* target, const CChar* character); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_STATLOCKS); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1601,11 +1577,8 @@ class PacketSpellbookContent : public PacketExtended public: PacketSpellbookContent(const CClient* target, const CItem* spellbook, word offset); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_SPELLBOOK); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1633,11 +1606,8 @@ class PacketHouseBeginCustomise : public PacketExtended public: PacketHouseBeginCustomise(const CClient* target, const CItemMultiCustom* house); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_CUSTOMMULTI) || state->isClientKR() || state->isClientEnhanced(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1665,11 +1635,8 @@ class PacketCombatDamageOld : public PacketExtended public: PacketCombatDamageOld(const CClient* target, byte damage, CUID defender); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_DAMAGE); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1749,11 +1716,8 @@ class PacketDisplayBookNew : public PacketSend public: PacketDisplayBookNew(const CClient* target, CItem* book); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_NEWBOOK) || state->isClientKR() || state->isClientEnhanced(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1774,7 +1738,7 @@ class PacketPropertyList : public PacketSend public: PacketPropertyList(const CObjBase* object, dword version, const std::vector> &data); PacketPropertyList(const CClient* target, const PacketPropertyList* other); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; inline CUID getObject(void) const { return m_object; } inline dword getVersion(void) const { return m_version; } @@ -1783,8 +1747,8 @@ class PacketPropertyList : public PacketSend bool hasExpired(int64 iTimeout) const; - virtual inline bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static inline bool CanSendTo(const CNetState* state) { return state->isClientVersionNumber(MINCLIVER_TOOLTIP); } + virtual inline bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1829,11 +1793,8 @@ class PacketHouseDesign : public PacketSend void flushStairData(void); void finalise(void); - virtual bool canSendTo(const CNetState* state) const override { return CanSendToClient(state); } - static bool CanSendToClient(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_CUSTOMMULTI) || state->isClientKR() || state->isClientEnhanced(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendToClient(state); } + static bool CanSendToClient(const CNetState* state); }; /*************************************************************************** @@ -1850,13 +1811,10 @@ class PacketPropertyListVersion : public PacketSend public: PacketPropertyListVersion(const CClient* target, const CObjBase* object, dword version); - virtual bool onSend(const CClient* client); + virtual bool onSend(const CClient* client) override; - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_TOOLTIPHASH); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1872,11 +1830,8 @@ class PacketBuff : public PacketSend PacketBuff(const CClient* target, const BUFF_ICONS iconId, const dword clilocOne, const dword clilocTwo, const word durationSeconds, lpctstr* args, uint argCount); // add buff PacketBuff(const CClient* target, const BUFF_ICONS iconId); // remove buff - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_BUFFS); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1905,10 +1860,7 @@ class PacketWaypointAdd : public PacketSend PacketWaypointAdd(const CClient *target, CObjBase *object, MAPWAYPOINT_TYPE type); virtual bool canSendTo(const CNetState *state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState *state) - { - return state->isClientVersionNumber(MINCLIVER_MAPWAYPOINT) || state->isClientKR() || state->isClientEnhanced(); - } + static bool CanSendTo(const CNetState *state); }; /*************************************************************************** @@ -1924,10 +1876,7 @@ class PacketWaypointRemove : public PacketSend PacketWaypointRemove(const CClient *target, CObjBase *object); virtual bool canSendTo(const CNetState *state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState *state) - { - return state->isClientVersionNumber(MINCLIVER_MAPWAYPOINT) || state->isClientKR() || state->isClientEnhanced(); - } + static bool CanSendTo(const CNetState *state); }; /*************************************************************************** @@ -1942,11 +1891,8 @@ class PacketToggleHotbar : public PacketSend public: PacketToggleHotbar(const CClient* target, bool enable); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientKR(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1961,11 +1907,8 @@ class PacketTimeSyncResponse : public PacketSend public: PacketTimeSyncResponse(const CClient* target); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_SA) || state->isClientEnhanced() || state->isClientKR(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -1992,11 +1935,8 @@ class PacketItemWorldNew : public PacketItemWorld PacketItemWorldNew(const CClient* target, const CItem* item); PacketItemWorldNew(const CClient* target, const CChar* mobile); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_SA) || state->isClientEnhanced(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -2011,11 +1951,8 @@ class PacketDisplayMapNew : public PacketSend public: PacketDisplayMapNew(const CClient* target, const CItemMap* map, const CRectMap& rect); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_NEWMAPDISPLAY) || state->isClientEnhanced(); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -2043,11 +1980,8 @@ class PacketContainer : public PacketSend public: PacketContainer(const CClient* target, CObjBase** objects, uint objectCount); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_HS); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; /*************************************************************************** @@ -2078,11 +2012,8 @@ class PacketGlobalChat : public PacketSend PacketGlobalChat(const CClient* target, byte unknown, byte action, byte stanza, lpctstr xml); - virtual bool canSendTo(const CNetState* state) const { return CanSendTo(state); } - static bool CanSendTo(const CNetState* state) - { - return state->isClientVersionNumber(MINCLIVER_GLOBALCHAT); - } + virtual bool canSendTo(const CNetState* state) const override { return CanSendTo(state); } + static bool CanSendTo(const CNetState* state); }; #ifdef __clang__ diff --git a/src/sphere.ini b/src/sphere.ini index f2cf06b75..6efee0d72 100644 --- a/src/sphere.ini +++ b/src/sphere.ini @@ -87,8 +87,8 @@ Log=logs/ //MapX = Sets X to the given number, so .map X will send work over these settings //MaxX = Sets Maximum X value for this map's size. Same for MaxY. //SectorSize = Sets the size of sectors, in each server's tick all sectors for all maps are read in a loop, this should be taken in consideration for performance along with SectorSleep. Default: 64 (8 x 8 tiles). -//MapReadID = Sets the mapX file to read from muls folder (This means which file will server read for walkchecks). -//MapSendID = Sets the mapX file to read for the client (this can be different than MapReadID but, obviously, not recomended for normal usage). +//MapReadID = Sets the mapX file to read from muls folder (This means which file will server read for walk checks). +//MapSendID = Sets the mapX file to read for the client (this can be different than MapReadID but, obviously, not recommended for normal usage). // Map of Felucca //Map0=6144,4096,-1,0,0 //Old size @@ -155,8 +155,8 @@ SaveStepMaxComplexity=500 //Code for servers account application process // 0=Closed, // Closed. Not accepting more. // 2=Free, // Anyone can just log in and create a full account. -// 3=GuestAuto, // You get to be a guest and are automatically sent email with u're new password. -// 4=GuestTrial, // You get to be a guest til u're accepted for full by an Admin. +// 3=GuestAuto, // You get to be a guest and are automatically sent email with your new password. +// 4=GuestTrial, // You get to be a guest until you are accepted for full by an Admin. // 6=Unspecified, // Not specified. // To enable auto account you must set this to 2 AccApp=0 @@ -380,8 +380,7 @@ PayFromPackOnly=0 // Disable weather effects? NoWeather=1 -// Set to 1 for items to keep their attr_newbie flag -// when item is transfered to an NPC. +// Set to 1 for items to keep their attr_newbie flag when item is transferred to an NPC. //AllowNewbTransfer=0 // Default light level in dungeons @@ -874,7 +873,7 @@ Experimental=0 // OF_OWNoDropCarriedItem 00400000 // When overweighted, don't drop items on ground when moving them (or using BOUNCE) and checking if you can carry them. // OF_AllowContainerInsideContainer 00800000 // Allow containers inside other containers even if they are heavier than the container being inserted into. // OF_VendorStockLimit 01000000 // Limits how much of an item a vendor can buy using the value set in the TEMPLATE. Format: BUY=ID,AMOUNT -// OF_EnableGuildAlignNotoriety 02000000 // If enabled, guilds with the same alignment will see each other as enemy or ally. +// OF_EnableGuildAlignNotoriety 02000000 // If enabled, guilds with the same alignment (order / chaos) will see each other as allies (green). // OF_NoDclickEquip 04000000 // If enabled, double-click does not equip items. // OF_PetBehaviorOwnerNeutral 08000000 // Should my pets always appear natural to me? // OF_NPCMovementOldStyle 010000000 // Required setting to make NPCs run like in the old Sphere versions (0.56b). diff --git a/src/sphere/GlobalInitializer.cpp b/src/sphere/GlobalInitializer.cpp new file mode 100644 index 000000000..80f0b9332 --- /dev/null +++ b/src/sphere/GlobalInitializer.cpp @@ -0,0 +1,98 @@ +#include "../common/sphereversion.h" +#include "../game/CObjBase.h" +#include "../game/uo_files/CUOItemTypeRec.h" +#include "GlobalInitializer.h" +#include + +extern std::string g_sServerDescription; + +// Dynamic initialization of some static members of other classes, which are used very soon after the server starts +dword CObjBase::sm_iCount = 0; // UID table. +#ifdef _WIN32 +llong CSTime::_kllTimeProfileFrequency = 1; // Default value. +#endif + + +// This method MUST be the first code running when the application starts! +GlobalInitializer::GlobalInitializer() +{ + // The order of the instructions is important! + + // Overwriting for safety, but it is created/allocated in spheresvr.cpp! + DummySphereThread::_instance = nullptr; + + std::stringstream ssServerDescription; + ssServerDescription << SPHERE_TITLE << " Version " << SPHERE_BUILD_INFO_STR; + ssServerDescription << " [" << get_target_os_str() << '-' << get_target_arch_str() << "]"; + ssServerDescription << " by www.spherecommunity.net"; + g_sServerDescription = ssServerDescription.str(); + + //-- Time + + /* + # *ifdef _WIN32 + // Ensure it's ACTUALLY necessary, before resorting to this. + timeBeginPeriod(1); // from timeapi.h, we need also to link against Winmm.lib... + #endif + */ + PeriodicSyncTimeConstants(); + + + //--- Sphere threading system + + DummySphereThread::createInstance(); + + { + // Ensure i have this to have context for ADDTOCALLSTACK and other operations. + const AbstractThread* curthread = ThreadHolder::get().current(); + ASSERT(curthread != nullptr); + ASSERT(dynamic_cast(curthread)); + UnreferencedParameter(curthread); + } + + //--- Exception handling + + #ifdef WINDOWS_SPHERE_SHOULD_HANDLE_STRUCTURED_EXCEPTIONS + SetWindowsStructuredExceptionTranslator(); + #endif + + // Set function to handle the invalid case where a pure virtual function is called. + SetPurecallHandler(); + + //--- Pre-startup sanity checks + + constexpr const char* m_sClassName = "GlobalInitializer"; + EXC_TRY("Pre-startup Init"); + + static_assert(MAX_BUFFER >= sizeof(CCommand)); + static_assert(MAX_BUFFER >= sizeof(CEvent)); + static_assert(sizeof(int) == sizeof(dword)); // make this assumption often. + static_assert(sizeof(ITEMID_TYPE) == sizeof(dword)); + static_assert(sizeof(word) == 2); + static_assert(sizeof(dword) == 4); + static_assert(sizeof(nword) == 2); + static_assert(sizeof(ndword) == 4); + static_assert(sizeof(wchar) == 2); // 16 bits + static_assert(sizeof(CUOItemTypeRec) == 37); // is byte packing working ? + + EXC_CATCH; +} + +void GlobalInitializer::InitRuntimeDefaultValues() // static +{ + CPointBase::InitRuntimeDefaultValues(); +} + +void GlobalInitializer::PeriodicSyncTimeConstants() // static +{ + // TODO: actually call it periodically! + + #ifdef _WIN32 + // Needed to get precise system time. + LARGE_INTEGER liProfFreq; + if (QueryPerformanceFrequency(&liProfFreq)) + { + CSTime::_kllTimeProfileFrequency = liProfFreq.QuadPart; + } + #endif // _WIN32 +} diff --git a/src/sphere/GlobalInitializer.h b/src/sphere/GlobalInitializer.h new file mode 100644 index 000000000..cff3ab89c --- /dev/null +++ b/src/sphere/GlobalInitializer.h @@ -0,0 +1,8 @@ +struct GlobalInitializer +{ + GlobalInitializer(); + ~GlobalInitializer() = default; + + static void InitRuntimeDefaultValues(); + static void PeriodicSyncTimeConstants(); +}; diff --git a/src/sphere/MainThread.cpp b/src/sphere/MainThread.cpp new file mode 100644 index 000000000..74ee5cbf7 --- /dev/null +++ b/src/sphere/MainThread.cpp @@ -0,0 +1,78 @@ +#include "MainThread.h" +#include "../game/CWorld.h" +#include "../game/CServer.h" +#include "../network/CNetworkManager.h" + +#ifdef _WIN32 +#include "../sphere/ntservice.h" // g_Service +#endif + +//******************************************************************* +// Main server loop + +static int Sphere_OnTick() +{ + // Give the world (CMainTask) a single tick. RETURN: 0 = everything is fine. + constexpr const char *m_sClassName = "SphereTick"; + EXC_TRY("Tick"); +#ifdef _WIN32 + EXC_SET_BLOCK("service"); + g_NTService._OnTick(); +#endif + + EXC_SET_BLOCK("world"); + g_World._OnTick(); + + // process incoming data + EXC_SET_BLOCK("network-in"); + g_NetworkManager.processAllInput(); + + EXC_SET_BLOCK("server"); + g_Serv._OnTick(); + + // push outgoing data + EXC_SET_BLOCK("network-out"); + g_NetworkManager.processAllOutput(); + + // don't put the network-tick between in.tick and out.tick, otherwise it will clean the out queue! + EXC_SET_BLOCK("network-tick"); + g_NetworkManager.tick(); // then this thread has to call the network tick + + EXC_CATCH; + return g_Serv.GetExitFlag(); +} + +MainThread::MainThread() +: AbstractSphereThread("T_Main", ThreadPriority::RealTime) +{ + m_profile.EnableProfile(PROFILE_NETWORK_RX); + m_profile.EnableProfile(PROFILE_CLIENTS); + //m_profile.EnableProfile(PROFILE_NETWORK_TX); + m_profile.EnableProfile(PROFILE_CHARS); + m_profile.EnableProfile(PROFILE_ITEMS); + m_profile.EnableProfile(PROFILE_MAP); + m_profile.EnableProfile(PROFILE_MULTIS); + m_profile.EnableProfile(PROFILE_NPC_AI); + m_profile.EnableProfile(PROFILE_SCRIPTS); + m_profile.EnableProfile(PROFILE_SHIPS); + m_profile.EnableProfile(PROFILE_TIMEDFUNCTIONS); + m_profile.EnableProfile(PROFILE_TIMERS); +} + +void MainThread::onStart() +{ + AbstractSphereThread::onStart(); +} + +void MainThread::tick() +{ + Sphere_OnTick(); +} + +bool MainThread::shouldExit() noexcept +{ + if (g_Serv.GetExitFlag() != 0) + return true; + return AbstractSphereThread::shouldExit(); +} + diff --git a/src/sphere/MainThread.h b/src/sphere/MainThread.h new file mode 100644 index 000000000..7b18e4bae --- /dev/null +++ b/src/sphere/MainThread.h @@ -0,0 +1,21 @@ +#include "../sphere/threads.h" + +class MainThread : public AbstractSphereThread +{ +public: + MainThread(); + virtual ~MainThread() { }; + + MainThread(const MainThread& copy) = delete; + MainThread& operator=(const MainThread& other) = delete; + +public: + // we increase the access level from protected to public in order to allow manual execution when + // configuration disables using threads + // TODO: in the future, such simulated functionality should lie in AbstractThread inself instead of hacks + virtual void tick() override; + +protected: + virtual void onStart() override; + virtual bool shouldExit() noexcept override; +}; diff --git a/src/sphere/ProfileTask.cpp b/src/sphere/ProfileTask.cpp index 9ff76d6b0..4752bc87b 100644 --- a/src/sphere/ProfileTask.cpp +++ b/src/sphere/ProfileTask.cpp @@ -1,5 +1,5 @@ #include "ProfileTask.h" -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "../game/CServerConfig.h" #include "threads.h" @@ -18,18 +18,18 @@ ProfileTask::ProfileTask(PROFILE_TYPE id) : return; auto& th = ThreadHolder::get(); - if (th.closing()) + if (th.isServClosing()) return; AbstractThread* icontext = th.current(); if (icontext == nullptr) { - // Thread was deleted, manually or by app closing signal. + // Thread was deleted, manually or by app servClosing signal. return; } m_context = static_cast(icontext); - if (m_context != nullptr && !m_context->closing()) + if (m_context != nullptr && !m_context->isClosing()) { ProfileData& pdata = m_context->m_profile; const PROFILE_TYPE task = pdata.GetCurrentTask(); @@ -56,7 +56,7 @@ ProfileTask::~ProfileTask(void) noexcept { EXC_TRY("destroy profiletask"); - if (m_context != nullptr && !m_context->closing()) + if (m_context != nullptr && !m_context->isClosing()) m_context->m_profile.Start(m_previousTask); EXC_CATCH; diff --git a/src/sphere/StartupMonitorAPI.h b/src/sphere/StartupMonitorAPI.h new file mode 100644 index 000000000..0f67cb324 --- /dev/null +++ b/src/sphere/StartupMonitorAPI.h @@ -0,0 +1,11 @@ +// sphere/StartupMonitorAPI.h +#ifndef _INC_STARTUP_MONITOR_API_H +#define _INC_STARTUP_MONITOR_API_H + +extern "C" { + void Sphere_AttachBootstrapContext(); + void Sphere_RenameBootstrapToMonitor(); + void Sphere_RunMonitorLoop(); +} + +#endif diff --git a/src/sphere/StartupMonitorThread.cpp b/src/sphere/StartupMonitorThread.cpp new file mode 100644 index 000000000..da5fd94db --- /dev/null +++ b/src/sphere/StartupMonitorThread.cpp @@ -0,0 +1,93 @@ +/** + * @file startup_monitor_thread.cpp + * + * Implementation of StartupMonitorThread: a Sphere thread context bound to the + * bootstrap OS thread for startup work, then re-used as the monitor thread. + */ + +#include "../common/CLog.h" +#include "../game/CServer.h" +#include "../game/CServerConfig.h" +#include "StartupMonitorThread.h" + +// External globals used by the monitor loop +extern CServer g_Serv; +extern CServerConfig g_Cfg; +extern CLog g_Log; + +StartupMonitorThread::StartupMonitorThread() +: AbstractSphereThread("T_SphereStartup", ThreadPriority::Highest) +{ + // Enable any profiles useful during startup/monitoring. + m_profile.EnableProfile(PROFILE_OVERHEAD); + m_profile.EnableProfile(PROFILE_STAT_FAULTS); + m_profile.EnableProfile(PROFILE_IDLE); +} + +void StartupMonitorThread::tick() +{ + // Not used: we don’t spawn a separate OS thread for this object. + // The monitor loop is run manually via runMonitorLoop() on the same OS thread. +} + +void StartupMonitorThread::renameAsMonitor() +{ + // Debugging. + AbstractThread * pThread = ThreadHolder::current(); + if (pThread != this) + { + g_Log.EventWarn("renameAsMonitor called, but current Sphere context is %s; refusing rename.\n", + pThread ? pThread->getName() : ""); + return; + } + + // Rename the current OS thread (bootstrap thread) to “T_Monitor” + // for better visibility in debuggers and logs. + AbstractThread::setThreadName("T_Monitor"); + // Keep our internal name in sync for logs using getName(). + overwriteInternalThreadName("T_Monitor"); +} + +void StartupMonitorThread::runMonitorLoop() +{ + constexpr const char *m_sClassName = "SphereMonitor"; + + while (!g_Serv.GetExitFlag()) + { + EXC_TRY("MainMonitorLoop"); + + if (g_Cfg.m_iFreezeRestartTime <= 0) + { + DEBUG_ERR(("Freeze Restart Time cannot be cleared at run time\n")); + g_Cfg.m_iFreezeRestartTime = 10; + } + + EXC_SET_BLOCK("Sleep"); + for (int i = 0; i < g_Cfg.m_iFreezeRestartTime; ++i) + { + if (g_Serv.GetExitFlag()) + break; + SLEEP(1000); + } + + EXC_SET_BLOCK("Checks"); + + // Don’t look for freezing when doing certain things. + if (g_Serv.IsLoadingGeneric() || !g_Cfg.m_fSecure || g_Serv.IsValidBusy()) + continue; + + #ifndef _DEBUG + EXC_SET_BLOCK("Check Stuck"); + // NOTE: We do not hold a pointer to g_Main here to avoid coupling; + // the original code called g_Main.checkStuck(). Keep that in the caller + // if needed (or expose a function to do it). + extern bool Sphere_CheckMainStuckAndRestart(); // declare a tiny wrapper in spheresvr.cpp + if (Sphere_CheckMainStuckAndRestart()) + { + g_Log.Event(LOGL_CRIT, "'T_Main' thread hang, restarting...\n"); + } + #endif + + EXC_CATCH; + } +} diff --git a/src/sphere/StartupMonitorThread.h b/src/sphere/StartupMonitorThread.h new file mode 100644 index 000000000..d9deef217 --- /dev/null +++ b/src/sphere/StartupMonitorThread.h @@ -0,0 +1,44 @@ +/** + * @file startup_monitor_thread.h + * + * StartupMonitorThread + * - Provides a proper Sphere thread context on the bootstrap OS thread during startup. + * - Later, it can act as the monitor thread on that same OS thread. + * + * Usage pattern: + * - Very early in main(): g_StartupMonitor.attachToCurrentThread("T_SphereStartup"); + * - Run all startup/bootstrap work (LoadIni, scripts, world loading). + * - If running core on separate OS thread: + * g_Main.start(); + * g_StartupMonitor.renameAsMonitor(); + * g_StartupMonitor.runMonitorLoop(); // blocking, replaces Sphere_MainMonitorLoop() + * Else (inline core on bootstrap thread): + * g_StartupMonitor.detachFromCurrentThread(); + * AbstractThread::ThreadBindingScope bind(g_Main, "T_Main"); + * while (!g_Serv.GetExitFlag()) g_Main.tick(); + */ + +#ifndef _INC_STARTUP_MONITOR_THREAD_H +#define _INC_STARTUP_MONITOR_THREAD_H + +#include "threads.h" + +class StartupMonitorThread final : public AbstractSphereThread +{ +public: + StartupMonitorThread(); + ~StartupMonitorThread() override = default; + + // Not used in attached/inline mode (we don’t spawn an OS thread for this class), + // but we must still define it. + void tick() override; + + // Rename the current OS thread for visibility once we switch from startup duties to monitor duties. + void renameAsMonitor(); + + // Blocking monitor loop (runs on the bootstrap OS thread after startup). + // Mirrors the previous Sphere_MainMonitorLoop() logic. + void runMonitorLoop(); +}; + +#endif // _INC_STARTUP_MONITOR_THREAD_H diff --git a/src/sphere/UnixTerminal.cpp b/src/sphere/UnixTerminal.cpp index 92c58d827..6619267b2 100644 --- a/src/sphere/UnixTerminal.cpp +++ b/src/sphere/UnixTerminal.cpp @@ -1,7 +1,7 @@ #ifndef _WIN32 -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header #include "UnixTerminal.h" #ifndef _USECURSES @@ -264,8 +264,8 @@ void UnixTerminal::prepareColor() void UnixTerminal::restore() { - ADDTOCALLSTACK("UnixTerminal::restore"); - ASSERT(m_prepared); + if (!m_prepared) + return; #ifdef _USECURSES endwin(); // clean up @@ -307,7 +307,7 @@ void UnixTerminal::tick() { this->ConsoleInterface::_ciQueueCV.wait(lock, [this]() { - return (this->m_terminateRequested || !this->ConsoleInterface::_qOutput.empty()); + return (this->m_fTerminateRequested || !this->ConsoleInterface::_qOutput.empty()); }); } @@ -328,7 +328,7 @@ void UnixTerminal::tick() void UnixTerminal::waitForClose() { - this->m_terminateRequested = true; + this->m_fTerminateRequested = true; this->ConsoleInterface::_ciQueueCV.notify_one(); //AbstractSphereThread::waitForClose(); //AbstractThread::terminate(true); diff --git a/src/sphere/asyncdb.cpp b/src/sphere/asyncdb.cpp index 3a3c4087d..08dd73012 100644 --- a/src/sphere/asyncdb.cpp +++ b/src/sphere/asyncdb.cpp @@ -1,11 +1,12 @@ #include "../common/CScriptObj.h" +//#include "../common/CScriptParserBufs.h" #include "../common/CScriptTriggerArgs.h" #include "../game/CServer.h" #include "asyncdb.h" -CDataBaseAsyncHelper::CDataBaseAsyncHelper(void) : AbstractSphereThread("AsyncDatabaseHelper", ThreadPriority::Low) +CDataBaseAsyncHelper::CDataBaseAsyncHelper(void) : AbstractSphereThread("T_AsyncDBHelper", ThreadPriority::Low) { } @@ -20,28 +21,29 @@ void CDataBaseAsyncHelper::onStart() void CDataBaseAsyncHelper::tick() { - if ( !m_queriesTodo.empty() ) - { - QueryBlob_t currentPair; - { - SimpleThreadLock lock(m_queryMutex); - currentPair = m_queriesTodo.front(); - m_queriesTodo.pop_front(); - } + SimpleThreadLock lock(m_queryMutex); + if ( m_queriesTodo.empty() ) + return; - FunctionQueryPair_t currentFunctionPair = currentPair.second; + QueryBlob_t currentPair; + currentPair = m_queriesTodo.front(); + m_queriesTodo.pop_front(); + lock.unlock(); - CScriptTriggerArgs * theArgs = new CScriptTriggerArgs(); - theArgs->m_iN1 = currentPair.first; - theArgs->m_s1 = currentFunctionPair.second; + FunctionQueryPair_t currentFunctionPair = currentPair.second; - if ( currentPair.first ) - theArgs->m_iN2 = g_Serv._hDb.query(currentFunctionPair.second, theArgs->m_VarsLocal); - else - theArgs->m_iN2 = g_Serv._hDb.exec(currentFunctionPair.second); + // Don't take it from CScriptParserBufs, since we are using a thread-unsafe object pool (script parsing is done in one thread only) + //CScriptTriggerArgsPtr theArgs = CScriptParserBufs::GetCScriptTriggerArgsPtr(); + auto theArgs = std::make_shared(); + theArgs->m_iN1 = currentPair.first; + theArgs->m_s1 = currentFunctionPair.second; - g_Serv._hDb.addQueryResult(currentFunctionPair.first, theArgs); - } + if ( currentPair.first ) + theArgs->m_iN2 = g_Serv._hDb.query(currentFunctionPair.second, theArgs->m_VarsLocal); + else + theArgs->m_iN2 = g_Serv._hDb.exec(currentFunctionPair.second); + + g_Serv._hDb.addQueryResult(currentFunctionPair.first, std::move(theArgs)); } void CDataBaseAsyncHelper::waitForClose() diff --git a/src/sphere/asyncdb.h b/src/sphere/asyncdb.h index 2bc3018c0..c7020c77a 100644 --- a/src/sphere/asyncdb.h +++ b/src/sphere/asyncdb.h @@ -25,15 +25,15 @@ class CDataBaseAsyncHelper : public AbstractSphereThread public: CDataBaseAsyncHelper(void); - ~CDataBaseAsyncHelper(void); -private: - CDataBaseAsyncHelper(const CDataBaseAsyncHelper& copy); - CDataBaseAsyncHelper& operator=(const CDataBaseAsyncHelper& other); + virtual ~CDataBaseAsyncHelper(void) override; + + CDataBaseAsyncHelper(const CDataBaseAsyncHelper& copy) = delete; + CDataBaseAsyncHelper& operator=(const CDataBaseAsyncHelper& other) = delete; public: - virtual void onStart(); - virtual void tick(); - virtual void waitForClose(); + virtual void onStart() override; + virtual void tick() override; + virtual void waitForClose() override; public: void addQuery(bool isQuery, lpctstr sFunction, lpctstr sQuery); diff --git a/src/sphere/containers.h b/src/sphere/containers.h index 5029a5e22..3bc32ff90 100644 --- a/src/sphere/containers.h +++ b/src/sphere/containers.h @@ -7,7 +7,7 @@ #define _INC_CONTAINERS_H #include -#include "../common/CException.h" +//#include "../common/CException.h" // included in the precompiled header // a thread-safe implementation of a queue container that doesn't use any locks // this only works as long as there is only a single reader thread and writer thread (can be different) diff --git a/src/sphere/ntservice.cpp b/src/sphere/ntservice.cpp index 32722c198..faf1c90aa 100644 --- a/src/sphere/ntservice.cpp +++ b/src/sphere/ntservice.cpp @@ -3,13 +3,14 @@ #ifdef _WIN32 #include -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/sphereversion.h" #include "../common/CLog.h" #include "../game/CObjBase.h" #include "../game/CServer.h" #include "../game/spheresvr.h" +#include "StartupMonitorAPI.h" #include "ntwindow.h" #include "ntservice.h" @@ -460,6 +461,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine { UnreferencedParameter(hPrevInstance); AbstractThread::setThreadName("T_SphereStartup"); + Sphere_AttachBootstrapContext(); // bind bootstrap OS thread to StartupMonitor context TCHAR *argv[32]; argv[0] = nullptr; diff --git a/src/sphere/ntservice.h b/src/sphere/ntservice.h index 124ffa518..33fc6eab2 100644 --- a/src/sphere/ntservice.h +++ b/src/sphere/ntservice.h @@ -17,7 +17,6 @@ #endif // NON_MSVC_COMPILER - extern class CNTService { private: diff --git a/src/sphere/ntwindow.cpp b/src/sphere/ntwindow.cpp index d5804aa4a..3d90997bf 100644 --- a/src/sphere/ntwindow.cpp +++ b/src/sphere/ntwindow.cpp @@ -3,8 +3,8 @@ #include "../common/sphere_library/CSWindow.h" #include "../common/sphere_library/CSString.h" -#include "../common/CException.h" -#include "../common/CExpression.h" +//#include "../common/CException.h" // included in the precompiled header +//#include "../common/CExpression.h" // included in the precompiled header #include "../common/CTextConsole.h" #include "../common/CLog.h" #include "../common/sphereversion.h" // sphere version @@ -166,7 +166,7 @@ BOOL CNTWindow::CStatusDlg::DefDialogProc( UINT message, WPARAM wParam, LPARAM l CNTWindow::CNTWindow() : AbstractSphereThread("T_ConsoleWindow", ThreadPriority::Highest), _NTWInitParams{}, m_zCommands {{}} { - _fKeepAliveAtShutdown = true; + m_fKeepAliveAtShutdown = true; m_iLogTextLen = 0; m_fLogScrollLock = false; @@ -188,7 +188,7 @@ void CNTWindow::exitActions() { g_Serv.SetExitFlag(5); NTWindow_DeleteIcon(); - _thread_selfTerminateAfterThisTick = true; + m_fThreadSelfTerminateAfterThisTick = true; } void CNTWindow::onStart() @@ -972,29 +972,32 @@ void CNTWindow::NTWindow_CheckUpdateWindowTitle() LPCTSTR pszMode; switch ( g_Serv.GetServerMode() ) { - case SERVMODE_RestockAll: // Major event. + case ServMode::RestockAll: // Major event. pszMode = "Restocking"; break; - case SERVMODE_GarbageCollection: // Major event. + case ServMode::GarbageCollection: // Major event. pszMode = "Collecting Garbage"; break; - case SERVMODE_Saving: // Forced save freezes the system. + case ServMode::Saving: // Forced save freezes the system. pszMode = "Saving"; break; - case SERVMODE_Run: // Game is up and running + case ServMode::Run: // Game is up and running pszMode = "Running"; break; - case SERVMODE_PreLoadingINI: - case SERVMODE_Loading: // Initial load. + case ServMode::StartupPreLoadingIni: + case ServMode::StartupLoadingScripts: // Initial load. pszMode = "Loading"; break; - case SERVMODE_ResyncPause: + case ServMode::StartupLoadingSaves: // Initial load. + pszMode = "Loading (savefiles)"; + break; + case ServMode::ResyncPause: pszMode = "Resync Pause"; break; - case SERVMODE_ResyncLoad: // Loading after resync + case ServMode::ResyncLoad: // Loading after resync pszMode = "Resync Load"; break; - case SERVMODE_Exiting: // Closing down + case ServMode::Exiting: // Closing down pszMode = "Exiting"; break; default: diff --git a/src/sphere/threads.cpp b/src/sphere/threads.cpp index 8724fbd55..6373063f2 100644 --- a/src/sphere/threads.cpp +++ b/src/sphere/threads.cpp @@ -1,64 +1,90 @@ +/** + * @file threads.cpp + */ + #ifdef _WIN32 - // this thing is somehow required to be able to initialise OLE - #define _WIN32_DCOM +# define _WIN32_DCOM +# include +# include +#else +# include +# if !defined(_BSD) && !defined(__APPLE__) +# include +# endif +# include +#endif + +#if defined(__linux__) +# include +# include +#elif defined(__FreeBSD__) +# include #endif #include "../common/sphere_library/sresetevents.h" #include "../common/sphere_library/sstringobjs.h" -#include "../common/basic_threading.h" -#include "../common/CException.h" +//#include "../common/basic_threading.h" #include "../common/CLog.h" #include "../game/CServer.h" + #include "ProfileTask.h" #include "threads.h" -#if defined(_WIN32) - #include - #include -#elif !defined(_BSD) && !defined(__APPLE__) - #include // to set thread name -#endif - #include -#include +#include +#include +#include +//#include +// External globals +extern CServer g_Serv; +extern CLog g_Log; -// number of exceptions after which we restart thread and think that the thread have gone in exceptioning loops -#define EXCEPTIONS_ALLOWED 10 +// Thread-local pointer for fast “current thread” lookup. +static thread_local AbstractSphereThread* sg_tlsCurrentSphereThread = nullptr; +// Avoid trying to get thread context while binding it. +static thread_local bool sg_tlsBindingInProgress = false; -// number of milliseconds to wait for a thread to close -#define THREADJOIN_TIMEOUT 60000 +// Global state flags +static std::atomic_bool sg_inStartup{true}; +static std::atomic_bool sg_servClosing{false}; + +// Constants +#define THREAD_EXCEPTIONS_ALLOWED 10 +#define THREAD_JOIN_TIMEOUT 60000 -// temporary string storage #define THREAD_TEMPSTRING_C_STORAGE 2048 #define THREAD_TEMPSTRING_OBJ_STORAGE 1024 - -// This implementation doesn't reserve preallocated strings for each thread or attached to a specific thread, -// but it creates a pool of preallocated strings which access is guarded by a Mutex. +/* + * Shared pools for temporary strings used during logging/formatting. + */ struct TemporaryStringsThreadSafeStateHolder { - // C-style string Buffer (char array) - SimpleMutex g_tmpCStringMutex; - std::atomic g_tmpCStringIndex; + // C-style char buffers + SimpleMutex g_tmpCStringMutex; + std::atomic g_tmpCStringIndex; std::unique_ptr g_tmpCStrings; - // TemporaryString Buffer - SimpleMutex g_tmpTemporaryStringMutex; - std::atomic g_tmpTemporaryStringIndex; - struct TemporaryStringStorage - { - char m_buffer[THREAD_STRING_LENGTH]; - char m_state; + // TemporaryString buffers with “in-use” flags + SimpleMutex g_tmpTemporaryStringMutex; + std::atomic g_tmpTemporaryStringIndex; + struct TemporaryStringStorage + { + char m_buffer[THREAD_STRING_LENGTH]; + char m_state; }; - std::unique_ptr g_tmpTemporaryStringStorage; + std::unique_ptr g_tmpTemporaryStringStorage; private: - TemporaryStringsThreadSafeStateHolder() : - g_tmpCStringIndex(0), g_tmpTemporaryStringIndex(0) + TemporaryStringsThreadSafeStateHolder() + : g_tmpCStringIndex(0) + , g_tmpTemporaryStringIndex(0) { g_tmpCStrings = std::make_unique(THREAD_TEMPSTRING_C_STORAGE * THREAD_STRING_LENGTH); g_tmpTemporaryStringStorage = std::make_unique(THREAD_TEMPSTRING_OBJ_STORAGE); + for (uint i = 0; i < THREAD_TEMPSTRING_OBJ_STORAGE; ++i) + g_tmpTemporaryStringStorage[i].m_state = 0; } public: @@ -69,553 +95,645 @@ struct TemporaryStringsThreadSafeStateHolder } }; +// ----- ThreadHolder ----- -/** - * ThreadHolder -**/ - -ThreadHolder::ThreadHolder() noexcept : - m_threadCount(0), m_closingThreads(false) +ThreadHolder::ThreadHolder() noexcept + : m_threadCount(0) { - // While we keep this as a global state and do not decide to get a set of string buffers attached to each AbstractSphereThread, - // we have to ensure that we construct the global string buffer holder as soon as possible. - TemporaryStringsThreadSafeStateHolder::get(); + // Ensure pools constructed early to avoid first-use cost in weird places. + (void)TemporaryStringsThreadSafeStateHolder::get(); } ThreadHolder& ThreadHolder::get() noexcept { - static ThreadHolder instance; - return instance; + static ThreadHolder instance; + return instance; } -bool ThreadHolder::closing() noexcept +bool ThreadHolder::isSystemIdRegistered(threadid_t sysId, AbstractSphereThread** outExisting) const noexcept { std::shared_lock lock(m_mutex); - volatile auto ret = m_closingThreads; - return ret; + auto it = std::find_if( + m_spherethreadpairs_systemid_ptr.begin(), + m_spherethreadpairs_systemid_ptr.end(), + [sysId](const spherethreadpair_t& elem) noexcept { + return elem.first == sysId; + } + ); + if (it == m_spherethreadpairs_systemid_ptr.end()) + return false; + if (outExisting) + *outExisting = it->second; + return true; } -AbstractThread* ThreadHolder::current() noexcept +void ThreadHolder::push(AbstractThread* pAbstractThread) noexcept { - // Do not use ASSERTs here, would cause recursion. - - // RETRY_SHARED_LOCK_FOR_TASK is used to try to not make mutex lock fail and, if needed, - // handle failure while allowing this function to be noexcept. - - AbstractThread* retval = nullptr; - RETRY_SHARED_LOCK_FOR_TASK(m_mutex, lock, retval, - ([this, &lock]() -> AbstractThread* - { - const threadid_t tid = AbstractThread::getCurrentThreadSystemId(); + if (!pAbstractThread) + { + stderrLog("ThreadHolder::push: nullptr thread.\n"); + return; + } - if (m_spherethreadpairs_systemid_ptr.empty()) - [[unlikely]] - { - if (m_closingThreads) [[unlikely]] - { - //STDERR_LOG("Closing?\n"); - return nullptr; - } - - auto thread = static_cast(DummySphereThread::getInstance()); - if (!thread) - [[unlikely]] - { - // Should never happen. - RaiseImmediateAbort(11); - } - - thread->m_threadSystemId = tid; - lock.unlock(); - push(thread); - return thread; - } + auto* pSphereThread = dynamic_cast(pAbstractThread); + if (!pSphereThread) + { + stderrLog("ThreadHolder::push: not an AbstractSphereThread.\n"); + return; + } - spherethreadpair_t *found = nullptr; - for (auto &elem : m_spherethreadpairs_systemid_ptr) - { - if (elem.first == tid) - { - found = &elem; - break; - } - } +#ifdef _DEBUG + int iIdForLogDebug = -1; + bool fShouldLogDebug = false; +#endif + const char* ptcNameForLog = pAbstractThread->getName(); - if (!found) - [[unlikely]] - { - //throw CSError(LOGL_FATAL, 0, "Thread handle not found in vector?"); - //STDERR_LOG("Thread handle not found in vector?"); + try + { + std::unique_lock lock(m_mutex); - // Should never happen. - RaiseImmediateAbort(12); + // 1) Idempotency: same pointer already registered. + auto itExistingPtr = std::find_if( + m_spherethreadpairs_systemid_ptr.begin(), + m_spherethreadpairs_systemid_ptr.end(), + [pSphereThread](const spherethreadpair_t& elem) noexcept { + return elem.second == pSphereThread; } + ); + if (itExistingPtr != m_spherethreadpairs_systemid_ptr.end()) + { + // Refresh holder id from current slot if present. + auto itSlot = std::find_if( + m_threads.begin(), + m_threads.end(), + [pAbstractThread](const SphereThreadData& s) noexcept { return s.m_ptr == pAbstractThread; } + ); + if (itSlot != m_threads.end()) + pAbstractThread->m_threadHolderId = static_cast(std::distance(m_threads.begin(), itSlot)); - auto thread = static_cast(found->second); - - ASSERT(thread->m_threadHolderId != -1); - SphereThreadData *tdata = &(m_threads[thread->m_threadHolderId]); - if (tdata->m_closed) - [[unlikely]] - { - //STDERR_LOG("Closed? Idx %u, Name %s.\n", thread->m_threadHolderId, thread->getName()); - return nullptr; - } +#ifdef _DEBUG + iIdForLogDebug = pAbstractThread->m_threadHolderId; + fShouldLogDebug = true; +#endif + lock.unlock(); +#ifdef _DEBUG + if (fShouldLogDebug) + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "ThreadHolder already registered: %s (ThreadHolder-ID %d).\n", + ptcNameForLog, iIdForLogDebug); +#endif + return; + } - if (m_closingThreads) [[unlikely]] - { - auto spherethread = dynamic_cast(thread); - if (!spherethread) - { - // Should never happen. - RaiseImmediateAbort(13); - } - - if (!spherethread->_fKeepAliveAtShutdown) - { - //STDERR_LOG("Closing?\n"); - return nullptr; - } + // 2) System-id uniqueness: refuse a second Sphere context on the same OS thread. + auto itExistingSys = std::find_if( + m_spherethreadpairs_systemid_ptr.begin(), + m_spherethreadpairs_systemid_ptr.end(), + [pSphereThread](const spherethreadpair_t& elem) noexcept { + // Correct equality for pthread_t vs DWORD handled by threadid_t typedef. + return elem.first == pSphereThread->m_threadSystemId; } - - // Uncomment it only for testing purposes, since this method is called very often and we don't need the additional overhead - //DEBUG_ASSERT( thread->isSameThread(thread->getId()) ); - - return thread; - })); - - return retval; -} - -void ThreadHolder::push(AbstractThread *thread) noexcept -{ - bool fExceptionThrown = false; - try - { - auto sphere_thread = dynamic_cast(thread); - if (!sphere_thread) + ); + if (itExistingSys != m_spherethreadpairs_systemid_ptr.end()) { - //throw CSError(LOGL_FATAL, 0, "AbstractThread not being an AbstractSphereThread?"); - STDERR_LOG("AbstractThread not being an AbstractSphereThread?"); - //fExceptionThrown = true; - goto soft_throw; + // Another object is already attached to this OS thread; refuse the second. + auto* other = itExistingSys->second; + lock.unlock(); + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "ThreadHolder: refusing to register %s on OS thread already owned by %s.\n", + ptcNameForLog, other ? other->getName() : ""); + EXC_NOTIFY_DEBUGGER; + return; } - std::unique_lock lock(m_mutex); - - ASSERT(thread->m_threadSystemId != 0); - ASSERT(thread->m_threadHolderId == -1); - - m_threads.emplace_back( SphereThreadData{ thread, false }); - thread->m_threadHolderId = m_threadCount; + // 3) New registration. + m_threads.emplace_back(SphereThreadData{ pAbstractThread, false }); + pAbstractThread->m_threadHolderId = m_threadCount; + m_spherethreadpairs_systemid_ptr.emplace_back(pSphereThread->m_threadSystemId, pSphereThread); + ++m_threadCount; #ifdef _DEBUG - auto it_thread = std::find_if( - m_spherethreadpairs_systemid_ptr.begin(), - m_spherethreadpairs_systemid_ptr.end(), - [sphere_thread](spherethreadpair_t const &elem) noexcept -> bool { return elem.second == sphere_thread; }); - - // I don't want duplicates. - DEBUG_ASSERT(it_thread == m_spherethreadpairs_systemid_ptr.end()); + iIdForLogDebug = pAbstractThread->m_threadHolderId; + fShouldLogDebug = true; #endif - m_spherethreadpairs_systemid_ptr.emplace_back(sphere_thread->m_threadSystemId, sphere_thread); - - ++ m_threadCount; + lock.unlock(); +#ifdef _DEBUG + if (fShouldLogDebug) + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "ThreadHolder registered %s (ThreadHolder-ID %d).\n", + ptcNameForLog, iIdForLogDebug); +#endif } - catch (CAssert const& e) - { - fExceptionThrown = true; - lptstr ptcBuf = Str_GetTemp(); - e.GetErrorMessage(ptcBuf, usize_narrow_u32(Str_TempLength())); - STDERR_LOG("ASSERT failed.\n%s\n", ptcBuf); - } - catch (std::system_error const& e) + catch (const std::exception& e) { - fExceptionThrown = true; - STDERR_LOG("Mutex cannot be acquired. Err: '%s'.\n", e.what()); + stderrLog("ThreadHolder::push: exception: %s.\n", e.what()); } catch (...) { - fExceptionThrown = true; - STDERR_LOG("Unknown exception thrown.\n"); + stderrLog("ThreadHolder::push: unknown exception.\n"); } +} + + +void ThreadHolder::remove(AbstractThread* pAbstractThread) CANTHROW +{ + if (!pAbstractThread) + throw CSError(LOGL_FATAL, 0, "ThreadHolder::remove: thread == nullptr"); + + AbstractSphereThread* pSphereThread = dynamic_cast(pAbstractThread); - if (fExceptionThrown) +#ifdef _DEBUG + threadid_t sysId = pSphereThread ? pSphereThread->m_threadSystemId : threadid_t{}; + const char* ptcName = pAbstractThread->getName(); +#endif + + std::unique_lock lock(m_mutex); + + // Remove from main list. + auto it = std::find_if(m_threads.begin(), m_threads.end(), + [pAbstractThread](const SphereThreadData& s) noexcept { return s.m_ptr == pAbstractThread; }); + if (it != m_threads.end()) + m_threads.erase(it); + + // Remove from sys-id map. + for (auto it2 = m_spherethreadpairs_systemid_ptr.begin(); it2 != m_spherethreadpairs_systemid_ptr.end(); ) { -soft_throw: - // Should never happen. - RaiseImmediateAbort(14); + if (it2->second == pSphereThread) + it2 = m_spherethreadpairs_systemid_ptr.erase(it2); + else + ++it2; } + // Mark as no longer registered to aid destructor diagnostics. + pAbstractThread->m_threadHolderId = ThreadHolder::m_kiInvalidThreadID; + + lock.unlock(); + #ifdef _DEBUG - if (dynamic_cast(thread)) + if (!isServClosing()) { - // Too early in the init process to use the console... - printf("Registered thread '%s' with ThreadHolder ID %d.\n", - thread->getName(), thread->m_threadHolderId); + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "ThreadHolder removed %s with sys-id %" PRIu64 ".\n", + ptcName, (uint64_t)sysId); } else { - g_Log.Event(LOGM_DEBUG|LOGL_EVENT|LOGF_CONSOLE_ONLY, - "Registered thread '%s' with ThreadHolder ID %d.\n", - thread->getName(), thread->m_threadHolderId); + // Logger may be shutting down; print directly to stdout to avoid loss. + fprintf(stdout, "DEBUG: ThreadHolder removed %s with sys-id %" PRIu64 ".\n", + ptcName, (uint64_t)sysId); + fflush(stdout); } #endif } -void ThreadHolder::remove(AbstractThread *thread) CANTHROW -{ - if (!thread) - throw CSError(LOGL_FATAL, 0, "thread == nullptr"); - - std::unique_lock lock(m_mutex); - - ASSERT(m_threadCount > 0); // Trying to de-queue thread while no threads are active? - auto sphere_thread = static_cast(thread); - auto it_thread = std::find_if( - m_spherethreadpairs_systemid_ptr.begin(), - m_spherethreadpairs_systemid_ptr.end(), - [sphere_thread](spherethreadpair_t const &elem) noexcept -> bool { return elem.second == sphere_thread; }); - ASSERT(it_thread != m_spherethreadpairs_systemid_ptr.end()); - - auto it_data = std::find_if(m_threads.begin(), m_threads.end(), - [thread](SphereThreadData const& elem) { - return elem.m_ptr == thread; - }); - ASSERT(it_data != m_threads.end()); // Ensure that the thread to de-queue is registered - - --m_threadCount; - - m_threads.erase(it_data); - m_spherethreadpairs_systemid_ptr.erase(it_thread); -} void ThreadHolder::markThreadsClosing() CANTHROW { - g_Log.Event(LOGM_INIT|LOGM_NOCONTEXT|LOGL_EVENT, "Marking threads as closing.\n"); + // Flip fast-path flag first so concurrent readers immediately see “server is Closing”. + ThreadHolder::markServClosing(); std::unique_lock lock(m_mutex); - m_closingThreads = true; - for (auto& thread_data : m_threads) - { - auto sphere_thread = static_cast(thread_data.m_ptr); - if (sphere_thread->_fKeepAliveAtShutdown) + for (auto& thread_data : m_threads) + { + auto sphere_thread = static_cast(thread_data.m_ptr); + if (!sphere_thread) + continue; + + if (sphere_thread->m_fKeepAliveAtShutdown) continue; - sphere_thread->_fIsClosing = true; - thread_data.m_closed = true; - } + sphere_thread->m_uiState = AbstractThread::eRunningState::Closing; + thread_data.m_closed = true; + } } AbstractThread * ThreadHolder::getThreadAt(size_t at) noexcept { - AbstractThread* retval = nullptr; - RETRY_SHARED_LOCK_FOR_TASK(m_mutex, lock, retval, - ([this, at]() -> AbstractThread* - { -// MSVC: warning C5101: use of preprocessor directive in function-like macro argument list is undefined behavior. -//#ifdef _DEBUG - if (getActiveThreads() != m_threads.size()) - [[unlikely]] - { - STDERR_LOG("Active threads %" PRIuSIZE_T ", threads container size %" PRIuSIZE_T ".\n", getActiveThreads(), m_threads.size()); - RaiseImmediateAbort(15); - } -//#endif + std::shared_lock lock(m_mutex); + if (at >= getActiveThreads()) + return nullptr; + return m_threads[at].m_ptr; +} - if ( at > getActiveThreads() ) - [[unlikely]] - { - return nullptr; - } +AbstractThread * ThreadHolder::current() noexcept // static +{ + // TLS fast path: the absolutely hottest path in the system. + AbstractSphereThread* tls = sg_tlsCurrentSphereThread; + if (tls != nullptr) [[likely]] + return tls; + + /* + In two cases, the thread-local pointer (g_tlsCurrentSphereThread) may not be set: + - We are on a thread that is not a registered Sphere thread: + Signal handler threads, or late after detach. + - Some context exists but the thread object was never bound: + Example: If we forget to attachToCurrentThread/bind a Sphere thread object (like StartupMonitorThread or g_Main in inline mode), the TLS pointer remains null. + */ + + // No TLS => not a Sphere thread or not yet attached. + if (sg_servClosing.load(std::memory_order_relaxed) == true) [[unlikely]] + return nullptr; + + if (sg_inStartup.load(std::memory_order_relaxed) == true) [[unlikely]] + return DummySphereThread::getInstance(); // may be null extremely early + + // Runtime (Run mode) and not Closing: missing context => nullptr (by policy). + return nullptr; +} +// Record that thread has started. +void ThreadHolder::markThreadStarted(AbstractThread* pThr) CANTHROW +{ + const int id = pThr->m_threadHolderId; + SphereThreadData& threadData = m_threads.at(id); + ASSERT(threadData.m_closed == false); + threadData.m_closed = false; +} - /* - for ( spherethreadlist_t::const_iterator it = m_threads.begin(), end = m_threads.end(); it != end; ++it ) - { - if ( at == 0 ) - return it->m_ptr; +// Record that the server entered Run mode (disable startup fallback in fast path). +void ThreadHolder::markServEnteredRunMode() noexcept // static +{ + sg_inStartup.store(false, std::memory_order_relaxed); +} - --at; - } - return nullptr; - */ - return m_threads[at].m_ptr; - })); +// Record that threads are servClosing (fast-path observable). +void ThreadHolder::markServClosing() noexcept // static +{ + sg_servClosing.store(true, std::memory_order_relaxed); +} - return retval; +bool ThreadHolder::isServClosing() noexcept { // static + return sg_servClosing.load(std::memory_order_relaxed); } +// ----- AbstractThread ----- -/* - * AbstractThread -*/ int AbstractThread::m_threadsAvailable = 0; -AbstractThread::AbstractThread(const char *name, ThreadPriority priority) : - m_threadSystemId(0), m_threadHolderId(-1), - _fKeepAliveAtShutdown(false), _fIsClosing(false) +static inline bool is_same_thread_id(threadid_t firstId, threadid_t secondId) noexcept { - if( AbstractThread::m_threadsAvailable == 0 ) - { - // no threads were started before - initialise thread subsystem +#if defined(_WIN32) || defined(__APPLE__) + return (firstId == secondId); +#else + return pthread_equal(firstId, secondId); +#endif +} + +static uint64_t os_current_tid() noexcept // Equivalent to old getCurrentThreadSystemId() +{ +#if defined(_WIN32) + return static_cast(::GetCurrentThreadId()); // Ret type: DWORD. +#elif defined(__APPLE__) + uint64_t tid = 0; + (void)pthread_threadid_np(nullptr, &tid); + return tid; +#elif defined(__linux__) + // gettid is the kernel TID seen by system tools + return static_cast(::syscall(SYS_gettid)); +#elif defined(__FreeBSD__) // TODO: does it work also for other BSD systems? + return static_cast(::pthread_getthreadid_np()); +#else + // Last-resort fallback: make a printable token from pthread_self() + return static_cast(reinterpret_cast(::pthread_self())); +#endif +} + +static void os_set_thread_name_portable(const char* name_trimmed) noexcept +{ +#if defined(_WIN32) + // Prefer SetThreadDescription (Windows 10+) if available + using SetThreadDescription_t = HRESULT (WINAPI *)(HANDLE, PCWSTR); + static SetThreadDescription_t pSetThreadDescription = + reinterpret_cast( + GetProcAddress(GetModuleHandleW(L"Kernel32.dll"), "SetThreadDescription")); + if (pSetThreadDescription) + { + wchar_t wname[64]; + ::MultiByteToWideChar(CP_UTF8, 0, name_trimmed, -1, wname, static_cast(std::size(wname))); + pSetThreadDescription(GetCurrentThread(), wname); + } + else + { +# if defined(_MSC_VER) +# pragma pack(push, 8) + typedef struct tagTHREADNAME_INFO { + DWORD dwType; // 0x1000 + LPCSTR szName; + DWORD dwThreadID; // -1 => current thread + DWORD dwFlags; + } THREADNAME_INFO; +# pragma pack(pop) + + static constexpr DWORD MS_VC_EXCEPTION = 0x406D1388; + THREADNAME_INFO info{}; + info.dwType = 0x1000; + info.szName = name_trimmed; + info.dwThreadID = static_cast(-1); // CRITICAL: current thread + info.dwFlags = 0; + + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } __except(EXCEPTION_EXECUTE_HANDLER) { + } +# else + stderrLog("WARN: no available implementation to set the thread name.\n"); +# endif + } +#elif defined(__APPLE__) + // macOS: pthread_setname_np only sets the current thread, 64-char limit + (void)pthread_setname_np(name_trimmed); +#elif defined(__linux__) +// Linux: prctl(PR_SET_NAME) sets current thread name, 16-byte limit incl. NUL +# if !defined(_BSD) && !defined(__APPLE__) + ::prctl(PR_SET_NAME, name_trimmed, 0, 0, 0); +# endif +// Also attempt pthread_setname_np where available for consistency +# if defined(__GLIBC__) || defined(__ANDROID__) + (void)pthread_setname_np(pthread_self(), name_trimmed); +# endif +#elif defined(__FreeBSD__) + (void)pthread_set_name_np(pthread_self(), name_trimmed); +#else + // TODO: support other BSD systems + // No-op on unknown platforms + (void)name_trimmed; + stderrLog("WARN: no available implementation to set the thread name for the current platform (unknown/unsupported).\n"); +#endif +} + +AbstractThread::AbstractThread(const char *name, ThreadPriority priority) +{ + if (AbstractThread::m_threadsAvailable == 0) + { #ifdef _WIN32 - if( CoInitializeEx(nullptr, COINIT_MULTITHREADED) != S_OK ) - { - throw CSError(LOGL_FATAL, 0, "OLE is not available, threading model unimplementable"); - } + if (CoInitializeEx(nullptr, COINIT_MULTITHREADED) != S_OK) + throw CSError(LOGL_FATAL, 0, "OLE init failed, threading unavailable"); #endif - ++AbstractThread::m_threadsAvailable; - } - m_threadSystemId = 0; + } + ++AbstractThread::m_threadsAvailable; + Str_CopyLimitNull(m_name, name, sizeof(m_name)); - m_hangCheck = 0; - _thread_selfTerminateAfterThisTick = true; - m_terminateRequested = true; - setPriority(priority); - m_sleepEvent = std::make_unique(); + m_fThreadSelfTerminateAfterThisTick = true; + m_fTerminateRequested = true; + + setPriority(priority); + + m_sleepEvent = std::make_unique(); m_terminateEvent = std::make_unique(); } +// threads.cpp — robust, flag-free destructor classification AbstractThread::~AbstractThread() { #ifdef _DEBUG - fprintf(stdout, "DEBUG: Destroying thread '%s' with ThreadHolder ID %d and system ID %" PRIu64 ".\n", - getName(), m_threadHolderId, (uint64)m_threadSystemId); + const char* name = getName(); + if (!name || !name[0]) name = "(unnamed)"; + + const bool stillRegistered = (m_threadHolderId != ThreadHolder::m_kiInvalidThreadID); + const bool everBound = (m_threadSystemId != 0); + const bool everRanLoop = (m_uiState != eRunningState::NeverStarted); + + const char* state = + stillRegistered ? "[registered, closing]" : + (everBound && everRanLoop) ? "[detached, closed]" : + (everBound && !everRanLoop) ? "[attached-only, closed]" : + "[not started]"; + + if (everBound) + fprintf(stdout, "DEBUG: Destroying AbstractThread '%s' (ThreadHolder-ID %d) %s, sys-id %" PRIu64 ".\n", + name, m_threadHolderId, state, (uint64_t)m_threadSystemId); + else + fprintf(stdout, "DEBUG: Destroying AbstractThread '%s' (ThreadHolder-ID %d) %s, sys-id n/a.\n", + name, m_threadHolderId, state); fflush(stdout); #endif terminate(true); --AbstractThread::m_threadsAvailable; - if( AbstractThread::m_threadsAvailable == 0 ) - { - // all running threads have gone, the thread subsystem is no longer needed #ifdef _WIN32 - CoUninitialize(); -#else - // No pthread equivalent + if (AbstractThread::m_threadsAvailable == 0) + CoUninitialize(); #endif - } } void AbstractThread::overwriteInternalThreadName(const char* name) noexcept { - // Use it only if you know what you are doing! - // This doesn't actually do the change of the thread name! Str_CopyLimitNull(m_name, name, sizeof(m_name)); } void AbstractThread::start() { - //ThreadHolder::get().push(this); - //printf("Starting thread '%s' with ThreadHolder ID %d.\n", - // getName(), m_threadHolderId); - //fflush(stdout); + g_Log.Event(LOGM_DEBUG|LOGL_EVENT|LOGF_CONSOLE_ONLY, + "Spawning new thread '%s' (AbstractThread* = 0x% " PRIxSIZE_T ")...\n", + getName(), reinterpret_cast(this)); #ifdef _WIN32 - m_handle = reinterpret_cast(_beginthreadex(nullptr, 0, &runner, this, 0, nullptr)); + m_handle = reinterpret_cast(_beginthreadex(nullptr, 0, &runner, this, 0, nullptr)); #else - pthread_attr_t threadAttr; - pthread_attr_init(&threadAttr); - pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED); + pthread_attr_t threadAttr; + pthread_attr_init(&threadAttr); + pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED); spherethread_t threadHandle{}; - int result = pthread_create( &threadHandle, &threadAttr, &runner, this ); - pthread_attr_destroy(&threadAttr); - - if (result != 0) - { + const int result = pthread_create(&threadHandle, &threadAttr, &runner, this); + pthread_attr_destroy(&threadAttr); + if (result != 0) + { m_handle = std::nullopt; - throw CSError(LOGL_FATAL, 0, "Unable to spawn a new thread"); - } + throw CSError(LOGL_FATAL, 0, "Unable to spawn a new thread"); + } m_handle = threadHandle; #endif - m_terminateEvent->reset(); } void AbstractThread::terminate(bool ended) { - if( isActive() ) - { - bool wasCurrentThread = isCurrentThread(); - if (ended == false) - { - g_Log.Event(LOGL_WARN, "Forcing thread '%s' to terminate...\n", getName()); - - // if the thread is current then terminating here will prevent cleanup from occurring - if (wasCurrentThread == false) - { + if (!isActive()) + return; + + const bool wasCurrentThread = isCurrentThread(); + + if (ended == false) + { + if (!wasCurrentThread) + { + if (!m_handle.has_value()) + { + stderrLog("AbstractThread::terminate: no handle available.\n"); + } + else + { #ifdef _WIN32 TerminateThread(m_handle.value(), 0); CloseHandle(m_handle.value()); #else - pthread_cancel(m_handle.value()); // IBM say it so + pthread_cancel(m_handle.value()); #endif - } - } - - // Common things - ThreadHolder::get().remove(this); - m_threadSystemId = 0; - m_handle = std::nullopt; + } + } + } - // let everyone know we have been terminated - m_terminateEvent->set(); + detachFromCurrentThread(); + m_handle = std::nullopt; + m_terminateEvent->set(); - // current thread can be terminated now - if (ended == false && wasCurrentThread) - { + if (ended == false && wasCurrentThread) + { #ifdef _WIN32 - _endthreadex(EXIT_SUCCESS); + _endthreadex(EXIT_SUCCESS); #else - //exit(EXIT_SUCCESS) - pthread_exit(nullptr); + pthread_exit(nullptr); #endif - } - } + } } void AbstractThread::run() { - // is the very first since there is a possibility of something being altered there - onStart(); + onStart(); - int exceptions = 0; - bool lastWasException = false; - _thread_selfTerminateAfterThisTick = false; - m_terminateRequested = false; + int exceptions = 0; + bool lastWasException = false; + m_fThreadSelfTerminateAfterThisTick = false; + m_fTerminateRequested = false; - for (;;) - { + setThreadName(getName()); + m_uiState = eRunningState::Running; + + for (;;) + { if (shouldExit()) break; - bool gotException = false; + bool gotException = false; - // report me being alive if I am being checked for status - if (m_hangCheck != 0) - { - m_hangCheck = 0; - } - - try - { - tick(); + if (m_uiHangCheck != 0) + m_uiHangCheck = 0; + try + { + tick(); if (shouldExit()) break; - // ensure this is recorded as 'idle' time for this thread (ideally this should - // be in tick() but we cannot guarantee it to be called there GetCurrentProfileData().Start(PROFILE_IDLE); - } - /* - catch( const CAssert& e ) + } + catch (const CSError& e) { gotException = true; - g_Log.CatchEvent(&e, "[TR] ExcType=CAssert in %s::tick", getName()); + g_Log.CatchEvent(&e, "[TR] CSError in %s::tick", getName()); GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); } - */ - catch( const CSError& e ) - { - gotException = true; - g_Log.CatchEvent(&e, "[TR] ExcType=CSError in %s::tick", getName()); - GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); - } - catch( const std::exception& e ) + catch (const std::exception& e) { gotException = true; - g_Log.CatchStdException(&e, "[TR] ExcType=std::exception in %s::tick", getName()); + g_Log.CatchStdException(&e, "[TR] std::exception in %s::tick", getName()); GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); } - catch( ... ) - { - gotException = true; - g_Log.CatchEvent(nullptr, "[TR] ExcType=pure in %s::tick", getName()); - GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); - } - - if( gotException ) - { - if( lastWasException ) - { - ++exceptions; - } - else - { - lastWasException = true; - exceptions = 0; - } - if( exceptions >= EXCEPTIONS_ALLOWED ) - { - // a bad thing really happened. ALL previous EXCEPTIONS_ALLOWED ticks resulted in exception - // almost for sure we have looped somewhere and have no way to get out from this situation - // probably a thread restart can fix the problems - // but there is no real need to restart a thread, we will just simulate a thread restart, - // notifying a subclass like we have been just restarted, so it will restart it's operations - g_Log.Event(LOGL_CRIT, "'%s' thread raised too many exceptions, restarting...\n", getName()); - onStart(); - lastWasException = false; - } - } - else - { - lastWasException = false; - } - - if( shouldExit() ) - break; - - m_sleepEvent->wait(m_tickPeriod); - } + catch (...) + { + gotException = true; + g_Log.CatchEvent(nullptr, "[TR] Unknown exception in %s::tick", getName()); + GetCurrentProfileData().Count(PROFILE_STAT_FAULTS, 1); + } + + if (gotException) + { + if (lastWasException) + ++exceptions; + else + { + lastWasException = true; + exceptions = 1; + } + + if (exceptions >= THREAD_EXCEPTIONS_ALLOWED) + { + g_Log.Event(LOGL_CRIT, "'%s' raised too many exceptions, soft-restarting...\n", getName()); + onStart(); + lastWasException = false; + } + } + else + { + lastWasException = false; + exceptions = 0; + } + + if (shouldExit()) + break; + + m_sleepEvent->wait(m_uiTickPeriod); + } + + m_uiState = eRunningState::Closing; } -SPHERE_THREADENTRY_RETNTYPE AbstractThread::runner(void *callerThread) +SPHERE_THREADENTRY_RETNTYPE SPHERE_THREADENTRY_CALLTYPE AbstractThread::runner(void *callerThread) { - // If the caller thread is an AbstractThread, call run, which starts it and make it enter in its main loop - AbstractThread * caller = reinterpret_cast(callerThread); - if (caller != nullptr) - { - caller->run(); - caller->terminate(true); - } - + auto* caller = reinterpret_cast(callerThread); + if (caller) + { + caller->run(); + caller->terminate(true); + } #ifdef _WIN32 return 0; #else - return nullptr; + return nullptr; #endif } + bool AbstractThread::isActive() const { - return m_handle.has_value(); + const bool fRet = m_handle.has_value(); + return fRet; +} + +bool AbstractThread::isClosing() const +{ + return (m_uiState == eRunningState::Closing); +} + +bool AbstractThread::checkStuck() +{ + if (!isActive()) + return false; + + if (m_uiHangCheck == 0) + m_uiHangCheck = 0xDEAD; + else if (m_uiHangCheck == 0xDEAD) + m_uiHangCheck = 0xDEADDEAD; + else + { + g_Log.Event(LOGL_CRIT, "'%s' hang detected, restarting thread...\n", m_name); + + m_fTerminateRequested = true; + awaken(); + waitForClose(); + + start(); + return true; + } + return false; } void AbstractThread::waitForClose() { - // Another thread has requested us to close and it's waiting for us to complete the current tick, - // or to forcefully be forcefully terminated after a THREADJOIN_TIMEOUT, which of the two happens first. - - if (isActive()) - { - if (isCurrentThread() == false) - { - // flag that we want the thread to terminate - m_terminateRequested = true; - awaken(); - - // give the thread a chance to close on its own, and then - // terminate anyway - m_terminateEvent->wait(THREADJOIN_TIMEOUT); - } - - terminate(false); - } + if (!isActive()) + return; + + if (!isCurrentThread()) + { + m_fTerminateRequested = true; + awaken(); + + m_terminateEvent->wait(THREAD_JOIN_TIMEOUT); + terminate(false); + } } void AbstractThread::awaken() @@ -626,473 +744,381 @@ void AbstractThread::awaken() bool AbstractThread::isCurrentThread() const noexcept { #ifdef _WIN32 - return (getId() == ::GetCurrentThreadId()); -#else - return m_handle.has_value() && pthread_equal(m_handle.value(), pthread_self()); + static_assert(sizeof(threadid_t) == sizeof(DWORD)); #endif -} - -bool AbstractThread::checkStuck() -{ - if( isActive() ) - { - if( m_hangCheck == 0 ) - { - // initiate hang check - m_hangCheck = 0xDEAD; - } - else if( m_hangCheck == 0xDEAD ) - { - // one time period was not answered, wait a bit more - m_hangCheck = 0xDEADDEADl; - // TODO: - //g_Log.Event(LOGL_CRIT, "'%s' thread seems being hang (frozen) at '%s'?\n", m_name, m_action); - } - else if( m_hangCheck == 0xDEADDEADl ) - { - // TODO: really ugly static_cast... - g_Log.Event(LOGL_CRIT, "'%s' thread hang, restarting...\n", m_name); - #ifdef THREAD_TRACK_CALLSTACK - static_cast(this)->printStackTrace(); - #endif - terminate(false); - run(); - start(); - return true; - } - } - - return false; + return is_same_thread_id(getId(), static_cast(os_current_tid())); } void AbstractThread::onStart() { - // start-up actions for each thread - // when implemented in derived classes this method must always be called too, preferably before - // the custom implementation + // Mark registration window so diagnostics don’t warn while TLS is not yet published. + sg_tlsBindingInProgress = true; + + // Capture OS thread id first. + m_threadSystemId = static_cast(os_current_tid()); - // we set the id here to ensure it is available before the first tick, otherwise there's - // a small delay when setting it from AbstractThread::start and it's possible for the id - // to not be set fast enough (particular when using pthreads) - m_threadSystemId = getCurrentThreadSystemId(); + // Try to register in the holder. + ThreadHolder& th = ThreadHolder::get(); + th.push(this); - ThreadHolder::get().push(this); + // If push was refused (e.g., this OS thread already owned), leave clean. + if (m_threadHolderId == ThreadHolder::m_kiInvalidThreadID) + { + m_threadSystemId = 0; + sg_tlsBindingInProgress = false; + EXC_NOTIFY_DEBUGGER; + return; + } - if (isActive()) // This thread has actually been spawned and the code is executing on a different thread - setThreadName(getName()); + // Mark started and publish TLS fast-path. + th.markThreadStarted(this); + sg_tlsCurrentSphereThread = dynamic_cast(this); #ifdef _DEBUG - g_Log.Event(LOGM_DEBUG|LOGL_EVENT|LOGF_CONSOLE_ONLY, - "Started thread '%s' with ThreadHolder ID %d and system ID %" PRIu64 ".\n", - getName(), m_threadHolderId, (uint64)m_threadSystemId); + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "Started thread loop for '%s' (ThreadHolder-ID %d), sys-id %" PRIu64 ".\n", + getName(), m_threadHolderId, (uint64)m_threadSystemId); #endif + + // End of the small registration window. + sg_tlsBindingInProgress = false; } + void AbstractThread::setPriority(ThreadPriority pri) { ASSERT(((pri >= ThreadPriority::Idle) && (pri <= ThreadPriority::RealTime)) || (pri == ThreadPriority::Disabled)); - m_priority = pri; - - // detect a sleep period for thread depending on priority - switch( m_priority ) - { - case ThreadPriority::Idle: - m_tickPeriod = 1000; - break; - case ThreadPriority::Low: - m_tickPeriod = 200; - break; - case ThreadPriority::Normal: - m_tickPeriod = 100; - break; - case ThreadPriority::High: - m_tickPeriod = 50; - break; - case ThreadPriority::Highest: - m_tickPeriod = 5; - break; - case ThreadPriority::RealTime: - m_tickPeriod = 0; - break; - case ThreadPriority::Disabled: - m_tickPeriod = AutoResetEvent::_kiInfinite; - break; + m_priority = pri; + + switch (m_priority) + { + case ThreadPriority::Idle: m_uiTickPeriod = 1000; break; + case ThreadPriority::Low: m_uiTickPeriod = 200; break; + case ThreadPriority::Normal: m_uiTickPeriod = 100; break; + case ThreadPriority::High: m_uiTickPeriod = 50; break; + case ThreadPriority::Highest: m_uiTickPeriod = 5; break; + case ThreadPriority::RealTime: m_uiTickPeriod = 0; break; + case ThreadPriority::Disabled: m_uiTickPeriod = AutoResetEvent::_kiInfinite; break; } } bool AbstractThread::shouldExit() noexcept { - return closing() || m_terminateRequested || _thread_selfTerminateAfterThisTick; + return (m_uiState == eRunningState::Closing) + || m_fTerminateRequested + || m_fThreadSelfTerminateAfterThisTick; } void AbstractThread::setThreadName(const char* name) { - // register the thread name - - // Unix uses prctl to set thread name - // thread name must be 16 bytes, zero-padded if shorter - char name_trimmed[m_nameMaxLength] = { '\0' }; // m_nameMaxLength = 16 + char name_trimmed[m_nameMaxLength] = {'\0'}; Str_CopyLimitNull(name_trimmed, name, m_nameMaxLength); -#if defined(_WIN32) -#if defined(MSVC_COMPILER) - // TODO: support thread naming when compiling with compilers other than Microsoft's - - // Windows uses THREADNAME_INFO structure to set thread name -#pragma pack(push, 8) - typedef struct tagTHREADNAME_INFO - { - DWORD dwType; - LPCSTR szName; - DWORD dwThreadID; - DWORD dwFlags; - } THREADNAME_INFO; -#pragma pack(pop) - static constexpr DWORD MS_VC_EXCEPTION = 0x406D1388; - - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = name_trimmed; - info.dwThreadID = (DWORD)(-1); - info.dwFlags = 0; - - __try - { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } -#endif // MSVC_COMPILER -#elif defined(__APPLE__) // Mac - pthread_setname_np(name_trimmed); -#elif !defined(_BSD) // Linux - prctl(PR_SET_NAME, name_trimmed, 0, 0, 0); -#elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(getCurrentThreadId(), name_trimmed); -#elif defined(__NetBSD__) - pthread_setname_np(getCurrentThreadId(), "%s", name_trimmed); -#endif - - auto athr = static_cast(ThreadHolder::get().current()); - ASSERT(athr); - -#ifdef _DEBUG - g_Log.Event(LOGF_CONSOLE_ONLY|LOGM_DEBUG|LOGL_EVENT, - "Setting thread (ThreadHolder ID %d, internal name '%s') system name: '%s'.\n", - athr->m_threadHolderId, athr->getName(), name_trimmed); -#endif - athr->overwriteInternalThreadName(name_trimmed); -} - - -/* - * AbstractSphereThread -*/ -AbstractSphereThread::AbstractSphereThread(const char *name, ThreadPriority priority) - : AbstractThread(name, priority) -#ifdef THREAD_TRACK_CALLSTACK - , m_stackInfo{}, m_stackInfoCopy{}, m_iStackPos(-1), - m_fFreezeCallStack(false), - m_iStackUnwindingStackPos(-1), m_iCaughtExceptionStackPos(-1) -#endif -{ - // profiles that apply to every thread - m_profile.EnableProfile(PROFILE_IDLE); - m_profile.EnableProfile(PROFILE_OVERHEAD); - m_profile.EnableProfile(PROFILE_STAT_FAULTS); -} + os_set_thread_name_portable(name_trimmed); -AbstractSphereThread::~AbstractSphereThread() -{ - AbstractThread::_fIsClosing = true; + // Update internal name only if a context exists + if (auto* cur = ThreadHolder::current()) + if (auto* athr = dynamic_cast(cur)) + athr->overwriteInternalThreadName(name_trimmed); } - -static auto getThreadRawStringBuffer() -> TemporaryStringsThreadSafeStateHolder::TemporaryStringStorage * +void AbstractThread::attachToCurrentThread(const char* osThreadName) noexcept { - auto& tsholder = TemporaryStringsThreadSafeStateHolder::get(); - SimpleThreadLock stlBuffer(tsholder.g_tmpCStringMutex); + lpctstr ptcThreadName = (osThreadName && osThreadName[0]) ? osThreadName : getName(); - int initialPosition = tsholder.g_tmpTemporaryStringIndex; - int index; - for (;;) + // Refuse if another Sphere context already owns this OS thread. + if (sg_tlsCurrentSphereThread && sg_tlsCurrentSphereThread != this) { - index = tsholder.g_tmpTemporaryStringIndex += 1; - if(tsholder.g_tmpTemporaryStringIndex >= THREAD_TEMPSTRING_OBJ_STORAGE ) - { - const int inc = tsholder.g_tmpTemporaryStringIndex % THREAD_TEMPSTRING_OBJ_STORAGE; - tsholder.g_tmpTemporaryStringIndex = inc; - index = inc; - } + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "attachToCurrentThread refused: current OS thread already bound to '%s'; wanted '%s'.\n", + sg_tlsCurrentSphereThread->getName(), ptcThreadName); + return; + } - if(tsholder.g_tmpTemporaryStringStorage[index].m_state == 0 ) - { - auto* store = &(tsholder.g_tmpTemporaryStringStorage[index]); - *store->m_buffer = '\0'; - return store; - } + g_Log.Event(LOGM_DEBUG | LOGL_EVENT | LOGF_CONSOLE_ONLY, + "Binding current context to thread '%s'...\n", ptcThreadName); - // a protection against deadlock. All string buffers are marked as being used somewhere, so we - // have few possibilities (the case shows that we have a bug and temporary strings used not such): - // a) return nullptr and wait for exceptions in the program - // b) allocate a string from a heap - if( initialPosition == index ) - { - // but the best is to throw an exception to give better formed information for end users - // rather than access violations - DEBUG_WARN(( "Thread temporary string buffer is full.\n" )); - throw CSError(LOGL_FATAL, 0, "Thread temporary string buffer is full"); - } - } + // Attempt to bind; onStart will self-guard and leave this object clean on refusal. + onStart(); + + // Only set the OS-visible name if registration succeeded. + if (m_threadHolderId != ThreadHolder::m_kiInvalidThreadID) + setThreadName(ptcThreadName); } -char *AbstractSphereThread::Strings::allocateBuffer() noexcept -{ - auto& tsholder = TemporaryStringsThreadSafeStateHolder::get(); - SimpleThreadLock stlBuffer(tsholder.g_tmpCStringMutex); - char * buffer = nullptr; - tsholder.g_tmpCStringIndex += 1; +void AbstractThread::detachFromCurrentThread() noexcept +{ + if (auto* s = dynamic_cast(this)) + if (sg_tlsCurrentSphereThread == s) + sg_tlsCurrentSphereThread = nullptr; - if (tsholder.g_tmpCStringIndex >= THREAD_TEMPSTRING_C_STORAGE ) - { - tsholder.g_tmpCStringIndex = tsholder.g_tmpCStringIndex % THREAD_TEMPSTRING_C_STORAGE; - } + // Keep m_threadSystemId as historical OS TID; invalidation is done via m_threadHolderId. + ThreadHolder::get().remove(this); +} - //buffer = tsholder->g_tmpStrings.get() + (tsholder->g_tmpStringIndex * THREAD_STRING_LENGTH); - if (!tsholder.g_tmpCStrings) { - // I shouldn't even try to do this. Maybe i'm at the end of server shutdown. - RaiseImmediateAbort(20); - //return nullptr; - } - buffer = &(tsholder.g_tmpCStrings[tsholder.g_tmpCStringIndex * THREAD_TEMPSTRING_C_STORAGE]); - *buffer = '\0'; - return buffer; -} +// ----- AbstractSphereThread ----- -void AbstractSphereThread::Strings::getBufferForStringObject(TemporaryString &string) noexcept +AbstractSphereThread::AbstractSphereThread(const char *name, ThreadPriority priority) + : AbstractThread(name, priority) + , m_pExpr{std::make_unique()} { - ADDTOCALLSTACK("alloc"); - auto& tsholder = TemporaryStringsThreadSafeStateHolder::get(); - SimpleThreadLock stlBuffer(tsholder.g_tmpTemporaryStringMutex); - - auto* store = getThreadRawStringBuffer(); - string.init(store->m_buffer, &store->m_state); + m_profile.EnableProfile(PROFILE_IDLE); + m_profile.EnableProfile(PROFILE_OVERHEAD); + m_profile.EnableProfile(PROFILE_STAT_FAULTS); } - -bool AbstractSphereThread::shouldExit() noexcept +AbstractSphereThread::~AbstractSphereThread() { - if ( g_Serv.GetExitFlag() != 0 ) - return true; + m_uiState = eRunningState::Closing; - return AbstractThread::shouldExit(); + if (sg_tlsCurrentSphereThread == this) + sg_tlsCurrentSphereThread = nullptr; } -#ifdef THREAD_TRACK_CALLSTACK +#if defined THREAD_TRACK_CALLSTACK && defined _EXCEPTIONS_DEBUG void AbstractSphereThread::signalExceptionCaught() noexcept { - if (m_iStackPos < 0 || (m_iStackPos >= (ssize_t)ARRAY_COUNT(m_stackInfo))) + if (m_iStackPos < 0) return; - m_iCaughtExceptionStackPos = std::max(m_iStackPos, m_iCaughtExceptionStackPos); + m_iCaughtExceptionStackPos = std::max(m_iStackPos, m_iCaughtExceptionStackPos); printStackTrace(); - memset(m_stackInfoCopy, 0, sizeof(m_stackInfo)); - m_iCaughtExceptionStackPos = -1; - m_iStackUnwindingStackPos = -1; + std::memset(m_stackInfoCopy, 0, sizeof(m_stackInfoCopy)); + m_iCaughtExceptionStackPos = -1; + m_iStackUnwindingStackPos = -1; } void AbstractSphereThread::signalExceptionStackUnwinding() noexcept { - //ASSERT(isCurrentThread()); - if (m_iStackPos < 0 || (m_iStackPos >= (ssize_t)ARRAY_COUNT(m_stackInfo))) + if (m_iStackPos < 0) return; + m_iStackUnwindingStackPos = std::max(m_iStackPos, m_iStackUnwindingStackPos); - memcpy(m_stackInfoCopy, m_stackInfo, sizeof(m_stackInfo)); + std::memcpy(m_stackInfoCopy, m_stackInfo, sizeof(m_stackInfo)); } -static thread_local ssize_t _stackpos = -1; void AbstractSphereThread::pushStackCall(const char *name) noexcept { - if (m_fFreezeCallStack == true) [[unlikely]] { + if (m_fFreezeCallStack) return; - } #ifdef _DEBUG - if (m_iStackPos < -1) [[unlikely]] { + if (m_iStackPos < -1) RaiseImmediateAbort(16); - } - if (m_iStackPos >= (ssize_t)ARRAY_COUNT(m_stackInfo)) [[unlikely]] { + if (m_iStackPos >= (ssize_t)ARRAY_COUNT(m_stackInfo) - 1) RaiseImmediateAbort(17); - } #endif ++m_iStackPos; - _stackpos = m_iStackPos; m_stackInfo[m_iStackPos].functionName = name; } void AbstractSphereThread::popStackCall() NOEXCEPT_NODEBUG { - if (m_fFreezeCallStack == true) + if (m_fFreezeCallStack) return; - --m_iStackPos; - _stackpos = m_iStackPos; - DEBUG_ASSERT(m_iStackPos >= -1); - +#ifdef _DEBUG + ASSERT(m_iStackPos >= 0); +#endif + if (m_iStackPos >= 0) + { + m_stackInfo[m_iStackPos].functionName = nullptr; + --m_iStackPos; + } } void AbstractSphereThread::printStackTrace() noexcept { - // don't allow call stack to be modified whilst we're printing it - freezeCallStack(true); + freezeCallStack(true); - const uint64_t threadId = static_cast(getId()); - const lpctstr threadName = getName(); - - //EXC_NOTIFY_DEBUGGER; + uint64_t threadId = //static_cast(getId()); +#if defined(_WIN32) || defined(__APPLE__) + static_cast(getId() ? getId() : os_current_tid()); +#else + reinterpret_cast(getId() ? getId() : os_current_tid()); +#endif + const lpctstr threadName = getName(); auto& stackInfo = (m_stackInfoCopy[0].functionName != nullptr) ? m_stackInfoCopy : m_stackInfo; - g_Log.EventDebug("Printing STACK TRACE for debugging purposes.\n"); - g_Log.EventDebug(" ______ thread (id) name _____ | # | _____________ function _____________ |\n"); - for ( ssize_t i = 0; i < (ssize_t)ARRAY_COUNT(m_stackInfoCopy); ++i ) - { - if( stackInfo[i].functionName == nullptr ) - break; + g_Log.EventDebug("Printing STACK TRACE for debugging purposes.\n"); + g_Log.EventDebug(" _ thread (id) name _ | # | _____________ function _____________ |\n"); + + for (ssize_t i = 0; i < (ssize_t)ARRAY_COUNT(m_stackInfoCopy); ++i) + { + if (stackInfo[i].functionName == nullptr) + break; lpctstr extra = ""; - if (i == m_iStackUnwindingStackPos) { - extra = "<-- last function call (stack unwinding detected here)"; - } - else if (i == m_iCaughtExceptionStackPos) { - if (m_iStackUnwindingStackPos == -1) - extra = "<-- exception catch point (below is guessed and could be incorrect!)"; - else - extra = "<-- exception catch point"; - } - /* - const bool origin = (i == (m_iStackPos - 1)); - if (origin) - { - if (m_uisignalExceptionStackUnwinding) - extra = "<-- last function call (stack unwinding began here)"; - else - extra = "<-- exception catch point (below is guessed and could be incorrect!)"; - } - */ + if (i == m_iStackUnwindingStackPos) + extra = "<-- last tracked function call (stack unwinding detected here)"; + else if (i == m_iCaughtExceptionStackPos) + extra = (m_iStackUnwindingStackPos == -1) + ? "<-- exception catch point (below is guessed and could be incorrect!)" + : "<-- exception catch point"; g_Log.EventDebug("(%" PRIx64 ") %15.15s | %3u | %36.36s | %s\n", threadId, threadName, (uint)i, stackInfo[i].functionName, extra); if (i == m_iStackUnwindingStackPos) - { - // Stop logging/writing functions called after the exception throw... break; - } - } + } - freezeCallStack(false); + freezeCallStack(false); } #endif -/* - * DummySphereThread -*/ -DummySphereThread *DummySphereThread::_instance = nullptr; +bool AbstractSphereThread::shouldExit() noexcept +{ + if (g_Serv.GetExitFlag() != 0) + return true; + return AbstractThread::shouldExit(); +} + +// ----- DummySphereThread ----- DummySphereThread::DummySphereThread() : AbstractSphereThread("dummy", ThreadPriority::Normal) { } -void DummySphereThread::createInstance() // static +void DummySphereThread::createInstance() { - // This dummy thread is created at the very beginning of the application startup. - // Before the server becomes operational, this won't be used anymore, since fully functional threads will be created. - - // Create this only once, it has to be one of the first operations to be done when the application starts. - ASSERT(_instance == nullptr); - _instance = new DummySphereThread(); + ASSERT(_instance == nullptr); + _instance = new DummySphereThread(); } -DummySphereThread *DummySphereThread::getInstance() noexcept // static +DummySphereThread* DummySphereThread::getInstance() noexcept { - return _instance; + return _instance; } void DummySphereThread::tick() { + // No-op } +// ----- Temporary string pool helpers ----- -/* -* StackDebugInformation -*/ +static TemporaryStringsThreadSafeStateHolder::TemporaryStringStorage* +getThreadRawStringBuffer() CANTHROW +{ + auto& tsholder = TemporaryStringsThreadSafeStateHolder::get(); + SimpleThreadLock stlBuffer(tsholder.g_tmpTemporaryStringMutex); -#ifdef THREAD_TRACK_CALLSTACK + const int initialPosition = static_cast(tsholder.g_tmpTemporaryStringIndex.load(std::memory_order_relaxed)); + int index = initialPosition; + + for (;;) + { + index = static_cast(++tsholder.g_tmpTemporaryStringIndex); + if (index >= (int)THREAD_TEMPSTRING_OBJ_STORAGE) + { + index %= THREAD_TEMPSTRING_OBJ_STORAGE; + tsholder.g_tmpTemporaryStringIndex.store(index, std::memory_order_relaxed); + } + + auto* store = &(tsholder.g_tmpTemporaryStringStorage[index]); + if (store->m_state == 0) + { + store->m_state = 1; + store->m_buffer[0] = '\0'; + return store; + } + + if (index == initialPosition) + { + DEBUG_WARN(("Thread temporary string buffer is full.\n")); + throw CSError(LOGL_FATAL, 0, "Thread temporary string buffer is full"); + } + } +} + +char* AbstractSphereThread::Strings::allocateBuffer() noexcept +{ + auto& tsholder = TemporaryStringsThreadSafeStateHolder::get(); + SimpleThreadLock stlBuffer(tsholder.g_tmpCStringMutex); + + uint index = ++tsholder.g_tmpCStringIndex; + if (index >= THREAD_TEMPSTRING_C_STORAGE) + { + index %= THREAD_TEMPSTRING_C_STORAGE; + tsholder.g_tmpCStringIndex.store(index, std::memory_order_relaxed); + } + + if (!tsholder.g_tmpCStrings) + RaiseImmediateAbort(20); + + char* buffer = &(tsholder.g_tmpCStrings[index * THREAD_STRING_LENGTH]); + buffer[0] = '\0'; + return buffer; +} + +void AbstractSphereThread::Strings::getBufferForStringObject(TemporaryString &string) CANTHROW +{ + ADDTOCALLSTACK("AbstractSphereThread::Strings::alloc"); + auto* store = getThreadRawStringBuffer(); // may throw + string.init(store->m_buffer, &store->m_state); +} +// ----- StackDebugInformation (RAII) ----- +#ifdef THREAD_TRACK_CALLSTACK StackDebugInformation::StackDebugInformation(const char *name) noexcept - : m_context(nullptr) + : m_context(nullptr) { - STATIC_ASSERT_NOEXCEPT_CONSTRUCTOR(StackDebugInformation, const char*); - STATIC_ASSERT_NOEXCEPT_MEMBER_FUNCTION(AbstractSphereThread, pushStackCall, const char*); - STATIC_ASSERT_NOEXCEPT_FREE_FUNCTION(ThreadHolder::get); - STATIC_ASSERT_NOEXCEPT_MEMBER_FUNCTION(ThreadHolder, current); + // Fast path: TLS + if (sg_tlsCurrentSphereThread && !sg_tlsCurrentSphereThread->isClosing()) + { + m_context = sg_tlsCurrentSphereThread; + m_context->pushStackCall(name); + return; + } + + // If a thread is in the middle of binding, don’t warn about missing context. + if (sg_tlsBindingInProgress) + return; - auto& th = ThreadHolder::get(); - if (th.closing()) [[unlikely]] + // Fallback: use current() + AbstractThread *icontext = ThreadHolder::current(); + if (!icontext) return; - AbstractThread *icontext = th.current(); - if (icontext == nullptr) - [[unlikely]] - { - // Thread was deleted, manually or by app closing signal. - return; - } - - m_context = static_cast(icontext); - if (m_context != nullptr && !m_context->closing()) - { - [[unlikely]] - m_context->pushStackCall(name); - } + m_context = dynamic_cast(icontext); + if (m_context && !m_context->isClosing()) + m_context->pushStackCall(name); } StackDebugInformation::~StackDebugInformation() noexcept { - if (!m_context || m_context->closing()) [[unlikely]] - return; + if (!m_context || m_context->isClosing()) + return; if (!m_context->isExceptionStackUnwinding()) { - if (std::uncaught_exceptions() != 0) [[unlikely]] - { - // Exception was thrown and stack unwinding is in progress. + if (std::uncaught_exceptions() != 0) m_context->signalExceptionStackUnwinding(); - } } m_context->popStackCall(); } -void StackDebugInformation::printStackTrace() noexcept // static +void StackDebugInformation::printStackTrace() noexcept { - AbstractThread* pThreadState = ThreadHolder::get().current(); - if (pThreadState) - static_cast(pThreadState)->printStackTrace(); + AbstractThread* pThreadState = ThreadHolder::current(); + if (pThreadState) + if (auto* s = dynamic_cast(pThreadState)) + s->printStackTrace(); } -void StackDebugInformation::freezeCallStack(bool freeze) noexcept // static +void StackDebugInformation::freezeCallStack(bool freeze) noexcept { - AbstractThread* pThreadState = ThreadHolder::get().current(); - if (pThreadState) - static_cast(pThreadState)->freezeCallStack(freeze); + AbstractThread* pThreadState = ThreadHolder::current(); + if (pThreadState) + if (auto* s = dynamic_cast(pThreadState)) + s->freezeCallStack(freeze); } - -#endif // THREAD_TRACK_CALLSTACK +#endif diff --git a/src/sphere/threads.h b/src/sphere/threads.h index 4f5701229..30a643cab 100644 --- a/src/sphere/threads.h +++ b/src/sphere/threads.h @@ -1,201 +1,122 @@ /** -* @file threads.h -* -*/ + * @file threads.h + */ #ifndef _INC_THREADS_H #define _INC_THREADS_H #include "../common/common.h" #include "../sphere/ProfileData.h" + #include +#include +#include +#include #include #ifndef _WIN32 - #include +#include #endif +class CExpression; class TemporaryString; - -/** - * Sphere threading system - * Threads should be inherited from AbstractThread with overridden tick() method - * Also useful to override onStart() in order to initialise class data variables for ticking - * which is triggered whenever the thread is starting/restarting -**/ - -// Types definition for different platforms -#ifdef _WIN32 - typedef HANDLE spherethread_t; - typedef DWORD threadid_t; - #define SPHERE_THREADENTRY_RETNTYPE unsigned - #define SPHERE_THREADENTRY_CALLTYPE __stdcall - #define SPHERE_THREADT_NULL nullptr -#else - typedef pthread_t spherethread_t; -# ifdef __APPLE__ - typedef uint64_t threadid_t; -# else - typedef pthread_t threadid_t; -#endif - - #define SPHERE_THREADENTRY_RETNTYPE void * - #define SPHERE_THREADENTRY_CALLTYPE -#endif - -class AbstractThread; class AutoResetEvent; class ManualResetEvent; /* -// stores a value unique to each thread, intended to hold -// a pointer (e.g. the current AbstractThread instance) -template -class TlsValue -{ -private: -#ifdef _WIN32 - dword _key; -#else - pthread_key_t _key; -#endif - bool _ready; - -public: - TlsValue(); - ~TlsValue(); - - TlsValue(const TlsValue& copy) = delete; - TlsValue& operator=(const TlsValue& other) = delete; - -public: - // allows assignment to set the current value - TlsValue& operator=(const T& value) - { - set(value); - return *this; - } - - // allows a cast to get current value - operator T() const { return get(); } - -public: - void set(const T value); // set the value for the current thread - T get() const; // get the value for the current thread -}; - -template -TlsValue::TlsValue() -{ - // allocate thread storage + * Platform types for OS thread identity and entry points. + */ #ifdef _WIN32 - _key = TlsAlloc(); - _ready = (_key != TLS_OUT_OF_INDEXES); +#include +typedef HANDLE spherethread_t; +typedef DWORD threadid_t; +#define SPHERE_THREADENTRY_RETNTYPE unsigned +#define SPHERE_THREADENTRY_CALLTYPE __stdcall +#define SPHERE_THREADT_NULL nullptr #else - _key = 0; - _ready = (pthread_key_create(&_key, nullptr) == 0); -#endif -} - -template -TlsValue::~TlsValue() -{ - // free the thread storage - if (_ready) -#ifdef _WIN32 - TlsFree(_key); +//#include +#include +typedef pthread_t spherethread_t; +#ifdef __APPLE__ +typedef uint64_t threadid_t; // from pthread_threadid_np #else - pthread_key_delete(_key); +typedef pthread_t threadid_t; #endif - _ready = false; -} - -template -void TlsValue::set(const T value) -{ - ASSERT(_ready); -#ifdef _WIN32 - TlsSetValue(_key, value); -#else - pthread_setspecific(_key, value); +#define SPHERE_THREADENTRY_RETNTYPE void* +#define SPHERE_THREADENTRY_CALLTYPE #endif -} - -template -T TlsValue::get() const -{ - if (_ready == false) - return nullptr; -#ifdef _WIN32 - return reinterpret_cast(TlsGetValue(_key)); -#else - return reinterpret_cast(pthread_getspecific(_key)); -#endif -} -*/ +/* + * Priority → tick cadence mapping (ms). + */ enum class ThreadPriority : int { - Idle, // tick 1000ms - Low, // tick 200ms - Normal, // tick 100ms - High, // tick 50ms - Highest, // tick 5ms - RealTime, // tick almost instantly - Disabled = 0xFF // tick never + Idle, + Low, + Normal, + High, + Highest, + RealTime, + Disabled = 0xFF }; -// Thread base implementation, without Sphere "extensions". class AbstractThread { friend class ThreadHolder; static int m_threadsAvailable; + public: - static constexpr uint m_nameMaxLength = 16; // Unix support a max 16 bytes thread name. + static constexpr uint m_nameMaxLength = 16; // OS limits for linux/bsd pthread names protected: - threadid_t m_threadSystemId; - int m_threadHolderId; - - bool _fKeepAliveAtShutdown; - volatile std::atomic_bool _thread_selfTerminateAfterThisTick; - volatile std::atomic_bool _fIsClosing; + // TODO: move at least m_threadHolderId to AbstractSphereThread, + // since it should theoretically be used only by Sphere custom thread class/AbstractSphereThread? + threadid_t m_threadSystemId{0}; // OS thread id for this object (0 => not bound) + int m_threadHolderId{-1}; // logical id in ThreadHolder + + bool m_fKeepAliveAtShutdown{false}; + bool m_fThreadSelfTerminateAfterThisTick{false}; + bool m_fTerminateRequested{false}; + + enum class eRunningState : uchar { + NeverStarted, + Running, + Closing + }; + std::atomic m_uiState{eRunningState::NeverStarted}; - volatile std::atomic_bool m_terminateRequested; private: - char m_name[30]; - - // pthread_t type is opaque (platform-defined). It can be an integer, a struct, a ptr something. Memset is the safest and more portable way. - //spherethread_t m_handle; - // Or, since we need here an "invalid" value, just use optional. - std::optional m_handle; - - uint m_hangCheck; - ThreadPriority m_priority; - uint m_tickPeriod; - // PImpl - std::unique_ptr m_sleepEvent; + char m_name[30]{}; // internal name (OS name uses trimmed variant) + + std::optional m_handle; // present if start() spawned an OS thread + + uint m_uiHangCheck{0}; + ThreadPriority m_priority{ThreadPriority::Normal}; + uint m_uiTickPeriod{100}; + + std::unique_ptr m_sleepEvent; std::unique_ptr m_terminateEvent; public: AbstractThread(const char *name, ThreadPriority priority = ThreadPriority::Normal); - virtual ~AbstractThread(); + virtual ~AbstractThread(); - AbstractThread(const AbstractThread& copy) = delete; - AbstractThread& operator=(const AbstractThread& other) = delete; + AbstractThread(const AbstractThread&) = delete; + AbstractThread& operator=(const AbstractThread&) = delete; public: - threadid_t getId() const noexcept { return m_threadSystemId; } + threadid_t getId() const noexcept { return m_threadSystemId; } virtual const char *getName() const noexcept { return m_name; } - virtual bool isActive() const; - virtual bool checkStuck(); + bool isActive() const; + bool checkStuck(); + bool isClosing() const; virtual void start(); virtual void terminate(bool ended); virtual void waitForClose(); - void awaken(); + void awaken(); void setPriority(ThreadPriority pri); ThreadPriority getPriority() const { return m_priority; } @@ -203,152 +124,131 @@ class AbstractThread void overwriteInternalThreadName(const char* name) noexcept; bool isCurrentThread() const noexcept; - protected: +protected: virtual void tick() = 0; - // NOTE: this should not be too long-lasted function, so no world loading, etc here!!! + // Called on the OS thread that will execute tick(). virtual void onStart(); virtual bool shouldExit() noexcept; - private: +private: void run(); static SPHERE_THREADENTRY_RETNTYPE SPHERE_THREADENTRY_CALLTYPE runner(void *callerThread); - public: +public: static void setThreadName(const char* name); - bool closing() const noexcept - { - return _fIsClosing; - } - - static inline threadid_t getCurrentThreadSystemId() noexcept - { -#if defined(_WIN32) - return ::GetCurrentThreadId(); -#elif defined(__APPLE__) - // On OSX, 'threadid_t' is not an integer but a '_opaque_pthread_t *'), so we need to resort to another method. - uint64_t threadid = 0; - pthread_threadid_np(pthread_self(), &threadid); - return threadid; -#else - return pthread_self(); -#endif - } - static inline bool isSameThreadId(threadid_t firstId, threadid_t secondId) noexcept + // Inline binding helper: attach/detach without creating a new OS thread. + class ThreadBindingScope { -#if defined(_WIN32) || defined(__APPLE__) - return (firstId == secondId); -#else - return pthread_equal(firstId,secondId); -#endif - } + AbstractThread* m_pAbstractThread; + public: + explicit ThreadBindingScope(AbstractThread& t, const char* pcOsName = nullptr) noexcept + : m_pAbstractThread(&t) { m_pAbstractThread->attachToCurrentThread(pcOsName); } + ~ThreadBindingScope() noexcept { if (m_pAbstractThread) m_pAbstractThread->detachFromCurrentThread(); } + ThreadBindingScope(const ThreadBindingScope&) = delete; + ThreadBindingScope& operator=(const ThreadBindingScope&) = delete; + }; - inline bool isSameThread(threadid_t otherThreadId) const noexcept - { - return isSameThreadId(getCurrentThreadSystemId(), otherThreadId); - } + void attachToCurrentThread(const char* osThreadName = nullptr) noexcept; + void detachFromCurrentThread() noexcept; }; - -// Sphere thread. Have some sphere-specific class AbstractSphereThread : public AbstractThread { - friend class ThreadHolder; + friend class ThreadHolder; #ifdef THREAD_TRACK_CALLSTACK - struct STACK_INFO_REC - { - const char *functionName; - }; - - STACK_INFO_REC m_stackInfo[0x500]; - STACK_INFO_REC m_stackInfoCopy[0x500]; - ssize_t m_iStackPos; - bool m_fFreezeCallStack; - ssize_t m_iStackUnwindingStackPos; - ssize_t m_iCaughtExceptionStackPos; + struct STACK_INFO_REC { const char *functionName; }; + + STACK_INFO_REC m_stackInfo[0x500]{}; + STACK_INFO_REC m_stackInfoCopy[0x500]{}; + + ssize_t m_iStackPos{-1}; + ssize_t m_iStackUnwindingStackPos{-1}; + ssize_t m_iCaughtExceptionStackPos{-1}; + bool m_fFreezeCallStack{false}; #endif +public: + std::unique_ptr m_pExpr; + public: AbstractSphereThread(const char *name, ThreadPriority priority = ThreadPriority::Normal); - virtual ~AbstractSphereThread(); + virtual ~AbstractSphereThread(); - AbstractSphereThread(const AbstractSphereThread& copy) = delete; - AbstractSphereThread& operator=(const AbstractSphereThread& other) = delete; + AbstractSphereThread(const AbstractSphereThread&) = delete; + AbstractSphereThread& operator=(const AbstractSphereThread&) = delete; class Strings { friend class TemporaryString; friend tchar* Str_GetTemp() noexcept; - // allocates a char* with size of THREAD_MAX_LINE_LENGTH characters from the thread local storage static char *allocateBuffer() noexcept; - - // allocates a manageable String from the thread local storage - static void getBufferForStringObject(TemporaryString &string) noexcept; + static void getBufferForStringObject(TemporaryString &string) CANTHROW; }; public: #ifdef THREAD_TRACK_CALLSTACK void signalExceptionCaught() noexcept; void signalExceptionStackUnwinding() noexcept; - inline bool isExceptionCaught() const noexcept; - inline bool isExceptionStackUnwinding() const noexcept; - inline void freezeCallStack(bool freeze) noexcept; + inline bool isExceptionCaught() const noexcept { return (m_iCaughtExceptionStackPos >= 0); } + inline bool isExceptionStackUnwinding() const noexcept { return (m_iStackUnwindingStackPos >= 0); } + + inline void freezeCallStack(bool freeze) noexcept { m_fFreezeCallStack = freeze; } - void pushStackCall(const char *name) noexcept; - void popStackCall() NOEXCEPT_NODEBUG; + void pushStackCall(const char *name) noexcept; + void popStackCall() NOEXCEPT_NODEBUG; - void printStackTrace() noexcept; + void printStackTrace() noexcept; #endif - ProfileData m_profile; // the current active statistical profile. + ProfileData m_profile; protected: - virtual bool shouldExit() noexcept; + virtual bool shouldExit() noexcept; }; -bool AbstractSphereThread::isExceptionCaught() const noexcept -{ - return (m_iCaughtExceptionStackPos >= 0); -} - -bool AbstractSphereThread::isExceptionStackUnwinding() const noexcept -{ - return (m_iStackUnwindingStackPos >= 0); -} - -void AbstractSphereThread::freezeCallStack(bool freeze) noexcept -{ - m_fFreezeCallStack = freeze; -} - -// Dummy thread for context when no thread really exists. To be called only once, at startup. +/* + * DummySphereThread: + * - Exists at startup so code has a safe expression/call-stack context. + * - After entering Run, missing contexts return nullptr by policy. + */ class DummySphereThread : public AbstractSphereThread { private: - static DummySphereThread *_instance; + friend struct GlobalInitializer; + static DummySphereThread *_instance; public: - static void createInstance(); - static DummySphereThread *getInstance() noexcept; + static void createInstance(); + static DummySphereThread *getInstance() noexcept; protected: - DummySphereThread(); - virtual void tick(); + DummySphereThread(); + virtual void tick(); }; - -// Singleton utility class for working with threads. Holds all running threads inside. +/* + * ThreadHolder: registry and state flags. The “current()” hot path is fully inline and lock-free. + * + * Fast-path policy: + * - If TLS is set (g_tlsCurrentSphereThread != nullptr) → return it. O(1). + * - Else, if servClosing → return nullptr. + * - Else, if still in startup → return Dummy (if exists). + * - Else (Run mode) → return nullptr. + * + * Slow-path operations (push/remove/getThreadAt) use a mutex; NEVER log while holding it. + */ class ThreadHolder { friend class AbstractThread; struct SphereThreadData { - AbstractThread *m_ptr; + AbstractThread * m_ptr; bool m_closed; }; using spherethreadlist_t = std::vector; @@ -357,76 +257,78 @@ class ThreadHolder using spherethreadpair_t = std::pair; std::vector m_spherethreadpairs_systemid_ptr; - int m_threadCount; - volatile std::atomic_bool m_closingThreads; - mutable std::shared_mutex m_mutex; + int m_threadCount; + mutable std::shared_mutex m_mutex; - ThreadHolder() noexcept; + ThreadHolder() noexcept; ~ThreadHolder() noexcept = default; - friend void atexit_handler(void); - friend void Sphere_ExitServer(void); - void markThreadsClosing() CANTHROW; +public: + static constexpr lpctstr m_sClassName = "ThreadHolder"; + static constexpr int m_kiInvalidThreadID = -1; - //SphereThreadData* findThreadData(AbstractThread* thread) noexcept; + // Singleton instance for slow-path ops + static ThreadHolder& get() noexcept; -public: - static constexpr lpctstr m_sClassName = "ThreadHolder"; - - static ThreadHolder& get() noexcept; - - bool closing() noexcept; - // returns current working thread or DummySphereThread * if no AbstractThread threads are running - AbstractThread *current() noexcept; - // records a thread to the list. Sould NOT be called, internal usage - void push(AbstractThread *thread) noexcept; - // removes a thread from the list. Sould NOT be called, internal usage - void remove(AbstractThread *thread) CANTHROW; - // returns thread at i pos - AbstractThread * getThreadAt(size_t at) noexcept; - - // returns number of running threads. Sould NOT be called, unit tests usage - inline size_t getActiveThreads() noexcept { return m_threadCount; } -}; + // High-performance fast path for “current” thread lookup. + static AbstractThread *current() noexcept; + + // Record that the server entered Run mode (disable startup fallback in fast path). + static void markServEnteredRunMode() noexcept; + + // Record that threads are servClosing (fast-path observable). + static void markServClosing() noexcept; + static bool isServClosing() noexcept; + + // Slow-path registry ops + void push(AbstractThread *pAbstractThread) noexcept; + void remove(AbstractThread *thread) CANTHROW; + + AbstractThread * getThreadAt(size_t at) noexcept; + inline size_t getActiveThreads() noexcept { return static_cast(m_threadCount); } + + void markThreadStarted(AbstractThread* pThr) CANTHROW; + + // Helper to mark servClosing and set flags. + void markThreadsClosing() CANTHROW; + +private: + bool isSystemIdRegistered(threadid_t sysId, AbstractSphereThread** outExisting) const noexcept; + +}; -// used to hold debug information for the function call stack #ifdef THREAD_TRACK_CALLSTACK class StackDebugInformation { private: - AbstractSphereThread* m_context; + AbstractSphereThread* m_context; public: - StackDebugInformation(const char *name) noexcept; - ~StackDebugInformation() noexcept; + StackDebugInformation(const char *name) noexcept; + ~StackDebugInformation() noexcept; - StackDebugInformation(const StackDebugInformation& copy) = delete; - StackDebugInformation& operator=(const StackDebugInformation& other) = delete; + StackDebugInformation(const StackDebugInformation&) = delete; + StackDebugInformation& operator=(const StackDebugInformation&) = delete; public: - static void printStackTrace() noexcept; - static void freezeCallStack(bool freeze) noexcept; + static void printStackTrace() noexcept; + static void freezeCallStack(bool freeze) noexcept; }; -// Remember, call stack is disabled on Release builds! -#define ADDTOCALLSTACK(_function_) const StackDebugInformation debugStack(_function_) +#define ADDTOCALLSTACK(_function_) const StackDebugInformation debugStack(_function_) -// Add to the call stack these functions only in debug mode, to have the most precise call stack -// even if these functions are thought to be very safe and (nearly) exception-free. #ifdef _DEBUG - #define ADDTOCALLSTACK_DEBUG(_function_) ADDTOCALLSTACK(_function_) +#define ADDTOCALLSTACK_DEBUG(_function_) ADDTOCALLSTACK(_function_) #else - #define ADDTOCALLSTACK_DEBUG(_function_) (void)0 +#define ADDTOCALLSTACK_DEBUG(_function_) (void)0 #endif +#else // ! THREAD_TRACK_CALLSTACK -#else // THREAD_TRACK_CALLSTACK - -#define ADDTOCALLSTACK(_function_) (void)0 -#define ADDTOCALLSTACK_DEBUG(_function_) (void)0 - -#endif // THREAD_TRACK_CALLSTACK +#define ADDTOCALLSTACK(_function_) (void)0 +#define ADDTOCALLSTACK_DEBUG(_function_) (void)0 +#endif #endif // _INC_THREADS_H diff --git a/src/tables/classnames.tbl b/src/tables/classnames.tbl index c499f313e..3ac820be1 100644 --- a/src/tables/classnames.tbl +++ b/src/tables/classnames.tbl @@ -63,6 +63,7 @@ ADD(CClient, "CClient"); ADD(CDialogDef, "CDialogDef"); ADD(CDialogResponseArgs, "CDialogResponseArgs"); ADD(CEntity, "CEntity"); +ADD(CExpression, "CExpression"); ADD(CSFileObj, "CSFileObj"); ADD(CSFileObjContainer, "CSFileObjContainer"); ADD(CScriptObj, "CScriptObj"); diff --git a/src/tables/defmessages.tbl b/src/tables/defmessages.tbl index aa54e7be7..9838b04cc 100644 --- a/src/tables/defmessages.tbl +++ b/src/tables/defmessages.tbl @@ -155,7 +155,7 @@ MSG(HEALING_CORPSEG, "Put the corpse on the ground.") MSG(HEALING_CURE_1, "You cure %s of poisons!") MSG(HEALING_CURE_2, "%s has cured you of poisons!") MSG(HEALING_CURE_3, "You have failed to cure your target!") -MSG(HEALING_CURE_4, "The attempt to cure you has failed.") +MSG(HEALING_CURE_4, "The attempt to cure you has failed.") MSG(HEALING_GHOST, "You can't heal a ghost! Try healing their corpse.") MSG(HEALING_HEALTHY, "You are healthy") MSG(HEALING_INTERRUPT, "Your healing was interrupted by the hit!") @@ -390,7 +390,7 @@ MSG(MSG_ARRDEP_1, "%s has %s %s.") MSG(MSG_ARRDEP_2, "arrived in") MSG(MSG_ARRDEP_3, "departed from") MSG(MSG_BOUNCE_PACK, "in your pack") -MSG(MSG_BOUNCE_CONT, "in the %s") +MSG(MSG_BOUNCE_CONT, "in the %s") MSG(MSG_CANTPUSH, "You are not strong enough to push %s out of the way.") MSG(MSG_CANTSLEEP, "Can't sleep here") MSG(MSG_CMD_LACKPRIV, "You lack privilege to do this.") @@ -575,7 +575,6 @@ MSG(MSG_UNCONSCIOUS, "You are unconscious and can't move.") MSG(MSG_WHERE_AREA, "I am in %s (%s)") MSG(MSG_WHERE_ROOM, "I am in %s in %s (%s)") MSG(MSG_WHERE, "I am at %s.") -MSG(MSG_WRESTLING_NOGO, "Sorry wrestling moves not available yet") MSG(MSG_YOUNOTICE_1, "You notice %s %s %s.") MSG(MSG_YOUNOTICE_2, "You notice %s %s %s%s %s") MSG(MSG_YOUNOTICE_S, "'s") @@ -733,6 +732,7 @@ MSG(PARTY_ADDED, "You have been added to the party.") MSG(PARTY_DECLINE_1, "%s: Does not wish to join the party.") MSG(PARTY_DECLINE_2, "You notify %s that you do not wish to join the party.") MSG(PARTY_DISBANDED, "Your party has disbanded.") +MSG(PARTY_CHANGE_LEADER, "%s was appointed the new party leader.") MSG(PARTY_INVITE, "You have invited %s to join the party.") MSG(PARTY_INVITE_TARG, "%s: You are invited to join the party. Type /accept to join or /decline to decline the offer.") MSG(PARTY_JOINED, "%s: joined the party.") diff --git a/tests/coverity_model.cpp b/static_analysis/coverity_model.cpp similarity index 100% rename from tests/coverity_model.cpp rename to static_analysis/coverity_model.cpp diff --git a/static_analysis/cppcheck-suppressions.txt b/static_analysis/cppcheck-suppressions.txt new file mode 100644 index 000000000..79cbf544f --- /dev/null +++ b/static_analysis/cppcheck-suppressions.txt @@ -0,0 +1,29 @@ +# 1) All warnings in any subdir “lib” +*:*/lib/* + +# 2) Specific warnings everywhere +unmatchedSuppression:* +missingIncludeSystem:* +unreachableCode:* +unusedFunction:* +duplInheritedMember:* +missingOverride:* +virtualCallInConstructor:* +passedByValue:* +funcArgNamesDifferent:* +noExplicitConstructor:* +useStlAlgorithm:* +# Performance: Not a priority right now: +returnByReference:* +useInitializationList:* +# Style: Not a priority right now: +functionConst:* +constParameterPointer:* +constVariablePointer:* +functionStatic:* +unreadVariable:* +variableScope:* + +#3) False positives in some files +nullPointer:*/src/common/sphere_library/ssorted_vector.h +knownConditionTrueFalse:*/src/common/sphere_library/ssorted_vector.h diff --git a/static_analysis/run-clang-tidy-analyzer-only.sh b/static_analysis/run-clang-tidy-analyzer-only.sh new file mode 100644 index 000000000..c9c4f858b --- /dev/null +++ b/static_analysis/run-clang-tidy-analyzer-only.sh @@ -0,0 +1,2 @@ +#!/usr/bin/sh +clang-tidy -p ../build/compile_commands.json -checks='-*,clang-analyzer*' ../src/**/*.cpp ../src/**/*.h diff --git a/static_analysis/run-clang-tidy-config.sh b/static_analysis/run-clang-tidy-config.sh new file mode 100644 index 000000000..6c310e897 --- /dev/null +++ b/static_analysis/run-clang-tidy-config.sh @@ -0,0 +1,2 @@ +#!/usr/bin/sh +clang-tidy -p ../build/compile_commands.json -config-file='../.clang-tidy' ../src/**/*.cpp ../src/**/*.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..ad61028da --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,41 @@ +#set(FETCHCONTENT_BASE_DIR +# "${CMAKE_SOURCE_DIR}/third_party" +#) + +message(STATUS "") +message(STATUS "Unit Tests requested") + +include("cmake/FetchGithubDep.cmake") +set(EXTERNAL_CACHE_DIR "${CMAKE_SOURCE_DIR}/external") + +# Fetch Doctest +fetch_github_library(doctest "https://github.com/doctest/doctest.git" "${EXTERNAL_CACHE_DIR}") + +# Enable testing +enable_testing() + +function(setup_tests) + foreach(tgt ${TARGETS}) + target_link_libraries(${tgt} PRIVATE doctest::doctest) + target_compile_definitions(${tgt} PRIVATE UNIT_TESTING) + # Doctest for some reason uses NaN, which is disabled by -ffast-math + if("${CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(${tgt} PRIVATE /fp:precise) + else() + target_compile_options(${tgt} PRIVATE -fno-finite-math-only) + endif() + + #[[ + add_custom_target(run_tests + COMMAND spheresvr --reporters=console + DEPENDS spheresvr + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + ]] + + # Register with CTest + add_test(NAME all_tests_${tgt} + COMMAND ${tgt}) + endforeach() +endfunction() + diff --git a/tests/CMakeSources.cmake b/tests/CMakeSources.cmake new file mode 100644 index 000000000..c82e4cfac --- /dev/null +++ b/tests/CMakeSources.cmake @@ -0,0 +1,9 @@ +set(SPHERE_TESTS_SOURCES + tests/src/t_num_parsing.cpp + tests/src/t_CPointBase.cpp + tests/src/t_CUOClientVersion.cpp +) +source_group(tests FILES ${SPHERE_TESTS_SOURCES}) + +#set(SPHERE_HEADERS ${SPHERE_HEADERS} ${SPHERE_TESTS_HEADERS}) +set(SPHERE_SOURCES ${SPHERE_SOURCES} ${SPHERE_TESTS_SOURCES}) diff --git a/tests/src/t_CPointBase.cpp b/tests/src/t_CPointBase.cpp new file mode 100644 index 000000000..eddf6fd95 --- /dev/null +++ b/tests/src/t_CPointBase.cpp @@ -0,0 +1,176 @@ +#include +#include "../../src/common/CPointBase.h" +#include "../../src/game/uo_files/uofiles_macros.h" + +// Reference implementations, inside anonymous namespace to make clear that the functions are local to this translation unit. +namespace { namespace ref_impl +{ + DIR_TYPE GetDir(const CPointBase &ptMe, const CPointBase &ptOther, DIR_TYPE DirDefault = DIR_QTY ) + { + // Direction to point pt + const int dx = (ptMe.m_x-ptOther.m_x); + const int dy = (ptMe.m_y-ptOther.m_y); + const int ax = abs(dx); + const int ay = abs(dy); + if ( ay > ax ) + { + if ( ! ax ) + return(( dy > 0 ) ? DIR_N : DIR_S ); + + const int slope = ay / ax; + if ( slope > 2 ) + return(( dy > 0 ) ? DIR_N : DIR_S ); + if ( dx > 0 ) // westish + return(( dy > 0 ) ? DIR_NW : DIR_SW ); + return(( dy > 0 ) ? DIR_NE : DIR_SE ); + } + else + { + if ( ! ay ) + { + if ( ! dx ) + return( DirDefault ); // here ? + return(( dx > 0 ) ? DIR_W : DIR_E ); + } + const int slope = ax / ay; + if ( slope > 2 ) + return(( dx > 0 ) ? DIR_W : DIR_E ); + if ( dy > 0 ) + return(( dx > 0 ) ? DIR_NW : DIR_NE ); + return(( dx > 0 ) ? DIR_SW : DIR_SE ); + } + } + + bool IsValidPoint(CPointBase const& pt) noexcept + { + if (!((pt.m_z > -127 /* -UO_SIZE_Z */) && (pt.m_z < 127 /* UO_SIZE_Z */))) + return false; + if ( (pt.m_x < 0) || (pt.m_y < 0) ) + return false; + if ( (pt.m_x >= 7168 /*g_MapList.GetMapSizeX(pt.m_map)*/) + || (pt.m_y >= 4096 /*g_MapList.GetMapSizeY(pt.m_map)*/)) + return false; + return true; + } + + bool IsCharValid(CPointBase const& pt) noexcept + { + if ( (pt.m_z <= -UO_SIZE_Z) || (pt.m_z >= UO_SIZE_Z) ) + return false; + if ((pt.m_x <= 0) || (pt.m_y <= 0)) + return false; + if ( (pt.m_x >= 7168 /*g_MapList.GetMapSizeX(pt.m_map)*/) + || (pt.m_y >= 4096 /*g_MapList.GetMapSizeY(pt.m_map)*/)) + return false; + return true; + } +}} + +TEST_CASE("CPointBase::GetDir")\ +// clazy:exclude=non-pod-global-static +{ + const CPointBase pt(100, 100, 0, 0); + CPointBase ptOther; + //DIR_TYPE def = DIR_N; + + for (short x : {-20, -5, 0, 5, 20}) + { + for (short y : {-20, -5, 0, 5, 20}) + { + ptOther = pt; + ptOther.m_x += x; + ptOther.m_y += y; + + CHECK_EQ(pt.GetDir(ptOther), ref_impl::GetDir(pt, ptOther)); + } + } +} + + +TEST_CASE("CPointBase::IsValidPoint")\ +// clazy:exclude=non-pod-global-static +{ + CPointBase pt; + + SUBCASE("Valid") + { + pt.Set(0, 0, 0, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(1, 2, 3, 4); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(0, 0, -126, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(0, 0, 126, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + } + + SUBCASE("Invalid") + { + // For now avoid negative values, the unsigned conversion trick will fail because sphere hasn't + // yet loaded map sizes and the default is -1. + //pt.Set(-11, 2, 3, 4); + //CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(0, 0, -128, 0); // uint8_t goes from [-128; 127] + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(0, 0, -127, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(0, 0, 127, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(7168, 4096, 127, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + } +} + + +TEST_CASE("CPointBase::IsCharValid")\ +// clazy:exclude=non-pod-global-static +{ + CPointBase pt; + + SUBCASE("Valid") + { + pt.Set(1, 2, 3, 4); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(1, 1, -126, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(1, 1, 126, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + } + + SUBCASE("Invalid") + { + pt.Set(0, 0, 0, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + //pt.Set(-11, 2, 3, 4); + //CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(1, 1, -127, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(1, 1, 127, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(1, 1, -128, 0); // uint8_t goes from [-128; 127] + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(7168, 4096, 127, 0); + CHECK_EQ(pt.IsValidPoint(), ref_impl::IsValidPoint(pt)); + + pt.Set(0, 1, 1, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + + pt.Set(1, 0, 1, 0); + CHECK_EQ(pt.IsCharValid(), ref_impl::IsCharValid(pt)); + } +} diff --git a/tests/src/t_CUOClientVersion.cpp b/tests/src/t_CUOClientVersion.cpp new file mode 100644 index 000000000..c84205c5d --- /dev/null +++ b/tests/src/t_CUOClientVersion.cpp @@ -0,0 +1,220 @@ +#include +#include "../../src/common/CUOClientVersion.h" +//#include "../../src/common/sphereproto.h" +#include // atoi +#include + +namespace { namespace ref_impl +{ +#ifdef _WIN32 +# define FMTdword "lu" // Windows uses '%lu' to format dec dword (unsigned long) +# define FMTdwordH "lx" // Windows uses '%lx' to format hex dword (unsigned long) +#else +# define FMTdword "u" // Linux uses '%u' to format dec dword (unsigned long) +# define FMTdwordH "x" // Linux uses '%x' to format hex dword (unsigned int) +#endif + + // From 0.56d + dword CCrypt_GetVerFromString(lpctstr pszVer) + { + // Get version of old clients, which report the client version as ASCII string (eg: '5.0.2b') + if ( (pszVer == nullptr) || (*pszVer == '\0') ) + return 0; + + byte bLetter = 0; + for ( size_t i = 0; i < strlen(pszVer); ++i ) + { + if ( IsAlpha(pszVer[i]) ) + { + bLetter = (pszVer[i] - 'a') + 1; + break; + } + } + + tchar *ppVer[3]; + Str_ParseCmds(const_cast(pszVer), ppVer, ARRAY_COUNT(ppVer), "."); + + // Don't rely on all values reported by client, because it can be easily faked. Injection users can report any + // client version they want, and some custom clients may also report client version as "Custom" instead X.X.Xy + if ( !ppVer[0] || !ppVer[1] || !ppVer[2] || (bLetter > 26) ) + return 0; + + return (atoi(ppVer[0]) * 1000000) + (atoi(ppVer[1]) * 10000) + (atoi(ppVer[2]) * 100) + bLetter; + } + + // From 0.56d + dword CCrypt_GetVerFromNumber(dword dwMajor, dword dwMinor, dword dwRevision, dword dwPatch) + { + // Get version of new clients (5.0.6.5+), which report the client version as numbers (eg: 5,0,6,5) + + return (dwMajor * 1000000) + (dwMinor * 10000) + (dwRevision * 100) + dwPatch; + } + + // From 0.56d + tchar *CCrypt_WriteClientVerString(dword dwVer, tchar *pszOutput) + { + if ( dwVer >= MINCLIVER_NEWVERSIONING ) + { + snprintf(pszOutput, 44, "%" FMTdword ".%" FMTdword ".%" FMTdword ".%" FMTdword, dwVer / 1000000, (dwVer / 10000) % 100, (dwVer % 10000) / 100, dwVer % 100); + } + else + { + int iVer = snprintf(pszOutput, 33, "%" FMTdword ".%" FMTdword ".%" FMTdword, dwVer / 1000000, (dwVer / 10000) % 100, (dwVer % 10000) / 100); + int iPatch = static_cast(dwVer % 100); + if ( iPatch ) + { + pszOutput[iVer++] = static_cast(iPatch + 'a' - 1); + pszOutput[iVer] = '\0'; + } + } + return pszOutput; + } + +}} + + +TEST_CASE("CUOClientVersion parsing")\ +// clazy:exclude=non-pod-global-static +{ + constexpr std::pair data_pair_str_old_format[] + { + {"1.0.1.0", CUOClientVersion(1u, 0u, 1u, 0u)}, // Ancient versioning, no letters. + {"2.0.0a", CUOClientVersion(2u, 0u, 0u, 'a')}, + {"2.0.0x", CUOClientVersion(2u, 0u, 0u, 'x')}, + {"2.0.1j", CUOClientVersion(2u, 0u, 1u, 'j')}, + {"2.0.8z", CUOClientVersion(2u, 0u, 8u, 'z')}, + {"4.0.4b2", CUOClientVersion(4u, 0u, 4u, 'b', 2)}, + {"5.0.2b", CUOClientVersion(5u, 0u, 2u, 'b')} + }; + constexpr std::pair data_pair_str_new_format[] + { + {"6.0.0.0", CUOClientVersion(6u, 0u, 0u, 0u)}, + {"7.1.2.34", CUOClientVersion(7u, 1u, 2u, 34u)}, + {"7.12.3.4", CUOClientVersion(7u, 12u, 3u, 4u)}, + {"7.1.23.4", CUOClientVersion(7u, 1u, 23u, 4u)}, + {"7.1.2.34", CUOClientVersion(7u, 1u, 2u, 34u)}, + {"7.12.34.5", CUOClientVersion(7u, 12u, 34u, 5u)}, + {"7.12.34.56", CUOClientVersion(7u, 12u, 34u, 56u)}, + {"7.100.0.0", CUOClientVersion(7u, 100u, 0u, 0u)}, + {"7.100.0.1", CUOClientVersion(7u, 100u, 0u, 1u)}, + {"7.101.1.5", CUOClientVersion(7u, 101u, 1u, 5u)} + }; + constexpr std::pair data_pair_str_enhanced_client[] + { + {"67.1.0.0", CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 1u, 0u, 0u)}, + {"67.5.0.5", CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 5u, 0u, 5u)}, + {"67.100.0.0", CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 100u, 0u, 0u)}, + {"67.101.1.5", CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 101u, 1u, 5u)} + }; + constexpr std::pair data_pair_num[] + { + {1253500, CUOClientVersion(1u, 25u, 35u, 0u)}, // Ancient versioning, no letters. + {2000000, CUOClientVersion(2u, 0u, 0u, 0u, 'x')}, // build_sub + {3000702, CUOClientVersion(3u, 0u, 7u, 'b')}, // Letter versioning. + {3000810, CUOClientVersion(3u, 0u, 8u, 'j')}, + {5000000, CUOClientVersion(5u, 0u, 0u, 0u)}, + {5000605, CUOClientVersion(5u, 0u, 6u, 'e')}, + {5000700, CUOClientVersion(5u, 0u, 7u, 0u)}, + {7004565, CUOClientVersion(7u, 0u, 45u, 65u)}, + {70010200, CUOClientVersion(7u, 0u, 102u, 0u)} + }; + constexpr std::pair data_pair_num_enhanced_client[] + { + {67000900, CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 0u, 9u, 0u)}, + {67009900, CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 0u, 99u, 0u)}, + {670010000, CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 0u, 100u, 0u)}, + {670110800, CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 1u, 108u, 0u)}, + {670110801, CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 1u, 108u, 1u)}, + {670110810, CUOClientVersion(CUOClientVersion::kuiECMajorVerOffset + 4u, 1u, 108u, 10u)} + }; + + SUBCASE("Read from string, old format") + { + for (auto const& elem : data_pair_str_old_format) + { + INFO("Test: " << std::string_view(elem.first) << " -> " << elem.second.GetVersionString()); + CUOClientVersion cv(elem.first); + CHECK_EQ(cv, elem.second); + //CHECK_EQ(ref_impl::CCrypt_GetVerFromString(elem.first), elem.second); + } + } + + SUBCASE("Read from string, new format") + { + for (auto const& elem : data_pair_str_new_format) + { + CUOClientVersion cv(elem.first); + CHECK_EQ(cv, elem.second); + } + } + + SUBCASE("Read from string, Enhanced Client") + { + for (auto const& elem : data_pair_str_enhanced_client) + { + CUOClientVersion cv(elem.first); + CHECK_EQ(cv, elem.second); + } + } + + SUBCASE("Read from number, all formats") + { + for (auto const& elem : data_pair_num) + { + CUOClientVersion cv(elem.first); + CHECK_EQ(cv, elem.second); + } + } + + SUBCASE("Write as string, old format") + { + std::string strVer; + for (auto const& elem : data_pair_str_old_format) + { + strVer = elem.second.GetVersionString(); + CHECK(!strcmp(strVer.c_str(), elem.first)); + } + } + + SUBCASE("Write as string, new format") + { + std::string strVer; + for (auto const& elem : data_pair_str_new_format) + { + strVer = elem.second.GetVersionString(); + CHECK(!strcmp(strVer.c_str(), elem.first)); + } + } + + SUBCASE("Write as string, Enhanced Client") + { + std::string strVer; + for (auto const& elem : data_pair_str_enhanced_client) + { + strVer = elem.second.GetVersionString(); + CHECK(!strcmp(strVer.c_str(), elem.first)); + } + } + + SUBCASE("Write as number, all formats") + { + for (auto const& elem : data_pair_num) + { + uint ver = elem.second.GetLegacyVersionNumber(); + CHECK_EQ(ver, elem.first); + + // Doesn't work for revision > 100 + //uint ver_ref_impl = ref_impl::CCrypt_GetVerFromNumber(elem.second.m_major, elem.second.m_minor, elem.second.m_revision, elem.second.m_build); + //CHECK_EQ(ver, ver_ref_impl); + } + } + + SUBCASE("Write as number, Enhanced Client") + { + for (auto const& elem : data_pair_num_enhanced_client) + { + uint ver = elem.second.GetLegacyVersionNumber(); + CHECK_EQ(ver, elem.first); + } + } +} diff --git a/tests/src/t_num_parsing.cpp b/tests/src/t_num_parsing.cpp new file mode 100644 index 000000000..143d1a4a3 --- /dev/null +++ b/tests/src/t_num_parsing.cpp @@ -0,0 +1,267 @@ +#include +#include "../../src/common/CExpression.h" +#include +#include +#include // toupper + +#ifndef MSVC_COMPILER +#define ATTR_NO_UBSAN __attribute__((no_sanitize("undefined"))) +#else +#define ATTR_NO_UBSAN //__declspec(no_sanitize_address) +#endif + + +namespace { namespace ref_impl +{ +/* + // From 0.56d, known to be faulty... Copying it here just for historical reasons. + int ahextoi(lpctstr pszArgs) // convert hex string to int + { + // Unfortunately the library func can't handle UINT_MAX + //tchar *pszEnd; return strtol(s, &pszEnd, 16); + + if ( !pszArgs || !*pszArgs ) + return 0; + + GETNONWHITESPACE(pszArgs); + + bool fHex = false; + if ( *pszArgs == '0' ) + { + if ( *++pszArgs != '.' ) + fHex = true; + --pszArgs; + } + + int iVal = 0; + for (;;) + { + tchar ch = static_cast(toupper(*pszArgs)); + if ( IsDigit(ch) ) + ch -= '0'; + else if ( fHex && (ch >= 'A') && (ch <= 'F') ) + ch -= 'A' - 10; + else if ( !fHex && (ch == '.') ) + { + ++pszArgs; + continue; + } + else + break; + + iVal *= (fHex ? 0x10 : 10); + iVal += ch; + ++pszArgs; + } + return iVal; + } + + // From 0.56d, known to be faulty... Copying it here just for historical reasons. + int64 ahextoi64(lpctstr pszArgs) // convert hex string to int64 + { + if ( !pszArgs || !*pszArgs ) + return 0; + + GETNONWHITESPACE(pszArgs); + + bool fHex = false; + if ( *pszArgs == '0' ) + { + if ( *++pszArgs != '.' ) + fHex = true; + --pszArgs; + } + + int64 iVal = 0; + for (;;) + { + tchar ch = static_cast(toupper(*pszArgs)); + if ( IsDigit(ch) ) + ch -= '0'; + else if ( fHex && (ch >= 'A') && (ch <= 'F') ) + ch -= 'A' - 10; + else if ( !fHex && (ch == '.') ) + { + ++pszArgs; + continue; + } + else + break; + + iVal *= (fHex ? 0x10 : 10); + iVal += ch; + ++pszArgs; + } + return iVal; + } +*/ + +}} + +// Small helper to check value and how far the input pointer advanced. +// Expects expr.GetSingle to consume the token in-place and leave 'in' pointing +// at the first non-numeric char following the parsed number. +#define CHECK_PARSE_AND_REMAINDER(input, expected_val, expected_remainder) { \ + lpctstr parsed_input_str = input; \ + const int64 actual_val = expr.GetSingle(parsed_input_str); \ + DOCTEST_CHECK_MESSAGE(actual_val == (int64)expected_val, \ + "Value mismatch!\nInput: '" << input << "'\nExpected: '" << expected_val << "'\nGot: '" << actual_val << "'"); \ + const std::string_view sv_post_parse(parsed_input_str); \ + const std::string_view sv_expected(expected_remainder); \ + DOCTEST_CHECK_MESSAGE(sv_expected == sv_post_parse, \ + "Remainder mismatch!\nInput: '" << input << "'\nExpected remainder: '" << sv_expected << "'\nGot: '" << sv_post_parse << "'"); \ +} + +TEST_CASE("Numerical string parsing")\ +// clazy:exclude=non-pod-global-static +{ + CExpression& expr = CExpression::GetExprParser(); + lpctstr in_str_consumable; + llong out_cur_impl, out_ref_impl; + + SUBCASE("Decimal integer") + { + constexpr std::pair test_data_ref_ok[] + { + {"0", 0}, + {"-1", -1}, + {"100", 100}, + {"2147483647", INT32_MAX}, + {"-2147483647", -INT32_MAX}, + {"-2147483648", INT32_MIN}, + {"4294967295", UINT32_MAX}, + {"9223372036854775807", INT64_MAX}, + {"-9223372036854775807", -INT64_MAX}, + //{"-9223372036854775808", INT64_MIN} + }; + + for (auto const& p : test_data_ref_ok) + { + in_str_consumable = p.first, out_cur_impl = expr.GetSingle(in_str_consumable); + CHECK_EQ(out_cur_impl, p.second); + + in_str_consumable = p.first, out_ref_impl = expr.GetSingle(in_str_consumable); + CHECK_EQ(out_cur_impl, out_ref_impl); + } + + // With this data the old reference implementation failed + constexpr std::pair test_data_ref_fail[] + { + {"9223372036854775808", /* INT64_MAX + 1 */ -1} // Overflow + }; + + for (auto const& p : test_data_ref_fail) + { + in_str_consumable = p.first, out_cur_impl = expr.GetSingle(in_str_consumable); + CHECK_EQ(out_cur_impl, p.second); + } + + } + + SUBCASE("Hexadecimal") + { + constexpr std::pair test_data_ref_ok[] + { + {"0", 0}, + {"07FFFFFFF", INT32_MAX}, + {"0100000000", 0x100000000}, + {"07FFFFFFFFFFFFFFF", 0x7FFFFFFFFFFFFFFF /* INT64_MAX */}, + {"0FFFFFFFFFFFFFFFF", -1}, + {"0FFFFFFFFFFFFFFFE", -2} + }; + + for (auto const& p : test_data_ref_ok) + { + in_str_consumable = p.first, out_cur_impl = expr.GetSingle(in_str_consumable); + CHECK_EQ(out_cur_impl, p.second); + + in_str_consumable = p.first, out_ref_impl = expr.GetSingle(in_str_consumable); + CHECK_EQ(out_cur_impl, out_ref_impl); + } + + // With this data the old reference implementation failed + constexpr std::pair test_data_ref_fail[] + { + {"0FFFFFFFF", -1}, // This is a SphereServer quirk for historical reasons + {"0FFFFFFFFFFFFFFFF1", -1} // Overflow + }; + + for (auto const& p : test_data_ref_fail) + { + in_str_consumable = p.first, out_cur_impl = expr.GetSingle(in_str_consumable); + CHECK_EQ(out_cur_impl, p.second); + } + } + + SUBCASE("Whitespace and trailing junk consumption") + { + CHECK_PARSE_AND_REMAINDER(" 42", 42, ""); // consume entire string + CHECK_PARSE_AND_REMAINDER(" 42 ", 42, " "); // stop at trailing space + CHECK_PARSE_AND_REMAINDER(" 3]", 3, "]"); // stop right before ']' + CHECK_PARSE_AND_REMAINDER(" 0F]", 15, "]"); // hex with trailing junk + CHECK_PARSE_AND_REMAINDER("123abc", 123, "abc"); // decimal then junk + CHECK_PARSE_AND_REMAINDER("0F xyz", 15, " xyz"); // hex then spaces+text + } + + SUBCASE("Leading dot quirk and 0. decimal disambiguation") + { + // Leading '.' is skipped; ".07" becomes hex marker "0" followed by '7' -> 7 + CHECK_PARSE_AND_REMAINDER(".07]", 7, "]"); + + // "0." is decimal; dots inside decimal are ignored as separators + CHECK_PARSE_AND_REMAINDER("0.", 0, ""); // bare 0. -> 0 + CHECK_PARSE_AND_REMAINDER("0.123", 123, ""); // 0.123 -> 123 + CHECK_PARSE_AND_REMAINDER(".0.1]", 1, "]"); // leading '.' skipped -> "0.1]" -> decimal 1 + CHECK_PARSE_AND_REMAINDER("1.234.567end", 1234567, "end"); + } + + SUBCASE("Hex width by significant digits (leading zeros ignored after marker)") + { + CHECK_PARSE_AND_REMAINDER("00000F", 15, ""); // marker '0' then digits "0000F" -> 15 + CHECK_PARSE_AND_REMAINDER("07FFFFFFF", INT32_MAX, ""); // 8 significant digits -> 32-bit + CHECK_PARSE_AND_REMAINDER("080000000", (llong)INT32_MIN, ""); // 8 significant digits -> 32-bit negative + CHECK_PARSE_AND_REMAINDER("0FFFFFFFF", -1, ""); // 8 significant -> 32-bit -1 + CHECK_PARSE_AND_REMAINDER("0FFFFFFFFFFFFFFFF", -1, "");// 16 significant -> 64-bit -1 + CHECK_PARSE_AND_REMAINDER("0FFFFFFFF00", 0xFFFFFFFF00LL, ""); // 64-bit positive + } + + /* + SUBCASE("Negative decimal vs negative hex") + { + // Negative decimals are supported + EXPECT_PARSE_AND_REMAINDER("-1", -1, ""); + EXPECT_PARSE_AND_REMAINDER("-0", 0, ""); + EXPECT_PARSE_AND_REMAINDER("-1.234", -1234, ""); + + // "-0." is decimal (supported), should parse as 0 with decimal semantics + EXPECT_PARSE_AND_REMAINDER("-0.", 0, ""); + + // Negative hex is unsupported, but parser should: + // - log a warning, + // - consume the '-', ignore the sign, + // - parse and consume the entire hex token. + EXPECT_PARSE_AND_REMAINDER("-00", 0, ""); // hex zero after sign + EXPECT_PARSE_AND_REMAINDER("-0F", 15, ""); // hex positive (sign ignored) + EXPECT_PARSE_AND_REMAINDER("-0FFFFFFFF", -1, ""); // hex, 32-bit width -> -1 (sign ignored) + EXPECT_PARSE_AND_REMAINDER(" -0F]", 15, "]"); // with whitespace and trailing junk + } + */ + + SUBCASE("Overflow behaviors") + { + // Decimal overflow: > INT64_MAX + CHECK_PARSE_AND_REMAINDER("9223372036854775808X", -1, "X"); + + // Hex overflow: >16 significant hex digits (leading zeros ignored before first non-zero) + CHECK_PARSE_AND_REMAINDER("0FFFFFFFFFFFFFFFFFFFFZ", -1, "Z"); // 20 significant digits + CHECK_PARSE_AND_REMAINDER("00000000000000000FFFFFFFFFFFFFFFFFFFF!", -1, "!"); // still overflow + } + + SUBCASE("Unsupported 0x prefix") + { + // Legacy: "0x" is not a hex prefix; hex marker is just '0'. + // Behavior: marker '0' is consumed, 'x' is non-hex -> stop; resulting value is 0; remainder begins with 'x'. + CHECK_PARSE_AND_REMAINDER("0xFF", 0, "xFF"); + CHECK_PARSE_AND_REMAINDER(" 0x7B", 0, "x7B"); + } +} diff --git a/utilities/configure_asan.bat b/utilities/configure-asan.bat similarity index 91% rename from utilities/configure_asan.bat rename to utilities/configure-asan.bat index 2f91dc276..6a7f47e63 100644 --- a/utilities/configure_asan.bat +++ b/utilities/configure-asan.bat @@ -3,6 +3,7 @@ REM SET ASAN_SYMBOLIZER_PATH="C:\Program Files\LLVM\bin\llvm-symbolizer.exe" REM ASAN_MORE=check_initialization_order=1: @echo on +REM use SETX to add asan options as environmental variables SET _SAN_COMMON_FLAGS=symbolize=1:handle_abort=true:abort_on_error=false SET ASAN_OPTIONS=%_SAN_COMMON_FLAGS%:strict_init_order=true:detect_stack_use_after_return=true SET ASAN_OPTIONS=%ASAN_OPTIONS%:sleep_before_dying=3:print_stats=true:stack_trace_format="[frame=%%n, function=%%f, location=%%S]" diff --git a/utilities/configure_asan.sh b/utilities/configure-asan.sh similarity index 97% rename from utilities/configure_asan.sh rename to utilities/configure-asan.sh index ece552bf6..57a6bbfd5 100644 --- a/utilities/configure_asan.sh +++ b/utilities/configure-asan.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh _SAN_COMMON_FLAGS=handle_abort=true:abort_on_error=false #ASAN_MORE=check_initialization_order=1 diff --git a/utilities/run_clang-format.sh b/utilities/run-clang-format.sh similarity index 100% rename from utilities/run_clang-format.sh rename to utilities/run-clang-format.sh diff --git a/utilities/run_gersemi_cmake.sh b/utilities/run-gersemi-cmake.sh similarity index 100% rename from utilities/run_gersemi_cmake.sh rename to utilities/run-gersemi-cmake.sh