diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..76c0ba7c07 --- /dev/null +++ b/.clang-format @@ -0,0 +1,21 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignEscapedNewlines: DontAlign +BreakBeforeBraces: Mozilla +BreakConstructorInitializers: AfterColon +BreakStringLiterals: false +ColumnLimit: 120 +IncludeCategories: + - Regex: '^"otpch\.h"$' + Priority: -1 + - Regex: '^".*"$' + Priority: 1 + - Regex: '^<.*>$' + Priority: 2 +IndentWidth: 4 +SpacesBeforeTrailingComments: 1 +TabWidth: 4 +UseTab: ForIndentation +... diff --git a/.editorconfig b/.editorconfig index fba3ed2837..5516cd9895 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,19 +4,8 @@ root = true [*] charset = utf-8 end_of_line = lf -indent_style = tab insert_final_newline = true trim_trailing_whitespace = true -[*.{cpp,h,lua,xml}] -indent_style = tab +[*.{lua,xml}] indent_size = 4 - -[**.{.cpp,.h}] -# Options not ubiquitous, but useful -indent_brace_style = K&R -spaces_around_brackets = none - -[.travis.yml] -indent_style = space -indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md index 7fec4fa3dd..369c91d28c 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -14,5 +14,5 @@ about: Requesting a new feature for the engine or data pack ### Prior art - diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 58bd24fcad..fcd28e8cac 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -6,9 +6,6 @@ on: - master - v* - tags: - - v* - paths: - cmake/** - src/** @@ -29,6 +26,7 @@ jobs: - name: Install dependencies run: > + sudo apt-get update && sudo apt-get install git cmake build-essential libluajit-5.1-dev libmysqlclient-dev libboost-date-time-dev libboost-system-dev libboost-iostreams-dev libboost-filesystem-dev libpugixml-dev libcrypto++-dev libfmt-dev diff --git a/.github/workflows/build-vcpkg.yml b/.github/workflows/build-vcpkg.yml index 232c61d386..da2c8e76c8 100644 --- a/.github/workflows/build-vcpkg.yml +++ b/.github/workflows/build-vcpkg.yml @@ -6,9 +6,6 @@ on: - master - v* - tags: - - v* - paths: - cmake/** - src/** @@ -21,25 +18,17 @@ on: - CMakeLists.txt jobs: - job: + unix: name: ${{ matrix.os }}-${{ matrix.cxx }}-${{ matrix.buildtype }}-luajit=${{ matrix.luajit }} runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false - max-parallel: 8 + max-parallel: 10 matrix: - name: [ubuntu-gcc, ubuntu-clang, macos-clang, windows-msvc] + name: [ubuntu-gcc, ubuntu-clang, macos-clang] buildtype: [Debug, Release] luajit: [on, off] include: - - name: windows-msvc - os: windows - cxx: cl.exe - cc: cl.exe - triplet: x64-windows - packages: > - boost-asio boost-iostreams boost-system boost-filesystem boost-variant boost-lockfree - lua luajit libmariadb pugixml cryptopp fmt - name: ubuntu-gcc os: ubuntu cxx: g++ @@ -64,6 +53,13 @@ jobs: packages: > boost-asio boost-iostreams boost-system boost-filesystem boost-variant boost-lockfree lua libmariadb pugixml cryptopp fmt + exclude: + - name: ubuntu-clang + buildtype: Release + - name: ubuntu-clang + luajit: off + - name: macos-clang + luajit: off steps: - uses: actions/checkout@v2 @@ -87,30 +83,27 @@ jobs: run: brew install luajit pkgconfig if: contains( matrix.os, 'macos') - - name: Windows - remove C:/mysql* - run: rm -r -fo C:/mysql-5.7.21-winx64 - if: contains( matrix.os, 'windows') - - name: Set Environment vars run: | echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV echo "CC=${{ matrix.cc }}" >> $GITHUB_ENV - name: Run vcpkg - uses: lukka/run-vcpkg@main + uses: lukka/run-vcpkg@v7 with: vcpkgArguments: ${{ matrix.packages }} vcpkgDirectory: ${{ runner.workspace }}/vcpkg/ vcpkgTriplet: ${{ matrix.triplet }} - vcpkgGitCommitId: 7db401cb1ef1fc559ec9f9ce814d064c328fd767 + appendedCacheKey: ${{ matrix.name }}${{ matrix.buildtype }}${{ matrix.luajit }} + vcpkgGitCommitId: 5568f110b509a9fd90711978a7cb76bae75bb092 - name: Build with CMake - uses: lukka/run-cmake@main + uses: lukka/run-cmake@v3 with: useVcpkgToolchainFile: true buildDirectory: ${{ runner.workspace }}/build - cmakeBuildType: ${{ matrix.buildtype }} - cmakeAppendedArgs: -DUSE_LUAJIT=${{ matrix.luajit }} + cmakeListsOrSettingsJson: CMakeListsTxtAdvanced + cmakeAppendedArgs: '-G Ninja -DCMAKE_BUILD_TYPE="${{ matrix.buildtype }}" -DUSE_LUAJIT="${{ matrix.luajit }}"' - name: dir run: find $RUNNER_WORKSPACE @@ -121,21 +114,88 @@ jobs: with: name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=${{ matrix.luajit }}-${{ github.sha }} path: ${{ runner.workspace }}/build/tfs - if: "! contains( matrix.os, 'windows')" - - name: Upload artifact binary (exe) + - name: Prepare datapack contents + run: find . -maxdepth 1 ! -name data ! -name config.lua.dist ! -name key.pem ! -name LICENSE ! -name README.md ! -name schema.sql -exec rm -r {} \; + shell: bash + + - name: Upload datapack contents uses: actions/upload-artifact@v2 with: name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=${{ matrix.luajit }}-${{ github.sha }} + path: ${{ github.workspace }} + + windows: + name: ${{ matrix.os }}-${{ matrix.cxx }}-${{ matrix.buildtype }}-luajit=on + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + max-parallel: 8 + matrix: + name: [windows-msvc] + buildtype: [Debug, Release] + include: + - name: windows-msvc + os: windows + cxx: cl.exe + cc: cl.exe + triplet: x64-windows + packages: > + boost-asio boost-iostreams boost-system boost-filesystem boost-variant boost-lockfree + lua luajit libmariadb pugixml cryptopp fmt + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Get latest CMake + # Using 'latest' branch, the latest CMake is installed. + uses: lukka/get-cmake@latest + + - name: Windows - remove C:/mysql* + run: rm -r -fo C:/mysql* + + - name: Set Environment vars + run: | + echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV + echo "CC=${{ matrix.cc }}" >> $GITHUB_ENV + + - name: Run vcpkg + uses: lukka/run-vcpkg@v7 + with: + vcpkgArguments: ${{ matrix.packages }} + vcpkgDirectory: ${{ runner.workspace }}/vcpkg/ + vcpkgTriplet: ${{ matrix.triplet }} + appendedCacheKey: ${{ matrix.name }}${{ matrix.buildtype }}${{ matrix.luajit }} + vcpkgGitCommitId: 5568f110b509a9fd90711978a7cb76bae75bb092 + + - name: Build with CMake + uses: lukka/run-cmake@v3 + with: + useVcpkgToolchainFile: true + buildDirectory: ${{ runner.workspace }}/build + cmakeListsOrSettingsJson: CMakeListsTxtAdvanced + cmakeAppendedArgs: '-G Ninja -DCMAKE_BUILD_TYPE="${{ matrix.buildtype }}" -DUSE_LUAJIT="on"' + + - name: dir + run: find $RUNNER_WORKSPACE + shell: bash + + - name: Upload artifact binary (exe) + uses: actions/upload-artifact@v2 + with: + name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=on-${{ github.sha }} path: ${{ runner.workspace }}/build/tfs.exe - if: contains( matrix.os, 'windows') - name: Upload artifact binary (dlls) uses: actions/upload-artifact@v2 with: - name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=${{ matrix.luajit }}-${{ github.sha }} + name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=on-${{ github.sha }} path: ${{ runner.workspace }}/build/*.dll - if: contains( matrix.os, 'windows') - name: Prepare datapack contents run: find . -maxdepth 1 ! -name data ! -name config.lua.dist ! -name key.pem ! -name LICENSE ! -name README.md ! -name schema.sql -exec rm -r {} \; @@ -144,5 +204,6 @@ jobs: - name: Upload datapack contents uses: actions/upload-artifact@v2 with: - name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=${{ matrix.luajit }}-${{ github.sha }} + name: tfs-${{ matrix.name }}-${{ matrix.buildtype }}-luajit=on-${{ github.sha }} path: ${{ github.workspace }} + diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000000..8a40d5c4b5 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,30 @@ +name: Check code style + +on: + push: + branches: + - master + - v* + + paths: + - .clang-format + - src/** + + pull_request: + paths: + - .clang-format + - src/** + +jobs: + check-format: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: > + sudo apt-get update && + sudo apt-get install clang-format + + - name: Check code style + run: clang-format -n -style=file --Werror src/*.{cpp,h} diff --git a/.github/workflows/lua-syntax.yml b/.github/workflows/lua-syntax.yml index eaf55cd20f..baa7288ea6 100644 --- a/.github/workflows/lua-syntax.yml +++ b/.github/workflows/lua-syntax.yml @@ -5,6 +5,8 @@ on: paths: - data/**.lua push: + branches: + - master paths: - data/**.lua diff --git a/.github/workflows/release-vcpkg.yml b/.github/workflows/release-vcpkg.yml new file mode 100644 index 0000000000..cc399413c3 --- /dev/null +++ b/.github/workflows/release-vcpkg.yml @@ -0,0 +1,192 @@ +name: Release builds with vcpkg + +on: + push: + tags: + - v* + +jobs: + unix: + name: ${{ matrix.os }}-${{ matrix.cxx }}-${{ matrix.buildtype }}-luajit=${{ matrix.luajit }} + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + max-parallel: 8 + matrix: + name: [ubuntu-gcc, macos-clang] + buildtype: [Release] + luajit: [on] + include: + - name: ubuntu-gcc + os: ubuntu + cxx: g++ + cc: gcc + triplet: x64-linux + packages: > + boost-asio boost-iostreams boost-system boost-filesystem boost-variant boost-lockfree + lua libmariadb pugixml cryptopp fmt + - name: macos-clang + os: macos + cxx: clang++ + cc: clang + triplet: x64-osx + packages: > + boost-asio boost-iostreams boost-system boost-filesystem boost-variant boost-lockfree + lua libmariadb pugixml cryptopp fmt + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Get latest CMake + # Using 'latest' branch, the latest CMake is installed. + uses: lukka/get-cmake@latest + + - name: Ubuntu - install luajit, remove libmysqlclient-dev + run: | + sudo apt-get install libluajit-5.1-dev + sudo apt-get remove -y libmysqlclient-dev + if: contains( matrix.os, 'ubuntu') + + - name: MacOS - install luajit pkgconfig + run: brew install luajit pkgconfig + if: contains( matrix.os, 'macos') + + - name: Set Environment vars + run: | + echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV + echo "CC=${{ matrix.cc }}" >> $GITHUB_ENV + + - name: Run vcpkg + uses: lukka/run-vcpkg@v7 + with: + vcpkgArguments: ${{ matrix.packages }} + vcpkgDirectory: ${{ runner.workspace }}/vcpkg/ + vcpkgTriplet: ${{ matrix.triplet }} + appendedCacheKey: ${{ matrix.name }}${{ matrix.buildtype }}${{ matrix.luajit }} + vcpkgGitCommitId: 5568f110b509a9fd90711978a7cb76bae75bb092 + + - name: Build with CMake + uses: lukka/run-cmake@v3 + with: + useVcpkgToolchainFile: true + buildDirectory: ${{ runner.workspace }}/build + cmakeListsOrSettingsJson: CMakeListsTxtAdvanced + cmakeAppendedArgs: '-G Ninja -DCMAKE_BUILD_TYPE="${{ matrix.buildtype }}" -DUSE_LUAJIT="${{ matrix.luajit }}"' + + - name: dir + run: find $RUNNER_WORKSPACE + shell: bash + + - name: Prepare datapack contents + run: | + pwd + ls -al + find . -maxdepth 1 ! -name data ! -name config.lua.dist ! -name key.pem ! -name LICENSE ! -name README.md ! -name schema.sql -exec rm -r {} \; + mv ../build/tfs . + shell: bash + + - name: Zip the release files + uses: thedoctor0/zip-release@master + with: + type: tar + filename: tfs-${{ github.ref_name }}-${{ matrix.name }}.tar.gz + path: forgottenserver + directory: ../ + + - name: Upload release artifact + uses: ncipollo/release-action@v1 + with: + artifacts: ../tfs-${{ github.ref_name }}-${{ matrix.name }}.tar.gz + generateReleaseNotes: true + allowUpdates: true + token: ${{ secrets.GITHUB_TOKEN }} + + windows: + name: ${{ matrix.os }}-${{ matrix.cxx }}-${{ matrix.buildtype }}-luajit=on + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + max-parallel: 8 + matrix: + name: [windows-msvc] + buildtype: [Release] + include: + - name: windows-msvc + os: windows + cxx: cl.exe + cc: cl.exe + triplet: x64-windows + packages: > + boost-asio boost-iostreams boost-system boost-filesystem boost-variant boost-lockfree + lua luajit libmariadb pugixml cryptopp fmt + + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Get latest CMake + # Using 'latest' branch, the latest CMake is installed. + uses: lukka/get-cmake@latest + + - name: Windows - remove C:/mysql* + run: rm -r -fo C:/mysql* + + - name: Set Environment vars + run: | + echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV + echo "CC=${{ matrix.cc }}" >> $GITHUB_ENV + + - name: Run vcpkg + uses: lukka/run-vcpkg@v7 + with: + vcpkgArguments: ${{ matrix.packages }} + vcpkgDirectory: ${{ runner.workspace }}/vcpkg/ + vcpkgTriplet: ${{ matrix.triplet }} + appendedCacheKey: ${{ matrix.name }}${{ matrix.buildtype }}${{ matrix.luajit }} + vcpkgGitCommitId: 5568f110b509a9fd90711978a7cb76bae75bb092 + + - name: Build with CMake + uses: lukka/run-cmake@v3 + with: + useVcpkgToolchainFile: true + buildDirectory: ${{ runner.workspace }}/build + cmakeListsOrSettingsJson: CMakeListsTxtAdvanced + cmakeAppendedArgs: '-G Ninja -DCMAKE_BUILD_TYPE="${{ matrix.buildtype }}" -DUSE_LUAJIT="on"' + + - name: dir + run: find $RUNNER_WORKSPACE + shell: bash + + - name: Prepare datapack contents + run: | + pwd + ls -al + find . -maxdepth 1 ! -name data ! -name config.lua.dist ! -name key.pem ! -name LICENSE ! -name README.md ! -name schema.sql -exec rm -r {} \; + mv ../build/tfs.exe* . + mv ../build/*.dll . + shell: bash + + - name: Zip the release files + uses: thedoctor0/zip-release@master + with: + type: zip + filename: tfs-${{ github.ref_name }}-${{ matrix.name }}.zip + path: forgottenserver + directory: ../ + + - name: Upload release artifact + uses: ncipollo/release-action@v1 + with: + artifacts: ../tfs-${{ github.ref_name }}-${{ matrix.name }}.zip + generateReleaseNotes: true + allowUpdates: true + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/xml-syntax.yml b/.github/workflows/xml-syntax.yml index dc1cebcafd..f9d0880ed1 100644 --- a/.github/workflows/xml-syntax.yml +++ b/.github/workflows/xml-syntax.yml @@ -5,6 +5,8 @@ on: paths: - data/**.xml push: + branches: + - master paths: - data/**.xml diff --git a/.gitignore b/.gitignore index fc99e71417..832197949d 100644 --- a/.gitignore +++ b/.gitignore @@ -177,6 +177,9 @@ pip-log.txt .*.swp .*.swo +# Notepad++ backups +*.bak + ############# ## TFS / OT ############# diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index eb18f5594d..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ -os: linux -dist: focal -language: cpp -compiler: - - clang - - gcc -env: - global: - # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created - # via the "travis encrypt" command using the project repo's public key - - secure: "jg/5nV1Xe6ePlplxIyRmyTaSEDmKhOxF/Z3cMMU6xOnEyhMTnKgcvi88YB0JA/Y7y3SBFyHdvzSfADgTPYsE+TDp2BTlhPpaS/x987nXfB+0jWB3WyEdxtUuscis9m3vAfdO9OraOfrthQDKQgt1mEoJcdVfLabnZ2hs+wzuFGA=" - jobs: - - LUAJIT=OFF - - LUAJIT=ON -cache: apt -addons: - apt: - packages: - - libboost-date-time-dev - - libboost-system-dev - - libboost-filesystem-dev - - libboost-iostreams-dev - - libcrypto++-dev - - libfmt-dev - - liblua5.2-dev - - libluajit-5.1-dev - - libmysqlclient-dev - - libpugixml-dev - coverity_scan: - project: - name: otland/forgottenserver - description: A free and open-source MMORPG server emulator written in C++ - build_command_prepend: "cmake -DCMAKE_BUILD_TYPE=Release -DUSE_LUAJIT=${LUAJIT} .." - build_command: make -j2 - branch_pattern: coverity_scan - notification_email: coverity@otland.net -before_install: - - echo -n | openssl s_client -connect https://scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- -before_script: - - mkdir build && cd build - - cmake -DCMAKE_BUILD_TYPE=Release -DUSE_LUAJIT=${LUAJIT} .. -script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make -j2; fi diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000000..a6a171610c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,197 @@ +The Forgotten Server Contributors/Developers + +Allan Ference +Andre Miles +andypsylon +Aritz Lizarraga +Arkadiusz +Artur Knopik +aymanm419 +bpawel10 +Bruno Carvalho +Bruno Minervino +cbrm +Codinablack +Colandus +conde2 +cosmosbr +Cristian Schonmeier +Crypton33 +Dagst +dalkon +Damian Jarek +Daniel Björkholm +Daniel Chabrowski +Daniel Speichert +Danyel Varejão +devcod +diath +Diego-OT +djayk1 +Dominique Verellen +Edgar Garcia +Eduardo Júnio Santos Macêdo +eduardodantas +emil92b +EPuncker +Erza +Esfomeado +EvanMC +Faith2531 +Fernando Matos +fsbcorrea +Gabriel +Gabriel Pedro +Gubihe +gunzino +Gustavo Aguiar +Gustavo Stor +idontreallywolf +Iñaki Amatria Barral +infernumx +jerryskye +Jerzy Skalski +Jesus Fernandez +Joseluis González +Joseph Bingham +Josue David Hernandez +Jovial1991 +jpmagg +Kairion +Kamenuvol +Kamil Wąż +Kevin Zou +Klaffen +Kornelijus Survila +KrecikOnDexin +kygo +Leo32 +Luan Cuba +Luan Luciano +luca +Lucas CP +lucasgrizante +Luckey1729 +LuisPro +maattch +mackerel225 +Malizia R +Marcin Michalski +Márcio Porto +Marco Oliveira +margohq +Mario Tettei +Mark Samman +Markus Elfring +Martin Nylind +Mateuso8 +mattyx14 +Mazen Mardini +Michal Kulesza +Mikołaj Herwart +MillhioreBT +Mithryll +Mkalo +Moisés dos Santos +mrianura123 +Nailson +nawyrus +nclx +Nekiro +octavio007 +Patryk Zajdler +paulosalvatore +Pegason +PrinterLUA +Ramon Bernardo +Ranieri Althoff +raymondtfr +Rey Aleman +Riverlance +Roland +Ron +Royalot +SeeingBlue +Sequitur +Shawak +Skillert +slavi +Sławek +soul4soul +souzajunior +sundance +t0kenz +Techrlz +thatmichaelguy +theshibbies +TheSumm +Tomasz Martyński +Tovar +tshain +Turhan Yağız Merpez +vankk +Vichoko +Victor Debone +Victor Oliveira +Vítor Cardoso Bertolucci +wgrriffel +WibbenZ +Wirox +Xawx +Xikini +yamaken93 +Zbizu +Znote + + +And everyone from OpenTibia: + +Acrimon +Aire +Anstice +Arkold Thos +assassina +Blackdemon +bruno +Decar +Evo +Fandoras +FightingElf +Gecko +GriZzm0 +hackerpotato +Haktivex +Heliton +iryont +j4K3xBl4sT3r +Jiddo +kilouco/aseverino +Mackan +Matkus +nfries88 +Nostradamus +Nuker +OsoSangre +Pedro B. +Pekay +Primer +Privateer +rafaelhamdan +Reebow +Remere/hjnilsson +Shi'Voc +SimOne/xeroc81 +Smygflik +Snack +Spin +Stormer +the fike +TiMMit +Tliff +Torvik +Tythor Zeth +verkon +Vitor +Winghawk +Wrzasq +Yorick diff --git a/CMakeLists.txt b/CMakeLists.txt index 1badade63f..6f057a3f28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) set(CMAKE_DISABLE_SOURCE_CHANGES ON) set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) -project(tfs) +project(tfs CXX) add_subdirectory(src) add_executable(tfs ${tfs_SRC}) @@ -13,15 +13,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set_target_properties(tfs PROPERTIES CXX_STANDARD 17) set_target_properties(tfs PROPERTIES CXX_STANDARD_REQUIRED ON) -if (${CMAKE_VERSION} VERSION_GREATER "3.16.0") - target_precompile_headers(tfs PUBLIC src/otpch.h) -else () - include(cotire) - set_target_properties(tfs PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/otpch.h") - set_target_properties(tfs PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) - cotire(tfs) -endif () - if (NOT WIN32) add_compile_options(-Wall -Werror -pipe -fvisibility=hidden) endif () @@ -40,7 +31,16 @@ else() find_package(Crypto++ REQUIRED) endif () find_package(fmt 6.1.2 REQUIRED) -find_package(MySQL REQUIRED) + +# Look for vcpkg-provided libmariadb first +# If we link to the file directly, we might miss its dependencies from vcpkg +find_package(unofficial-libmariadb CONFIG QUIET) +if (unofficial-libmariadb_FOUND) + set(MYSQL_CLIENT_LIBS "libmariadb") +else () + find_package(MySQL REQUIRED) +endif () + find_package(Threads REQUIRED) find_package(PugiXML REQUIRED) @@ -61,13 +61,12 @@ else () find_package(Lua REQUIRED) endif () -find_package(Boost 1.66.0 REQUIRED COMPONENTS date_time system filesystem iostreams) +find_package(Boost 1.66.0 REQUIRED COMPONENTS date_time system iostreams) include_directories(${Boost_INCLUDE_DIRS} ${Crypto++_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${MYSQL_INCLUDE_DIR} ${PUGIXML_INCLUDE_DIR}) target_link_libraries(tfs PRIVATE Boost::date_time Boost::system - Boost::filesystem Boost::iostreams fmt::fmt ${CMAKE_THREAD_LIBS_INIT} @@ -103,3 +102,14 @@ if(NOT SKIP_GIT) endif() endif() ### END Git Version ### + +# Precompiled header +# note: cotire() must be called last on a target +if (${CMAKE_VERSION} VERSION_GREATER "3.16.0") + target_precompile_headers(tfs PUBLIC src/otpch.h) +else () + include(cotire) + set_target_properties(tfs PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/otpch.h") + set_target_properties(tfs PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) + cotire(tfs) +endif () diff --git a/Dockerfile b/Dockerfile index 223529d6cd..b2044070c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM alpine:3.13.0 AS build +FROM alpine:3.15.0 AS build # crypto++-dev is in edge/testing -RUN apk add --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ \ +RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \ binutils \ boost-dev \ build-base \ @@ -21,12 +21,11 @@ COPY CMakeLists.txt /usr/src/forgottenserver/ WORKDIR /usr/src/forgottenserver/build RUN cmake .. && make -FROM alpine:3.13.0 +FROM alpine:3.15.0 # crypto++ is in edge/testing -RUN apk add --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ \ +RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \ boost-iostreams \ boost-system \ - boost-filesystem \ crypto++ \ fmt \ gmp \ diff --git a/README.md b/README.md index f5160787cc..b024649ed3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -forgottenserver [![Build Status](https://travis-ci.org/otland/forgottenserver.svg?branch=master)](https://travis-ci.org/otland/forgottenserver "Travis CI status") [![Build status](https://ci.appveyor.com/api/projects/status/599x38f3a0luessl?svg=true)](https://ci.appveyor.com/project/otland/forgottenserver "Download builds for Windows") [![Docker status](https://images.microbadger.com/badges/image/otland/forgottenserver.svg)](https://microbadger.com/images/otland/forgottenserver "Docker image status") +forgottenserver [![Build Status](https://github.com/otland/forgottenserver/actions/workflows/build-vcpkg.yml/badge.svg?branch=master)](https://github.com/otland/forgottenserver/actions/workflows/build-vcpkg.yml "vcpkg build status") [![Build Status](https://github.com/otland/forgottenserver/actions/workflows/docker-image.yml/badge.svg?branch=master)](https://github.com/otland/forgottenserver/actions/workflows/docker-image.yml "Docker image build status") =============== The Forgotten Server is a free and open-source MMORPG server emulator written in C++. It is a fork of the [OpenTibia Server](https://github.com/opentibia/server) project. To connect to the server, you can use [OTClient](https://github.com/edubart/otclient). ### Getting Started -* [Compiling](https://github.com/otland/forgottenserver/wiki/Compiling), alternatively download [AppVeyor builds for Windows](https://ci.appveyor.com/project/otland/forgottenserver) +* [Compiling](https://github.com/otland/forgottenserver/wiki/Compiling), alternatively download [releases](https://github.com/otland/forgottenserver/releases) * [Scripting Reference](https://github.com/otland/forgottenserver/wiki/Script-Interface) * [Contributing](https://github.com/otland/forgottenserver/wiki/Contributing) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index bbbd642c92..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,48 +0,0 @@ -image: Visual Studio 2019 - -shallow_clone: true - -platform: - - x64 - -configuration: - - Debug - - Release - -matrix: - fast_finish: true - -only_commits: - files: - - src/ - - vc14/ - - appveyor.yml - -install: - - cmd : vcpkg install boost-iostreams:x64-windows - - cmd : vcpkg install boost-asio:x64-windows - - cmd : vcpkg install boost-system:x64-windows - - cmd : vcpkg install boost-filesystem:x64-windows - - cmd : vcpkg install boost-variant:x64-windows - - cmd : vcpkg install boost-lockfree:x64-windows - - cmd : vcpkg install cryptopp:x64-windows - - cmd : vcpkg install fmt:x64-windows - - cmd : vcpkg install luajit:x64-windows - - cmd : vcpkg install --recurse libmariadb:x64-windows - - cmd : vcpkg install pugixml:x64-windows - -build: - parallel: true - # MSBuild verbosity level - #verbosity: detailed - -cache: - - c:\tools\vcpkg\installed\ - -after_build: - - 7z a -tzip tfs-win-%PLATFORM%-%CONFIGURATION%.zip -r %APPVEYOR_BUILD_FOLDER%\vc14\%PLATFORM%\%CONFIGURATION%\*.dll %APPVEYOR_BUILD_FOLDER%\vc14\%PLATFORM%\%CONFIGURATION%\theforgottenserver*.exe %APPVEYOR_BUILD_FOLDER%\data %APPVEYOR_BUILD_FOLDER%\config.lua.dist %APPVEYOR_BUILD_FOLDER%\key.pem %APPVEYOR_BUILD_FOLDER%\LICENSE %APPVEYOR_BUILD_FOLDER%\README.md %APPVEYOR_BUILD_FOLDER%\schema.sql - -artifacts: - - path: vc14\%PLATFORM%\%CONFIGURATION%\theforgottenserver*.exe - - path: vc14\%PLATFORM%\%CONFIGURATION%\*.dll - - path: tfs-win-%PLATFORM%-%CONFIGURATION%.zip diff --git a/config.lua.dist b/config.lua.dist index 245fe126a6..ceb522a1dc 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -24,13 +24,13 @@ expFromPlayersLevelRange = 75 -- Connection Config -- NOTE: maxPlayers set to 0 means no limit -- NOTE: allowWalkthrough is only applicable to players +-- NOTE: two-factor auth requires token and timestamp in session key ip = "127.0.0.1" bindOnlyGlobalAddress = false loginProtocolPort = 7171 gameProtocolPort = 7172 statusProtocolPort = 7171 maxPlayers = 0 -motd = "Welcome to The Forgotten Server!" onePlayerOnlinePerAccount = true allowClones = false allowWalkthrough = true @@ -38,6 +38,7 @@ serverName = "Forgotten" statusTimeout = 5000 replaceKickOnLogin = true maxPacketsPerSecond = 25 +enableTwoFactorAuth = true -- Deaths -- NOTE: Leave deathLosePercent as -1 if you want to use the default @@ -91,17 +92,26 @@ maxMessageBuffer = 4 emoteSpells = false classicEquipmentSlots = false classicAttackSpeed = false -showScriptsLogInConsole = true +showScriptsLogInConsole = false showOnlineStatusInCharlist = false yellMinimumLevel = 2 yellAlwaysAllowPremium = false +minimumLevelToSendPrivate = 1 +premiumToSendPrivate = false forceMonsterTypesOnLoad = true cleanProtectionZones = false -luaItemDesc = false +showPlayerLogInConsole = true --- VIP +-- VIP and Depot limits +-- NOTE: you can set custom limits per group in data/XML/groups.xml vipFreeLimit = 20 vipPremiumLimit = 100 +depotFreeLimit = 2000 +depotPremiumLimit = 15000 + +-- Quest Tracker limits +questTrackerFreeLimit = 10 +questTrackerPremiumLimit = 15 -- World Light -- NOTE: if defaultWorldLight is set to true the world light algorithm will @@ -142,9 +152,11 @@ rateSpawn = 1 -- despawnRange is the amount of floors a monster can be from its spawn position -- despawnRadius is how many tiles away it can be from its spawn position -- removeOnDespawn will remove the monster if true or teleport it back to its spawn position if false +-- walkToSpawnRadius is the allowed distance that the monster will stay away from spawn position when left with no targets, 0 to disable deSpawnRange = 2 deSpawnRadius = 50 removeOnDespawn = true +walkToSpawnRadius = 15 -- Stamina staminaSystem = true diff --git a/data/XML/groups.xml b/data/XML/groups.xml index cea155637b..935cb2cd2f 100644 --- a/data/XML/groups.xml +++ b/data/XML/groups.xml @@ -1,12 +1,69 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -16,15 +73,15 @@ - - + + - + - + @@ -40,9 +97,11 @@ + + - + @@ -65,7 +124,7 @@ - + @@ -81,6 +140,8 @@ + + diff --git a/data/XML/mounts.xml b/data/XML/mounts.xml index 625190c465..417682cd0f 100644 --- a/data/XML/mounts.xml +++ b/data/XML/mounts.xml @@ -102,4 +102,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml index e6c43299de..7d25a66ca1 100644 --- a/data/XML/outfits.xml +++ b/data/XML/outfits.xml @@ -56,6 +56,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -113,4 +163,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/vocations.xml b/data/XML/vocations.xml index 6ddebd0c99..5bbf7ac18c 100644 --- a/data/XML/vocations.xml +++ b/data/XML/vocations.xml @@ -1,6 +1,6 @@ - + @@ -10,7 +10,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + diff --git a/data/actions/actions.xml b/data/actions/actions.xml index 3c96742217..62ec3ce3db 100644 --- a/data/actions/actions.xml +++ b/data/actions/actions.xml @@ -85,6 +85,16 @@ + + + + + + + + + + @@ -142,7 +152,7 @@ - + @@ -154,9 +164,6 @@ - - - diff --git a/data/actions/lib/actions.lua b/data/actions/lib/actions.lua index 7ae46259bc..21ba19d80b 100644 --- a/data/actions/lib/actions.lua +++ b/data/actions/lib/actions.lua @@ -13,7 +13,7 @@ local holeId = { -- usable rope holes, for rope spots see global.lua 8567, 8585, 8595, 8596, 8972, 9606, 9625, 13190, 14461, 19519, 21536, 23713, 26020 } -local holes = {468, 481, 483, 7932, 23712} -- holes opened by shovel +local holes = {468, 481, 483, 23712} -- holes opened by shovel local fruits = {2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2684, 2685, 5097, 8839, 8840, 8841} -- fruits to make decorated cake with knife function destroyItem(player, target, toPosition) @@ -138,7 +138,7 @@ function onUseRope(player, item, fromPosition, target, toPosition, isHotkey) local ground = tile:getGround() - if ground and table.contains(ropeSpots, ground:getId()) then + if ground and table.contains(ropeSpots, ground:getId()) or tile:getItemById(14435) then tile = Tile(toPosition:moveUpstairs()) if not tile then return false @@ -200,6 +200,10 @@ function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) toPosition.z = toPosition.z + 1 tile:relocateTo(toPosition) player:addAchievementProgress("The Undertaker", 500) + elseif target.itemid == 7932 then -- large hole + target:transform(7933) + target:decay() + player:addAchievementProgress("The Undertaker", 500) elseif target.itemid == 20230 then -- swamp digging if (player:getStorageValue(PlayerStorageKeys.swampDigging)) <= os.time() then local chance = math.random(100) diff --git a/data/actions/scripts/other/change_gold.lua b/data/actions/scripts/other/change_gold.lua deleted file mode 100644 index 6af63855a1..0000000000 --- a/data/actions/scripts/other/change_gold.lua +++ /dev/null @@ -1,19 +0,0 @@ -local config = { - [ITEM_GOLD_COIN] = {changeTo = ITEM_PLATINUM_COIN}, - [ITEM_PLATINUM_COIN] = {changeBack = ITEM_GOLD_COIN, changeTo = ITEM_CRYSTAL_COIN}, - [ITEM_CRYSTAL_COIN] = {changeBack = ITEM_PLATINUM_COIN} -} - -function onUse(player, item, fromPosition, target, toPosition, isHotkey) - local coin = config[item:getId()] - if coin.changeTo and item.type == 100 then - item:remove() - player:addItem(coin.changeTo, 1) - elseif coin.changeBack then - item:remove(1) - player:addItem(coin.changeBack, 100) - else - return false - end - return true -end diff --git a/data/actions/scripts/other/construction_kits.lua b/data/actions/scripts/other/construction_kits.lua index 1e12d56876..41cb0720ef 100644 --- a/data/actions/scripts/other/construction_kits.lua +++ b/data/actions/scripts/other/construction_kits.lua @@ -1,6 +1,6 @@ local constructionKits = { [3901] = 1666, [3902] = 1670, [3903] = 1652, [3904] = 1674, [3905] = 1658, - [3906] = 3813, [3907] = 3817, [3908] = 1619, [3909] = 12799, [3910] = 2105, + [3906] = 3813, [3907] = 3817, [3908] = 1619, [3909] = 2105, [3910] = 12799, [3911] = 1614, [3912] = 3806, [3913] = 3807, [3914] = 3809, [3915] = 1716, [3916] = 1724, [3917] = 1732, [3918] = 1775, [3919] = 1774, [3920] = 1750, [3921] = 3832, [3922] = 2095, [3923] = 2098, [3924] = 2064, [3925] = 2582, @@ -19,13 +19,16 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return false end - if fromPosition.x == CONTAINER_POSITION then - player:sendTextMessage(MESSAGE_STATUS_SMALL, "Put the construction kit on the floor first.") - elseif not Tile(fromPosition):getHouse() then - player:sendTextMessage(MESSAGE_STATUS_SMALL, "You may construct this only inside a house.") + local tile = Tile(item:getPosition()) + if tile and tile:getHouse() then + if fromPosition.x ~= CONTAINER_POSITION or item:getParent():getId() == ITEM_BROWSEFIELD then + item:transform(kit) + fromPosition:sendMagicEffect(CONST_ME_POFF) + else + player:sendTextMessage(MESSAGE_STATUS_SMALL, "Put the construction kit on the floor first.") + end else - item:transform(kit) - fromPosition:sendMagicEffect(CONST_ME_POFF) + player:sendTextMessage(MESSAGE_STATUS_SMALL, "You may construct this only inside a house.") end return true end diff --git a/data/actions/scripts/other/create_bread.lua b/data/actions/scripts/other/create_bread.lua index 20be59f0e7..04cd81f4aa 100644 --- a/data/actions/scripts/other/create_bread.lua +++ b/data/actions/scripts/other/create_bread.lua @@ -4,10 +4,10 @@ local millstones = {1381, 1382, 1383, 1384} function onUse(player, item, fromPosition, target, toPosition, isHotkey) local itemId = item:getId() if itemId == 2692 then - if target.type == 1 and table.contains(liquidContainers, target.itemid) then + if target.type == FLUID_WATER and table.contains(liquidContainers, target.itemid) then item:remove(1) player:addItem(2693, 1) - target:transform(target.itemid, 0) + target:transform(target.itemid, FLUID_NONE) return true end elseif table.contains(millstones, target.itemid) then diff --git a/data/actions/scripts/other/draw_well.lua b/data/actions/scripts/other/draw_well.lua new file mode 100644 index 0000000000..da9e86eb4e --- /dev/null +++ b/data/actions/scripts/other/draw_well.lua @@ -0,0 +1,7 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + if item:getActionId() == actionIds.drawWell then + fromPosition.z = fromPosition.z + 1 + player:teleportTo(fromPosition, false) + return true + end +end diff --git a/data/actions/scripts/other/enchanting.lua b/data/actions/scripts/other/enchanting.lua index f302eb9b99..be27503285 100644 --- a/data/actions/scripts/other/enchanting.lua +++ b/data/actions/scripts/other/enchanting.lua @@ -150,6 +150,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) player:addManaSpent(items.valuables.mana) player:addItem(targetType.id) player:getPosition():sendMagicEffect(items.valuables.effect) + player:sendSupplyUsed(item) item:remove(1) else local targetItem = targetType[items[itemId].combatType] @@ -171,6 +172,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) if targetItem.targetId then item:transform(targetItem.id) item:decay() + player:sendSupplyUsed(target) target:remove(1) else if targetItem.usesStorage then @@ -201,6 +203,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) if target:hasAttribute(ITEM_ATTRIBUTE_CHARGES) then target:setAttribute(ITEM_ATTRIBUTE_CHARGES, items.equipment.charges) end + player:sendSupplyUsed(item) item:remove(1) end end diff --git a/data/actions/scripts/other/fluids.lua b/data/actions/scripts/other/fluids.lua index 015f3903f6..1a6fd28051 100644 --- a/data/actions/scripts/other/fluids.lua +++ b/data/actions/scripts/other/fluids.lua @@ -10,17 +10,17 @@ poison:setParameter(CONDITION_PARAM_TICKINTERVAL, 4000) poison:setParameter(CONDITION_PARAM_FORCEUPDATE, true) local fluidMessage = { - [3] = "Aah...", - [4] = "Urgh!", - [5] = "Mmmh.", - [7] = "Aaaah...", - [10] = "Aaaah...", - [11] = "Urgh!", - [13] = "Urgh!", - [15] = "Aah...", - [19] = "Urgh!", - [27] = "Aah...", - [43] = "Aaaah..." + [FLUID_BEER] = "Aah...", + [FLUID_SLIME] = "Urgh!", + [FLUID_LEMONADE] = "Mmmh.", + [FLUID_MANA] = "Aaaah...", + [FLUID_LIFE] = "Aaaah...", + [FLUID_OIL] = "Urgh!", + [FLUID_URINE] = "Urgh!", + [FLUID_WINE] = "Aah...", + [FLUID_MUD] = "Urgh!", + [FLUID_RUM] = "Aah...", + [FLUID_MEAD] = "Aaaah..." } local distillery = {[5513] = 5469, [5514] = 5470} @@ -28,57 +28,57 @@ local distillery = {[5513] = 5469, [5514] = 5470} function onUse(player, item, fromPosition, target, toPosition, isHotkey) local targetItemType = ItemType(target.itemid) if targetItemType and targetItemType:isFluidContainer() then - if target.type == 0 and item.type ~= 0 then + if target.type == FLUID_NONE and item.type ~= FLUID_NONE then target:transform(target:getId(), item.type) - item:transform(item:getId(), 0) + item:transform(item:getId(), FLUID_NONE) return true - elseif target.type ~= 0 and item.type == 0 then - target:transform(target:getId(), 0) + elseif target.type ~= FLUID_NONE and item.type == FLUID_NONE then item:transform(item:getId(), target.type) + target:transform(target:getId(), FLUID_NONE) return true end end if target.itemid == 1 then - if item.type == 0 then + if item.type == FLUID_NONE then player:sendTextMessage(MESSAGE_STATUS_SMALL, "It is empty.") elseif target.uid == player.uid then - if table.contains({3, 15, 43}, item.type) then + if table.contains({FLUID_BEER, FLUID_WINE, FLUID_MEAD}, item.type) then player:addCondition(drunk) - elseif item.type == 4 then + elseif item.type == FLUID_SLIME then player:addCondition(poison) - elseif item.type == 7 then + elseif item.type == FLUID_MANA then player:addMana(math.random(50, 150)) fromPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE) - elseif item.type == 10 then + elseif item.type == FLUID_LIFE then player:addHealth(60) fromPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE) end player:say(fluidMessage[item.type] or "Gulp.", TALKTYPE_MONSTER_SAY) - item:transform(item:getId(), 0) + item:transform(item:getId(), FLUID_NONE) else Game.createItem(2016, item.type, toPosition):decay() - item:transform(item:getId(), 0) + item:transform(item:getId(), FLUID_NONE) end else - local fluidSource = targetItemType and targetItemType:getFluidSource() or 0 - if fluidSource ~= 0 then + local fluidSource = targetItemType and targetItemType:getFluidSource() or FLUID_NONE + if fluidSource ~= FLUID_NONE then item:transform(item:getId(), fluidSource) elseif table.contains(distillery, target.itemid) then local tmp = distillery[target.itemid] if tmp then - item:transform(item:getId(), 0) + item:transform(item:getId(), FLUID_NONE) else player:sendCancelMessage("You have to process the bunch into the distillery to get rum.") end - elseif item.type == 0 then + elseif item.type == FLUID_NONE then player:sendTextMessage(MESSAGE_STATUS_SMALL, "It is empty.") else if toPosition.x == CONTAINER_POSITION then toPosition = player:getPosition() end Game.createItem(2016, item.type, toPosition):decay() - item:transform(item:getId(), 0) + item:transform(item:getId(), FLUID_NONE) end end return true diff --git a/data/actions/scripts/other/food.lua b/data/actions/scripts/other/food.lua index 6ac7c0cb80..829a04edfe 100644 --- a/data/actions/scripts/other/food.lua +++ b/data/actions/scripts/other/food.lua @@ -104,7 +104,17 @@ local foods = { [24841] = {12, "Yum."}, -- prickly pear [24843] = {60, "Chomp."}, -- roasted meat [26191] = {25, "Mmmm."}, -- energy bar - [26201] = {15, "Mmmm."} -- energy drink + [26201] = {15, "Mmmm."}, -- energy drink + [27038] = {20, "Urgh."}, -- bug meat + [27039] = {10, "Gulp."}, -- cave turnip + [27052] = {60, "Mmmm."}, -- birthday cake + [27095] = {4, "Slurp."}, -- shell + [27604] = {10, "Slurp."}, -- bottle of Wine + [28348] = {15, "Mmmmm!"}, -- fresh fruit + [32854] = {40, "Mmmm."}, -- meringue cake + [32858] = {15, "Slurp."}, -- winterberry liquor + [34216] = {40, "Slurp."}, -- goanna meat + [34725] = {15, "Slurp."}, -- candy floss } function onUse(player, item, fromPosition, target, toPosition, isHotkey) @@ -119,6 +129,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) else player:feed(food[1] * 12) player:say(food[2], TALKTYPE_MONSTER_SAY) + player:sendSupplyUsed(item) item:remove(1) end return true diff --git a/data/actions/scripts/other/potions.lua b/data/actions/scripts/other/potions.lua index 0d774e450d..039a093f1f 100644 --- a/data/actions/scripts/other/potions.lua +++ b/data/actions/scripts/other/potions.lua @@ -50,17 +50,16 @@ local potions = { }, [7589] = { -- strong mana potion mana = {115, 185}, - vocations = {1, 2, 3, 5, 6, 7}, level = 50, flask = 7634, - description = "Only sorcerers, druids and paladins of level 50 or above may drink this fluid." + description = "Only players of level 50 or above may drink this fluid." }, [7590] = { -- great mana potion mana = {150, 250}, - vocations = {1, 2, 5, 6}, + vocations = {1, 2, 3, 5, 6, 7}, level = 80, flask = 7635, - description = "Only druids and sorcerers of level 80 or above may drink this fluid." + description = "Only sorcerers, druids and paladins of level 80 or above may drink this fluid." }, [7591] = { -- great health potion health = {425, 575}, @@ -107,15 +106,15 @@ local potions = { flask = 7635, description = "Only druids and sorcerers of level 130 or above may drink this fluid." }, - [26030] = { -- supreme health potion - health = {420, 580}, - mana = {200, 350}, + [26030] = { -- ultimate spirit potion + health = {410, 580}, + mana = {150, 250}, vocations = {3, 7}, level = 130, flask = 7635, description = "Only paladins of level 130 or above may drink this fluid." }, - [26031] = { -- ultimate spirit potion + [26031] = { -- supreme health potion health = {875, 1125}, vocations = {4, 8}, level = 200, @@ -130,14 +129,14 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) end local potion = potions[item:getId()] - if potion.level and player:getLevel() < potion.level or potion.vocations and not table.contains(potion.vocations, player:getVocation():getId()) then - player:say(potion.description, TALKTYPE_MONSTER_SAY) + if not player:getGroup():getAccess() and (potion.level and player:getLevel() < potion.level or potion.vocations and not table.contains(potion.vocations, player:getVocation():getId())) then + player:say(potion.description, TALKTYPE_POTION) return true end if potion.condition then player:addCondition(potion.condition) - player:say(potion.text, TALKTYPE_MONSTER_SAY) + player:say(potion.text, TALKTYPE_POTION) player:getPosition():sendMagicEffect(potion.effect) elseif potion.transform then local reward = potion.transform[math.random(#potion.transform)] @@ -152,11 +151,11 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) return true else if potion.health then - doTargetCombat(0, target, COMBAT_HEALING, potion.health[1], potion.health[2]) + doTargetCombat(player, target, COMBAT_HEALING, potion.health[1], potion.health[2]) end if potion.mana then - doTargetCombat(0, target, COMBAT_MANADRAIN, potion.mana[1], potion.mana[2]) + doTargetCombat(player, target, COMBAT_MANADRAIN, potion.mana[1], potion.mana[2]) end if potion.antidote then @@ -165,7 +164,7 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) player:addAchievementProgress("Potion Addict", 100000) player:addItem(potion.flask) - target:say("Aaaah...", TALKTYPE_MONSTER_SAY) + target:say("Aaaah...", TALKTYPE_POTION) target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) end diff --git a/data/chatchannels/scripts/advertising-rook.lua b/data/chatchannels/scripts/advertising-rook.lua index e652a2423d..af1257f728 100644 --- a/data/chatchannels/scripts/advertising-rook.lua +++ b/data/chatchannels/scripts/advertising-rook.lua @@ -16,11 +16,6 @@ function onSpeak(player, type, message) return true end - if player:getLevel() == 1 then - player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") - return false - end - if player:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_ADVERTISING_ROOK) then player:sendCancelMessage("You may only place one offer in two minutes.") return false @@ -38,3 +33,9 @@ function onSpeak(player, type, message) end return type end + +function onJoin(player) + sendChannelMessage(CHANNEL_ADVERTISING_ROOK, MESSAGE_GUILD, "Here you can advertise all kinds of things. Among others, you can trade items, advertise ingame events, seek characters for a quest or a hunting group, find members for your guild or look for somebody to help you with something.") + sendChannelMessage(CHANNEL_ADVERTISING_ROOK, MESSAGE_GUILD, "It goes without saying that all advertisements must conform to the Rules, e.g. it is illegal to advertise trades including real money.") + return true +end diff --git a/data/chatchannels/scripts/advertising.lua b/data/chatchannels/scripts/advertising.lua index 0998faa203..1de484e2dc 100644 --- a/data/chatchannels/scripts/advertising.lua +++ b/data/chatchannels/scripts/advertising.lua @@ -16,8 +16,8 @@ function onSpeak(player, type, message) return true end - if player:getLevel() == 1 then - player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + if player:getLevel() < 20 and not player:isPremium() then + player:sendCancelMessage("You may not speak in this channel unless you have reached level 20 or your account has premium status.") return false end @@ -38,3 +38,9 @@ function onSpeak(player, type, message) end return type end + +function onJoin(player) + sendChannelMessage(CHANNEL_ADVERTISING, MESSAGE_GUILD, "Here you can advertise all kinds of things. Among others, you can trade items, advertise ingame events, seek characters for a quest or a hunting group, find members for your guild or look for somebody to help you with something.") + sendChannelMessage(CHANNEL_ADVERTISING, MESSAGE_GUILD, "It goes without saying that all advertisements must conform to the Rules, e.g. it is illegal to advertise trades including real money.") + return true +end diff --git a/data/chatchannels/scripts/englishchat.lua b/data/chatchannels/scripts/englishchat.lua index 42517d2a59..7bedb4665e 100644 --- a/data/chatchannels/scripts/englishchat.lua +++ b/data/chatchannels/scripts/englishchat.lua @@ -1,10 +1,10 @@ function onSpeak(player, type, message) - local playerAccountType = player:getAccountType() - if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then - player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + if player:getLevel() < 20 and not player:isPremium() then + player:sendCancelMessage("You may not speak in this channel unless you have reached level 20 or your account has premium status.") return false end + local playerAccountType = player:getAccountType() if type == TALKTYPE_CHANNEL_Y then if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_O diff --git a/data/chatchannels/scripts/help.lua b/data/chatchannels/scripts/help.lua index 8e13a7844b..14b4d39a4e 100644 --- a/data/chatchannels/scripts/help.lua +++ b/data/chatchannels/scripts/help.lua @@ -5,17 +5,12 @@ muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_HELP) muted:setParameter(CONDITION_PARAM_TICKS, 3600000) function onSpeak(player, type, message) - local playerAccountType = player:getAccountType() - if player:getLevel() == 1 and playerAccountType == ACCOUNT_TYPE_NORMAL then - player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") - return false - end - if player:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then player:sendCancelMessage("You are muted from the Help channel for using it inappropriately.") return false end + local playerAccountType = player:getAccountType() if playerAccountType >= ACCOUNT_TYPE_TUTOR then if string.sub(message, 1, 6) == "!mute " then local targetName = string.sub(message, 7) diff --git a/data/chatchannels/scripts/worldchat.lua b/data/chatchannels/scripts/worldchat.lua index 42517d2a59..7bedb4665e 100644 --- a/data/chatchannels/scripts/worldchat.lua +++ b/data/chatchannels/scripts/worldchat.lua @@ -1,10 +1,10 @@ function onSpeak(player, type, message) - local playerAccountType = player:getAccountType() - if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then - player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + if player:getLevel() < 20 and not player:isPremium() then + player:sendCancelMessage("You may not speak in this channel unless you have reached level 20 or your account has premium status.") return false end + local playerAccountType = player:getAccountType() if type == TALKTYPE_CHANNEL_Y then if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then type = TALKTYPE_CHANNEL_O diff --git a/data/creaturescripts/scripts/login.lua b/data/creaturescripts/scripts/login.lua index 95456dbdba..2ef8cdab96 100644 --- a/data/creaturescripts/scripts/login.lua +++ b/data/creaturescripts/scripts/login.lua @@ -1,5 +1,6 @@ function onLogin(player) - local loginStr = "Welcome to " .. configManager.getString(configKeys.SERVER_NAME) .. "!" + local serverName = configManager.getString(configKeys.SERVER_NAME) + local loginStr = "Welcome to " .. serverName .. "!" if player:getLastLoginSaved() <= 0 then loginStr = loginStr .. " Please choose your outfit." player:sendOutfitWindow() @@ -8,21 +9,16 @@ function onLogin(player) player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) end - loginStr = string.format("Your last visit was on %s.", os.date("%a %b %d %X %Y", player:getLastLoginSaved())) + loginStr = string.format("Your last visit in %s: %s.", serverName, os.date("%d %b %Y %X", player:getLastLoginSaved())) end player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) - -- Stamina - nextUseStaminaTime[player.uid] = 0 - -- Promotion local vocation = player:getVocation() local promotion = vocation:getPromotion() if player:isPremium() then local value = player:getStorageValue(PlayerStorageKeys.promotion) - if not promotion and value ~= 1 then - player:setStorageValue(PlayerStorageKeys.promotion, 1) - elseif value == 1 then + if value == 1 then player:setVocation(promotion) end elseif not promotion then diff --git a/data/creaturescripts/scripts/offlinetraining.lua b/data/creaturescripts/scripts/offlinetraining.lua index 325f6ddc0d..cb4b78fca0 100644 --- a/data/creaturescripts/scripts/offlinetraining.lua +++ b/data/creaturescripts/scripts/offlinetraining.lua @@ -10,7 +10,7 @@ function onLogin(player) player:setOfflineTrainingSkill(-1) if offlineTime < 600 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You must be logged out for more than 10 minutes to start offline training.") + player:sendTextMessage(MESSAGE_OFFLINE_TRAINING, "You must be logged out for more than 10 minutes to start offline training.") return true end @@ -48,7 +48,7 @@ function onLogin(player) end text = string.format("%s.", text) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, text) + player:sendTextMessage(MESSAGE_OFFLINE_TRAINING, text) local vocation = player:getVocation() local promotion = vocation:getPromotion() diff --git a/data/creaturescripts/scripts/playerdeath.lua b/data/creaturescripts/scripts/playerdeath.lua index 943977d038..4e5dddd5d5 100644 --- a/data/creaturescripts/scripts/playerdeath.lua +++ b/data/creaturescripts/scripts/playerdeath.lua @@ -52,12 +52,12 @@ function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, m local deathRecords = 0 local tmpResultId = resultId - while tmpResultId ~= false do + while tmpResultId do tmpResultId = result.next(resultId) deathRecords = deathRecords + 1 end - if resultId ~= false then + if resultId then result.free(resultId) end @@ -75,12 +75,12 @@ function onDeath(player, corpse, killer, mostDamageKiller, lastHitUnjustified, m if killerGuild ~= 0 and targetGuild ~= killerGuild and isInWar(playerId, killer:getId()) then local warId = false resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. killerGuild .. " AND `guild2` = " .. targetGuild .. ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. killerGuild .. "))") - if resultId ~= false then + if resultId then warId = result.getNumber(resultId, "id") result.free(resultId) end - if warId ~= false then + if warId then db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. db.escapeString(killerName) .. ", " .. db.escapeString(player:getName()) .. ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. warId .. ")") end end diff --git a/data/creaturescripts/scripts/regeneratestamina.lua b/data/creaturescripts/scripts/regeneratestamina.lua index 04409058bb..ead8348bdc 100644 --- a/data/creaturescripts/scripts/regeneratestamina.lua +++ b/data/creaturescripts/scripts/regeneratestamina.lua @@ -12,12 +12,12 @@ function onLogin(player) end local staminaMinutes = player:getStamina() - local maxNormalStaminaRegen = 2400 - math.min(2400, staminaMinutes) + local maxNormalStaminaRegen = 2340 - math.min(2340, staminaMinutes) local regainStaminaMinutes = offlineTime / 180 if regainStaminaMinutes > maxNormalStaminaRegen then local happyHourStaminaRegen = (offlineTime - (maxNormalStaminaRegen * 180)) / 600 - staminaMinutes = math.min(2520, math.max(2400, staminaMinutes) + happyHourStaminaRegen) + staminaMinutes = math.min(2520, math.max(2340, staminaMinutes) + happyHourStaminaRegen) else staminaMinutes = staminaMinutes + regainStaminaMinutes end diff --git a/data/events/events.xml b/data/events/events.xml index 9fe8bce9a3..10eedcfa51 100644 --- a/data/events/events.xml +++ b/data/events/events.xml @@ -18,6 +18,7 @@ + @@ -27,10 +28,13 @@ + + + diff --git a/data/events/scripts/creature.lua b/data/events/scripts/creature.lua index dd9a9e4032..5f586e5800 100644 --- a/data/events/scripts/creature.lua +++ b/data/events/scripts/creature.lua @@ -1,34 +1,31 @@ function Creature:onChangeOutfit(outfit) - if hasEventCallback(EVENT_CALLBACK_ONCHANGEMOUNT) then - if not EventCallback(EVENT_CALLBACK_ONCHANGEMOUNT, self, outfit.lookMount) then + if EventCallback.onChangeMount then + if not EventCallback.onChangeMount(self, outfit.lookMount) then return false end end - if hasEventCallback(EVENT_CALLBACK_ONCHANGEOUTFIT) then - return EventCallback(EVENT_CALLBACK_ONCHANGEOUTFIT, self, outfit) - else - return true + if EventCallback.onChangeOutfit then + return EventCallback.onChangeOutfit(self, outfit) end + return true end function Creature:onAreaCombat(tile, isAggressive) - if hasEventCallback(EVENT_CALLBACK_ONAREACOMBAT) then - return EventCallback(EVENT_CALLBACK_ONAREACOMBAT, self, tile, isAggressive) - else - return RETURNVALUE_NOERROR + if EventCallback.onAreaCombat then + return EventCallback.onAreaCombat(self, tile, isAggressive) end + return RETURNVALUE_NOERROR end function Creature:onTargetCombat(target) - if hasEventCallback(EVENT_CALLBACK_ONTARGETCOMBAT) then - return EventCallback(EVENT_CALLBACK_ONTARGETCOMBAT, self, target) - else - return RETURNVALUE_NOERROR + if EventCallback.onTargetCombat then + return EventCallback.onTargetCombat(self, target) end + return RETURNVALUE_NOERROR end function Creature:onHear(speaker, words, type) - if hasEventCallback(EVENT_CALLBACK_ONHEAR) then - EventCallback(EVENT_CALLBACK_ONHEAR, self, speaker, words, type) + if EventCallback.onHear then + EventCallback.onHear(self, speaker, words, type) end end diff --git a/data/events/scripts/monster.lua b/data/events/scripts/monster.lua index a0e892a6cf..5101cb30d7 100644 --- a/data/events/scripts/monster.lua +++ b/data/events/scripts/monster.lua @@ -1,13 +1,16 @@ function Monster:onDropLoot(corpse) - if hasEventCallback(EVENT_CALLBACK_ONDROPLOOT) then - EventCallback(EVENT_CALLBACK_ONDROPLOOT, self, corpse) + local player = Player(corpse:getCorpseOwner()) + if player then + player:updateKillTracker(self, corpse) + end + if EventCallback.onDropLoot then + EventCallback.onDropLoot(self, corpse) end end function Monster:onSpawn(position, startup, artificial) - if hasEventCallback(EVENT_CALLBACK_ONSPAWN) then - return EventCallback(EVENT_CALLBACK_ONSPAWN, self, position, startup, artificial) - else - return true + if EventCallback.onSpawn then + return EventCallback.onSpawn(self, position, startup, artificial) end + return true end diff --git a/data/events/scripts/party.lua b/data/events/scripts/party.lua index 1c95f83ce0..31933bf938 100644 --- a/data/events/scripts/party.lua +++ b/data/events/scripts/party.lua @@ -1,25 +1,22 @@ function Party:onJoin(player) - if hasEventCallback(EVENT_CALLBACK_ONJOIN) then - return EventCallback(EVENT_CALLBACK_ONJOIN, self, player) - else - return true + if EventCallback.onJoin then + return EventCallback.onJoin(self, player) end + return true end function Party:onLeave(player) - if hasEventCallback(EVENT_CALLBACK_ONLEAVE) then - return EventCallback(EVENT_CALLBACK_ONLEAVE, self, player) - else - return true + if EventCallback.onLeave then + return EventCallback.onLeave(self, player) end + return true end function Party:onDisband() - if hasEventCallback(EVENT_CALLBACK_ONDISBAND) then - return EventCallback(EVENT_CALLBACK_ONDISBAND, self) - else - return true + if EventCallback.onDisband then + return EventCallback.onDisband(self) end + return true end function Party:onShareExperience(exp) @@ -44,6 +41,6 @@ function Party:onShareExperience(exp) sharedExperienceMultiplier = 1.0 + ((size * (5 * (size - 1) + 10)) / 100) end - exp = (exp * sharedExperienceMultiplier) / (#self:getMembers() + 1) - return hasEventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONSHAREEXPERIENCE, self, exp, rawExp) or exp + exp = math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1)) + return EventCallback.onShareExperience and EventCallback.onShareExperience(self, exp, rawExp) or exp end diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index 94fb903003..1fd6d67806 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -1,97 +1,194 @@ function Player:onBrowseField(position) - if hasEventCallback(EVENT_CALLBACK_ONBROWSEFIELD) then - return EventCallback(EVENT_CALLBACK_ONBROWSEFIELD, self, position) - else - return true + if EventCallback.onBrowseField then + return EventCallback.onBrowseField(self, position) end + return true end function Player:onLook(thing, position, distance) - local ret = EventCallback(EVENT_CALLBACK_ONLOOK, self, thing, position, distance) - self:sendTextMessage(MESSAGE_INFO_DESCR, ret) + local description = "" + if EventCallback.onLook then + description = EventCallback.onLook(self, thing, position, distance, description) + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) end function Player:onLookInBattleList(creature, distance) - local ret = EventCallback(EVENT_CALLBACK_ONLOOKINBATTLELIST, self, creature, distance) - self:sendTextMessage(MESSAGE_INFO_DESCR, ret) + local description = "" + if EventCallback.onLookInBattleList then + description = EventCallback.onLookInBattleList(self, creature, distance, description) + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) end function Player:onLookInTrade(partner, item, distance) local description = "You see " .. item:getDescription(distance) - local ret = hasEventCallback(EVENT_CALLBACK_ONLOOKINTRADE) and EventCallback(EVENT_CALLBACK_ONLOOKINTRADE, self, partner, item, distance, description) or description - self:sendTextMessage(MESSAGE_INFO_DESCR, ret) + if EventCallback.onLookInTrade then + description = EventCallback.onLookInTrade(self, partner, item, distance, description) + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) +end + +function Player:onLookInShop(itemType, count) + local description = "You see " + if EventCallback.onLookInShop then + description = EventCallback.onLookInShop(self, itemType, count, description) + end + self:sendTextMessage(MESSAGE_INFO_DESCR, description) end -function Player:onLookInShop(itemType, count, description) - local description = "You see " .. description - local ret = hasEventCallback(EVENT_CALLBACK_ONLOOKINSHOP) and EventCallback(EVENT_CALLBACK_ONLOOKINSHOP, self, itemType, count, description) or description - self:sendTextMessage(MESSAGE_INFO_DESCR, ret) +function Player:onLookInMarket(itemType) + if EventCallback.onLookInMarket then + EventCallback.onLookInMarket(self, itemType) + end end function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if hasEventCallback(EVENT_CALLBACK_ONMOVEITEM) then - return EventCallback(EVENT_CALLBACK_ONMOVEITEM, self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) - else - return true + if EventCallback.onMoveItem then + return EventCallback.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) end + return RETURNVALUE_NOERROR end function Player:onItemMoved(item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if hasEventCallback(EVENT_CALLBACK_ONITEMMOVED) then - EventCallback(EVENT_CALLBACK_ONITEMMOVED, self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + if EventCallback.onItemMoved then + EventCallback.onItemMoved(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) end end function Player:onMoveCreature(creature, fromPosition, toPosition) - if hasEventCallback(EVENT_CALLBACK_ONMOVECREATURE) then - return EventCallback(EVENT_CALLBACK_ONMOVECREATURE, self, creature, fromPosition, toPosition) - else - return true + if EventCallback.onMoveCreature then + return EventCallback.onMoveCreature(self, creature, fromPosition, toPosition) end + return true end function Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) - if hasEventCallback(EVENT_CALLBACK_ONREPORTRULEVIOLATION) then - EventCallback(EVENT_CALLBACK_ONREPORTRULEVIOLATION, self, targetName, reportType, reportReason, comment, translation) + if EventCallback.onReportRuleViolation then + EventCallback.onReportRuleViolation(self, targetName, reportType, reportReason, comment, translation) end end function Player:onReportBug(message, position, category) - if hasEventCallback(EVENT_CALLBACK_ONREPORTBUG) then - return EventCallback(EVENT_CALLBACK_ONREPORTBUG, self, message, position, category) - else - return true + if EventCallback.onReportBug then + return EventCallback.onReportBug(self, message, position, category) end + return true end function Player:onTurn(direction) - if hasEventCallback(EVENT_CALLBACK_ONTURN) then - return EventCallback(EVENT_CALLBACK_ONTURN, self, direction) - else - return true + if EventCallback.onTurn then + return EventCallback.onTurn(self, direction) end + return true end function Player:onTradeRequest(target, item) - if hasEventCallback(EVENT_CALLBACK_ONTRADEREQUEST) then - return EventCallback(EVENT_CALLBACK_ONTRADEREQUEST, self, target, item) - else - return true + if EventCallback.onTradeRequest then + return EventCallback.onTradeRequest(self, target, item) end + return true end function Player:onTradeAccept(target, item, targetItem) - if hasEventCallback(EVENT_CALLBACK_ONTRADEACCEPT) then - return EventCallback(EVENT_CALLBACK_ONTRADEACCEPT, self, target, item, targetItem) - else - return true + if EventCallback.onTradeAccept then + return EventCallback.onTradeAccept(self, target, item, targetItem) end + return true end function Player:onTradeCompleted(target, item, targetItem, isSuccess) - if hasEventCallback(EVENT_CALLBACK_ONTRADECOMPLETED) then - EventCallback(EVENT_CALLBACK_ONTRADECOMPLETED, self, target, item, targetItem, isSuccess) + if EventCallback.onTradeCompleted then + EventCallback.onTradeCompleted(self, target, item, targetItem, isSuccess) + end +end + +function Player:onPodiumRequest(item) + if not item:isPodium() then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return + end + + self:sendEditPodium(item) +end + +function Player:onPodiumEdit(item, outfit, direction, isVisible) + if not item:isPodium() then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return end + + if not self:getGroup():getAccess() then + -- check if the player is in melee range + if getDistanceBetween(self:getPosition(), item:getPosition()) > 1 then + self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return + end + + -- reset outfit if unable to wear + if not self:canWearOutfit(outfit.lookType, outfit.lookAddons) then + outfit.lookType = 0 + end + + -- reset mount if unable to ride + local mount = Game.getMountIdByLookType(outfit.lookMount) + if not (mount and self:hasMount(mount)) then + outfit.lookMount = 0 + end + end + + local podiumOutfit = item:getOutfit() + local playerOutfit = self:getOutfit() + + -- use player outfit if podium is empty + if podiumOutfit.lookType == 0 then + podiumOutfit.lookType = playerOutfit.lookType + podiumOutfit.lookHead = playerOutfit.lookHead + podiumOutfit.lookBody = playerOutfit.lookBody + podiumOutfit.lookLegs = playerOutfit.lookLegs + podiumOutfit.lookFeet = playerOutfit.lookFeet + podiumOutfit.lookAddons = playerOutfit.lookAddons + end + + -- set player mount colors podium is empty + if podiumOutfit.lookMount == 0 then + podiumOutfit.lookMount = playerOutfit.lookMount + podiumOutfit.lookMountHead = playerOutfit.lookMountHead + podiumOutfit.lookMountBody = playerOutfit.lookMountBody + podiumOutfit.lookMountLegs = playerOutfit.lookMountLegs + podiumOutfit.lookMountFeet = playerOutfit.lookMountFeet + end + + -- "outfit" box checked + if outfit.lookType ~= 0 then + podiumOutfit.lookType = outfit.lookType + podiumOutfit.lookHead = outfit.lookHead + podiumOutfit.lookBody = outfit.lookBody + podiumOutfit.lookLegs = outfit.lookLegs + podiumOutfit.lookFeet = outfit.lookFeet + podiumOutfit.lookAddons = outfit.lookAddons + end + + -- "mount" box checked + if outfit.lookMount ~= 0 then + podiumOutfit.lookMount = outfit.lookMount + podiumOutfit.lookMountHead = outfit.lookMountHead + podiumOutfit.lookMountBody = outfit.lookMountBody + podiumOutfit.lookMountLegs = outfit.lookMountLegs + podiumOutfit.lookMountFeet = outfit.lookMountFeet + end + + -- prevent invisible podium state + if outfit.lookType == 0 and outfit.lookMount == 0 then + isVisible = true + end + + -- save player choices + item:setFlag(PODIUM_SHOW_PLATFORM, isVisible) + item:setFlag(PODIUM_SHOW_OUTFIT, outfit.lookType ~= 0) + item:setFlag(PODIUM_SHOW_MOUNT, outfit.lookMount ~= 0) + item:setDirection(direction < DIRECTION_NORTHEAST and direction or DIRECTION_SOUTH) + item:setOutfit(podiumOutfit) end local soulCondition = Condition(CONDITION_SOUL, CONDITIONID_DEFAULT) @@ -105,6 +202,10 @@ local function useStamina(player) end local playerId = player:getId() + if not nextUseStaminaTime[playerId] then + nextUseStaminaTime[playerId] = 0 + end + local currentTime = os.time() local timePassed = currentTime - nextUseStaminaTime[playerId] if timePassed <= 0 then @@ -145,31 +246,31 @@ function Player:onGainExperience(source, exp, rawExp) useStamina(self) local staminaMinutes = self:getStamina() - if staminaMinutes > 2400 and self:isPremium() then + if staminaMinutes > 2340 and self:isPremium() then exp = exp * 1.5 elseif staminaMinutes <= 840 then exp = exp * 0.5 end end - return hasEventCallback(EVENT_CALLBACK_ONGAINEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONGAINEXPERIENCE, self, source, exp, rawExp) or exp + return EventCallback.onGainExperience and EventCallback.onGainExperience(self, source, exp, rawExp) or exp end function Player:onLoseExperience(exp) - return hasEventCallback(EVENT_CALLBACK_ONLOSEEXPERIENCE) and EventCallback(EVENT_CALLBACK_ONLOSEEXPERIENCE, self, exp) or exp + return EventCallback.onLoseExperience and EventCallback.onLoseExperience(self, exp) or exp end function Player:onGainSkillTries(skill, tries) - if APPLY_SKILL_MULTIPLIER == false then - return hasEventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES) and EventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES, self, skill, tries) or tries + if not APPLY_SKILL_MULTIPLIER then + return EventCallback.onGainSkillTries and EventCallback.onGainSkillTries(self, skill, tries) or tries end if skill == SKILL_MAGLEVEL then tries = tries * configManager.getNumber(configKeys.RATE_MAGIC) - return hasEventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES) and EventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES, self, skill, tries) or tries + return EventCallback.onGainSkillTries and EventCallback.onGainSkillTries(self, skill, tries) or tries end tries = tries * configManager.getNumber(configKeys.RATE_SKILL) - return hasEventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES) and EventCallback(EVENT_CALLBACK_ONGAINSKILLTRIES, self, skill, tries) or tries + return EventCallback.onGainSkillTries and EventCallback.onGainSkillTries(self, skill, tries) or tries end function Player:onWrapItem(item) @@ -199,7 +300,7 @@ function Player:onWrapItem(item) return end - if not hasEventCallback(EVENT_CALLBACK_ONWRAPITEM) or EventCallback(EVENT_CALLBACK_ONWRAPITEM, self, item) then + if not EventCallback.onWrapItem or EventCallback.onWrapItem(self, item) then local oldId = item:getId() item:remove(1) local item = tile:addItem(wrapId) @@ -208,3 +309,9 @@ function Player:onWrapItem(item) end end end + +function Player:onInventoryUpdate(item, slot, equip) + if EventCallback.onInventoryUpdate then + EventCallback.onInventoryUpdate(self, item, slot, equip) + end +end diff --git a/data/global.lua b/data/global.lua index 72bc3f2039..14bd2ff135 100644 --- a/data/global.lua +++ b/data/global.lua @@ -1,16 +1,78 @@ math.randomseed(os.time()) dofile('data/lib/lib.lua') -ropeSpots = {384, 418, 8278, 8592, 13189, 14435, 14436, 14857, 15635, 19518, 24621, 24622, 24623, 24624, 26019} - -doors = {[1209] = 1211, [1210] = 1211, [1212] = 1214, [1213] = 1214, [1219] = 1220, [1221] = 1222, [1231] = 1233, [1232] = 1233, [1234] = 1236, [1235] = 1236, [1237] = 1238, [1239] = 1240, [1249] = 1251, [1250] = 1251, [1252] = 1254, [1253] = 1254, [1539] = 1540, [1541] = 1542, [3535] = 3537, [3536] = 3537, [3538] = 3539, [3544] = 3546, [3545] = 3546, [3547] = 3548, [4913] = 4915, [4914] = 4915, [4916] = 4918, [4917] = 4918, [5082] = 5083, [5084] = 5085, [5098] = 5100, [5099] = 5100, [5101] = 5102, [5107] = 5109, [5108] = 5109, [5110] = 5111, [5116] = 5118, [5117] = 5118, [5119] = 5120, [5125] = 5127, [5126] = 5127, [5128] = 5129, [5134] = 5136, [5135] = 5136, [5137] = 5139, [5138] = 5139, [5140] = 5142, [5141] = 5142, [5143] = 5145, [5144] = 5145, [5278] = 5280, [5279] = 5280, [5281] = 5283, [5282] = 5283, [5284] = 5285, [5286] = 5287, [5515] = 5516, [5517] = 5518, [5732] = 5734, [5733] = 5734, [5735] = 5737, [5736] = 5737, [6192] = 6194, [6193] = 6194, [6195] = 6197, [6196] = 6197, [6198] = 6199, [6200] = 6201, [6249] = 6251, [6250] = 6251, [6252] = 6254, [6253] = 6254, [6255] = 6256, [6257] = 6258, [6795] = 6796, [6797] = 6798, [6799] = 6800, [6801] = 6802, [6891] = 6893, [6892] = 6893, [6894] = 6895, [6900] = 6902, [6901] = 6902, [6903] = 6904, [7033] = 7035, [7034] = 7035, [7036] = 7037, [7042] = 7044, [7043] = 7044, [7045] = 7046, [7054] = 7055, [7056] = 7057, [8541] = 8543, [8542] = 8543, [8544] = 8546, [8545] = 8546, [8547] = 8548, [8549] = 8550, [9165] = 9167, [9166] = 9167, [9168] = 9170, [9169] = 9170, [9171] = 9172, [9173] = 9174, [9267] = 9269, [9268] = 9269, [9270] = 9272, [9271] = 9272, [9273] = 9274, [9275] = 9276, [10276] = 10277, [10274] = 10275, [10268] = 10270, [10269] = 10270, [10271] = 10273, [10272] = 10273, [10471] = 10472, [10480] = 10481, [10477] = 10479, [10478] = 10479, [10468] = 10470, [10469] = 10470, [10775] = 10777, [10776] = 10777, [12092] = 12094, [12093] = 12094, [12188] = 12190, [12189] = 12190, [19840] = 19842, [19841] = 19842, [19843] = 19844, [19980] = 19982, [19981] = 19982, [19983] = 19984, [20273] = 20275, [20274] = 20275, [20276] = 20277, [17235] = 17236, [18208] = 18209, [13022] = 13023, [10784] = 10786, [10785] = 10786, [12099] = 12101, [12100] = 12101, [12197] = 12199, [12198] = 12199, [19849] = 19851, [19850] = 19851, [19852] = 19853, [19989] = 19991, [19990] = 19991, [19992] = 19993, [20282] = 20284, [20283] = 20284, [20285] = 20286, [17237] = 17238, [13020] = 13021, [10780] = 10781, [12095] = 12096, [12195] = 12196, [10789] = 10790, [12102] = 12103, [10782] = 10783, [12097] = 12098, [12193] = 12194, [10791] = 10792, [12104] = 12105, [12202] = 12203, [19856] = 19857, [19996] = 19997, [20289] = 20290, [25158] = 25159, [25160] = 25161} -verticalOpenDoors = {1211, 1220, 1224, 1228, 1233, 1238, 1242, 1246, 1251, 1256, 1260, 1540, 3546, 3548, 3550, 3552, 4915, 5083, 5109, 5111, 5113, 5115, 5127, 5129, 5131, 5133, 5142, 5145, 5283, 5285, 5289, 5293, 5516, 5737, 5749, 6194, 6199, 6203, 6207, 6251, 6256, 6260, 6264, 6798, 6802, 6902, 6904, 6906, 6908, 7044, 7046, 7048, 7050, 7055, 8543, 8548, 8552, 8556, 9167, 9172, 9176, 9180, 9269, 9274, 9274, 9269, 9278, 9282, 10270, 10275, 10279, 10283, 10479, 10481, 10485, 10483, 10786, 12101, 12199, 19851, 19853, 19991, 19993, 20284, 20286, 17238, 13021, 10790, 12103, 12205, 19855, 19995, 20288, 10792, 12105, 12203, 19857, 19997, 20290} -horizontalOpenDoors = {1214, 1222, 1226, 1230, 1236, 1240, 1244, 1248, 1254, 1258, 1262, 1542, 3537, 3539, 3541, 3543, 4918, 5085, 5100, 5102, 5104, 5106, 5118, 5120, 5122, 5124, 5136, 5139, 5280, 5287, 5291, 5295, 5518, 5734, 5746, 6197, 6201, 6205, 6209, 6254, 6258, 6262, 6266, 6796, 6800, 6893, 6895, 6897, 6899, 7035, 7037, 7039, 7041, 7057, 8546, 8550, 8554, 8558, 9170, 9174, 9178, 9182, 9272, 9276, 9280, 9284, 10273, 10277, 10281, 10285, 10470, 10472, 10476, 10474, 10777, 12094, 12190, 19842, 19844, 19982, 19984, 20275, 20277, 17236, 18209, 13023, 10781, 12096, 12196, 19846, 19986, 20279, 10783, 12098, 12194, 19848, 19988, 20281} -openQuestDoors = {1224, 1226, 1242, 1244, 1256, 1258, 3543, 3552, 5106, 5115, 5124, 5133, 5289, 5291, 5746, 5749, 6203, 6205, 6260, 6262, 6899, 6908, 7041, 7050, 8552, 8554, 9176, 9178, 9278, 9280, 10279, 10281, 10476, 10485, 10783, 10792, 12098, 12105, 12194, 12203, 19848, 19857, 19988, 19997, 20281, 20290} -openLevelDoors = {1228, 1230, 1246, 1248, 1260, 1262, 3541, 3550, 5104, 5113, 5122, 5131, 5293, 5295, 6207, 6209, 6264, 6266, 6897, 6906, 7039, 7048, 8556, 8558, 9180, 9182, 9282, 9284, 10283, 10285, 10474, 10483, 10781, 10790, 12096, 12103, 12196, 12205, 19846, 19855, 19986, 19995, 20279, 20288} -questDoors = {1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, 5290, 5745, 5748, 6202, 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484, 10782, 10791, 12097, 12104, 12193, 12202, 19847, 19856, 19987, 19996, 20280, 20289} -levelDoors = {1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, 5294, 6206, 6208, 6263, 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, 9281, 9283, 10282, 10284, 10473, 10482, 10780, 10789, 10780, 12095, 12102, 12204, 12195, 19845, 19854, 19985, 19994, 20278, 20287} -keys = {2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032} +ropeSpots = { + 384, 418, 8278, 8592, 13189, 14435, 14436, 14857, 15635, 19518, 24621, 24622, 24623, 24624, 26019 +} + +keys = { + 2086, 2087, 2088, 2089, 2090, 2091, 2092, 10032 +} + +openDoors = { + 1211, 1214, 1233, 1236, 1251, 1254, 3546, 3537, 4915, 4918, 5100, 5109, 5118, 5127, 5136, 5139, 5142, + 5145, 5280, 5283, 5734, 5737, 6194, 6197, 6251, 6254, 6893, 6902, 7035, 7044, 8543, 8546, 9167, 9170, + 9269, 9272, 10270, 10273, 10470, 10479, 10777, 10786, 12094, 12101, 12190, 12199, 19842, 19851, 19982, + 19991, 20275, 20284, 22816, 22825, 25285, 25292 +} +closedDoors = { + 1210, 1213, 1232, 1235, 1250, 1253, 3536, 3545, 4914, 4917, 5099, 5108, 5117, 5126, 5135, 5138, 5141, + 5144, 5279, 5282, 5733, 5736, 6193, 6196, 6250, 6253, 6892, 6901, 7034, 7043, 8542, 8545, 9166, 9169, + 9268, 9271, 10269, 10272, 10766, 10785, 10469, 10478, 12093, 12100, 12189, 12198, 19841, 19850, 19981, + 19990, 20274, 20283, 22815, 22824, 25284, 25291 +} +lockedDoors = { + 1209, 1212, 1231, 1234, 1249, 1252, 3535, 3544, 4913, 4916, 5098, 5107, 5116, 5125, 5134, 5137, 5140, + 5143, 5278, 5281, 5732, 5735, 6192, 6195, 6249, 6252, 6891, 6900, 7033, 7042, 8541, 8544, 9165, 9168, + 9267, 9270, 10268, 10271, 10468, 10477, 10775, 10784, 12092, 12099, 12188, 12197, 19840, 19849, 19980, + 19989, 20273, 20282, 22814, 22823, 25283, 25290 +} + +openExtraDoors = { + 1540, 1542, 6796, 6798, 6800, 6802, 6960, 6962, 7055, 7057, 12695, 12703, 14635, 17236, 17238, 25159, 25161 +} +closedExtraDoors = { + 1539, 1541, 6795, 6797, 6799, 6801, 6959, 6961, 7054, 7056, 12692, 12701, 14633, 17235, 17237, 25158, 25160 +} + +openHouseDoors = { + 1220, 1222, 1238, 1240, 3539, 3548, 5083, 5085, 5102, 5111, 5120, 5129, 5285, 5287, 5516, 5518, 6199, + 6201, 6256, 6258, 6895, 6904, 7037, 7046, 8548, 8550, 9172, 9174, 9274, 9276, 10275, 10277, 10472, 10481, + 13021, 13023, 18209, 19844, 19853, 19984, 19993, 20277, 20286, 22818, 22827 +} +closedHouseDoors = { + 1219, 1221, 1237, 1239, 3538, 3547, 5082, 5084, 5101, 5110, 5119, 5128, 5284, 5286, 5515, 5517, 6198, + 6200, 6255, 6257, 6894, 6903, 7036, 7045, 8547, 8549, 9171, 9173, 9273, 9275, 10274, 10276, 10471, 10480, + 13020, 13022, 18208, 19843, 19852, 19983, 19992, 20276, 20285, 22817, 22826 +} + +--[[ (Not currently used, but probably useful to keep up to date) +openQuestDoors = { + 1224, 1226, 1242, 1244, 1256, 1258, 3543, 3552, 5106, 5115, 5124, 5133, 5289, 5291, 5746, 5749, 6203, + 6205, 6260, 6262, 6899, 6908, 7041, 7050, 8552, 8554, 9176, 9178, 9278, 9280, 10279, 10281, 10476, 10485, + 10783, 10792, 12098, 12105, 12194, 12203, 19848, 19857, 19988, 19997, 20281, 20290, 22822, 22831, 25163, + 25165, 25289, 25296 +} +]]-- +closedQuestDoors = { + 1223, 1225, 1241, 1243, 1255, 1257, 3542, 3551, 5105, 5114, 5123, 5132, 5288, 5290, 5745, 5748, 6202, + 6204, 6259, 6261, 6898, 6907, 7040, 7049, 8551, 8553, 9175, 9177, 9277, 9279, 10278, 10280, 10475, 10484, + 10782, 10791, 12097, 12104, 12193, 12202, 19847, 19856, 19987, 19996, 20280, 20289, 22821, 22830, 25162, + 25164, 25288, 25295 +} + +--[[ (Not currently used, but probably useful to keep up to date) +openLevelDoors = { + 1228, 1230, 1246, 1248, 1260, 1262, 3541, 3550, 5104, 5113, 5122, 5131, 5293, 5295, 6207, 6209, 6264, + 6266, 6897, 6906, 7039, 7048, 8556, 8558, 9180, 9182, 9282, 9284, 10283, 10285, 10474, 10483, 10781, + 10790, 12096, 12103, 12196, 12205, 19846, 19855, 19986, 19995, 20279, 20288, 22820, 22829, 25287, 25294 +} +]]-- +closedLevelDoors = { + 1227, 1229, 1245, 1247, 1259, 1261, 3540, 3549, 5103, 5112, 5121, 5130, 5292, 5294, 6206, 6208, 6263, + 6265, 6896, 6905, 7038, 7047, 8555, 8557, 9179, 9181, 9281, 9283, 10282, 10284, 10473, 10482, 10780, + 10789, 12095, 12102, 12195, 12204, 19845, 19854, 19985, 19994, 20278, 20287, 22819, 22828, 25286, 25293 +} function getDistanceBetween(firstPosition, secondPosition) local xDif = math.abs(firstPosition.x - secondPosition.x) @@ -165,7 +227,3 @@ function getPlayerDatabaseInfo(name_or_guid) result.free(query) return info end - -function getExperienceForLevel(level) - return math.floor((((level - 6) * level + 17) * level - 12) / 6) * 100 -end diff --git a/data/globalevents/scripts/serversave.lua b/data/globalevents/scripts/serversave.lua index f6210abf28..57eb57d004 100644 --- a/data/globalevents/scripts/serversave.lua +++ b/data/globalevents/scripts/serversave.lua @@ -1,14 +1,21 @@ local function ServerSave() - if configManager.getBoolean(configKeys.SERVER_SAVE_CLEAN_MAP) then - cleanMap() - end - - if configManager.getBoolean(configKeys.SERVER_SAVE_CLOSE) then - Game.setGameState(GAME_STATE_CLOSED) - end - if configManager.getBoolean(configKeys.SERVER_SAVE_SHUTDOWN) then Game.setGameState(GAME_STATE_SHUTDOWN) + else + local closeAtServerSave = configManager.getBoolean(configKeys.SERVER_SAVE_CLOSE) + if closeAtServerSave then + Game.setGameState(GAME_STATE_CLOSED) + end + + saveServer() + + if configManager.getBoolean(configKeys.SERVER_SAVE_CLEAN_MAP) then + cleanMap() + end + + if closeAtServerSave then + Game.setGameState(GAME_STATE_NORMAL) + end end end @@ -16,7 +23,7 @@ local function ServerSaveWarning(time) local remaningTime = tonumber(time) - 60000 if configManager.getBoolean(configKeys.SERVER_SAVE_NOTIFY_MESSAGE) then - Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING) + Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .. " minute(s). Please logout.", MESSAGE_STATUS_WARNING) end if remaningTime > 60000 then @@ -29,7 +36,7 @@ end function onTime(interval) local remaningTime = configManager.getNumber(configKeys.SERVER_SAVE_NOTIFY_DURATION) * 60000 if configManager.getBoolean(configKeys.SERVER_SAVE_NOTIFY_MESSAGE) then - Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING) + Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .. " minute(s). Please logout.", MESSAGE_STATUS_WARNING) end addEvent(ServerSaveWarning, 60000, remaningTime) diff --git a/data/globalevents/scripts/startup.lua b/data/globalevents/scripts/startup.lua index c60a94dc34..38a1d4a94e 100644 --- a/data/globalevents/scripts/startup.lua +++ b/data/globalevents/scripts/startup.lua @@ -8,7 +8,7 @@ function onStartup() -- Move expired bans to ban history local resultId = db.storeQuery("SELECT * FROM `account_bans` WHERE `expires_at` != 0 AND `expires_at` <= " .. os.time()) - if resultId ~= false then + if resultId then repeat local accountId = result.getNumber(resultId, "account_id") db.asyncQuery("INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" .. accountId .. ", " .. db.escapeString(result.getString(resultId, "reason")) .. ", " .. result.getNumber(resultId, "banned_at") .. ", " .. result.getNumber(resultId, "expires_at") .. ", " .. result.getNumber(resultId, "banned_by") .. ")") @@ -19,7 +19,7 @@ function onStartup() -- Check house auctions local resultId = db.storeQuery("SELECT `id`, `highest_bidder`, `last_bid`, (SELECT `balance` FROM `players` WHERE `players`.`id` = `highest_bidder`) AS `balance` FROM `houses` WHERE `owner` = 0 AND `bid_end` != 0 AND `bid_end` < " .. os.time()) - if resultId ~= false then + if resultId then repeat local house = House(result.getNumber(resultId, "id")) if house then diff --git a/data/items/items.otb b/data/items/items.otb index da589f23e0..b157195b44 100644 Binary files a/data/items/items.otb and b/data/items/items.otb differ diff --git a/data/items/items.xml b/data/items/items.xml index 3ef860a6eb..78a2ca45fc 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -7,6 +7,7 @@ + @@ -28,7 +29,7 @@ - + @@ -88,7 +89,7 @@ - + @@ -105,10 +106,10 @@ - + - + @@ -136,10 +137,10 @@ - + - - + + @@ -196,18 +197,18 @@ - - - - - + + + + + - - + + - + @@ -219,11 +220,10 @@ - - - - - + + + + @@ -232,18 +232,23 @@ - + + - + + + + + @@ -433,7 +438,7 @@ - + @@ -452,8 +457,8 @@ - - + + @@ -470,7 +475,7 @@ - + @@ -521,8 +526,12 @@ - - + + + + + + @@ -539,7 +548,8 @@ - + + @@ -548,33 +558,51 @@ - - - + + + + + - + - + + + - - + + + + + + - - - - - + + + + + + + + + + + + + + + @@ -598,9 +626,9 @@ - + - + @@ -614,7 +642,7 @@ - + @@ -625,7 +653,7 @@ - + @@ -635,13 +663,13 @@ - + - + @@ -661,9 +689,8 @@ - + - @@ -671,32 +698,28 @@ - + - - + - - - + - @@ -711,13 +734,13 @@ - + - + @@ -728,7 +751,7 @@ - + @@ -738,13 +761,13 @@ - + - + @@ -777,7 +800,7 @@ - + @@ -876,14 +899,29 @@ - + + + + - + + + + + + + + + + - + + + + @@ -1019,22 +1057,22 @@ - + - + - + - + @@ -1269,14 +1307,14 @@ - - - - + - - + + + + + @@ -1285,8 +1323,8 @@ - - + + @@ -1299,29 +1337,31 @@ - + + + + - + + + + - - + - - - - + @@ -1386,7 +1426,8 @@ - + + @@ -1399,11 +1440,10 @@ - + - - + @@ -1433,7 +1473,7 @@ - + @@ -1448,7 +1488,7 @@ - + @@ -1478,7 +1518,7 @@ - + @@ -1674,7 +1714,7 @@ - + @@ -1729,7 +1769,7 @@ - + @@ -1842,7 +1882,7 @@ - + @@ -1856,10 +1896,10 @@ - + - + @@ -2137,6 +2177,7 @@ + @@ -2150,6 +2191,7 @@ + @@ -2174,6 +2216,7 @@ + @@ -2182,6 +2225,7 @@ + @@ -2204,6 +2248,7 @@ + @@ -2211,6 +2256,7 @@ + @@ -2218,6 +2264,7 @@ + @@ -2225,6 +2272,7 @@ + @@ -2232,6 +2280,7 @@ + @@ -2239,6 +2288,7 @@ + @@ -2247,6 +2297,7 @@ + @@ -2261,11 +2312,13 @@ + + @@ -2290,7 +2343,7 @@ - + @@ -2391,6 +2444,7 @@ + @@ -2400,6 +2454,7 @@ + @@ -2408,6 +2463,7 @@ + @@ -2416,6 +2472,7 @@ + @@ -2424,8 +2481,9 @@ + - + @@ -2435,7 +2493,7 @@ - + @@ -2444,7 +2502,7 @@ - + @@ -2454,7 +2512,7 @@ - + @@ -2476,6 +2534,7 @@ + @@ -2483,6 +2542,7 @@ + @@ -2490,6 +2550,7 @@ + @@ -2497,8 +2558,9 @@ + - + @@ -2507,7 +2569,7 @@ - + @@ -2516,7 +2578,7 @@ - + @@ -2531,6 +2593,7 @@ + @@ -2538,8 +2601,9 @@ + - + @@ -2549,7 +2613,7 @@ - + @@ -2605,10 +2669,10 @@ - + - + @@ -2629,7 +2693,7 @@ - + @@ -2647,13 +2711,13 @@ - - + + - + @@ -2661,6 +2725,7 @@ + @@ -2743,7 +2808,7 @@ - + @@ -2909,7 +2974,7 @@ - + @@ -2973,7 +3038,7 @@ - + @@ -3096,11 +3161,11 @@ - + - + @@ -3136,7 +3201,7 @@ - + @@ -3293,6 +3358,7 @@ + @@ -4170,6 +4236,7 @@ + @@ -4179,6 +4246,7 @@ + @@ -4188,6 +4256,7 @@ + @@ -4197,6 +4266,7 @@ + @@ -4206,6 +4276,7 @@ + @@ -4282,19 +4353,19 @@ - + - + - + - + @@ -4344,7 +4415,7 @@ - + @@ -4400,10 +4471,10 @@ - + - + @@ -4418,16 +4489,16 @@ - + - + - + @@ -4573,7 +4644,7 @@ - + @@ -4642,17 +4713,18 @@ - + - + + @@ -4669,7 +4741,7 @@ - + @@ -4789,7 +4861,7 @@ - + @@ -4990,7 +5062,7 @@ - + @@ -5017,7 +5089,7 @@ - + @@ -5065,7 +5137,7 @@ - + @@ -5179,18 +5251,15 @@ - + - - - - - + + @@ -5203,18 +5272,15 @@ - + - - - - - + + @@ -5227,21 +5293,11 @@ - + - - - - - - - - - - - + @@ -5948,7 +6004,7 @@ - + @@ -5985,20 +6041,20 @@ - + - + - + @@ -6062,19 +6118,19 @@ - + - + - + @@ -6168,19 +6224,19 @@ - + - + - + @@ -6210,7 +6266,7 @@ - + @@ -6489,7 +6545,7 @@ - + @@ -6513,17 +6569,17 @@ - + - + - + @@ -6546,7 +6602,7 @@ - + @@ -6562,15 +6618,10 @@ - + - - - - - - + @@ -6618,7 +6669,7 @@ - + @@ -6631,6 +6682,7 @@ + @@ -6641,7 +6693,7 @@ - + @@ -6649,7 +6701,7 @@ - + @@ -6663,6 +6715,7 @@ + @@ -6673,7 +6726,7 @@ - + @@ -6686,43 +6739,43 @@ - + - + - + - + - + - + - + - + @@ -6738,11 +6791,11 @@ - + - + @@ -6753,7 +6806,7 @@ - + @@ -6761,9 +6814,11 @@ - + + + - + @@ -6774,13 +6829,14 @@ - + - + + @@ -6792,9 +6848,7 @@ - - - + @@ -6804,7 +6858,9 @@ - + + + @@ -6941,7 +6997,9 @@ - + + + @@ -6971,7 +7029,7 @@ - + @@ -7098,8 +7156,8 @@ - - + + @@ -7107,7 +7165,7 @@ - + @@ -7142,13 +7200,14 @@ - - - - + + + + + @@ -7451,7 +7510,7 @@ - + @@ -7459,13 +7518,13 @@ - + - + @@ -7482,7 +7541,7 @@ - + @@ -7491,7 +7550,7 @@ - + @@ -7943,12 +8002,6 @@ - - - - - - @@ -8027,7 +8080,8 @@ - + + @@ -8040,11 +8094,11 @@ - + - + @@ -8080,7 +8134,7 @@ - + @@ -8125,7 +8179,7 @@ - + @@ -8182,7 +8236,10 @@ - + + + + @@ -8222,10 +8279,11 @@ - + - + + @@ -8238,18 +8296,19 @@ - + - + + @@ -8302,7 +8361,7 @@ - + @@ -8544,7 +8603,8 @@ - + + @@ -8629,14 +8689,8 @@ - - - - - - - - + + @@ -8651,15 +8705,16 @@ - + - + + - + - + - + @@ -8672,19 +8727,19 @@ - + - + - + - + @@ -8693,7 +8748,7 @@ - + @@ -8704,7 +8759,7 @@ - + @@ -8811,13 +8866,13 @@ - + - + @@ -9022,7 +9077,9 @@ - + + + @@ -9030,13 +9087,11 @@ - + - - - - - + + + @@ -9055,7 +9110,7 @@ - + @@ -9094,8 +9149,8 @@ - + @@ -9105,6 +9160,7 @@ + @@ -9123,7 +9179,7 @@ - + @@ -9153,10 +9209,10 @@ - + - + @@ -9259,11 +9315,12 @@ + - + @@ -9271,33 +9328,34 @@ - + + - + - + - + - + - + - + @@ -9307,7 +9365,7 @@ - + @@ -9388,17 +9446,17 @@ - + - + - + @@ -9479,19 +9537,19 @@ - + - + - + @@ -9516,7 +9574,7 @@ - + @@ -9557,7 +9615,7 @@ - + @@ -9574,7 +9632,7 @@ - + @@ -10007,7 +10065,7 @@ - + @@ -10031,7 +10089,7 @@ - + @@ -10068,7 +10126,7 @@ - + @@ -10393,7 +10451,7 @@ - + @@ -10421,7 +10479,7 @@ - + @@ -10429,7 +10487,7 @@ - + @@ -10484,7 +10542,7 @@ - + @@ -10509,7 +10567,7 @@ - + @@ -10556,8 +10614,10 @@ - - + + + + @@ -10578,7 +10638,7 @@ - + @@ -10587,7 +10647,7 @@ - + @@ -10669,7 +10729,7 @@ - + @@ -10739,11 +10799,11 @@ - + - + - + @@ -10769,6 +10829,7 @@ + @@ -10882,32 +10943,32 @@ - + - + - + - + - + @@ -11195,9 +11256,12 @@ - + + + - + + @@ -11222,19 +11286,19 @@ - + - + - - + + - + @@ -11244,11 +11308,11 @@ - + - + @@ -11258,23 +11322,23 @@ - + - + - + - + - + @@ -11304,7 +11368,7 @@ - + @@ -11403,6 +11467,7 @@ + @@ -11455,17 +11520,17 @@ - + - + - + - + @@ -11486,9 +11551,9 @@ - + - + @@ -11633,8 +11698,12 @@ - - + + + + + + @@ -11653,7 +11722,7 @@ - + @@ -11666,7 +11735,7 @@ - + @@ -11682,10 +11751,10 @@ - + - + @@ -11789,10 +11858,15 @@ - + + + + + + - + @@ -11892,7 +11966,7 @@ - + @@ -11912,8 +11986,8 @@ - - + + @@ -11938,7 +12012,7 @@ - + @@ -11946,7 +12020,7 @@ - + @@ -11959,7 +12033,11 @@ - + + + + + @@ -11967,7 +12045,7 @@ - + @@ -12000,13 +12078,13 @@ - + - + @@ -12060,7 +12138,7 @@ - + @@ -12150,7 +12228,7 @@ - + @@ -12373,7 +12451,7 @@ - + @@ -12393,6 +12471,7 @@ + @@ -12402,6 +12481,7 @@ + @@ -12411,6 +12491,7 @@ + @@ -12437,13 +12518,13 @@ - + - + - + @@ -12478,7 +12559,7 @@ - + @@ -12878,10 +12959,12 @@ + + @@ -12893,6 +12976,7 @@ + @@ -13010,7 +13094,7 @@ - + @@ -13157,7 +13241,7 @@ - + @@ -13173,7 +13257,7 @@ - + @@ -13188,18 +13272,18 @@ - + + - + - - + @@ -13210,8 +13294,7 @@ - - + @@ -13219,28 +13302,36 @@ + - + + - + + + - + + + + + @@ -13311,15 +13402,15 @@ - + - + - + @@ -13348,7 +13439,7 @@ - + @@ -13358,14 +13449,14 @@ - + - + @@ -13382,7 +13473,7 @@ - + @@ -13477,7 +13568,7 @@ - + @@ -13512,7 +13603,7 @@ - + @@ -13545,10 +13636,10 @@ - + - + @@ -13589,8 +13680,8 @@ - - + + @@ -14199,14 +14290,22 @@ - - - - + + + + + + + + + + + + - + @@ -14216,6 +14315,7 @@ + @@ -14226,6 +14326,7 @@ + @@ -14236,6 +14337,7 @@ + @@ -14285,8 +14387,9 @@ + - + @@ -14646,6 +14749,7 @@ + @@ -14655,6 +14759,7 @@ + @@ -14664,6 +14769,7 @@ + @@ -14673,6 +14779,7 @@ + @@ -14781,7 +14888,7 @@ - + @@ -14817,7 +14924,7 @@ - + @@ -14840,7 +14947,7 @@ - + @@ -14860,7 +14967,7 @@ - + @@ -14910,7 +15017,7 @@ - + @@ -14940,12 +15047,14 @@ - + + - - + + + @@ -14994,13 +15103,14 @@ - + - + + @@ -15045,7 +15155,7 @@ - + @@ -15068,12 +15178,18 @@ - - - + + + + + + + + - + + @@ -15086,7 +15202,8 @@ - + + @@ -15109,31 +15226,31 @@ - + - + - + - + - + @@ -15154,12 +15271,12 @@ - - + + - + @@ -15234,13 +15351,13 @@ - - + - + + @@ -15270,53 +15387,64 @@ - + - - - - + - + + + - + - - - + + + + + - + + + + + + - + + + + + + @@ -15370,20 +15498,17 @@ - - - - + - - - + + + - + @@ -15397,7 +15522,7 @@ - + @@ -15414,16 +15539,16 @@ - + - + - + @@ -15465,16 +15590,21 @@ - + + + - - - + + + + + - + + @@ -15525,18 +15655,20 @@ - + - + + + - + - + @@ -15549,10 +15681,10 @@ - + - + @@ -15574,7 +15706,7 @@ - + @@ -15606,28 +15738,28 @@ - - + + - + - + - + - + - + @@ -15647,7 +15779,9 @@ - + + + @@ -15663,15 +15797,22 @@ - - - - - + + + + + + + + + + - - - + + + + + @@ -15693,8 +15834,12 @@ - - + + + + + + @@ -15832,7 +15977,7 @@ - + @@ -16301,32 +16446,32 @@ - + - + - + - + - + @@ -16414,32 +16559,32 @@ - + - + - + - + - + @@ -16467,7 +16612,7 @@ - + @@ -16475,11 +16620,11 @@ - + - + @@ -16508,7 +16653,7 @@ - + @@ -16538,24 +16683,24 @@ - + - + - + - + @@ -16572,9 +16717,7 @@ - - - + @@ -16615,9 +16758,9 @@ - - - + + + @@ -16633,7 +16776,12 @@ - + + + + + + @@ -16680,7 +16828,7 @@ - + @@ -16693,14 +16841,24 @@ - + + + + + + - + + + + + + @@ -16762,7 +16920,9 @@ - + + + @@ -16770,14 +16930,24 @@ - + + + + + + - + + + + + + @@ -16831,21 +17001,23 @@ - - - - - - - + + + + + + + + + - - + + - - + + @@ -16856,10 +17028,12 @@ - + + + - - + + @@ -16886,7 +17060,11 @@ - + + + + + @@ -16894,7 +17072,7 @@ - + @@ -16903,10 +17081,10 @@ - - + + - + @@ -16916,7 +17094,7 @@ - + @@ -16933,57 +17111,60 @@ - + - + - + - + - + - - + + + + - + - + + - + - + @@ -17048,10 +17229,10 @@ - + - + @@ -17071,16 +17252,17 @@ - + - + + - + - + @@ -17093,7 +17275,7 @@ - + @@ -17108,22 +17290,27 @@ - + - + - - + + + + + + + - + + - @@ -17155,7 +17342,7 @@ - + @@ -17163,7 +17350,7 @@ - + @@ -17171,7 +17358,7 @@ - + @@ -17179,7 +17366,7 @@ - + @@ -17189,7 +17376,7 @@ - + @@ -17218,27 +17405,67 @@ - + + + + + - + + + + + + + + + - + + + + + + + + + + + - + + + + + + + - + + + + + + + + + + + + + @@ -17282,15 +17509,24 @@ - + - + + + + + + + + - + + + @@ -17410,13 +17646,13 @@ - + - + @@ -17498,7 +17734,7 @@ - + @@ -17510,11 +17746,11 @@ - + - + @@ -17526,7 +17762,7 @@ - + @@ -17571,7 +17807,9 @@ - + + + @@ -17586,7 +17824,8 @@ - + + @@ -17614,45 +17853,46 @@ + - + - + - + - + - + - + - + @@ -17761,19 +18001,19 @@ - + - + - + - + @@ -17785,7 +18025,7 @@ - + @@ -17848,12 +18088,11 @@ - - - + + - + @@ -17861,7 +18100,8 @@ - + + @@ -17903,10 +18143,11 @@ - + - + + @@ -17958,11 +18199,11 @@ - + - + @@ -17971,8 +18212,8 @@ - - + + @@ -18001,7 +18242,7 @@ - + @@ -18028,7 +18269,9 @@ - + + + @@ -18052,7 +18295,8 @@ - + + @@ -18061,24 +18305,24 @@ - - + + - + - + - + - + @@ -18099,7 +18343,7 @@ - + @@ -18314,7 +18558,7 @@ - + @@ -18322,13 +18566,13 @@ - + - - + + @@ -18339,6 +18583,7 @@ + @@ -18351,6 +18596,7 @@ + @@ -18361,8 +18607,9 @@ + - + @@ -18370,6 +18617,7 @@ + @@ -18379,7 +18627,9 @@ + + @@ -18391,6 +18641,7 @@ + @@ -18467,7 +18718,7 @@ - + @@ -18491,7 +18742,7 @@ - + @@ -18583,7 +18834,7 @@ - + @@ -18595,7 +18846,7 @@ - + @@ -18607,14 +18858,14 @@ - + - + @@ -18645,13 +18896,13 @@ - + - + @@ -18671,28 +18922,28 @@ - + - + - + - + @@ -18701,15 +18952,18 @@ + - - + + + + - + @@ -18717,7 +18971,7 @@ - + @@ -18727,18 +18981,23 @@ - - + + + + + + - + - - + + + @@ -18848,12 +19107,12 @@ - + - + - + @@ -18950,7 +19209,7 @@ - + @@ -18980,25 +19239,30 @@ - + + - - + + + + + + - + - + - + @@ -19010,7 +19274,7 @@ - + @@ -19019,7 +19283,7 @@ - + @@ -19058,7 +19322,7 @@ - + @@ -19103,26 +19367,32 @@ - + + - + + + + + - + + @@ -19144,7 +19414,12 @@ - + + + + + + @@ -19161,13 +19436,18 @@ - + + - - - + + + + + + + - + @@ -19178,28 +19458,32 @@ - - - - + + + + - - - + + + - + - - - + + + + + + - - + + + @@ -19213,7 +19497,8 @@ - + + @@ -19241,8 +19526,8 @@ - - + + @@ -19258,49 +19543,57 @@ - - - + + + + + + + - - + + - + - + - - - - - - - + + + + + + + - + + - - + + - - - - - + + + - + - - + + + + + + + @@ -19317,24 +19610,23 @@ - + - - - - + + + + - - - - + + + + - + - @@ -19342,18 +19634,16 @@ - + - - + - @@ -19361,19 +19651,23 @@ - - - - + + + + + + + + - + - - + + - - - + + + @@ -19382,9 +19676,10 @@ - + + - + @@ -19403,9 +19698,9 @@ - - - + + + @@ -19437,7 +19732,7 @@ - + @@ -19445,12 +19740,12 @@ - - + + - + @@ -19473,8 +19768,9 @@ - - + + + @@ -19488,10 +19784,10 @@ - + - + @@ -19521,44 +19817,47 @@ - + + - + - + - + - + - + - + - + + + - + - + @@ -19616,16 +19915,16 @@ - + - + - + @@ -19637,7 +19936,7 @@ - + @@ -19691,10 +19990,10 @@ - + - + @@ -19706,7 +20005,7 @@ - + @@ -19802,7 +20101,7 @@ - + @@ -19947,14 +20246,14 @@ - - + + - + @@ -19972,13 +20271,13 @@ - + - + @@ -19991,7 +20290,7 @@ - + @@ -20012,7 +20311,7 @@ - + @@ -20080,7 +20379,7 @@ - + @@ -20201,7 +20500,7 @@ - + @@ -20209,19 +20508,19 @@ - + - + - + @@ -20313,7 +20612,8 @@ - + + @@ -20356,6 +20656,9 @@ + + + @@ -20386,63 +20689,106 @@ - + - + - + - + - + - + - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + - + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + @@ -20466,6 +20812,9 @@ + + + @@ -20476,8 +20825,11 @@ - - + + + + + @@ -20486,64 +20838,110 @@ + + + + + + + + + + - - - + + + + + + + - + - + - + - - - - - + + + + + + + + - - - + + + + + + + + + + + + + + - - + + - + + - + - - - + + + - - - + + + + - + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -20571,7 +20969,7 @@ - + @@ -20598,28 +20996,36 @@ - + + + + + + - - - + + - - - - - + + + + + + + - - - - - - + + + + + + + + - + @@ -20627,7 +21033,7 @@ - + @@ -20643,11 +21049,12 @@ - + + - + @@ -20655,7 +21062,7 @@ - + @@ -20671,15 +21078,24 @@ - + - - - - - + + + + + + + + + + + + + + @@ -20702,36 +21118,36 @@ - - - + + + - + - + - + - - + + - + @@ -20745,7 +21161,8 @@ - + + @@ -20764,21 +21181,25 @@ - + + - + + + - - + + - + + @@ -20801,7 +21222,7 @@ - + @@ -20937,7 +21358,7 @@ - + @@ -20950,7 +21371,10 @@ - + + + + @@ -20970,32 +21394,34 @@ - + - + - + - + - - + - + - + + + + - + - + @@ -21007,19 +21433,19 @@ - + - + - + - + - + @@ -21051,7 +21477,8 @@ - + + @@ -21087,18 +21514,22 @@ - + - + - + - + + + + + @@ -21345,6 +21776,7 @@ + @@ -21415,7 +21847,7 @@ - + @@ -21498,8 +21930,10 @@ - - + + + + @@ -21519,7 +21953,7 @@ - + @@ -21537,59 +21971,60 @@ - + - + - + - + - + - - + + - + - + + - - + - + + - + - + @@ -21602,15 +22037,20 @@ - - - - + + + + + + + + + + - - - + + @@ -21647,61 +22087,73 @@ - + + - - - + - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - - + + + + - - + + + + + + - - - + + + + + + + + - - + + - - - - - - - + + + + + + + - + @@ -21717,21 +22169,22 @@ + - + - + - + @@ -21742,93 +22195,111 @@ + - + - - - - - - - + + + + + + + + + + + + + + + + + - - + + + + + + + - + - + - - - - - - + + + + + + + + - + - + - + - + - - + + - + - + - + - + - + - + - + - + @@ -21860,30 +22331,30 @@ - - - + + + - + - + - + - + - + - + @@ -21891,15 +22362,16 @@ - + - + - + + @@ -21907,7 +22379,7 @@ - + @@ -21924,20 +22396,22 @@ - - + + + + - + - + @@ -21974,9 +22448,8 @@ - - - + + @@ -21993,7 +22466,7 @@ - + @@ -22133,20 +22606,24 @@ - + - + - + - + + + + + @@ -22184,32 +22661,30 @@ - - - - - - + - - - - - - + + + + + + + + + - + - + @@ -22266,7 +22741,7 @@ - + @@ -22276,7 +22751,7 @@ - + @@ -22341,7 +22816,7 @@ - + @@ -22410,20 +22885,20 @@ - + - + - + @@ -22435,8 +22910,8 @@ - - + + @@ -22444,13 +22919,13 @@ - + - + - + @@ -22502,7 +22977,8 @@ - + + @@ -22513,7 +22989,7 @@ - + @@ -22544,6 +23020,7 @@ + @@ -22551,7 +23028,7 @@ - + @@ -22573,7 +23050,8 @@ - + + @@ -22591,7 +23069,7 @@ - + @@ -22625,28 +23103,38 @@ - - - + + + - - - - + + + + + + - - + + + + + + + + - + + + @@ -22662,7 +23150,7 @@ - + @@ -22672,9 +23160,11 @@ + + @@ -22709,7 +23199,7 @@ - + @@ -22723,16 +23213,18 @@ - - + + + - + + - + - + @@ -22743,13 +23235,11 @@ - - + - - - - + + + @@ -22764,56 +23254,56 @@ - + - + - + - + - + - + - + - + - + @@ -22823,36 +23313,37 @@ - + - + - - + + + - + - + - + @@ -22892,7 +23383,9 @@ - + + + @@ -22916,19 +23409,19 @@ - - + + - + - - - - + + + + @@ -22963,7 +23456,7 @@ - + @@ -22975,7 +23468,8 @@ - + + @@ -22993,10 +23487,12 @@ - - - - + + + + + + @@ -23004,8 +23500,8 @@ - - + + @@ -23027,17 +23523,18 @@ + - + - + - + @@ -23045,7 +23542,7 @@ - + @@ -23069,22 +23566,22 @@ - + - + - + - + - + @@ -23092,7 +23589,7 @@ - + @@ -23106,14 +23603,14 @@ - + - + @@ -23160,41 +23657,41 @@ - + - + - + - + - + - + @@ -23255,10 +23752,15 @@ + + - + + + + @@ -23289,11 +23791,11 @@ - + - + @@ -23302,13 +23804,17 @@ - + - + + + + + @@ -23316,10 +23822,6 @@ - - - - @@ -23350,13 +23852,13 @@ - - - - + + + + @@ -23365,10 +23867,10 @@ - + - + @@ -23395,68 +23897,162 @@ - + + + + + + + + + - - - - - + + + + + + + + + + + - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + + + + - - + + + + - - - - - - + + + + + + + + + + + + + + + + - + - + - - - + + + - + + + + + @@ -23465,13 +24061,21 @@ - - - + + + + + + + + + + - + + @@ -23521,8 +24125,13 @@ - + + + + + + @@ -23532,19 +24141,24 @@ - + + - + + - + + + + @@ -23617,6 +24231,7 @@ + @@ -23645,7 +24260,7 @@ - + @@ -23728,8 +24343,12 @@ - - + + + + + + @@ -23739,28 +24358,28 @@ - + - + - + - + @@ -23770,7 +24389,7 @@ - + @@ -23787,6 +24406,13 @@ + + + + + + + @@ -23864,6 +24490,7 @@ + @@ -23921,6 +24548,7 @@ + @@ -23940,6 +24568,7 @@ + @@ -23968,8 +24597,7 @@ - - + @@ -24007,7 +24635,7 @@ - + @@ -24077,6 +24705,7 @@ + @@ -24117,7 +24746,7 @@ - + @@ -24132,7 +24761,7 @@ - + @@ -24210,7 +24839,7 @@ - + @@ -24233,10 +24862,10 @@ - + - + @@ -24310,8 +24939,8 @@ - - + + @@ -24328,13 +24957,15 @@ - - + + - + + + @@ -24359,7 +24990,7 @@ - + @@ -24374,7 +25005,7 @@ - + @@ -24392,6 +25023,7 @@ + @@ -24424,6 +25056,7 @@ + @@ -24437,6 +25070,8 @@ + + @@ -24446,12 +25081,16 @@ - - + + + - + + + + @@ -24463,20 +25102,20 @@ - - + + - + - + @@ -24493,11 +25132,11 @@ - + - + @@ -24510,23 +25149,23 @@ - - + + + + - + - - - + @@ -24569,6 +25208,7 @@ + @@ -24578,6 +25218,7 @@ + @@ -24589,12 +25230,12 @@ - + + - - + @@ -24639,13 +25280,15 @@ - + - + + + @@ -24667,7 +25310,7 @@ - + @@ -24706,11 +25349,11 @@ - + - + @@ -24743,8 +25386,14 @@ + - + + + + + + @@ -24773,10 +25422,11 @@ - + - - + + + @@ -24823,7 +25473,7 @@ - + @@ -24871,7 +25521,8 @@ - + + @@ -25021,30 +25672,51 @@ - + + - + + + + - + + + + + - + + + - + + + - - - - + + + + + - - + + - - - - - + + + + + + + + + + + + + @@ -25061,20 +25733,19 @@ - - + - + - + - + @@ -25082,21 +25753,27 @@ - - - + + + + + + + + - - - - - + + + + - + + + @@ -25117,7 +25794,7 @@ - + @@ -25129,7 +25806,7 @@ - + @@ -25138,6 +25815,7 @@ + @@ -25152,6 +25830,7 @@ + @@ -25159,6 +25838,7 @@ + @@ -25167,13 +25847,15 @@ - + + + - + @@ -25241,28 +25923,40 @@ - + + + + + + + + + + + - + + - + + @@ -25285,7 +25979,7 @@ - + @@ -25316,21 +26010,29 @@ - - - + + + + + + + - - - - - + + + + + + + + - + + @@ -25340,6 +26042,7 @@ + @@ -25403,7 +26106,7 @@ - + @@ -25414,12 +26117,12 @@ - - + + - + @@ -25436,6 +26139,7 @@ + @@ -25495,24 +26199,24 @@ - + - + - + - + @@ -25579,7 +26283,7 @@ - + @@ -25605,6 +26309,7 @@ + @@ -25643,6 +26348,7 @@ + @@ -25653,6 +26359,7 @@ + @@ -25760,6 +26467,7 @@ + @@ -25769,6 +26477,7 @@ + @@ -25779,6 +26488,7 @@ + @@ -25804,7 +26514,7 @@ - + @@ -25838,7 +26548,7 @@ - + @@ -25906,18 +26616,19 @@ - + - + + - + - + @@ -25968,6 +26679,9 @@ + + + @@ -26034,16 +26748,17 @@ + - + - + @@ -26052,10 +26767,10 @@ - + - + @@ -26064,10 +26779,10 @@ - + - + @@ -26088,7 +26803,9 @@ + + @@ -26132,11 +26849,11 @@ - + - - + + @@ -26160,13 +26877,15 @@ + - + + - + + - - + @@ -26174,33 +26893,41 @@ + - - + + + - - + + - + + - - + - - + + - - + + + + + + + + @@ -26233,10 +26960,18 @@ - - - + + + + + + + + + + + @@ -26253,24 +26988,29 @@ - + - + + + - + + + + @@ -26282,9 +27022,10 @@ - - - + + + + @@ -26293,17 +27034,12 @@ - - - - - - - - + + + - + @@ -26315,16 +27051,20 @@ - - + + + + + - + + - + @@ -26395,7 +27135,8 @@ - + + @@ -26634,14 +27375,18 @@ - + + + + - + + @@ -26654,13 +27399,21 @@ + + + + - - + + + + + + - + @@ -26690,11 +27443,12 @@ - + + - + @@ -26725,7 +27479,8 @@ - + + @@ -26755,9 +27510,12 @@ + + + @@ -26778,7 +27536,10 @@ + + + @@ -26827,36 +27588,38 @@ - + - + - + - + - + + + - + @@ -26886,11 +27649,12 @@ - + + - + @@ -26945,13 +27709,19 @@ - - + + + + + + + + - + @@ -26984,13 +27754,13 @@ - + - + - + @@ -27008,7 +27778,7 @@ - + @@ -27030,6 +27800,11 @@ + + + + + @@ -27046,7 +27821,7 @@ - + @@ -27060,7 +27835,7 @@ - + @@ -27096,18 +27871,21 @@ - + + + - + + @@ -27173,19 +27951,24 @@ + + - - + + - + - - + + + + + @@ -27203,37 +27986,112 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - + - + - + - + + + + @@ -27622,80 +28480,80 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -28011,52 +28869,52 @@ - + - + - + - + - + - + - + - + @@ -28141,25 +28999,25 @@ - + - + - + - + @@ -28422,78 +29280,78 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -28578,26 +29436,26 @@ - + - + - + - + @@ -28708,52 +29566,52 @@ - + - + - + - + - + - + - + - + @@ -28786,182 +29644,182 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -29020,47 +29878,47 @@ - + - + - + - + - + - + - + @@ -29134,26 +29992,26 @@ - + - + - + - + @@ -29229,32 +30087,35 @@ - + - + + + - + - + + - - + + + - - - + + @@ -29264,21 +30125,25 @@ - - + + - + - + + + - + + + @@ -29290,9 +30155,15 @@ - + + + - + + + + + @@ -29309,6 +30180,7 @@ + @@ -29359,6 +30231,7 @@ + @@ -29440,12 +30313,13 @@ - - - - + + + + + @@ -29474,6 +30348,7 @@ + @@ -29524,7 +30399,7 @@ - + @@ -29603,7 +30478,7 @@ - + @@ -29611,18 +30486,18 @@ - + - + - + @@ -29630,27 +30505,27 @@ - + - + - + - + @@ -29660,25 +30535,25 @@ - + - + - + - + @@ -29760,77 +30635,77 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -29911,7 +30786,7 @@ - + @@ -29985,7 +30860,7 @@ - + @@ -30058,7 +30933,7 @@ - + @@ -30073,28 +30948,28 @@ - + - + - + - + @@ -30116,7 +30991,7 @@ - + @@ -30127,7 +31002,7 @@ - + @@ -30156,20 +31031,21 @@ - + + - - - - + + + + - + @@ -30179,10 +31055,12 @@ + + @@ -30192,6 +31070,8 @@ + + @@ -30223,7 +31103,7 @@ - + @@ -30249,7 +31129,8 @@ - + + @@ -30270,7 +31151,9 @@ - + + + @@ -30324,11 +31207,12 @@ - + + - + - + @@ -30338,17 +31222,26 @@ - + - - - - - - - + + + + + + + + + + + + + + + + @@ -30425,7 +31318,7 @@ - + @@ -30441,112 +31334,152 @@ - + - + - - - - - - - - - + + + + + + + + + + - - - + + + + + - - - + + + + + + + - + + + - - - - + + + + + + + + + - - - + + + + + + - + - + - - + + - + - + + + - + + - + - + + + + - + + + - + - - - - - - + + + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + @@ -30556,7 +31489,11 @@ - + + + + + @@ -30575,10 +31512,10 @@ - + - + @@ -30630,7 +31567,7 @@ - + @@ -30852,13 +31789,16 @@ - - + + - + + + + @@ -30870,24 +31810,28 @@ - + + - + + - - + + - + + + - - + + @@ -30896,16 +31840,16 @@ - - + + - + - - + + @@ -31069,6 +32013,7 @@ + @@ -31076,7 +32021,7 @@ - + @@ -31112,9 +32057,10 @@ - - - + + + + @@ -31128,14 +32074,14 @@ - - + + - + @@ -31149,14 +32095,17 @@ - + + + + - + @@ -31224,44 +32173,47 @@ - - - - + + + + + + - + - + - + - + - + + @@ -31295,21 +32247,24 @@ - - + + - + - + - + - + + + + @@ -31359,7 +32314,7 @@ - + @@ -31414,13 +32369,26 @@ - + - + + + + + + + + + + + + + + @@ -31435,31 +32403,41 @@ + + + + + + + + - - + + + + - + - + - + @@ -31487,8 +32465,17 @@ - + + + + + + + + + + @@ -31506,7 +32493,7 @@ - + @@ -31514,15 +32501,27 @@ - - + + + + + - - - - + + + + + + + + + + + + + - + @@ -31540,11 +32539,12 @@ - + + - + @@ -31554,7 +32554,7 @@ - + @@ -31572,11 +32572,12 @@ - + + - + @@ -31588,113 +32589,158 @@ - + - + + - - + + - + - - + + - + - - + + - - + + - - - - + + + + + + + - + + - - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + - - + - + - - + + - - + + - - - - + + + - + + - - + + - - + + + + + + + - - - - - - + + + + + + + + + + + + - + - - + + + + @@ -31935,23 +32981,32 @@ - - + + + + + - - + + + + + + + + - - - - + + + + - + @@ -32048,7 +33103,7 @@ - + @@ -32057,35 +33112,35 @@ - + - + - + - + - + @@ -32106,8 +33161,8 @@ - - + + @@ -32131,16 +33186,17 @@ - + + - + - + @@ -32162,11 +33218,11 @@ - - - - - + + + + + @@ -32320,6 +33376,7 @@ + @@ -32344,7 +33401,7 @@ - + @@ -32357,7 +33414,7 @@ - + @@ -32384,22 +33441,22 @@ - - + + - - + + - + @@ -32422,10 +33479,10 @@ - + - + @@ -32437,13 +33494,16 @@ - + + + + - - - - - + + + + + @@ -32453,20 +33513,19 @@ - - - - + + - + - - + + + @@ -32486,6 +33545,10 @@ + + + + @@ -32495,6 +33558,7 @@ + @@ -32502,16 +33566,16 @@ - - - + + + - + @@ -32520,17 +33584,19 @@ + + - + @@ -32541,7 +33607,7 @@ - + @@ -32557,10 +33623,7 @@ - - - - + @@ -32586,11 +33649,12 @@ - + + @@ -32607,7 +33671,7 @@ - + @@ -32618,15 +33682,15 @@ - - + + - + @@ -32663,10 +33727,11 @@ + - + @@ -32675,13 +33740,13 @@ - + - + - + @@ -32700,8 +33765,8 @@ - - + + @@ -32714,7 +33779,7 @@ - + @@ -32804,9 +33869,9 @@ - + - + @@ -32828,6 +33893,7 @@ + @@ -32844,7 +33910,7 @@ - + @@ -32855,18 +33921,20 @@ - + + + - + - + - + @@ -32874,7 +33942,7 @@ - + @@ -32887,20 +33955,20 @@ - - + + - + - + - + @@ -32910,13 +33978,19 @@ - - - - - - - + + + + + + + + + + + + + @@ -32939,7 +34013,7 @@ - + @@ -32947,7 +34021,7 @@ - + @@ -32955,7 +34029,7 @@ - + @@ -33005,10 +34079,11 @@ + - + @@ -33027,7 +34102,7 @@ - + @@ -33038,26 +34113,33 @@ - + - + + + + + + + + - + @@ -33071,10 +34153,11 @@ - + + - + @@ -33085,30 +34168,30 @@ - + - + - + - + - + - + - + @@ -33121,18 +34204,18 @@ - + - + - + @@ -33180,26 +34263,26 @@ - + - + - + - - + + - + @@ -33232,7 +34315,7 @@ - + @@ -33297,10 +34380,10 @@ - + - + @@ -33312,13 +34395,13 @@ - + - + @@ -33330,44 +34413,46 @@ - + - + - + - + - + - + - - + + + + @@ -33376,10 +34461,10 @@ - + - + @@ -33405,7 +34490,7 @@ - + @@ -33416,7 +34501,7 @@ - + @@ -33432,7 +34517,8 @@ - + + @@ -33443,7 +34529,7 @@ - + @@ -33481,7 +34567,7 @@ - + @@ -33493,7 +34579,7 @@ - + @@ -33514,91 +34600,91 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -33628,31 +34714,31 @@ - + - + - + - + - + @@ -33670,13 +34756,13 @@ - + - + @@ -33771,9 +34857,12 @@ - - - + + + + + + @@ -33786,10 +34875,10 @@ - + - + @@ -33825,6 +34914,7 @@ + @@ -33877,35 +34967,34 @@ - - - + + - + - + - + - + - + @@ -33937,25 +35026,25 @@ - + - + - + - + @@ -33964,7 +35053,7 @@ - + @@ -33974,10 +35063,10 @@ - + - + @@ -33985,24 +35074,24 @@ - + - + - + - + @@ -34013,28 +35102,29 @@ - + + - - + + - + - + - + @@ -34045,7 +35135,7 @@ - + @@ -34056,7 +35146,7 @@ - + @@ -34071,94 +35161,94 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -34166,14 +35256,14 @@ - + - + @@ -34183,12 +35273,12 @@ - - - - - - + + + + + + @@ -34206,7 +35296,8 @@ - + + @@ -34223,7 +35314,7 @@ - + @@ -34255,22 +35346,24 @@ - - + + - + + + - + @@ -34278,7 +35371,7 @@ - + @@ -34287,7 +35380,7 @@ - + @@ -34296,15 +35389,15 @@ - + + - - + - + @@ -34337,7 +35430,7 @@ - + @@ -34500,20 +35593,75 @@ - + - + + + - + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -34521,28 +35669,28 @@ - + - + - + - + - + - + @@ -34565,23 +35713,28 @@ - + - + + + + + + - + - + - + @@ -34612,8 +35765,8 @@ - - + + @@ -34631,14 +35784,14 @@ - + - + @@ -34653,7 +35806,7 @@ - + @@ -34666,28 +35819,28 @@ - + - + - + - + - + - + @@ -34732,14 +35885,14 @@ - + - + - + @@ -34784,11 +35937,11 @@ - + - + @@ -34838,7 +35991,7 @@ - + @@ -34851,14 +36004,14 @@ - + - + @@ -34868,7 +36021,7 @@ - + @@ -34879,13 +36032,13 @@ - + - + @@ -34930,7 +36083,7 @@ - + @@ -35002,8 +36155,8 @@ - - + + @@ -35060,7 +36213,7 @@ - + @@ -35087,7 +36240,7 @@ - + @@ -35098,18 +36251,17 @@ - - - + + + - - - - + + + - + @@ -35123,29 +36275,55 @@ - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + @@ -35224,85 +36402,67 @@ - + - + - - + + - + - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + @@ -35311,10 +36471,7 @@ - - - - + @@ -35323,64 +36480,58 @@ - - - - + - - - - + - + - + - + - + - + - + - + @@ -35389,7 +36540,7 @@ - + @@ -35398,68 +36549,76 @@ - + - + - - + + - + + + + - + - - - - + + + + - + - - - - + + + + - - - - + + + + - - + + - - - - + + + + + + + + + @@ -35480,22 +36639,22 @@ - + - + - + - + @@ -35506,11 +36665,11 @@ - + - + @@ -35554,7 +36713,7 @@ - + @@ -35562,12 +36721,12 @@ - + - + @@ -35631,7 +36790,7 @@ - + @@ -35646,10 +36805,10 @@ - + - + @@ -35658,10 +36817,10 @@ - + - + @@ -35669,14 +36828,12 @@ - - + - - - - + + + @@ -35685,30 +36842,37 @@ - + - + - + - + + + - + + + + + + @@ -35777,18 +36941,18 @@ - + - + - + @@ -35842,7 +37006,7 @@ - + @@ -35904,8 +37068,9 @@ + - + @@ -35929,15 +37094,17 @@ + - + + - + @@ -35947,14 +37114,15 @@ - + + - + @@ -35967,14 +37135,15 @@ - + + - + @@ -36003,8 +37172,7 @@ - - + @@ -36032,88 +37200,142 @@ - - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -36234,13 +37456,13 @@ - + - + - + @@ -36252,13 +37474,13 @@ - + - + - + @@ -36275,4 +37497,4429 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/lib/compat/compat.lua b/data/lib/compat/compat.lua index ac4e62b9ce..d3ac272e1d 100644 --- a/data/lib/compat/compat.lua +++ b/data/lib/compat/compat.lua @@ -25,6 +25,10 @@ THING_TYPE_NPC = CREATURETYPE_NPC + 1 COMBAT_POISONDAMAGE = COMBAT_EARTHDAMAGE CONDITION_EXHAUST = CONDITION_EXHAUST_WEAPON +MESSAGE_STATUS_CONSOLE_BLUE = MESSAGE_INFO_DESCR +MESSAGE_STATUS_CONSOLE_RED = MESSAGE_STATUS_WARNING +MESSAGE_EVENT_ORANGE = MESSAGE_STATUS_WARNING +MESSAGE_STATUS_CONSOLE_ORANGE = MESSAGE_STATUS_WARNING TALKTYPE_ORANGE_1 = TALKTYPE_MONSTER_SAY TALKTYPE_ORANGE_2 = TALKTYPE_MONSTER_YELL @@ -37,7 +41,31 @@ SOUTHEAST = DIRECTION_SOUTHEAST NORTHWEST = DIRECTION_NORTHWEST NORTHEAST = DIRECTION_NORTHEAST +SPEECHBUBBLE_QUESTTRADER = SPEECHBUBBLE_QUEST + do + local function storageProxy(player) + return setmetatable({}, { + __index = function(self, key) + return player:getStorageValue(key) + end, + __newindex = function(self, key, value) + player:setStorageValue(key, value) + end + }) + end + + local function accountStorageProxy(player) + return setmetatable({}, { + __index = function(self, key) + return Game.getAccountStorageValue(player:getAccountId(), key) + end, + __newindex = function(self, key, value) + Game.setAccountStorageValue(player:getAccountId(), key, value) + end + }) + end + local function CreatureIndex(self, key) local methods = getmetatable(self) if key == "uid" then @@ -56,7 +84,16 @@ do return 1 elseif key == "actionid" then return 0 + elseif key == "storage" then + if methods.isPlayer(self) then + return storageProxy(self) + end + elseif key == "accountStorage" then + if methods.isPlayer(self) then + return accountStorageProxy(self) + end end + return methods[key] end rawgetmetatable("Player").__index = CreatureIndex @@ -81,6 +118,7 @@ do rawgetmetatable("Item").__index = ItemIndex rawgetmetatable("Container").__index = ItemIndex rawgetmetatable("Teleport").__index = ItemIndex + rawgetmetatable("Podium").__index = ItemIndex end do @@ -302,12 +340,12 @@ setCombatFormula = Combat.setFormula setCombatParam = Combat.setParameter Combat.setCondition = function(...) - print("[Warning] Function Combat.setCondition was renamed to Combat.addCondition and will be removed in the future") + print("[Warning - " .. debug.getinfo(2).source:match("@?(.*)") .. "] Function Combat.setCondition was renamed to Combat.addCondition and will be removed in the future") Combat.addCondition(...) end setCombatCondition = function(...) - print("[Warning] Function setCombatCondition was renamed to addCombatCondition and will be removed in the future") + print("[Warning - " .. debug.getinfo(2).source:match("@?(.*)") .. "] Function setCombatCondition was renamed to addCombatCondition and will be removed in the future") Combat.addCondition(...) end @@ -369,7 +407,7 @@ end function getCreatureSummons(cid) local c = Creature(cid) - if c == nil then + if not c then return false end @@ -428,7 +466,7 @@ function getPlayerRequiredMana(cid, magicLevel) local p = Player(cid) return p a function getPlayerRequiredSkillTries(cid, skillId) local p = Player(cid) return p and p:getVocation():getRequiredSkillTries(skillId) or false end function getPlayerAccess(cid) local player = Player(cid) - if player == nil then + if not player then return false end return player:getGroup():getAccess() and 1 or 0 @@ -457,29 +495,29 @@ function getPlayerLossPercent(cid) local p = Player(cid) return p and p:getDeath function getPlayerMount(cid, mountId) local p = Player(cid) return p and p:hasMount(mountId) or false end function getPlayerPremiumDays(cid) local p = Player(cid) return p and p:getPremiumDays() or false end function getPlayerBlessing(cid, blessing) local p = Player(cid) return p and p:hasBlessing(blessing) or false end -function getPlayerFlagValue(cid, flag) local p = Player(cid) return p ~= nil and p:hasFlag(flag) or false end +function getPlayerFlagValue(cid, flag) local p = Player(cid) return p and p:hasFlag(flag) or false end function getPlayerCustomFlagValue() debugPrint("Deprecated function, use player:hasFlag(flag) instead.") return true end function getPlayerParty(cid) local player = Player(cid) - if player == nil then + if not player then return false end local party = player:getParty() - if party == nil then + if not party then return nil end return party:getLeader():getId() end function getPlayerGuildId(cid) local player = Player(cid) - if player == nil then + if not player then return false end local guild = player:getGuild() - if guild == nil then + if not guild then return false end return guild:getId() @@ -487,24 +525,24 @@ end function getPlayerGuildLevel(cid) local p = Player(cid) return p and p:getGuildLevel() or false end function getPlayerGuildName(cid) local player = Player(cid) - if player == nil then + if not player then return false end local guild = player:getGuild() - if guild == nil then + if not guild then return false end return guild:getName() end function getPlayerGuildRank(cid) local player = Player(cid) - if player == nil then + if not player then return false end local guild = player:getGuild() - if guild == nil then + if not guild then return false end @@ -518,21 +556,21 @@ function getPlayerItemCount(cid, itemId, ...) local p = Player(cid) return p and function getPlayerWeapon(cid) local p = Player(cid) return p and p:getWeaponType() or false end function getPlayerSlotItem(cid, slot) local player = Player(cid) - if player == nil then + if not player then return pushThing(nil) end return pushThing(player:getSlotItem(slot)) end function getPlayerItemById(cid, deepSearch, itemId, ...) local player = Player(cid) - if player == nil then + if not player then return pushThing(nil) end return pushThing(player:getItemById(itemId, deepSearch, ...)) end function getPlayerFood(cid) local player = Player(cid) - if player == nil then + if not player then return false end local c = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) return c and math.floor(c:getTicks() / 1000) or 0 @@ -543,7 +581,7 @@ function isPlayerGhost(cid) local p = Player(cid) return p and p:isInGhostMode() function isPlayerPzLocked(cid) local p = Player(cid) return p and p:isPzLocked() or false end function isPremium(cid) local p = Player(cid) return p and p:isPremium() or false end function getPlayersByIPAddress(ip, mask) - if mask == nil then mask = 0xFFFFFFFF end + if not mask then mask = 0xFFFFFFFF end local masked = bit.band(ip, mask) local result = {} for _, player in ipairs(Game.getPlayers()) do @@ -578,7 +616,7 @@ function getPlayerGUIDByName(name) end local resultId = db.storeQuery("SELECT `id` FROM `players` WHERE `name` = " .. db.escapeString(name)) - if resultId ~= false then + if resultId then local guid = result.getNumber(resultId, "id") result.free(resultId) return guid @@ -592,7 +630,7 @@ function getAccountNumberByPlayerName(name) end local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(name)) - if resultId ~= false then + if resultId then local accountId = result.getNumber(resultId, "account_id") result.free(resultId) return accountId @@ -640,16 +678,16 @@ function doPlayerAddBlessing(cid, blessing) local p = Player(cid) return p and p function doPlayerAddOutfit(cid, lookType, addons) local p = Player(cid) return p and p:addOutfitAddon(lookType, addons) or false end function doPlayerRemOutfit(cid, lookType, addons) local player = Player(cid) - if player == nil then + if not player then return false end if addons == 255 then return player:removeOutfit(lookType) - else - return player:removeOutfitAddon(lookType, addons) end + return player:removeOutfitAddon(lookType, addons) end doPlayerRemoveOutfit = doPlayerRemOutfit +function doPlayerAddAddons(cid, addon) local p = Player(cid) return p and p:addAddonToAllOutfits(addon) or false end function canPlayerWearOutfit(cid, lookType, addons) local p = Player(cid) return p and p:hasOutfit(lookType, addons) or false end function doPlayerAddMount(cid, mountId) local p = Player(cid) return p and p:addMount(mountId) or false end function doPlayerRemoveMount(cid, mountId) local p = Player(cid) return p and p:removeMount(mountId) or false end @@ -668,12 +706,12 @@ function doPlayerSendTextMessage(cid, type, text, ...) local p = Player(cid) ret function doSendAnimatedText() debugPrint("Deprecated function.") return true end function getPlayerAccountManager() debugPrint("Deprecated function.") return true end function doPlayerSetExperienceRate() debugPrint("Deprecated function, use Player:onGainExperience event instead.") return true end -function doPlayerSetSkillLevel(cid, skill, value, ...) local p = Player(cid) return p and p:addSkill(skillId, value, round) end +function doPlayerSetSkillLevel(cid, skillId, value, ...) local p = Player(cid) return p and p:addSkill(skillId, value, ...) end function doPlayerSetMagicLevel(cid, value) local p = Player(cid) return p and p:addMagicLevel(value) end function doPlayerAddLevel(cid, amount, round) local p = Player(cid) return p and p:addLevel(amount, round) end function doPlayerAddExp(cid, exp, useMult, ...) local player = Player(cid) - if player == nil then + if not player then return false end @@ -689,7 +727,7 @@ function doPlayerAddSkillTry(cid, skillid, n) local p = Player(cid) return p and function doPlayerAddMana(cid, mana, ...) local p = Player(cid) return p and p:addMana(mana, ...) or false end function doPlayerJoinParty(cid, leaderId) local player = Player(cid) - if player == nil then + if not player then return false end @@ -699,12 +737,12 @@ function doPlayerJoinParty(cid, leaderId) end local leader = Player(leaderId) - if leader == nil then + if not leader then return false end local party = leader:getParty() - if party == nil or party:getLeader() ~= leader then + if not party or party:getLeader() ~= leader then return true end @@ -719,12 +757,12 @@ function doPlayerJoinParty(cid, leaderId) end function getPartyMembers(cid) local player = Player(cid) - if player == nil then + if not player then return false end local party = player:getParty() - if party == nil then + if not party then return false end @@ -739,7 +777,7 @@ doPlayerSendDefaultCancel = doPlayerSendCancel function getMonsterTargetList(cid) local monster = Monster(cid) - if monster == nil then + if not monster then return false end @@ -753,7 +791,7 @@ function getMonsterTargetList(cid) end function getMonsterFriendList(cid) local monster = Monster(cid) - if monster == nil then + if not monster then return false end @@ -769,7 +807,7 @@ function getMonsterFriendList(cid) end function doSetMonsterTarget(cid, target) local monster = Monster(cid) - if monster == nil then + if not monster then return false end @@ -778,7 +816,7 @@ function doSetMonsterTarget(cid, target) end local target = Creature(cid) - if target == nil then + if not target then return false end @@ -788,7 +826,7 @@ end doMonsterSetTarget = doSetMonsterTarget function doMonsterChangeTarget(cid) local monster = Monster(cid) - if monster == nil then + if not monster then return false end @@ -808,12 +846,12 @@ end doCreateMonster = doSummonCreature function doConvinceCreature(cid, target) local creature = Creature(cid) - if creature == nil then + if not creature then return false end local targetCreature = Creature(target) - if targetCreature == nil then + if not targetCreature then return false end @@ -847,7 +885,7 @@ function getContainerSize(uid) local c = Container(uid) return c and c:getSize() function getContainerCap(uid) local c = Container(uid) return c and c:getCapacity() or false end function getContainerItem(uid, slot) local container = Container(uid) - if container == nil then + if not container then return pushThing(nil) end return pushThing(container:getItem(slot)) @@ -855,12 +893,12 @@ end function doAddContainerItemEx(uid, virtualId) local container = Container(uid) - if container == nil then + if not container then return false end local res = container:addItemEx(Item(virtualId)) - if res == nil then + if not res then return false end return res @@ -872,12 +910,12 @@ function isSightClear(fromPos, toPos, floorCheck) return Position(fromPos):isSig function getPromotedVocation(vocationId) local vocation = Vocation(vocationId) - if vocation == nil then + if not vocation then return 0 end local promotedVocation = vocation:getPromotion() - if promotedVocation == nil then + if not promotedVocation then return 0 end return promotedVocation:getId() @@ -886,7 +924,7 @@ getPlayerPromotionLevel = getPromotedVocation function getGuildId(guildName) local resultId = db.storeQuery("SELECT `id` FROM `guilds` WHERE `name` = " .. db.escapeString(guildName)) - if resultId == false then + if not resultId then return false end @@ -898,7 +936,7 @@ end function getHouseName(houseId) local h = House(houseId) return h and h:getName() or false end function getHouseOwner(houseId) local h = House(houseId) return h and h:getOwnerGuid() or false end function getHouseEntry(houseId) local h = House(houseId) return h and h:getExitPosition() or false end -function getHouseTown(houseId) local h = House(houseId) if h == nil then return false end local t = h:getTown() return t and t:getId() or false end +function getHouseTown(houseId) local h = House(houseId) if not h then return false end local t = h:getTown() return t and t:getId() or false end function getHouseTilesSize(houseId) local h = House(houseId) return h and h:getTileCount() or false end function isItemStackable(itemId) return ItemType(itemId):isStackable() end @@ -933,7 +971,7 @@ function getItemIdByName(name) end function getItemWeightByUID(uid, ...) local item = Item(uid) - if item == nil then + if not item then return false end @@ -942,7 +980,7 @@ function getItemWeightByUID(uid, ...) end function getItemRWInfo(uid) local item = Item(uid) - if item == nil then + if not item then return false end @@ -961,21 +999,20 @@ function getContainerCapById(itemId) return ItemType(itemId):getCapacity() end function getFluidSourceType(itemId) local it = ItemType(itemId) return it.id ~= 0 and it:getFluidSource() or false end function hasProperty(uid, prop) local item = Item(uid) - if item == nil then + if not item then return false end local parent = item:getParent() if parent:isTile() and item == parent:getGround() then return parent:hasProperty(prop) - else - return item:hasProperty(prop) end + return item:hasProperty(prop) end function doSetItemText(uid, text) local item = Item(uid) - if item == nil then + if not item then return false end @@ -988,7 +1025,7 @@ function doSetItemText(uid, text) end function doSetItemSpecialDescription(uid, desc) local item = Item(uid) - if item == nil then + if not item then return false end @@ -1017,7 +1054,7 @@ end function getTileHouseInfo(pos) local t = Tile(pos) - if t == nil then + if not t then return false end local h = t:getHouse() @@ -1026,7 +1063,7 @@ end function getTilePzInfo(position) local t = Tile(position) - if t == nil then + if not t then return false end return t:hasFlag(TILESTATE_PROTECTIONZONE) @@ -1034,7 +1071,7 @@ end function getTileInfo(position) local t = Tile(position) - if t == nil then + if not t then return false end @@ -1043,7 +1080,7 @@ function getTileInfo(position) ret.nopz = ret.protection ret.nologout = t:hasFlag(TILESTATE_NOLOGOUT) ret.refresh = t:hasFlag(TILESTATE_REFRESH) - ret.house = t:getHouse() ~= nil + ret.house = t:getHouse() ret.bed = t:hasFlag(TILESTATE_BED) ret.depot = t:hasFlag(TILESTATE_DEPOT) @@ -1057,7 +1094,7 @@ end function getTileItemByType(position, itemType) local t = Tile(position) - if t == nil then + if not t then return pushThing(nil) end return pushThing(t:getItemByType(itemType)) @@ -1065,7 +1102,7 @@ end function getTileItemById(position, itemId, ...) local t = Tile(position) - if t == nil then + if not t then return pushThing(nil) end return pushThing(t:getItemById(itemId, ...)) @@ -1073,7 +1110,7 @@ end function getTileThingByPos(position) local t = Tile(position) - if t == nil then + if not t then if position.stackpos == -1 then return -1 end @@ -1088,7 +1125,7 @@ end function getTileThingByTopOrder(position, topOrder) local t = Tile(position) - if t == nil then + if not t then return pushThing(nil) end return pushThing(t:getItemByTopOrder(topOrder)) @@ -1096,7 +1133,7 @@ end function getTopCreature(position) local t = Tile(position) - if t == nil then + if not t then return pushThing(nil) end return pushThing(t:getTopCreature()) @@ -1108,20 +1145,17 @@ function doTeleportThing(uid, dest, pushMovement) if type(uid) == "userdata" then if uid:isCreature() then return uid:teleportTo(dest, pushMovement or false) - else - return uid:moveTo(dest) end + + return uid:moveTo(dest) else - if uid >= 0x10000000 then - local creature = Creature(uid) - if creature then - return creature:teleportTo(dest, pushMovement or false) - end - else - local item = Item(uid) - if item then - return item:moveTo(dest) + local thing = getThing(uid) + if thing then + if thing:isCreature() then + return thing:teleportTo(dest, pushMovement or false) end + + return thing:moveTo(dest) end end return false @@ -1130,16 +1164,12 @@ end function getThingPos(uid) local thing if type(uid) ~= "userdata" then - if uid >= 0x10000000 then - thing = Creature(uid) - else - thing = Item(uid) - end + thing = getThing(uid) else thing = uid end - if thing == nil then + if not thing then return false end @@ -1157,7 +1187,7 @@ getThingPosition = getThingPos function getThingfromPos(pos) local tile = Tile(pos) - if tile == nil then + if not tile then return pushThing(nil) end @@ -1165,7 +1195,7 @@ function getThingfromPos(pos) local stackpos = pos.stackpos or 0 if stackpos == STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE then thing = tile:getTopCreature() - if thing == nil then + if not thing then local item = tile:getTopDownItem() if item and item:getType():isMovable() then thing = item @@ -1187,11 +1217,11 @@ function doRelocate(fromPos, toPos) end local fromTile = Tile(fromPos) - if fromTile == nil then + if not fromTile then return false end - if Tile(toPos) == nil then + if not Tile(toPos) then return false end @@ -1211,7 +1241,7 @@ function doRelocate(fromPos, toPos) end function getThing(uid) - return uid >= 0x10000000 and pushThing(Creature(uid)) or pushThing(Item(uid)) + return uid >= CREATURE_ID_MIN and pushThing(Creature(uid)) or pushThing(Item(uid)) end function getConfigInfo(info) @@ -1305,7 +1335,7 @@ end doBroadcastMessage = broadcastMessage function Guild.addMember(self, player) - return player:setGuild(guild) + return player:setGuild(self) end function Guild.removeMember(self, player) return player:getGuild() == self and player:setGuild(nil) @@ -1335,9 +1365,7 @@ function doSetCreatureOutfit(cid, outfit, time) end local condition = Condition(CONDITION_OUTFIT) - condition:setOutfit({ - lookTypeEx = itemType:getId() - }) + condition:setOutfit(outfit) condition:setTicks(time) creature:addCondition(condition) @@ -1381,7 +1409,7 @@ function doCreateItemEx(itemid, count) return false end -function doMoveCreature(cid, direction) local c = Creature(cid) return c ~= nil and c:move(direction) end +function doMoveCreature(cid, direction) local c = Creature(cid) return c and c:move(direction) end function createFunctions(class) local exclude = {[2] = {"is"}, [3] = {"get", "set", "add", "can"}, [4] = {"need"}} @@ -1411,7 +1439,7 @@ function createFunctions(class) end function isNumber(str) - return tonumber(str) ~= nil + return tonumber(str) end function doSetCreatureLight(cid, lightLevel, lightColor, time) @@ -1428,6 +1456,8 @@ function doSetCreatureLight(cid, lightLevel, lightColor, time) return true end +function getExperienceForLevel(level) return Game.getExperienceForLevel(level) end + do local combats = { [COMBAT_PHYSICALDAMAGE] = 'physical', @@ -1469,12 +1499,12 @@ end do local specialSkills = { - [SPECIALSKILL_CRITICALHITCHANCE] = 'critical hit chance', - [SPECIALSKILL_CRITICALHITAMOUNT] = 'critical extra damage', - [SPECIALSKILL_LIFELEECHCHANCE] = 'hitpoints leech chance', - [SPECIALSKILL_LIFELEECHAMOUNT] = 'hitpoints leech amount', - [SPECIALSKILL_MANALEECHCHANCE] = 'manapoints leech chance', - [SPECIALSKILL_MANALEECHAMOUNT] = 'manapoints leech amount' + [SPECIALSKILL_CRITICALHITCHANCE] = 'critical hit chance', -- format: x% + [SPECIALSKILL_CRITICALHITAMOUNT] = 'critical extra damage', -- format: +y% + [SPECIALSKILL_LIFELEECHCHANCE] = 'life leech chance', + [SPECIALSKILL_LIFELEECHAMOUNT] = 'life leech amount', + [SPECIALSKILL_MANALEECHCHANCE] = 'mana leech chance', + [SPECIALSKILL_MANALEECHAMOUNT] = 'mana leech amount', } function getSpecialSkillName(specialSkill) @@ -1482,6 +1512,30 @@ do end end +do + local stats = { + [STAT_MAXHITPOINTS] = 'hitpoints', + [STAT_MAXMANAPOINTS] = 'mana', + [STAT_SOULPOINTS] = 'soul points', + [STAT_MAGICPOINTS] = 'magic level' + } + + function getStatName(stat) + return stats[stat] or 'unknown' + end +end + +do + local mounts = {} + for _, mountData in pairs(Game.getMounts()) do + mounts[mountData.clientId] = mountData.name + end + + function getMountNameByLookType(lookType) + return mounts[lookType] + end +end + function indexToCombatType(idx) return bit.lshift(1, idx) end @@ -1491,4 +1545,4 @@ function showpos(v) end -- this is a fix for lua52 or higher which has the function renamed to table.unpack, while luajit still uses unpack -if unpack == nil then unpack = table.unpack end +if not unpack then unpack = table.unpack end diff --git a/data/lib/core/achievements.lua b/data/lib/core/achievements.lua index 42539abfec..92a618b6ef 100644 --- a/data/lib/core/achievements.lua +++ b/data/lib/core/achievements.lua @@ -459,6 +459,136 @@ achievements = -- 10.94 [397] = {name = "Ender of the End", grade = 2, points = 5, description = "You have entered the heart of destruction and valiantly defeated the world devourer. By your actions you have postponed the end of the world — at least for a while."}, [398] = {name = "Vortex Tamer", grade = 2, points = 5, description = "After a long journey and dedication you were favoured by fortune and have tamed all three elusive beasts of the vortex. Unless the Vortexion decides you're a tasty morsel you can enjoy your small stable of ravaging beasts from beyond."}, + + -- 11.02 + [399] = {name = "Forbidden Fruit", grade = 1, points = 1, secret = true, description = "You could not resist the taste of the forbidden fruit. Since you don't feel changed at all, it couldn't have been that bad after all. Or could it?"}, + [400] = {name = "Forbidden Knowledge", grade = 1, points = 1, secret = true, description = "Perhaps with so much acquired knowledge, never meant for you, you know even when to stop! Time will tell whether this knowledge will do more harm or good."}, + [401] = {name = "Rhino Rider", grade = 1, points = 1, description = "Don't forget, even your rhino sometimes needs a hug. A careful one in this case."}, + + -- 11.03? + [402] = {name = "Treasure Hunter", grade = 1, points = 3, description = "You wandered the world in search of the ancient dragons' hoards. You are sure, you found them all."}, + + -- 11.40 + [403] = {name = "Corruption Contained", grade = 2, points = 5, description = "You have managed to stall the worst incursion of corruption. Still this is just one battle won in an all out war for your world."}, + [404] = {name = "Fairy Teasing", grade = 1, points = 1, secret = true, description = "Teasing fairies is fun. They leave behind such pretty clouds of glittering dust when chased. Just hope they don't get you back for it."}, + [405] = {name = "Toothfairy Assistant", grade = 1, points = 1, description = "You assisted a very prominent fae and you fought tooth and nail to earn this title."}, + + -- 11.50 + [406] = {name = "Buried the Baron", grade = 1, points = 1, description = "You defeated the Baron from Below and destroyed his lava pump!"}, + [407] = {name = "Contender", grade = 1, points = 3, description = "You have fully unlocked 10 medium monsters in the cyclopedia."}, + [408] = {name = "Death in the Depths", grade = 1, points = 2, description = "The Baron from Below, Duke of the Depths and the Count of the Core are no more!"}, + [409] = {name = "Duked It Out", grade = 1, points = 1, description = "You defeated the Duke of the Depths and destroyed his lava pump!"}, + [410] = {name = "His Days are Counted", grade = 1, points = 1, description = "You defeated the Count of the Core and destroyed his lava pump!"}, + [411] = {name = "Hunting Permit", grade = 1, points = 1, description = "You have fully unlocked your very first monster in the cyclopedia."}, + [412] = {name = "Little Adventure", grade = 1, points = 1, description = "You have fully unlocked 10 easy monsters in the cyclopedia."}, + [413] = {name = "Little Big Adventure", grade = 1, points = 2, secret = true, description = "You have fully unlocked 100 easy monsters in the cyclopedia."}, + [414] = {name = "Master Hunter", grade = 2, points = 6, secret = true, description = "You have fully unlocked 100 hard monsters in the cyclopedia."}, + [415] = {name = "Over the Moon", grade = 2, points = 5, description = "The Curse of the Full Moon transforms harmless citizens into feral beasts. But with your help, Edron and Cormaya are safe - fairly."}, + [416] = {name = "Scourge of Scarabs", grade = 1, points = 3, description = "You took the heat and defeated the Ancient Spawn of Morgathla!"}, + [417] = {name = "Serious Contender", grade = 2, points = 4, secret = true, description = "You have fully unlocked 100 medium monsters in the cyclopedia."}, + [418] = {name = "Skilled Hunter", grade = 2, points = 5, description = "You have fully unlocked 10 hard monsters in the cyclopedia."}, + + -- 11.80 + [419] = {name = "All Hail the King", grade = 1, points = 1, description = "Old temples, a meadowy countryside and the splendour of Thais - you really know every corner of King' realm now."}, + [420] = {name = "Ancient Splendor", grade = 1, points = 1, description = "You've braved the perils of Yalahar and learned of its gloomy shadows of long gone greatness."}, + [421] = {name = "Battle Mage", grade = 2, points = 6, description = "Wielding dangerous knowledge as well as the sword is your expertise. You have proven yourself versatile in all manner of situations."}, + [422] = {name = "Bibliomaniac", grade = 1, points = 3, description = "You passion for reading was somewhat diminished by biting books and aggressive quills. But this flying specimen proved to be a loyal companion. Never judge a book by its cover!"}, + [423] = {name = "Daraman's Footsteps", grade = 1, points = 1, description = "You journeyed through Darashia and the sea of sand around it, while fighting the perils of the desert."}, + [424] = {name = "Dwarven Mines", grade = 1, points = 1, description = "Vast mines, an orc fortress and the magnificence of Kazordoon - you really know every corner of North-Eastern Mainland now."}, + [425] = {name = "Elven Woods", grade = 1, points = 1, description = "Tall trees, deep forests and and the beauty of Ab'Dendriel - you really know every corner of the elven lands now."}, + [426] = {name = "Glooth Punk", grade = 1, points = 1, description = "Glooth is the substance that powers a whole continent and all its weird inhabitants, workshops and factories. You travelled this strange smorgasbord of curiosities in its entirety - just in time for tea."}, + [427] = {name = "High and Dry", grade = 1, points = 2, description = "You asked Captain Charles to take a shortcut quite a few times. Now you are all too familiar with desert islands all over the world."}, + [428] = {name = "Jewel in the Swamp", grade = 1, points = 1, description = "Damp swamps, a dry desert and the opulence of Venore - you really know every corner of Eastern Mainland now."}, + [429] = {name = "King of the Jungle", grade = 1, points = 1, description = "You have searched Port Hope and the jungle that thoroughly, that you are up to adoption by a friendly ape family."}, + [430] = {name = "Liberty Bay Watch", grade = 1, points = 1, description = "A pirate's haven and a burglar's hideout. You found your way around Liberty Bay and its surroundings - land, ho!"}, + [431] = {name = "Library Liberator", grade = 1, points = 3, description = "Though you couldn't prevent the theft of the godbreaker knowledge, you still managed to fight of the invasion of the library and to kill the scourge of oblivion, a powerful servant of the enemy."}, + [432] = {name = "Lizard Kingdom", grade = 1, points = 1, description = "From the southern steppe through the Dragonblaze Mountains and the Muggy Plains to the forbidden city of Razzachai - you really know every corner of Zao now."}, + [433] = {name = "Long Live the Queen", grade = 1, points = 1, description = "Ancient battlefields, amazons and the glory of Carlin - you really know every corner of Queen Eloise's realm now."}, + [434] = {name = "Master Debater", grade = 1, points = 1, secret = true, description = "You truly are the grand master of verbal debate! Now going forth and putting this wisdom to good use in everyday life... is probably debatable."}, + [435] = {name = "Millennial Falcon", grade = 1, points = 3, secret = true, description = "You defeated Grand Master Oberon and the remnants of the Order of the Falcon, no matter the odds."}, + [436] = {name = "Mummy's Dearest", grade = 1, points = 1, description = "You have combed the desert and searched the pyramid city of Ankrahmun."}, + [437] = {name = "Race to the Pole", grade = 1, points = 1, description = "You have expelled the fog of the unknown from the islands of Svargrond. Maybe not as first, but that's not what matters in the end."}, + [438] = {name = "Realms of Dreams", grade = 1, points = 1, description = "Lush meadows, colourful fairies and sentient stones - you really know every corner of Feyrist now."}, + [439] = {name = "Spectulation", grade = 1, points = 1, secret = true, description = "You checked out a strange temple deep in the jungles of Tiquanda. Spectulus was right, it was indeed overrun by strange fish-men you now call Deathlings."}, + [440] = {name = "Stronghold of Edron", grade = 1, points = 1, description = "Strong fortresses, sprawling woods and ivory towers - you really know every corner of Edron now."}, + [441] = {name = "The Ogre Steppe", grade = 1, points = 1, description = "A vast steppe, voracious ogres and dried out salt seas - you really know every corner of Krailos now."}, + [442] = {name = "Trip to the Beach", grade = 1, points = 1, description = "Braving a hive full of unimaginable proportions and its grotesque creatures on the surface is only one side of Gray Beach. Your full trip of the island also included a dive into the black nothingness of the deep sea, facing the wrath of the Njey."}, + [443] = {name = "Twisted Dreams", grade = 1, points = 1, description = "A journey through a dreamscape of evil is no small feat. Yet you traversed the nightmarish lands of Roshamuul and live to tell the tale. Don't fall asleep now..."}, + [444] = {name = "Widely Travelled", grade = 3, points = 7, description = "As a true globetrotter you can now show your colours proudly with this extraordinary outfit."}, + + -- 11.86 + [445] = {name = "Exalted Battle Mage", grade = 1, points = 2, description = "Not only did you master the battlefield as a mage, you were also induced to the most inner secrets of the art of magical warfare and prevailed."}, + [446] = {name = "Running the Rift", grade = 1, points = 3, description = "You don't just have a permission to ride a rift runner, you literally went through hell and earned it!"}, + + -- 12.00 + [447] = {name = "Champion of Summer", grade = 1, points = 2, secret = true, description = "You have vanquished numerous arena champions in the name of the Summer Court."}, + [448] = {name = "Champion of Winter", grade = 1, points = 2, secret = true, description = "You have vanquished numerous arena champions in the name of the Winter Court."}, + [449] = {name = "Dream Catcher", grade = 1, points = 3, description = "You are the slayer of the ancient nightmare beast and prevented the nightmare to spread its madness."}, + [450] = {name = "Dream Warrior", grade = 2, points = 6, description = "You became an acquaintance of the courts of dreams and acquired the right to display your new status and title of 'dream warrior'."}, + [451] = {name = "Keeper of the 7 Keys", grade = 1, points = 2, description = "You found the Seven Keys to unlock ... no, not the seven seas. But at least seven doors in the realm of dreams."}, + [452] = {name = "Lacewing Catcher", grade = 1, points = 3, description = "You caught a lacewing moth with your lantern. It will follow you in companionship as the bearer of the lantern will be its guide through the darkness now."}, + [453] = {name = "Moth Whisperer", grade = 1, points = 3, description = "Your lantern was too bewitching for a hibernal moth. It couldn't withstand and follows you, the bearer of the lantern, now."}, + [454] = {name = "Tied the Knot", grade = 1, points = 1, secret = true, description = "You figured out the right order of spells in the buried cathedral, how enchanting!"}, + + -- 12.02 + [455] = {name = "No Horse Open Sleigh", grade = 1, points = 3, description = "This sleigh is not driven by magic but pushed by a percht. Hopefully you two get along well together...!"}, + [456] = {name = "Raider in the Dark", grade = 2, points = 6, description = "But can you truly be one of them?"}, + + -- 12.20 + [457] = {name = "A Study in Scarlett", grade = 1, points = 3, secret = true, description = "You ended the regn of Scarlett Etzel. All-seeing yet blind, ever powerful yet ultimately helpless, she never got a second chance to truly see. Or has she..."}, + [458] = {name = "Avid Spectral Reader", grade = 1, points = 1, secret = true, description = "What draws things to other dimensions, one wonders. You read the almanac at just the right spot to end up... where, of all places? That is the problem with dimensional travel: you will never know. Or you have always known. And everything in between."}, + [459] = {name = "Gryphon Rider", grade = 1, points = 3, description = "Unmasking spies, killing demons, discovering omens, solving puzzles and fighting ogres, manticores and feral sphinxes. - Nobody said it was easy to become a gryphon rider."}, + [460] = {name = "Hippofoddermus", grade = 1, points = 1, secret = true, description = "You did the hippo population of Kilmaresh a great favour. A well-fed hippo is a happy hippo."}, + [461] = {name = "Inquisition's Hand", grade = 1, points = 3, secret = true, description = "You defeated the Lich Knights and became the hand of the Inquisition, allowed to wear their special garb."}, + [462] = {name = "Sculptor Apprentice", grade = 1, points = 2, secret = true, description = "Granted, you didn't carve those lifelike animal figurines yourself. But helping a medusa to find proper objects and even watching her using her petrifying gaze is almost as rewarding."}, + [463] = {name = "Sun and Sea", grade = 2, points = 5, description = "You made sure that the balance of sun and sea is preserved in Kilmaresh. The Golden City of Issavi won't forget your favour."}, + [464] = {name = "The Empire's Glory", grade = 1, points = 1, description = "Mythical creatures, forgotten catacombs and the Golden City - you really know every corner of Kilmaresh now."}, + + -- 12.20.9066 + [465] = {name = "Do a Barrel Roll!", grade = 1, points = 3, description = "Riding a traditional beer barrel from the Orcsoberfest is a once-in-a-lifetime experience. Beer sold separately."}, + [466] = {name = "Orcsoberfest Welcome", grade = 1, points = 3, secret = true, description = "The Orcsoberfest is not only known for its traditional food, beer and customs but also fun events and excitement! You took part in all of that and can now truly say: \"I survived!\""}, + [467] = {name = "Traditionalist", grade = 2, points = 0, description = "You proudly wear the traditional Orcsoberfest garb, same as it ever was and as it always will be."}, + + -- 12.30 + [468] = {name = "Beyonder", grade = 1, points = 3, description = "Adventurous beyond death, you travelled the Netherworld. Although you had just the ghost of a chance you survived and even came back from the realm of the dead."}, + [469] = {name = "Falconer", grade = 1, points = 3, description = "A true beastmaster learns the language of his animal companions. Now you as well can bolster your unique bond with nature and help preserve the balance of life as a proud falconer."}, + [470] = {name = "Mainstreet Nightmare", grade = 1, points = 2, description = "Now you are able to wander around the world wearing an angst-inducing vestment."}, + [471] = {name = "Monsterhunter", grade = 1, points = 2, description = "Fear me, monsters! There is some more slaying to come!"}, + [472] = {name = "Nothing but Hot Air", grade = 1, points = 2, description = "You have tamed the ghostly mists to do your bidding. For now ..."}, + [473] = {name = "Prospectre", grade = 1, points = 1, secret = true, description = "You made acquaintance with the Thaian. A strange contemporary with a dark history. No man but a derivate of green and obsession."}, + [474] = {name = "Steppe Elegance", grade = 1, points = 2, description = "Champion of the wildlands, a swift strider among the creatures of the wild. The elegant nature of the gallop, this envoy of speed has mastered, indicates the precise understanding of its terrain and environment."}, + [475] = {name = "Taskmaster", grade = 1, points = 2, description = "Having hunted and bested them all, you live for the thrill of the hunt!"}, + [476] = {name = "Verminbane", grade = 1, points = 2, description = "And so it begins!"}, + + -- 11.53 + [477] = {name = "Up the Molehill", grade = 1, points = 3, description = "Putting this candle stump on your new mount was kind of a waiting game. You're even tempted to call it whack-a-mole. But in the end you found a loyal companion for your journeys into the depths."}, + + -- 11.90.7293 + [478] = {name = "Areas of Effect", grade = 1, points = 3, description = "Wisely contributing your resources to areas, you pushed creatures to maximum effect, allowing improved respawn for everyone! Well done!"}, + + -- 12.40 + [479] = {name = "Drama in Darama", grade = 1, points = 3, description = "f a pride of lions and a pack of hyaenas feud, it is not called a catfight but a ... whatsoever. For sure, it caused a lot of drama in the Darama Desert."}, + [480] = {name = "Lionheart", grade = 1, points = 3, description = "You bested the maleficent duo Drume and Fugue and restored order to the besieged town of Bounac. You conquered the exotic stronghold of the Order of the Cobra and bested the undead knights of the Order of the Falcon. A true knight in heart and mind."}, + [481] = {name = "Malefitz", grade = 1, points = 1, secret = true, description = "Made acquaintance with three brothers Fitz."}, + [482] = {name = "Unleash the Beast", grade = 3, points = 8, description = "You defeated the manifestation of Goshnar's evil traits by fighting your way through beasts you didn't even want to imagine. It transformed you and now you can also look the part."}, + [483] = {name = "Well Roared, Lion!", grade = 1, points = 1, description = "You helped Domizian and thus proved yourself worthy to enter the werelion sanctum underneath Lion's Rock. You faced the mighty werelions there and one of the rare white lions even chose to accompany you."}, + [484] = {name = "You Got Horse Power", grade = 3, points = 8, description = "Brought back to the realm of the living this magnificent creature will carry you through death and everything that lays beyond."}, + + -- 12.60 + [485] = {name = "Honorary Rascoohan", grade = 1, points = 2, description = "When in Rascacoon, do as the Rascoohans do!"}, + [486] = {name = "Pied Piper", grade = 1, points = 3, description = "You are not exactly the Pied Piper of Hamelin but at least you managed to fend off a decent amount of pirats and helped to keep them out of the cities."}, + [487] = {name = "Release the Kraken", grade = 1, points = 3, description = "Riding around on this squishy companion gives you the feeling of flying through the air... uhm... swimming through the seven seas!"}, + + -- 12.70 + [488] = {name = "Bounacean Chivalry", grade = 1, points = 2, secret = true, description = "Yselda forever stands watch against the carnisylvan menace. Ever awake, waiting in the dark, her heart longs to be united with her king once again. Deep empathy let a hero to bring her Kesar's tulip as a token of his love. That hero was you."}, + [489] = {name = "Citizen of Issavi", grade = 1, points = 2, description = "It was not the first time that you helped the Sapphire Blade or the Midnight Flame with a difficult task. You may now wear the Kilmareshian robes as well as the tagralt blade and the eye-embroidered veil of the seers as a sign of Issavi's gratitude."}, + [490] = {name = "Hot on the Trail", grade = 1, points = 3, description = "Since it is fireproof, this flaming creature feels right at home in raging infernos. But remember: just because it doesn't burn, you still do!"}, + [491] = {name = "King's Council", grade = 1, points = 3, description = "Your continued efforts in keeping Bounac and the people of Kesar the Younger safe, earned you a permanent place at the royal court as an advisor to the king."}, + [492] = {name = "Knowledge Raider", grade = 1, points = 3, description = "Your thirst for knowledge is insatiable. In the task of helping your gnomish friends, flawless execution is just the icing on the cake."}, + [493] = {name = "Phantastic!", grade = 1, points = 3, description = "This mighty pachyderm will march into battle as if just taking its Sunday stroll. The cost of friendship was only a few drome points!"}, + [494] = {name = "Shell we take a Ride", grade = 1, points = 3, description = "Equipped with the shell of a tortoise and claws of a lobster this insect like companion will help you through every hardship."}, + [495] = {name = "Some Like It Hot", grade = 1, points = 2, description = "You have braved the searing heat in the tunnels deep below Kazordoon and vanquished the Brainstealer. The voices inside your head are finally silenced."}, + [496] = {name = "Woodcarver", grade = 1, points = 3, secret = true, description = "You defeated Megasylvan Yselda in the wake of the sleeping carnisylvan menace deep under Bounac."}, } ACHIEVEMENT_FIRST = 1 @@ -520,7 +650,7 @@ end function isAchievementSecret(ach) local achievement - if tonumber(ach) ~= nil then + if tonumber(ach) then achievement = getAchievementInfoById(ach) else achievement = getAchievementInfoByName(ach) @@ -535,7 +665,7 @@ end function Player.hasAchievement(self, ach) local achievement - if tonumber(ach) ~= nil then + if tonumber(ach) then achievement = getAchievementInfoById(ach) else achievement = getAchievementInfoByName(ach) @@ -560,7 +690,7 @@ end function Player.addAchievement(self, ach, hideMsg) local achievement - if tonumber(ach) ~= nil then + if tonumber(ach) then achievement = getAchievementInfoById(ach) else achievement = getAchievementInfoByName(ach) @@ -581,7 +711,7 @@ end function Player.removeAchievement(self, ach) local achievement - if tonumber(ach) ~= nil then + if tonumber(ach) then achievement = getAchievementInfoById(ach) else achievement = getAchievementInfoByName(ach) @@ -648,7 +778,7 @@ function Player.getAchievementPoints(self) end function Player.addAchievementProgress(self, ach, value) - local achievement = tonumber(ach) ~= nil and getAchievementInfoById(ach) or getAchievementInfoByName(ach) + local achievement = tonumber(ach) and getAchievementInfoById(ach) or getAchievementInfoByName(ach) if not achievement then print('[!] -> Invalid achievement "' .. ach .. '".') return true diff --git a/data/lib/core/actionids.lua b/data/lib/core/actionids.lua index d4092e410e..b1382f5ca2 100644 --- a/data/lib/core/actionids.lua +++ b/data/lib/core/actionids.lua @@ -1,6 +1,7 @@ actionIds = { sandHole = 100, -- hidden sand hole pickHole = 105, -- hidden mud hole + drawWell = 106, -- draw well teleport levelDoor = 1000, -- level door citizenship = 30020, -- citizenship teleport citizenshipLast = 30050, -- citizenship teleport last diff --git a/data/lib/core/container.lua b/data/lib/core/container.lua index b8fa86fb95..672d5832e4 100644 --- a/data/lib/core/container.lua +++ b/data/lib/core/container.lua @@ -9,16 +9,25 @@ function Container.createLootItem(self, item) local itemCount = 0 local randvalue = getLootRandom() + local itemType = ItemType(item.itemId) + if randvalue < item.chance then - if ItemType(item.itemId):isStackable() then + if itemType:isStackable() then itemCount = randvalue % item.maxCount + 1 else itemCount = 1 end end - if itemCount > 0 then - local tmpItem = Game.createItem(item.itemId, math.min(itemCount, 100)) + while itemCount > 0 do + local count = math.min(100, itemCount) + + local subType = count + if itemType:isFluidContainer() then + subType = math.max(0, item.subType) + end + + local tmpItem = Game.createItem(item.itemId, subType) if not tmpItem then return false end @@ -49,7 +58,26 @@ function Container.createLootItem(self, item) tmpItem:setText(item.text) end - self:addItemEx(tmpItem) + local ret = self:addItemEx(tmpItem) + if ret ~= RETURNVALUE_NOERROR then + tmpItem:remove() + end + + itemCount = itemCount - count end return true end + +function Container:getContentDescription() + local items = self:getItems() + if items and #items > 0 then + local loot = {} + for i = 1, #items do + loot[#loot + 1] = string.format("{%d|%s}", items[i]:getType():getClientId(), items[i]:getNameDescription(items[i]:getSubType(), true)) + end + + return table.concat(loot, ", ") + end + + return "nothing" +end diff --git a/data/lib/core/core.lua b/data/lib/core/core.lua index ef4d6e2293..ee806e03e2 100644 --- a/data/lib/core/core.lua +++ b/data/lib/core/core.lua @@ -12,6 +12,7 @@ dofile('data/lib/core/item.lua') dofile('data/lib/core/itemtype.lua') dofile('data/lib/core/party.lua') dofile('data/lib/core/player.lua') +dofile('data/lib/core/podium.lua') dofile('data/lib/core/position.lua') dofile('data/lib/core/teleport.lua') dofile('data/lib/core/tile.lua') diff --git a/data/lib/core/creature.lua b/data/lib/core/creature.lua index 7c173fb27b..c047b4ddbe 100644 --- a/data/lib/core/creature.lua +++ b/data/lib/core/creature.lua @@ -2,7 +2,7 @@ function Creature.getClosestFreePosition(self, position, maxRadius, mustBeReacha maxRadius = maxRadius or 1 -- backward compatability (extended) - if maxRadius == true then + if maxRadius then maxRadius = 2 end @@ -19,7 +19,7 @@ function Creature.getClosestFreePosition(self, position, maxRadius, mustBeReacha end local tile = Tile(checkPosition) - if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and + if tile and tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and (not mustBeReachable or self:getPathTo(checkPosition)) then return checkPosition end diff --git a/data/lib/core/game.lua b/data/lib/core/game.lua index 7d6497e98d..ba73365cab 100644 --- a/data/lib/core/game.lua +++ b/data/lib/core/game.lua @@ -55,6 +55,62 @@ function Game.getSkillType(weaponType) return SKILL_FIST end +do + local cdShort = {"d", "h", "m", "s"} + local cdLong = {" day", " hour", " minute", " second"} + local function getTimeUnitGrammar(amount, unitID, isLong) + return isLong and string.format("%s%s", cdLong[unitID], amount ~= 1 and "s" or "") or cdShort[unitID] + end + + function Game.getCountdownString(duration, longVersion, hideZero) + if duration < 0 then + return "expired" + end + + local days = math.floor(duration / 86400) + local hours = math.floor((duration % 86400) / 3600) + local minutes = math.floor((duration % 3600) / 60) + local seconds = math.floor(duration % 60) + + local response = {} + if hideZero then + if days > 0 then + response[#response + 1] = days .. getTimeUnitGrammar(days, 1, longVersion) + end + + if hours > 0 then + response[#response + 1] = hours .. getTimeUnitGrammar(hours, 2, longVersion) + end + + if minutes > 0 then + response[#response + 1] = minutes .. getTimeUnitGrammar(minutes, 3, longVersion) + end + + if seconds > 0 then + response[#response + 1] = seconds .. getTimeUnitGrammar(seconds, 4, longVersion) + end + else + if days > 0 then + response[#response + 1] = days .. getTimeUnitGrammar(days, 1, longVersion) + response[#response + 1] = hours .. getTimeUnitGrammar(hours, 2, longVersion) + response[#response + 1] = minutes .. getTimeUnitGrammar(minutes, 3, longVersion) + response[#response + 1] = seconds .. getTimeUnitGrammar(seconds, 4, longVersion) + elseif hours > 0 then + response[#response + 1] = hours .. getTimeUnitGrammar(hours, 2, longVersion) + response[#response + 1] = minutes .. getTimeUnitGrammar(minutes, 3, longVersion) + response[#response + 1] = seconds .. getTimeUnitGrammar(seconds, 4, longVersion) + elseif minutes > 0 then + response[#response + 1] = minutes .. getTimeUnitGrammar(minutes, 3, longVersion) + response[#response + 1] = seconds .. getTimeUnitGrammar(seconds, 4, longVersion) + elseif seconds >= 0 then + response[#response + 1] = seconds .. getTimeUnitGrammar(seconds, 4, longVersion) + end + end + + return table.concat(response, " ") + end +end + if not globalStorageTable then globalStorageTable = {} end diff --git a/data/lib/core/item.lua b/data/lib/core/item.lua index dd8337eca8..86f39740b0 100644 --- a/data/lib/core/item.lua +++ b/data/lib/core/item.lua @@ -2,6 +2,10 @@ function Item.getType(self) return ItemType(self:getId()) end +function Item:getClassification() + return self:getType():getClassification() +end + function Item.isContainer(self) return false end @@ -26,534 +30,752 @@ function Item.isTeleport(self) return false end -function Item.isTile(self) +function Item.isPodium(self) return false end --- Helper class to make string formatting prettier +function Item.isTile(self) + return false +end -StringStream = {} +function Item:isItemType() + return false +end -setmetatable(StringStream, { - __call = function(self) - local obj = {} - return setmetatable(obj, {__index = StringStream}) +do + local aux = { + ["Defense"] = {key = ITEM_ATTRIBUTE_DEFENSE}, + ["ExtraDefense"] = {key = ITEM_ATTRIBUTE_EXTRADEFENSE}, + ["Attack"] = {key = ITEM_ATTRIBUTE_ATTACK}, + ["AttackSpeed"] = {key = ITEM_ATTRIBUTE_ATTACK_SPEED}, + ["HitChance"] = {key = ITEM_ATTRIBUTE_HITCHANCE}, + ["ShootRange"] = {key = ITEM_ATTRIBUTE_SHOOTRANGE}, + ["Armor"] = {key = ITEM_ATTRIBUTE_ARMOR}, + ["Duration"] = {key = ITEM_ATTRIBUTE_DURATION, cmp = function(v) return v > 0 end}, + ["Text"] = {key = ITEM_ATTRIBUTE_TEXT, cmp = function(v) return v ~= "" end}, + ["Date"] = {key = ITEM_ATTRIBUTE_DATE}, + ["Writer"] = {key = ITEM_ATTRIBUTE_WRITER, cmp = function(v) return v ~= "" end}, + ["Tier"] = {key = ITEM_ATTRIBUTE_TIER} + } + + function setAuxFunctions() + for name, def in pairs(aux) do + Item["get" .. name] = function(self) + local attr = self:getAttribute(def.key) + if def.cmp and def.cmp(attr) then + return attr + elseif not def.cmp and attr and attr ~= 0 then + return attr + end + local default = ItemType["get" .. name] + return default and default(self:getType()) or nil + end + end end -}) - -function StringStream.append(self, str, ...) - self[#self+1] = string.format(str, ...) + setAuxFunctions() end -function StringStream.concat(self, sep) - return table.concat(self, sep) -end +do + StringStream = {} -local aux = { - ['Duration'] = {key = ITEM_ATTRIBUTE_DURATION}, - ['Defense'] = {key = ITEM_ATTRIBUTE_DEFENSE}, - ['ExtraDefense'] = {key = ITEM_ATTRIBUTE_EXTRADEFENSE}, - ['Attack'] = {key = ITEM_ATTRIBUTE_ATTACK}, - ['HitChance'] = {key = ITEM_ATTRIBUTE_HITCHANCE}, - ['ShootRange'] = {key = ITEM_ATTRIBUTE_SHOOTRANGE}, - ['Armor'] = {key = ITEM_ATTRIBUTE_ARMOR}, - ['Duration'] = {key = ITEM_ATTRIBUTE_DURATION, cmp = function(v) return v > 0 end}, - ['Text'] = {key = ITEM_ATTRIBUTE_TEXT, cmp = function(v) return v ~= '' end}, - ['Date'] = {key = ITEM_ATTRIBUTE_DATE}, - ['Writer'] = {key = ITEM_ATTRIBUTE_WRITER, cmp = function(v) return v ~= '' end} -} - -function setAuxFunctions() - for name, def in pairs(aux) do - Item['get'.. name] = function(self) - local attr = self:getAttribute(def.key) - if def.cmp and def.cmp(attr) then - return attr - elseif not def.cmp and attr and attr ~= 0 then - return attr - end - local default = ItemType['get'.. name] - return default and default(self:getType()) or nil + setmetatable(StringStream, { + __call = function(self) + local obj = {} + return setmetatable(obj, {__index = StringStream}) end + }) + + function StringStream.append(self, str, ...) + self[#self + 1] = string.format(str, ...) end -end -setAuxFunctions() + function StringStream.concat(self, sep) + return table.concat(self, sep) + end -do local function internalItemGetNameDescription(it, item, subType, addArticle) - local subType = subType or (item and item:getSubType() or -1) + subType = subType or (item and item:getSubType() or -1) local ss = StringStream() local obj = item or it local name = obj:getName() - if name ~= '' then - if it:isStackable() and subType > 1 then + if name ~= "" then + if it:isStackable() and subType > 1 then if it:hasShowCount() then - ss:append('%d ', subType) + ss:append("%d ", subType) end - ss:append('%s', obj:getPluralName()) + ss:append("%s", obj:getPluralName()) else - if addArticle and obj:getArticle() ~= '' then - ss:append('%s ', obj:getArticle()) + if addArticle and obj:getArticle() ~= "" then + ss:append("%s ", obj:getArticle()) end - ss:append('%s', obj:getName()) + ss:append("%s", obj:getName()) end else - ss:append('an item of type %d', obj:getId()) + ss:append("an item of type %d", obj:getId()) end return ss:concat() end - function Item.getNameDescription(self, subType, addArticle) + function Item:getNameDescription(subType, addArticle) return internalItemGetNameDescription(self:getType(), self, subType, addArticle) end - function ItemType.getNameDescription(self, subType, addArticle) + function ItemType:getNameDescription(subType, addArticle) return internalItemGetNameDescription(self, nil, subType, addArticle) end end do - local function addSeparator(ss, begin) - if begin then - begin = false - ss:append(' (') - else - ss:append(', ') + -- Lua item descriptions created by Zbizu + local housePriceVisible = configManager.getBoolean(configKeys.HOUSE_DOOR_SHOW_PRICE) + local pricePerSQM = configManager.getNumber(configKeys.HOUSE_PRICE) + + local showAtkWeaponTypes = {WEAPON_CLUB, WEAPON_SWORD, WEAPON_AXE, WEAPON_DISTANCE} + local showDefWeaponTypes = {WEAPON_CLUB, WEAPON_SWORD, WEAPON_AXE, WEAPON_DISTANCE, WEAPON_SHIELD} + local suppressedConditionNames = { + -- NOTE: these names are made up just to match dwarven ring attribute style + [CONDITION_POISON] = "antidote", + [CONDITION_FIRE] = "non-inflammable", + [CONDITION_ENERGY] = "antistatic", + [CONDITION_BLEEDING] = "antiseptic", + [CONDITION_HASTE] = "heavy", + [CONDITION_PARALYZE] = "slow immunity", + [CONDITION_DRUNK] = "hard drinking", + [CONDITION_DROWN] = "water breathing", + [CONDITION_FREEZING] = "warm", + [CONDITION_DAZZLED] = "dazzle immunity", + [CONDITION_CURSED] = "curse immunity" + } + + -- first argument: Item, itemType or item id + local function internalItemGetDescription(item, lookDistance, subType, addArticle) + -- optional, but true by default + if addArticle == nil then + addArticle = true end - return begin - end - local function addGenerics(item, it, abilities, ss, begin) - local obj = item or it - if it:getWeaponType() == WEAPON_DISTANCE and it:getAmmoType() ~= 0 then - ss:append(' (Range:%d', obj:getShootRange()) - local attack = obj:getAttack() - local hitChance = obj:getHitChance() - if attack ~= 0 then - ss:append(', Atk%s%d', showpos(attack), math.abs(attack)) + local isVirtual = false -- check if we're inspecting a real item or itemType only + local itemType + + -- determine the kind of item data to process + if tonumber(item) then + -- number in function argument + -- pull data from itemType instead + isVirtual = true + itemType = ItemType(item) + if not itemType then + return end - if hitChance ~= 0 then - ss:append(', Hit%%%s%d', showpos(hitChance), math.abs(hitChance)) + -- polymorphism for item attributes (atk, def, etc) + item = itemType + elseif item:isItemType() then + isVirtual = true + itemType = item + else + itemType = item:getType() + end + + -- use default values if not provided + lookDistance = lookDistance or -1 + subType = subType or (not isVirtual and item:getSubType() or -1) + + -- possibility of picking the item up (will be reused later) + local isPickupable = itemType:isMovable() and itemType:isPickupable() + + -- has uniqueId tag + local isUnique = false + if not isVirtual then + local uid = item:getUniqueId() + if uid > 100 and uid < 0xFFFF then + isUnique = true end + end - begin = false - elseif it:getWeaponType() ~= WEAPON_AMMO then - local attack = obj:getAttack() - local defense = obj:getDefense() - local extraDefense = obj:getExtraDefense() + -- read item name with article + local itemName = item:getNameDescription(subType, addArticle) - if attack ~= 0 then - begin = false - ss:append(' (Atk:%d', attack) + -- things that will go in parenthesis "(...)" + local descriptions = {} - if abilities.elementType ~= COMBAT_NONE and abilities.elementDamage ~= 0 then - ss:append(' physical + %d %s', abilities.elementDamage, getCombatName(abilities.elementType)) - end + -- spell words + do + local spellName = itemType:getRuneSpellName() + if spellName then + descriptions[#descriptions + 1] = string.format('"%s"', spellName) end + end - if defense ~= 0 or extraDefense ~= 0 then - begin = addSeparator(ss, begin) - ss:append('Def:%d', defense) - if extraDefense ~= 0 then - ss:append(' %s%d', showpos(extraDefense), math.abs(extraDefense)) + -- container capacity + do + -- show container capacity only on non-quest containers + if not isUnique then + local isContainer = itemType:isContainer() + if isContainer then + descriptions[#descriptions + 1] = string.format("Vol:%d", itemType:getCapacity()) end end end - -- Skills - for skill, value in ipairs(abilities.skills) do - if value ~= 0 then - begin = addSeparator(ss, begin) - ss:append('%s %s%d', getSkillName(skill - 1), showpos(value), math.abs(value)) + -- actionId (will be reused) + local actionId = 0 + if not isVirtual then + actionId = item:getActionId() + end + + -- key + do + if not isVirtual and itemType:isKey() then + descriptions[#descriptions + 1] = string.format("Key:%0.4d", actionId) end end - -- Special Skills - for specialSkill, value in ipairs(abilities.specialSkills) do - if value ~= 0 then - begin = addSeparator(ss, begin) - ss:append('%s %s%d%%', getSpecialSkillName(specialSkill - 1), showpos(value), math.abs(value)) + -- weapon type (will be reused) + local weaponType = itemType:getWeaponType() + + -- attack attributes + do + local attack = item:getAttack() + + -- bows and crossbows + -- range, attack, hit% + if itemType:isBow() then + descriptions[#descriptions + 1] = string.format("Range:%d", item:getShootRange()) + + if attack ~= 0 then + descriptions[#descriptions + 1] = string.format("Atk:%+d", attack) + end + + local hitPercent = item:getHitChance() + if hitPercent ~= 0 then + descriptions[#descriptions + 1] = string.format("Hit%%:%+d", item:hitPercent()) + end + + -- melee weapons and missiles + -- atk x physical +y% element + elseif table.contains(showAtkWeaponTypes, weaponType) then + local atkString = string.format("Atk:%d", attack) + local elementDmg = itemType:getElementDamage() + if elementDmg ~= 0 then + atkString = string.format("%s physical %+d %s", atkString, elementDmg, getCombatName(itemType:getElementType())) + end + + descriptions[#descriptions + 1] = atkString end end - local magicPoints = abilities.stats[4] - if magicPoints ~= 0 then - begin = addSeparator(ss, begin) - ss:append('magic level %s%d', showpos(magicPoints), math.abs(magicPoints)) + -- attack speed + do + local atkSpeed = item:getAttackSpeed() + if atkSpeed ~= 0 then + descriptions[#descriptions + 1] = string.format("AS:%0.2f/turn", 2000 / atkSpeed) + end end - -- Absorb + -- defense attributes + do + local showDef = table.contains(showDefWeaponTypes, weaponType) + local ammoType = itemType:getAmmoType() + if showDef then + local defense = item:getDefense() + local defAttrs = {} + if weaponType == WEAPON_DISTANCE then + -- throwables + if ammoType ~= AMMO_ARROW and ammoType ~= AMMO_BOLT then + defAttrs[#defAttrs + 1] = defense + end + else + defAttrs[#defAttrs + 1] = defense + end + + -- extra def + local xD = item:getExtraDefense() + if xD ~= 0 then + defAttrs[#defAttrs + 1] = string.format("%+d", xD) + end - local show = abilities.absorbPercent[1] - if show ~= 0 then - for _, value in ipairs(abilities.absorbPercent) do - if value ~= show then - show = 0 + if #defAttrs > 0 then + descriptions[#descriptions + 1] = string.format("Def:%s", table.concat(defAttrs, " ")) end end end - if show == 0 then - local tmp = true - for i, value in ipairs(abilities.absorbPercent) do + -- armor + do + local arm = item:getArmor() + if arm > 0 then + descriptions[#descriptions + 1] = string.format("Arm:%d", arm) + end + end + + -- abilities (will be reused) + local abilities = itemType:getAbilities() + + -- stats: hp/mp/soul/magic level + do + local stats = {} + -- flat buffs + for stat, value in pairs(abilities.stats) do + stats[stat] = {name = getStatName(stat-1)} if value ~= 0 then - if tmp then - tmp = false - begin = addSeparator(ss, begin) - ss:append('protection ') - else - ss:append(', ') - end - ss:append('%s %s%d%%', getCombatName(indexToCombatType(i - 1)), showpos(value), math.abs(value)) + stats[stat].flat = value + end + end + + -- percent buffs + for stat, value in pairs(abilities.statsPercent) do + if value ~= 0 then + stats[stat].percent = value + end + end + + -- display the buffs + for _, statData in pairs(stats) do + local displayValues = {} + if statData.flat then + displayValues[#displayValues + 1] = statData.flat + end + + if statData.percent then + displayValues[#displayValues + 1] = statData.percent + end + + -- desired format examples: + -- +5% + -- +20 and 5% + if #displayValues > 0 then + displayValues[1] = string.format("%+d", displayValues[1]) + descriptions[#descriptions + 1] = string.format("%s %s", statData.name, table.concat(displayValues, " and ")) end end - else - begin = addSeparator(ss, begin) - ss:append('protection all %s%d%%', showpos(show), math.abs(show)) end - -- Field absorb + -- skill boosts + do + for skill, value in pairs(abilities.skills) do + if value ~= 0 then + descriptions[#descriptions + 1] = string.format("%s %+d", getSkillName(skill-1), value) + end + end + end - local show = abilities.fieldAbsorbPercent[1] - if show ~= 0 then - for _, value in ipairs(abilities.fieldAbsorbPercent) do - if value ~= show then - show = 0 + -- element magic level + do + for element, value in pairs(abilities.specialMagicLevel) do + if value ~= 0 then + descriptions[#descriptions + 1] = string.format("%s magic level %+d", getCombatName(2^(element-1)), value) end end end - if show == 0 then - local tmp = true - for i, value in ipairs(abilities.fieldAbsorbPercent) do + -- special skills + do + for skill, value in pairs(abilities.specialSkills) do if value ~= 0 then - if tmp then - tmp = false - begin = addSeparator(ss, begin) - ss:append('protection ') - else - ss:append(', ') + -- add + symbol to special skill "amount" fields + if skill-1 < 6 and skill % 2 == 1 then + value = string.format("%+d", value) + elseif skill-1 >= 6 then + -- fatal, dodge, momentum coming from the item natively + -- (stats coming from tier are near tier info) + value = string.format("%0.2f", value/100) end - ss:append('%s field %s%d%%', getCombatName(indexToCombatType(i - 1)), showpos(value), math.abs(value)) + + descriptions[#descriptions + 1] = string.format("%s %s%%", getSpecialSkillName(skill-1), value) end end - else - begin = addSeparator(ss, begin) - ss:append('protection all fields %s%d%%', showpos(show), math.abs(show)) end + -- cleave + -- perfect shot + -- to do + + -- protections + do + local protections = {} + for element, value in pairs(abilities.absorbPercent) do + if value ~= 0 then + protections[#protections + 1] = string.format("%s %+d%%", getCombatName(2^(element-1)), value) + end + end + + if #protections > 0 then + descriptions[#descriptions + 1] = string.format("protection %s", table.concat(protections, ", ")) + end + end + + -- damage reflection + -- to do + + -- magic shield (classic) + if abilities.manaShield then + descriptions[#descriptions + 1] = "magic shield" + end + + -- magic shield capacity +flat and x% + -- to do + + -- regeneration + if abilities.manaGain > 0 or abilities.healthGain > 0 or abilities.regeneration then + descriptions[#descriptions + 1] = "faster regeneration" + end + + -- invisibility + if abilities.invisible then + descriptions[#descriptions + 1] = "invisibility" + end + + -- condition immunities + do + local suppressions = abilities.conditionSuppressions + for conditionId, conditionName in pairs(suppressedConditionNames) do + if bit.band(abilities.conditionSuppressions, conditionId) ~= 0 then + descriptions[#descriptions + 1] = conditionName + end + end + end + + -- speed if abilities.speed ~= 0 then - begin = addSeparator(ss, begin) - ss:append('speed %s%d', showpos(abilities.speed), math.abs(abilities.speed / 2)) + descriptions[#descriptions + 1] = string.format("speed %+d", math.floor(abilities.speed / 2)) end - return begin - end - local function internalItemGetDescription(it, lookDistance, item, subType, addArticle) - local abilities = it:getAbilities() - local ss = StringStream() - local subType = subType or (item and item:getSubType() or -1) - local text = nil - local begin = true - local obj = item or it + -- collecting attributes finished + -- build the output text + local response = {itemName} - if item then - ss:append(item:getNameDescription(subType, addArticle or true)) - else - ss:append(it:getNameDescription(subType, addArticle or true)) - end - - if it:isRune() then - local rune = Spell(it:getId()) - if rune then - if rune:runeLevel() and rune:runeLevel() > 0 or rune:runeMagicLevel() and rune:runeMagicLevel() > 0 then - local tmpVocMap = rune:vocation() - local vocMap = {} - for k, vocName in ipairs(tmpVocMap) do - local vocation = Vocation(vocName) - if vocation and vocation:getPromotion() then - vocMap[#vocMap + 1] = vocName - end - end + -- item group (will be reused) + local itemGroup = itemType:getGroup() - ss:append('. %s can only be used by', it:isStackable() and subType > 1 and 'They' or 'It') + -- fluid type + do + if (itemGroup == ITEM_GROUP_FLUID or itemGroup == ITEM_GROUP_SPLASH) and subType > 0 then + local subTypeName = getSubTypeName(subType) + response[#response + 1] = string.format(" of %s", (subTypeName ~= '' and subTypeName or "unknown")) + end + end - -- Only show base vocations in description; promotions should be a given - if #vocMap == 0 then - ss:append(' players') - else - for i = 1, #vocMap - 1 do - local vocName = vocMap[i] - local vocation = Vocation(vocName) - ss:append(' %ss', vocName:lower()) - if i + 1 == #vocMap then - ss:append(' and') - else - ss:append(',') - end - end - local vocName = vocMap[#vocMap] - ss:append(' %ss', vocName:lower()) + -- door check (will be reused) + local isDoor = itemType:isDoor() + + -- door info + if isDoor then + if not isVirtual then + local minLevelDoor = itemType:getLevelDoor() + if minLevelDoor ~= 0 then + if actionId >= minLevelDoor then + response[#response + 1] = string.format(" for level %d", actionId - minLevelDoor) end + end + end + end - ss:append(' with') + -- primary attributes parenthesis + if #descriptions > 0 then + response[#response + 1] = string.format(" (%s)", table.concat(descriptions, ", ")) + end - if rune:runeLevel() > 0 then - ss:append(' level %d', rune:runeLevel()) + -- podium description + if not isVirtual and itemType:isPodium() then + local outfit = item:getOutfit() + local hasOutfit = item:hasFlag(PODIUM_SHOW_OUTFIT) + local hasMount = item:hasFlag(PODIUM_SHOW_MOUNT) + if outfit then + local podiumParts = {} + local outfitType = Outfit(outfit.lookType) + if outfitType then + if hasOutfit then + podiumParts[#podiumParts + 1] = string.format("%s outfit", outfitType.name) end - if rune:runeMagicLevel() > 0 then - if rune:runeLevel() > 0 then - ss:append(' and ') - end - ss:append('magic level %d', rune:runeMagicLevel()) + -- search mount + local mountName = getMountNameByLookType(outfit.lookMount) + if mountName and hasMount then + podiumParts[#podiumParts + 1] = string.format("%s mount", mountName) + end + else + -- search mount + local mountName = getMountNameByLookType(outfit.lookMount) + local isEnabled = mountName and hasMount + if not mountName then + mountName = getMountNameByLookType(outfit.lookType) + isEnabled = mountName and hasOutfit end - ss:append(' or higher') + if mountName and isEnabled then + podiumParts[#podiumParts + 1] = string.format("%s mount", mountName) + end end - if not begin then - ss:append(')') + if #podiumParts > 0 then + response[#response + 1] = string.format(" displaying the %s", table.concat(podiumParts, " on the ")) end end - elseif it:getWeaponType() ~= WEAPON_NONE then - begin = addGenerics(item, it, abilities, ss, begin) - if not begin then - ss:append(')') - end - elseif obj:getArmor() ~= 0 or it:hasShowAttributes() then - if obj:getArmor() ~= 0 then - ss:append(' (Arm:%d', obj:getArmor()) - begin = false - end - begin = addGenerics(item, it, abilities, ss, begin) - if not begin then - ss:append(')') + end + + -- charges and duration + do + local expireInfo = {} + + -- charges + if itemType:hasShowCharges() then + local charges = item:getCharges() + expireInfo[#expireInfo + 1] = string.format("has %d charge%s left", charges, (charges ~= 1 and "s" or "")) end - elseif it:isContainer() or item and item:isContainer() then - local volume = 0 - if not item or not item:hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) then - if it:isContainer() then - volume = it:getCapacity() - else - volume = item:getCapacity() + -- duration + if itemType:hasShowDuration() then + local currentDuration = item:getDuration() + if isVirtual then + currentDuration = currentDuration * 1000 end - end - if volume ~= 0 then - ss:append(' (Vol:%d)', volume) + local maxDuration = itemType:getDuration() * 1000 + if maxDuration == 0 then + local transferType = itemType:getTransformEquipId() + if transferType ~= 0 then + transferType = ItemType(transferType) + maxDuration = transferType and transferType:getDuration() * 1000 or maxDuration + end + end + + if currentDuration == maxDuration then + expireInfo[#expireInfo + 1] = "is brand-new" + elseif currentDuration ~= 0 then + expireInfo[#expireInfo + 1] = string.format("will expire in %s", Game.getCountdownString(math.floor(currentDuration/1000), true, true)) + end end - else - local found = true - - if abilities.speed > 0 then - ss:append(' (speed %s%d)', showpos(abilities.speed), math.abs(abilities.speed / 2)) - elseif bit.band(abilities.conditionSuppressions, CONDITION_DRUNK) == 1 then - ss:append(' (hard drinking)') - elseif abilities.invisible then - ss:append(' (invisibility)') - elseif abilities.regeneration then - ss:append(' (faster regeneration)') - elseif abilities.manashield then - ss:append(' (mana shield)') - else - found = false + + if #expireInfo > 0 then + response[#response + 1] = string.format(" that %s", table.concat(expireInfo, " and ")) end + end - if not found then - if it:getType() == ITEM_TYPE_KEY then - local aid = item and item:getActionId() or 0 - ss:append(' (Key:%s)', ('0'):rep(4 - #tostring(aid)) .. aid) - elseif it:getGroup() == ITEM_GROUP_FLUID then - if subType > 0 then - local name = getSubTypeName(subType) - ss:append(' of %s', name ~= '' and name or 'unknown') - else - ss:append('. It is empty') + -- dot after primary attributes info + response[#response + 1] = "." + + -- empty fluid container suffix + if itemGroup == ITEM_GROUP_FLUID and subType < 1 then + response[#response + 1] = " It is empty." + end + + -- house door + if isDoor and not isVirtual then + local tile = item:getTile() + if tile then + local house = tile:getHouse() + if house then + local houseName = house:getName() + local houseOwnerName = house:getOwnerName() + local isForSale = false + + if not houseOwnerName or houseOwnerName:len() == 0 then + houseOwnerName = "Nobody" + isForSale = true end - elseif it:getGroup() == ITEM_GROUP_SPLASH then - local name = getSubTypeName(subType) - ss:append(' of ') - if subType > 0 and name ~= '' then - ss:append(name) - else - ss:append('unknown') + + response[#response + 1] = string.format(" It belongs to house '%s'. %s owns this house.", houseName, houseOwnerName) + if housePriceVisible and isForSale and pricePerSQM > 0 then + response[#response + 1] = string.format(" It costs %d gold coins.", pricePerSQM * house:getTileCount()) end - elseif it:hasAllowDistRead() and (it:getId() < 7369 or it:getId() > 7371) then - ss:append('.\n') - if lookDistance <= 4 then - if item then - if not text then - text = item:getText() - end - if text then - local writer = item:getWriter() - if writer then - local date = item:getDate() - ss:append('%s wrote', writer) - if date then - ss:append(' on %s', os.date('%d %b %Y')) - end - ss:append(': ') - else - ss:append('You read: ') - end - ss:append(text) - else - ss:append('Nothing is written on it') - end + end + end + end + + -- imbuements (to do) + -- \nImbuements: (Basic Strike 2:30h, Basic Void 2:30h, Empty Slot). + + -- item class + -- Classification: x Tier: y (0.50% Onslaught). + do + local classification = itemType:getClassification() + local tier = isVirtual and 0 or item:getTier() or 0 + + if classification > 0 or tier > 0 then + if classification == 0 then + classification = "other" + end + + local tierString = tier + if tier > 0 then + local bonusType, bonusValue = itemType:getTierBonus(tier) + if bonusType ~= -1 then + if bonusType > 5 then + tierString = string.format("%d (%0.2f%% %s)", tier, bonusValue, getSpecialSkillName(bonusType)) else - ss:append('Nothing is written on it') + tierString = string.format("%d (%d%% %s)", tier, bonusValue, getSpecialSkillName(bonusType)) end - else - ss:append('You are too far away to read it.') - end - elseif it:getLevelDoor() ~= 0 and item then - local aid = item:getActionId() - if aid >= it:getLevelDoor() then - ss:append(' for level %d', aid - it:getLevelDoor()) end end + + response[#response + 1] = string.format("\nClassification: %s Tier: %s.", classification, tierString) end end - if it:hasShowCharges() then - ss:append(' that has %d charge%s left', subType, subType ~= -1 and 's' or '') - end + -- item count (will be reused later) + local count = isVirtual and 1 or item:getCount() + + -- wield info + do + if itemType:isRune() then + local rune = Spell(itemType:getId()) + if rune then + local runeLevel = rune:runeLevel() + local runeMagLevel = rune:runeMagicLevel() + local runeVocMap = rune:vocation() + + local hasLevel = runeLevel > 0 + local hasMLvl = runeMagLevel > 0 + local hasVoc = #runeVocMap > 0 + if hasLevel or hasMLvl or hasVoc then + local vocAttrs = {} + if not hasVoc then + vocAttrs[#vocAttrs + 1] = "players" + else + for _, vocName in ipairs(runeVocMap) do + local vocation = Vocation(vocName) + if vocation and vocation:getPromotion() then + vocAttrs[#vocAttrs + 1] = vocName + end + end + end - -- show duration - if it:hasShowDuration() then - if item and item:hasAttribute(ITEM_ATTRIBUTE_DURATION) then - local duration = item:getDuration() / 1000 - if duration > 0 then - ss:append(' that will expire in ') + if #vocAttrs > 1 then + vocAttrs[#vocAttrs - 1] = string.format("and %s", vocAttrs[#vocAttrs - 1]) + end - if duration >= 86400 then - local days = math.floor(duration / 86400) - local hours = math.floor((duration % 86400) / 3600) - ss:append('%d day%s', days, days ~= 1 and 's' or '') - if hours > 0 then - ss:append(' and %d hour%s', hours, hours ~= 1 and 's' or '') + local levelInfo = {} + if hasLevel then + levelInfo[#levelInfo + 1] = string.format("level %d", runeLevel) end - elseif duration >= 3600 then - local hours = math.floor(duration / 3600) - local minutes = math.floor((duration % 3600) / 60) - ss:append('%d hour%s', hours, hours ~= 1 and 's' or '') - if minutes > 0 then - ss:append(' and %d minute%s', minutes, minutes ~= 1 and 's' or '') + + if hasMLvl then + levelInfo[#levelInfo + 1] = string.format("magic level %d", runeMagLevel) end - elseif duration >= 60 then - local minutes = math.floor(duration / 60) - local seconds = duration % 60 - ss:append('%d minute%s', minutes, minutes ~= 1 and 's' or '') - if seconds > 0 then - ss:append(' and %d second%s', seconds, seconds ~= 1 and 's' or '') + + local levelStr = "" + if #levelInfo > 0 then + levelStr = string.format(" of %s or higher", table.concat(levelInfo, " and ")) end - else - ss:append('%d second%s', duration, duration ~= 1 and 's' or '') + + response[#response + 1] = string.format( + "\n%s can only be used properly by %s%s.", + (count > 1 and "They" or "It"), + table.concat(vocAttrs, ", "), + levelStr + ) end end else - ss:append(' that is brand-new') - end - end - - if not it:hasAllowDistRead() or (it:getId() >= 7369 and it:getId() <= 7371) then - ss:append('.') - else - if not text and item then - text = item:getText() - end - if not text or text == '' then - ss:append('.') - end - end + local wieldInfo = itemType:getWieldInfo() + if wieldInfo ~= 0 then + local wieldAttrs = {} + if bit.band(wieldInfo, WIELDINFO_PREMIUM) ~= 0 then + wieldAttrs[#wieldAttrs + 1] = "premium" + end - local wieldInfo = it:getWieldInfo() - if wieldInfo ~= 0 then - ss:append('\nIt can only be wielded properly by ') + local vocStr = itemType:getVocationString() + if vocStr ~= '' then + wieldAttrs[#wieldAttrs + 1] = vocStr + else + wieldAttrs[#wieldAttrs + 1] = "players" + end - if bit.band(wieldInfo, WIELDINFO_PREMIUM) ~= 0 then - ss:append('premium ') - end + local levelInfo = {} + if bit.band(wieldInfo, WIELDINFO_LEVEL) ~= 0 then + levelInfo[#levelInfo + 1] = string.format("level %d", itemType:getMinReqLevel()) + end - local vocStr = it:getVocationString() - if vocStr ~= '' then - ss:append(vocStr) - else - ss:append('players') - end + if bit.band(wieldInfo, WIELDINFO_MAGLV) ~= 0 then + levelInfo[#levelInfo + 1] = string.format("magic level %d", itemType:getMinReqMagicLevel()) + end - if bit.band(wieldInfo, WIELDINFO_LEVEL) ~= 0 then - ss:append(' of level %d or higher', it:getMinReqLevel()) - end + if #levelInfo > 0 then + wieldAttrs[#wieldAttrs + 1] = string.format("of %s or higher", table.concat(levelInfo, " and ")) + end - if bit.band(wieldInfo, WIELDINFO_MAGLV) ~= 0 then - if bit.band(wieldInfo, WIELDINFO_LEVEL) ~= 0 then - ss:append(' and') - else - ss:append(' of') + response[#response + 1] = string.format( + "\n%s can only be wielded properly by %s.", + (count > 1 and "They" or "It"), + table.concat(wieldAttrs, " ") + ) end - ss:append(' magic level %d or higher', it:getMinReqMagicLevel()) end - ss:append('.') end if lookDistance <= 1 then - local weight = obj:getWeight() - local count = item and item:getCount() or 1 - if weight ~= 0 and it:isPickupable() then - ss:append('\n') - if it:isStackable() and count > 1 and it:hasShowCount() then - ss:append('They weigh ') + local weight = item:getWeight() + if isPickupable and not isUnique then + response[#response + 1] = string.format("\n%s %0.2f oz.", (count == 1 or not itemType:hasShowCount()) and "It weighs" or "They weigh", weight / 100) + end + end + + -- item text + if not isVirtual and itemType:hasAllowDistRead() then + local text = item:getText() + if text and text:len() > 0 then + if lookDistance <= 4 then + local writer = item:getAttribute(ITEM_ATTRIBUTE_WRITER) + local writeDate = item:getAttribute(ITEM_ATTRIBUTE_DATE) + local writeInfo = {} + if writer and writer:len() > 0 then + writeInfo[#writeInfo + 1] = string.format("\n%s wrote", writer) + + if writeDate and writeDate > 0 then + writeInfo[#writeInfo + 1] = string.format(" on %s", os.date("%d %b %Y", writeDate)) + end + end + + if #writeInfo > 0 then + response[#response + 1] = string.format("%s:\n%s", table.concat(writeInfo, ""), text) + else + response[#response + 1] = string.format("\nYou read: %s", text) + end else - ss:append('It weighs ') + response[#response + 1] = "\nYou are too far away to read it." end - ss:append('%.2f oz.', weight / 100) + else + response[#response + 1] = "\nNothing is written on it." end end - local desc = it:getDescription() - if item then - local specialDesc = item:getSpecialDescription() - if specialDesc ~= '' then - ss:append('\n%s', specialDesc) - elseif lookDistance <= 1 and desc ~= '' then - ss:append('\n%s', desc) + -- item description + local isBed = itemType:isBed() + if lookDistance <= 1 or (not isVirtual and (isDoor or isBed)) then + -- custom item description + local desc = not isVirtual and item:getSpecialDescription() + + -- native item description + if not desc or desc == "" then + desc = itemType:getDescription() end - elseif lookDistance <= 1 and desc ~= '' then - ss:append('\n%s', it:getDescription()) - end - if it:hasAllowDistRead() or (it:getId() >= 7369 and it:getId() <= 7371) then - if not text and item then - text = item:getText() + -- level door description + if isDoor and lookDistance <= 1 and (not desc or desc == "") and itemType:getLevelDoor() ~= 0 then + desc = "Only the worthy may pass." end - if text and text ~= '' then - ss:append('\n%s', text) + if desc and desc:len() > 0 then + if not (isBed and desc == "Nobody is sleeping there.") then + response[#response + 1] = string.format("\n%s", desc) + end end end - return ss:concat() - end + -- pickupable items with store flag + if not isVirtual and isPickupable and item:isStoreItem() then + response[#response + 1] = "\nThis item cannot be traded." + end - if not oldItemDesc then - oldItemDesc = Item.getDescription + -- turn response into a single string + return table.concat(response, "") end - if configManager.getBoolean(configKeys.LUA_ITEM_DESC) then - function Item.getDescription(self, lookDistance, subType) - return internalItemGetDescription(self:getType(), lookDistance, self, subType) - end + function Item:getDescription(lookDistance, subType, addArticle) + return internalItemGetDescription(self, lookDistance, subType, addArticle) + end - function ItemType.getItemDescription(self, lookDistance, subType) - return internalItemGetDescription(self, lookDistance, nil, subType) - end - else - Item.getDescription = oldItemDesc + function ItemType:getItemDescription(lookDistance, subType, addArticle) + return internalItemGetDescription(self, lookDistance, subType, addArticle) end end diff --git a/data/lib/core/itemtype.lua b/data/lib/core/itemtype.lua index c94ba7f1b4..de90585f75 100644 --- a/data/lib/core/itemtype.lua +++ b/data/lib/core/itemtype.lua @@ -1,16 +1,124 @@ -local slotBits = { - [CONST_SLOT_HEAD] = SLOTP_HEAD, - [CONST_SLOT_NECKLACE] = SLOTP_NECKLACE, - [CONST_SLOT_BACKPACK] = SLOTP_BACKPACK, - [CONST_SLOT_ARMOR] = SLOTP_ARMOR, - [CONST_SLOT_RIGHT] = SLOTP_RIGHT, - [CONST_SLOT_LEFT] = SLOTP_LEFT, - [CONST_SLOT_LEGS] = SLOTP_LEGS, - [CONST_SLOT_FEET] = SLOTP_FEET, - [CONST_SLOT_RING] = SLOTP_RING, - [CONST_SLOT_AMMO] = SLOTP_AMMO -} - -function ItemType.usesSlot(self, slot) - return bit.band(self:getSlotPosition(), slotBits[slot] or 0) ~= 0 +function ItemType:isItemType() + return true +end + +do + local slotBits = { + [CONST_SLOT_HEAD] = SLOTP_HEAD, + [CONST_SLOT_NECKLACE] = SLOTP_NECKLACE, + [CONST_SLOT_BACKPACK] = SLOTP_BACKPACK, + [CONST_SLOT_ARMOR] = SLOTP_ARMOR, + [CONST_SLOT_RIGHT] = SLOTP_RIGHT, + [CONST_SLOT_LEFT] = SLOTP_LEFT, + [CONST_SLOT_LEGS] = SLOTP_LEGS, + [CONST_SLOT_FEET] = SLOTP_FEET, + [CONST_SLOT_RING] = SLOTP_RING, + [CONST_SLOT_AMMO] = SLOTP_AMMO + } + + function ItemType:usesSlot(slot) + return bit.band(self:getSlotPosition(), slotBits[slot] or 0) ~= 0 + end +end + +function ItemType:isHelmet() + return self:usesSlot(CONST_SLOT_HEAD) +end + +function ItemType:isArmor() + return self:usesSlot(CONST_SLOT_ARMOR) +end + +function ItemType:isLegs() + return self:usesSlot(CONST_SLOT_LEGS) +end + +function ItemType:isBoots() + return self:usesSlot(CONST_SLOT_FEET) +end + +local notWeapons = {WEAPON_NONE, WEAPON_SHIELD, WEAPON_AMMO} +function ItemType:isWeapon() + return not table.contains(notWeapons, self:getWeaponType()) +end + +function ItemType:isTwoHanded() + return bit.band(self:getSlotPosition(), SLOTP_TWO_HAND) ~= 0 +end + +function ItemType:isBow() + local ammoType = self:getAmmoType() + return self:getWeaponType() == WEAPON_DISTANCE and (ammoType == AMMO_ARROW or ammoType == AMMO_BOLT) +end + +function ItemType:isMissile() + local ammoType = self:getAmmoType() + return self:getWeaponType() == WEAPON_DISTANCE and ammoType ~= AMMO_ARROW and ammoType ~= AMMO_BOLT +end + +function ItemType:isWand() + return self:getWeaponType() == WEAPON_WAND +end + +function ItemType:isShield() + return self:getWeaponType() == WEAPON_SHIELD +end + +function ItemType:isBackpack() + return self:usesSlot(CONST_SLOT_BACKPACK) +end + +function ItemType:isNecklace() + return self:usesSlot(CONST_SLOT_NECKLACE) +end + +function ItemType:isRing() + return self:usesSlot(CONST_SLOT_RING) +end + +function ItemType:isAmmo() + return self:getWeaponType() == WEAPON_AMMO +end + +function ItemType:isTrinket() + return self:usesSlot(CONST_SLOT_AMMO) and self:getWeaponType() == WEAPON_NONE +end + +function ItemType:isKey() + return self:getType() == ITEM_TYPE_KEY +end + +function ItemType:isBed() + return self:getType() == ITEM_TYPE_BED +end + +function ItemType:isSplash() + return self:getGroup() == ITEM_GROUP_SPLASH +end + +function ItemType:isPodium() + return self:getGroup() == ITEM_GROUP_PODIUM +end + +function ItemType:getWeaponString() + local weaponType = self:getWeaponType() + local weaponString = "unknown" + + if weaponType == WEAPON_CLUB then + weaponString = "blunt instrument" + elseif weaponType == WEAPON_SWORD then + weaponString = "stabbing weapon" + elseif weaponType == WEAPON_AXE then + weaponString = "cutting weapon" + elseif weaponType == WEAPON_DISTANCE then + weaponString = self:isBow() and "firearm" or "missile" + elseif weaponType == WEAPON_WAND then + weaponString = "wand/rod" + end + + if self:isTwoHanded() then + weaponString = string.format("%s, two-handed", weaponString) + end + + return weaponString end diff --git a/data/lib/core/party.lua b/data/lib/core/party.lua index 64fef7fed8..fd9e36d23a 100644 --- a/data/lib/core/party.lua +++ b/data/lib/core/party.lua @@ -1,10 +1,10 @@ function Party.broadcastPartyLoot(self, text) - self:getLeader():sendTextMessage(MESSAGE_INFO_DESCR, text) + self:getLeader():sendTextMessage(MESSAGE_LOOT, text) local membersList = self:getMembers() for i = 1, #membersList do local player = membersList[i] if player then - player:sendTextMessage(MESSAGE_INFO_DESCR, text) + player:sendTextMessage(MESSAGE_LOOT, text) end end end diff --git a/data/lib/core/player.lua b/data/lib/core/player.lua index f34deeba64..4230f9f1ba 100644 --- a/data/lib/core/player.lua +++ b/data/lib/core/player.lua @@ -92,7 +92,7 @@ function Player.removePremiumDays(self, days) end function Player.isPremium(self) - return self:getPremiumTime() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM) + return self:getPremiumTime() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM) or self:hasFlag(PlayerFlag_IsAlwaysPremium) end function Player.sendCancelMessage(self, message) @@ -171,40 +171,20 @@ function Player.canCarryMoney(self, amount) local totalWeight = 0 local inventorySlots = 0 - -- Add crystal coins to totalWeight and inventorySlots - local type_crystal = ItemType(ITEM_CRYSTAL_COIN) - local crystalCoins = math.floor(amount / 10000) - if crystalCoins > 0 then - amount = amount - (crystalCoins * 10000) - while crystalCoins > 0 do - local count = math.min(100, crystalCoins) - totalWeight = totalWeight + type_crystal:getWeight(count) - crystalCoins = crystalCoins - count - inventorySlots = inventorySlots + 1 - end - end - - -- Add platinum coins to totalWeight and inventorySlots - local type_platinum = ItemType(ITEM_PLATINUM_COIN) - local platinumCoins = math.floor(amount / 100) - if platinumCoins > 0 then - amount = amount - (platinumCoins * 100) - while platinumCoins > 0 do - local count = math.min(100, platinumCoins) - totalWeight = totalWeight + type_platinum:getWeight(count) - platinumCoins = platinumCoins - count - inventorySlots = inventorySlots + 1 - end - end - - -- Add gold coins to totalWeight and inventorySlots - local type_gold = ItemType(ITEM_GOLD_COIN) - if amount > 0 then - while amount > 0 do - local count = math.min(100, amount) - totalWeight = totalWeight + type_gold:getWeight(count) - amount = amount - count - inventorySlots = inventorySlots + 1 + local currencyItems = Game.getCurrencyItems() + for index = #currencyItems, 1, -1 do + local currency = currencyItems[index] + -- Add currency coins to totalWeight and inventorySlots + local worth = currency:getWorth() + local currencyCoins = math.floor(amount / worth) + if currencyCoins > 0 then + amount = amount - (currencyCoins * worth) + while currencyCoins > 0 do + local count = math.min(100, currencyCoins) + totalWeight = totalWeight + currency:getWeight(count) + currencyCoins = currencyCoins - count + inventorySlots = inventorySlots + 1 + end end end @@ -240,33 +220,154 @@ function Player.depositMoney(self, amount) return true end +function Player.removeTotalMoney(self, amount) + local moneyCount = self:getMoney() + local bankCount = self:getBankBalance() + if amount <= moneyCount then + self:removeMoney(amount) + return true + elseif amount <= (moneyCount + bankCount) then + if moneyCount ~= 0 then + self:removeMoney(moneyCount) + local remains = amount - moneyCount + self:setBankBalance(bankCount - remains) + self:sendTextMessage(MESSAGE_INFO_DESCR, ("Paid %d from inventory and %d gold from bank account. Your account balance is now %d gold."):format(moneyCount, amount - moneyCount, self:getBankBalance())) + return true + end + + self:setBankBalance(bankCount - amount) + self:sendTextMessage(MESSAGE_INFO_DESCR, ("Paid %d gold from bank account. Your account balance is now %d gold."):format(amount, self:getBankBalance())) + return true + end + return false +end + function Player.addLevel(self, amount, round) - local experience, level, amount = 0, self:getLevel(), amount or 1 + round = round or false + local level, amount = self:getLevel(), amount or 1 if amount > 0 then - experience = getExperienceForLevel(level + amount) - (round and self:getExperience() or getExperienceForLevel(level)) - else - experience = -((round and self:getExperience() or getExperienceForLevel(level)) - getExperienceForLevel(level + amount)) + return self:addExperience(Game.getExperienceForLevel(level + amount) - (round and self:getExperience() or Game.getExperienceForLevel(level))) end - return self:addExperience(experience) + return self:removeExperience(((round and self:getExperience() or Game.getExperienceForLevel(level)) - Game.getExperienceForLevel(level + amount))) end function Player.addMagicLevel(self, value) - return self:addManaSpent(self:getVocation():getRequiredManaSpent(self:getBaseMagicLevel() + value + 1) - self:getManaSpent()) + local currentMagLevel = self:getBaseMagicLevel() + local sum = 0 + + if value > 0 then + while value > 0 do + sum = sum + self:getVocation():getRequiredManaSpent(currentMagLevel + value) + value = value - 1 + end + + return self:addManaSpent(sum - self:getManaSpent()) + end + + value = math.min(currentMagLevel, math.abs(value)) + while value > 0 do + sum = sum + self:getVocation():getRequiredManaSpent(currentMagLevel - value + 1) + value = value - 1 + end + + return self:removeManaSpent(sum + self:getManaSpent()) +end + +function Player.addSkillLevel(self, skillId, value) + local currentSkillLevel = self:getSkillLevel(skillId) + local sum = 0 + + if value > 0 then + while value > 0 do + sum = sum + self:getVocation():getRequiredSkillTries(skillId, currentSkillLevel + value) + value = value - 1 + end + + return self:addSkillTries(skillId, sum - self:getSkillTries(skillId)) + end + + value = math.min(currentSkillLevel, math.abs(value)) + while value > 0 do + sum = sum + self:getVocation():getRequiredSkillTries(skillId, currentSkillLevel - value + 1) + value = value - 1 + end + + return self:removeSkillTries(skillId, sum + self:getSkillTries(skillId), true) end function Player.addSkill(self, skillId, value, round) if skillId == SKILL_LEVEL then - return self:addLevel(value, round) + return self:addLevel(value, round or false) elseif skillId == SKILL_MAGLEVEL then return self:addMagicLevel(value) end - return self:addSkillTries(skillId, self:getVocation():getRequiredSkillTries(skillId, self:getSkillLevel(skillId) + value) - self:getSkillTries(skillId)) + return self:addSkillLevel(skillId, value) end -function Player.getWeaponType() +function Player.getWeaponType(self) local weapon = self:getSlotItem(CONST_SLOT_LEFT) if weapon then - return ItemType(weapon:getId()):getWeaponType() + return weapon:getType():getWeaponType() end return WEAPON_NONE end + +function Player.updateKillTracker(self, monster, corpse) + local monsterType = monster:getType() + if not monsterType then + return false + end + + local msg = NetworkMessage() + msg:addByte(0xD1) + msg:addString(monster:getName()) + + local monsterOutfit = monsterType:getOutfit() + msg:addU16(monsterOutfit.lookType or 19) + msg:addByte(monsterOutfit.lookHead) + msg:addByte(monsterOutfit.lookBody) + msg:addByte(monsterOutfit.lookLegs) + msg:addByte(monsterOutfit.lookFeet) + msg:addByte(monsterOutfit.lookAddons) + + local corpseSize = corpse:getSize() + msg:addByte(corpseSize) + for index = corpseSize - 1, 0, -1 do + msg:addItem(corpse:getItem(index)) + end + + local party = self:getParty() + if party then + local members = party:getMembers() + members[#members + 1] = party:getLeader() + + for _, member in ipairs(members) do + msg:sendToPlayer(member) + end + else + msg:sendToPlayer(self) + end + + msg:delete() + return true +end + +function Player.getTotalMoney(self) + return self:getMoney() + self:getBankBalance() +end + +function Player.addAddonToAllOutfits(self, addon) + for sex = 0, 1 do + local outfits = Game.getOutfits(sex) + for outfit = 1, #outfits do + self:addOutfitAddon(outfits[outfit].lookType, addon) + end + end +end + +function Player.addAllMounts(self) + local mounts = Game.getMounts() + for mount = 1, #mounts do + self:addMount(mounts[mount].id) + end +end diff --git a/data/lib/core/podium.lua b/data/lib/core/podium.lua new file mode 100644 index 0000000000..8d50f6ab6a --- /dev/null +++ b/data/lib/core/podium.lua @@ -0,0 +1,3 @@ +function Podium.isPodium(self) + return true +end diff --git a/data/lib/core/position.lua b/data/lib/core/position.lua index a40f8ea915..c151df9f52 100644 --- a/data/lib/core/position.lua +++ b/data/lib/core/position.lua @@ -19,7 +19,7 @@ function Position:getNextPosition(direction, steps) end function Position:moveUpstairs() - local swap = function (lhs, rhs) + local swap = function(lhs, rhs) lhs.x, rhs.x = rhs.x, lhs.x lhs.y, rhs.y = rhs.y, lhs.y lhs.z, rhs.z = rhs.z, lhs.z @@ -63,7 +63,7 @@ function Position:isInRange(from, to) } } - if self.x >= zone.nW.x and self.x <= zone.sE.x + if self.x >= zone.nW.x and self.x <= zone.sE.x and self.y >= zone.nW.y and self.y <= zone.sE.y and self.z >= zone.nW.z and self.z <= zone.sE.z then return true diff --git a/data/lib/core/storages.lua b/data/lib/core/storages.lua index 36f87ba9f0..32e8278fb5 100644 --- a/data/lib/core/storages.lua +++ b/data/lib/core/storages.lua @@ -4,22 +4,30 @@ Reserved storage ranges: - 20000 to 21000+ reserved for achievement progress - 10000000 to 20000000 reserved for outfits and mounts on source ]]-- + PlayerStorageKeys = { annihilatorReward = 30015, + goldenOutfit = 30016, + -- empty: 30017 promotion = 30018, delayLargeSeaShell = 30019, firstRod = 30020, delayWallMirror = 30021, + -- empty: 30022 madSheepSummon = 30023, crateUsable = 30024, + -- empty: 30025 afflictedOutfit = 30026, afflictedPlagueMask = 30027, afflictedPlagueBell = 30028, + -- empty: 30029 + -- empty: 30030 nailCaseUseCount = 30031, swampDigging = 30032, insectoidCell = 30033, vortexTamer = 30034, mutatedPumpkin = 30035, + achievementsBase = 300000, achievementsCounter = 20000, } diff --git a/data/lib/debugging/lua_version.lua b/data/lib/debugging/lua_version.lua index 18294c1150..48fb7f51e2 100644 --- a/data/lib/debugging/lua_version.lua +++ b/data/lib/debugging/lua_version.lua @@ -1,5 +1,5 @@ if type(jit) == 'table' then - print('>> Using ' .. jit.version) --LuaJIT 2.0.2 + print('>> Using ' .. jit.version) --LuaJIT 2.0.2 else print('>> Using ' .. _VERSION) end diff --git a/data/migrations/10.lua b/data/migrations/10.lua index d79547dc35..6f57155e34 100644 --- a/data/migrations/10.lua +++ b/data/migrations/10.lua @@ -7,10 +7,10 @@ function onUpdateDatabase() db.query("ALTER TABLE `player_storage` ADD PRIMARY KEY (`player_id`, `key`)") local resultId = db.storeQuery("SELECT `players`.`id` AS `player_id`, `players`.`rank_id` AS `rank_id`, `players`.`guildnick` AS `guild_nick`, `guild_ranks`.`guild_id` AS `guild_id` FROM `guild_ranks` INNER JOIN `players` ON `guild_ranks`.`id` = `players`.`rank_id`") - if resultId ~= false then + if resultId then local stmt = "INSERT INTO `guild_membership` (`player_id`, `guild_id`, `rank_id`, `nick`) VALUES " repeat - stmt = stmt .. "(" .. result.getNumber(resultId, "player_id") .. "," .. result.getNumber(resultId, "guild_id") .. "," .. result.getNumber(resultId, "rank_id") .. "," .. db.escapeString(result.getString(resultId, "guild_nick")) .. ")," + stmt = stmt .. "(" .. result.getNumber(resultId, "player_id") .. ", " .. result.getNumber(resultId, "guild_id") .. ", " .. result.getNumber(resultId, "rank_id") .. ", " .. db.escapeString(result.getString(resultId, "guild_nick")) .. ")," until not result.next(resultId) result.free(resultId) diff --git a/data/migrations/13.lua b/data/migrations/13.lua index ea4f072d95..a096c408ef 100644 --- a/data/migrations/13.lua +++ b/data/migrations/13.lua @@ -7,10 +7,10 @@ function onUpdateDatabase() db.query("CREATE TABLE IF NOT EXISTS `player_namelocks` (`player_id` int NOT NULL, `reason` varchar(255) NOT NULL, `namelocked_at` bigint NOT NULL, `namelocked_by` int NOT NULL, PRIMARY KEY (`player_id`), KEY `namelocked_by` (`namelocked_by`), FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`namelocked_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB") local resultId = db.storeQuery("SELECT `player`, `time` FROM `bans` WHERE `type` = 2") - if resultId ~= false then + if resultId then local stmt = "INSERT INTO `player_namelocks` (`player_id`, `namelocked_at`, `namelocked_by`) VALUES " repeat - stmt = stmt .. "(" .. result.getNumber(resultId, "player") .. "," .. result.getNumber(resultId, "time") .. "," .. result.getNumber(resultId, "player") .. ")," + stmt = stmt .. "(" .. result.getNumber(resultId, "player") .. ", " .. result.getNumber(resultId, "time") .. ", " .. result.getNumber(resultId, "player") .. ")," until not result.next(resultId) result.free(resultId) @@ -31,7 +31,7 @@ function onUpdateDatabase() print("DELIMITER //") print("CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players`") print(" FOR EACH ROW BEGIN") - print(" UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`;") + print(" UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`;") print("END //") return true end diff --git a/data/migrations/14.lua b/data/migrations/14.lua index 84e2e74d61..729c0a8105 100644 --- a/data/migrations/14.lua +++ b/data/migrations/14.lua @@ -14,7 +14,7 @@ function onUpdateDatabase() groupsFile:write("\r\n") local resultId = db.storeQuery("SELECT `id`, `name`, `flags`, `access`, `maxdepotitems`, `maxviplist` FROM `groups` ORDER BY `id` ASC") - if resultId ~= false then + if resultId then repeat groupsFile:write("\t\r\n") until not result.next(resultId) diff --git a/data/migrations/29.lua b/data/migrations/29.lua index d0ffd9c0cb..6a2554d454 100644 --- a/data/migrations/29.lua +++ b/data/migrations/29.lua @@ -1,3 +1,13 @@ function onUpdateDatabase() - return false + print("> Updating database to version 29 (account storages)") + db.query([[ + CREATE TABLE IF NOT EXISTS `account_storage` ( + `account_id` int NOT NULL, + `key` int unsigned NOT NULL, + `value` int NOT NULL, + PRIMARY KEY (`account_id`, `key`), + FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + ]]) + return true end diff --git a/data/migrations/30.lua b/data/migrations/30.lua new file mode 100644 index 0000000000..e66f37992e --- /dev/null +++ b/data/migrations/30.lua @@ -0,0 +1,12 @@ +function onUpdateDatabase() + print("> Updating database to version 30 (mount colors)") + db.query([[ + ALTER TABLE `players` + ADD COLUMN `lookmount` int DEFAULT 0 NOT NULL AFTER `lookaddons`, + ADD COLUMN `lookmounthead` int DEFAULT 0 NOT NULL AFTER `lookmount`, + ADD COLUMN `lookmountbody` int DEFAULT 0 NOT NULL AFTER `lookmounthead`, + ADD COLUMN `lookmountlegs` int DEFAULT 0 NOT NULL AFTER `lookmountbody`, + ADD COLUMN `lookmountfeet` int DEFAULT 0 NOT NULL AFTER `lookmountlegs`; + ]]) + return true +end diff --git a/data/migrations/31.lua b/data/migrations/31.lua new file mode 100644 index 0000000000..45f4ee108b --- /dev/null +++ b/data/migrations/31.lua @@ -0,0 +1,6 @@ +function onUpdateDatabase() + print("> Updating database to version 31 (64 bit market prices)") + db.query("ALTER TABLE `market_offers` MODIFY `price` BIGINT UNSIGNED;") + db.query("ALTER TABLE `market_history` MODIFY `price` BIGINT UNSIGNED;") + return true +end diff --git a/data/migrations/32.lua b/data/migrations/32.lua new file mode 100644 index 0000000000..68b5d57dcb --- /dev/null +++ b/data/migrations/32.lua @@ -0,0 +1,6 @@ +function onUpdateDatabase() + print("> Updating database to version 32 (removed MOTD)") + db.query("DELETE FROM `server_config` WHERE `config` = 'motd_num'") + db.query("DELETE FROM `server_config` WHERE `config` = 'motd_hash'") + return true +end diff --git a/data/migrations/33.lua b/data/migrations/33.lua new file mode 100644 index 0000000000..d0ffd9c0cb --- /dev/null +++ b/data/migrations/33.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false +end diff --git a/data/migrations/7.lua b/data/migrations/7.lua index 793f695ad1..5e990886e5 100644 --- a/data/migrations/7.lua +++ b/data/migrations/7.lua @@ -14,7 +14,7 @@ function onUpdateDatabase() -- Remove duplicates local resultId = db.storeQuery("SELECT `account_id`, `player_id`, COUNT(*) AS `count` FROM `account_viplist` GROUP BY `account_id`, `player_id` HAVING COUNT(*) > 1") - if resultId ~= false then + if resultId then repeat db.query("DELETE FROM `account_viplist` WHERE `account_id` = " .. result.getNumber(resultId, "account_id") .. " AND `player_id` = " .. result.getNumber(resultId, "player_id") .. " LIMIT " .. (result.getNumber(resultId, "count") - 1)) until not result.next(resultId) @@ -23,7 +23,7 @@ function onUpdateDatabase() -- Remove if an account has over 200 entries resultId = db.storeQuery("SELECT `account_id`, COUNT(*) AS `count` FROM `account_viplist` GROUP BY `account_id` HAVING COUNT(*) > 200") - if resultId ~= false then + if resultId then repeat db.query("DELETE FROM `account_viplist` WHERE `account_id` = " .. result.getNumber(resultId, "account_id") .. " LIMIT " .. (result.getNumber(resultId, "count") - 200)) until not result.next(resultId) diff --git a/data/migrations/8.lua b/data/migrations/8.lua index 166a2247e7..f4ccf67a12 100644 --- a/data/migrations/8.lua +++ b/data/migrations/8.lua @@ -1,13 +1,13 @@ function onUpdateDatabase() print("> Updating database to version 9 (global inbox)") - db.query("CREATE TABLE IF NOT EXISTS `player_inboxitems` (`player_id` int NOT NULL, `sid` int NOT NULL, `pid` int NOT NULL DEFAULT '0', `itemtype` smallint NOT NULL, `count` smallint NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`,`sid`), KEY `player_id` (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1") + db.query("CREATE TABLE IF NOT EXISTS `player_inboxitems` (`player_id` int NOT NULL, `sid` int NOT NULL, `pid` int NOT NULL DEFAULT '0', `itemtype` smallint NOT NULL, `count` smallint NOT NULL DEFAULT '0', `attributes` blob NOT NULL, UNIQUE KEY `player_id_2` (`player_id`, `sid`), KEY `player_id` (`player_id`), FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1") -- Delete "market" item db.query("DELETE FROM `player_depotitems` WHERE `itemtype` = 14405") -- Move up items in depot chests local resultId = db.storeQuery("SELECT `player_id`, `pid`, (SELECT `dp2`.`sid` FROM `player_depotitems` AS `dp2` WHERE `dp2`.`player_id` = `dp1`.`player_id` AND `dp2`.`pid` = `dp1`.`sid` AND `itemtype` = 2594) AS `sid` FROM `player_depotitems` AS `dp1` WHERE `itemtype` = 2589") - if resultId ~= false then + if resultId then repeat db.query("UPDATE `player_depotitems` SET `pid` = " .. result.getNumber(resultId, "pid") .. " WHERE `player_id` = " .. result.getNumber(resultId, "player_id") .. " AND `pid` = " .. result.getNumber(resultId, "sid")) until not result.next(resultId) @@ -21,7 +21,7 @@ function onUpdateDatabase() db.query("DELETE FROM `player_depotitems` WHERE `itemtype` = 2594") resultId = db.storeQuery("SELECT DISTINCT `player_id` FROM `player_depotitems` WHERE `itemtype` = 14404") - if resultId ~= false then + if resultId then repeat local playerId = result.getNumber(resultId, "player_id") @@ -30,7 +30,7 @@ function onUpdateDatabase() local stmt = "INSERT INTO `player_inboxitems` (`player_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES " local resultId2 = db.storeQuery("SELECT `sid` FROM `player_depotitems` WHERE `player_id` = " .. playerId .. " AND `itemtype` = 14404") - if resultId2 ~= false then + if resultId2 then repeat local sids = {} sids[#sids + 1] = result.getNumber(resultId2, "sid") @@ -39,11 +39,11 @@ function onUpdateDatabase() sids[#sids] = nil local resultId3 = db.storeQuery("SELECT * FROM `player_depotitems` WHERE `player_id` = " .. playerId .. " AND `pid` = " .. sid) - if resultId3 ~= false then + if resultId3 then repeat local attr, attrSize = result.getStream(resultId3, "attributes") runningId = runningId + 1 - stmt = stmt .. "(" .. playerId .. "," .. runningId .. ",0," .. result.getNumber(resultId3, "itemtype") .. "," .. result.getNumber(resultId3, "count") .. "," .. db.escapeBlob(attr, attrSize) .. ")," + stmt = stmt .. "(" .. playerId .. ", " .. runningId .. ", 0, " .. result.getNumber(resultId3, "itemtype") .. ", " .. result.getNumber(resultId3, "count") .. ", " .. db.escapeBlob(attr, attrSize) .. ")," sids[#sids + 1] = result.getNumber(resultId3, "sid") db.query("DELETE FROM `player_depotitems` WHERE `player_id` = " .. result.getNumber(resultId, "player_id") .. " AND `sid` = " .. result.getNumber(resultId3, "sid")) diff --git a/data/monster/lua/#example.lua b/data/monster/lua/#example.lua index f0bee8de80..d6b328d4f3 100644 --- a/data/monster/lua/#example.lua +++ b/data/monster/lua/#example.lua @@ -14,7 +14,7 @@ monster.speed = 280 monster.maxSummons = 2 monster.changeTarget = { - interval = 4*1000, + interval = 4 * 1000, chance = 20 } @@ -22,7 +22,9 @@ monster.flags = { summonable = false, attackable = true, hostile = true, + challengeable = true, convinceable = false, + ignoreSpawnBlock = false, illusionable = false, canPushItems = true, canPushCreatures = true, @@ -31,7 +33,7 @@ monster.flags = { } monster.summons = { - {name = "demon", chance = 10, interval = 2*1000} + {name = "demon", chance = 10, interval = 2 * 1000} } monster.voices = { @@ -52,20 +54,20 @@ monster.loot = { } monster.attacks = { - {name = "melee", attack = 130, skill = 70, effect = CONST_ME_DRAWBLOOD, interval = 2*1000}, - {name = "energy strike", range = 1, chance = 10, interval = 2*1000, minDamage = -210, maxDamage = -300, target = true}, - {name = "combat", type = COMBAT_MANADRAIN, chance = 10, interval = 2*1000, minDamage = 0, maxDamage = -120, target = true, range = 7, effect = CONST_ME_MAGIC_BLUE}, - {name = "combat", type = COMBAT_FIREDAMAGE, chance = 20, interval = 2*1000, minDamage = -150, maxDamage = -250, radius = 1, target = true, effect = CONST_ME_FIREAREA, shootEffect = CONST_ANI_FIRE}, - {name = "speed", chance = 15, interval = 2*1000, speed = -700, radius = 1, target = true, duration = 30*1000, effect = CONST_ME_MAGIC_RED}, - {name = "firefield", chance = 10, interval = 2*1000, range = 7, radius = 1, target = true, shootEffect = CONST_ANI_FIRE}, - {name = "combat", type = COMBAT_LIFEDRAIN, chance = 10, interval = 2*1000, length = 8, spread = 0, minDamage = -300, maxDamage = -490, effect = CONST_ME_PURPLEENERGY} + {name = "melee", attack = 130, skill = 70, effect = CONST_ME_DRAWBLOOD, interval = 2 * 1000}, + {name = "energy strike", range = 1, chance = 10, interval = 2 * 1000, minDamage = -210, maxDamage = -300, target = true}, + {name = "combat", type = COMBAT_MANADRAIN, chance = 10, interval = 2 * 1000, minDamage = 0, maxDamage = -120, target = true, range = 7, effect = CONST_ME_MAGIC_BLUE}, + {name = "combat", type = COMBAT_FIREDAMAGE, chance = 20, interval = 2 * 1000, minDamage = -150, maxDamage = -250, radius = 1, target = true, effect = CONST_ME_FIREAREA, shootEffect = CONST_ANI_FIRE}, + {name = "speed", chance = 15, interval = 2 * 1000, speed = -700, radius = 1, target = true, duration = 30 * 1000, effect = CONST_ME_MAGIC_RED}, + {name = "firefield", chance = 10, interval = 2 * 1000, range = 7, radius = 1, target = true, shootEffect = CONST_ANI_FIRE}, + {name = "combat", type = COMBAT_LIFEDRAIN, chance = 10, interval = 2 * 1000, length = 8, spread = 0, minDamage = -300, maxDamage = -490, effect = CONST_ME_PURPLEENERGY} } monster.defenses = { defense = 55, armor = 55, - {name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2*1000, minDamage = 180, maxDamage = 250, effect = CONST_ME_MAGIC_BLUE}, - {name = "speed", chance = 15, interval = 2*1000, speed = 320, effect = CONST_ME_MAGIC_RED} + {name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2 * 1000, minDamage = 180, maxDamage = 250, effect = CONST_ME_MAGIC_BLUE}, + {name = "speed", chance = 15, interval = 2 * 1000, speed = 320, effect = CONST_ME_MAGIC_RED} } monster.elements = { diff --git a/data/monster/monsters.xml b/data/monster/monsters.xml index 940d521687..7080b0eb13 100644 --- a/data/monster/monsters.xml +++ b/data/monster/monsters.xml @@ -50,6 +50,7 @@ + @@ -117,6 +118,7 @@ + @@ -175,14 +177,15 @@ + + - @@ -197,8 +200,8 @@ - + @@ -213,12 +216,13 @@ + - + @@ -253,6 +257,7 @@ + @@ -260,6 +265,12 @@ + + + + + + @@ -275,6 +286,7 @@ + @@ -300,6 +312,7 @@ + @@ -337,11 +350,12 @@ + - + @@ -400,18 +414,24 @@ + + + + + + @@ -434,11 +454,16 @@ + + + + + @@ -454,6 +479,7 @@ + @@ -497,9 +523,12 @@ + + + @@ -509,9 +538,12 @@ + + + @@ -548,10 +580,12 @@ + + @@ -584,6 +618,7 @@ + @@ -592,6 +627,7 @@ + @@ -612,12 +648,14 @@ + + @@ -652,6 +690,8 @@ + + @@ -672,8 +712,8 @@ - + @@ -683,6 +723,7 @@ + @@ -695,6 +736,7 @@ + diff --git a/data/monster/monsters/apprentice_sheng.xml b/data/monster/monsters/apprentice_sheng.xml index 4e8d48fb51..593375a944 100644 --- a/data/monster/monsters/apprentice_sheng.xml +++ b/data/monster/monsters/apprentice_sheng.xml @@ -50,7 +50,7 @@ - + diff --git a/data/monster/monsters/arachir_the_ancient_one.xml b/data/monster/monsters/arachir_the_ancient_one.xml index bddc7fb0b0..acb2bb8454 100644 --- a/data/monster/monsters/arachir_the_ancient_one.xml +++ b/data/monster/monsters/arachir_the_ancient_one.xml @@ -59,7 +59,7 @@ - + diff --git a/data/monster/monsters/bazir.xml b/data/monster/monsters/bazir.xml index 96b9e6d59b..b6a95004c7 100644 --- a/data/monster/monsters/bazir.xml +++ b/data/monster/monsters/bazir.xml @@ -36,7 +36,7 @@ - + diff --git a/data/monster/monsters/black_knight.xml b/data/monster/monsters/black_knight.xml index 89ba987d2b..8126b4d156 100644 --- a/data/monster/monsters/black_knight.xml +++ b/data/monster/monsters/black_knight.xml @@ -7,6 +7,7 @@ + @@ -50,7 +51,7 @@ - + diff --git a/data/monster/monsters/blood_beast.xml b/data/monster/monsters/blood_beast.xml new file mode 100644 index 0000000000..e68fbe5329 --- /dev/null +++ b/data/monster/monsters/blood_beast.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/bug.xml b/data/monster/monsters/bug.xml index 9737ddc08d..52dfd98ddb 100644 --- a/data/monster/monsters/bug.xml +++ b/data/monster/monsters/bug.xml @@ -28,6 +28,6 @@ - + diff --git a/data/monster/monsters/cyclops.xml b/data/monster/monsters/cyclops.xml index 5d79d39627..cc0903f694 100644 --- a/data/monster/monsters/cyclops.xml +++ b/data/monster/monsters/cyclops.xml @@ -44,7 +44,7 @@ - + diff --git a/data/monster/monsters/dawnfire_asura.xml b/data/monster/monsters/dawnfire_asura.xml new file mode 100644 index 0000000000..61af99cb00 --- /dev/null +++ b/data/monster/monsters/dawnfire_asura.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/diblis_the_fair.xml b/data/monster/monsters/diblis_the_fair.xml index 34b7819991..4c3ab36d15 100644 --- a/data/monster/monsters/diblis_the_fair.xml +++ b/data/monster/monsters/diblis_the_fair.xml @@ -53,7 +53,7 @@ - + diff --git a/data/monster/monsters/dragon.xml b/data/monster/monsters/dragon.xml index 32ee1e6eec..3b2d39d1fc 100644 --- a/data/monster/monsters/dragon.xml +++ b/data/monster/monsters/dragon.xml @@ -52,7 +52,7 @@ - + diff --git a/data/monster/monsters/dragon_lord.xml b/data/monster/monsters/dragon_lord.xml index 745f7c6f45..c89ba63405 100644 --- a/data/monster/monsters/dragon_lord.xml +++ b/data/monster/monsters/dragon_lord.xml @@ -56,7 +56,7 @@ - + @@ -70,6 +70,6 @@ - + diff --git a/data/monster/monsters/druid_familiar.xml b/data/monster/monsters/druid_familiar.xml new file mode 100644 index 0000000000..6ca9cd7f0a --- /dev/null +++ b/data/monster/monsters/druid_familiar.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/elf.xml b/data/monster/monsters/elf.xml index ac836c3e88..388303b6cb 100644 --- a/data/monster/monsters/elf.xml +++ b/data/monster/monsters/elf.xml @@ -48,7 +48,7 @@ - + diff --git a/data/monster/monsters/elf_arcanist.xml b/data/monster/monsters/elf_arcanist.xml index cca9e41899..3a3ed23b47 100644 --- a/data/monster/monsters/elf_arcanist.xml +++ b/data/monster/monsters/elf_arcanist.xml @@ -63,7 +63,7 @@ - + @@ -74,7 +74,7 @@ - + diff --git a/data/monster/monsters/elf_scout.xml b/data/monster/monsters/elf_scout.xml index 979f2ce61e..45ba635c56 100644 --- a/data/monster/monsters/elf_scout.xml +++ b/data/monster/monsters/elf_scout.xml @@ -42,7 +42,7 @@ - + diff --git a/data/monster/monsters/execowtioner.xml b/data/monster/monsters/execowtioner.xml new file mode 100644 index 0000000000..1f83e0f2a7 --- /dev/null +++ b/data/monster/monsters/execowtioner.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/frazzlemaw.xml b/data/monster/monsters/frazzlemaw.xml index 8fabd2baae..225facd77c 100644 --- a/data/monster/monsters/frazzlemaw.xml +++ b/data/monster/monsters/frazzlemaw.xml @@ -66,7 +66,7 @@ - + diff --git a/data/monster/monsters/ghost_wolf.xml b/data/monster/monsters/ghost_wolf.xml new file mode 100644 index 0000000000..c10a2053de --- /dev/null +++ b/data/monster/monsters/ghost_wolf.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/gloom_wolf.xml b/data/monster/monsters/gloom_wolf.xml new file mode 100644 index 0000000000..295259b476 --- /dev/null +++ b/data/monster/monsters/gloom_wolf.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/glooth_anemone.xml b/data/monster/monsters/glooth_anemone.xml new file mode 100644 index 0000000000..ef963d133c --- /dev/null +++ b/data/monster/monsters/glooth_anemone.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/glooth_bandit.xml b/data/monster/monsters/glooth_bandit.xml new file mode 100644 index 0000000000..a7ecb6b6e7 --- /dev/null +++ b/data/monster/monsters/glooth_bandit.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/glooth_blob.xml b/data/monster/monsters/glooth_blob.xml new file mode 100644 index 0000000000..a0ccc5640c --- /dev/null +++ b/data/monster/monsters/glooth_blob.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/glooth_brigand.xml b/data/monster/monsters/glooth_brigand.xml new file mode 100644 index 0000000000..bfa39b0f41 --- /dev/null +++ b/data/monster/monsters/glooth_brigand.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/glooth_golem.xml b/data/monster/monsters/glooth_golem.xml new file mode 100644 index 0000000000..8a86ec98b3 --- /dev/null +++ b/data/monster/monsters/glooth_golem.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/gravelord_oshuran.xml b/data/monster/monsters/gravelord_oshuran.xml new file mode 100644 index 0000000000..79d68b84f5 --- /dev/null +++ b/data/monster/monsters/gravelord_oshuran.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/guzzlemaw.xml b/data/monster/monsters/guzzlemaw.xml index a5e1fbb663..ce68902ec8 100644 --- a/data/monster/monsters/guzzlemaw.xml +++ b/data/monster/monsters/guzzlemaw.xml @@ -66,7 +66,7 @@ - + diff --git a/data/monster/monsters/hellgorak.xml b/data/monster/monsters/hellgorak.xml index c0149028de..2d96bf5e33 100644 --- a/data/monster/monsters/hellgorak.xml +++ b/data/monster/monsters/hellgorak.xml @@ -61,52 +61,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/high_templar_cobrass.xml b/data/monster/monsters/high_templar_cobrass.xml new file mode 100644 index 0000000000..65ec06c82e --- /dev/null +++ b/data/monster/monsters/high_templar_cobrass.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/horadron.xml b/data/monster/monsters/horadron.xml index 4d5c4e8670..15a2c2fdf6 100644 --- a/data/monster/monsters/horadron.xml +++ b/data/monster/monsters/horadron.xml @@ -35,7 +35,7 @@ - + diff --git a/data/monster/monsters/hunter.xml b/data/monster/monsters/hunter.xml index bc563c6d0f..66d5818db6 100644 --- a/data/monster/monsters/hunter.xml +++ b/data/monster/monsters/hunter.xml @@ -37,12 +37,12 @@ - + - + diff --git a/data/monster/monsters/knight_familiar.xml b/data/monster/monsters/knight_familiar.xml new file mode 100644 index 0000000000..205679c82e --- /dev/null +++ b/data/monster/monsters/knight_familiar.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/lost_basher.xml b/data/monster/monsters/lost_basher.xml index 2b7342533b..c27a1d7007 100644 --- a/data/monster/monsters/lost_basher.xml +++ b/data/monster/monsters/lost_basher.xml @@ -55,7 +55,7 @@ - + diff --git a/data/monster/monsters/lost_husher.xml b/data/monster/monsters/lost_husher.xml index c3f29d3a4e..af5b5c7620 100644 --- a/data/monster/monsters/lost_husher.xml +++ b/data/monster/monsters/lost_husher.xml @@ -65,7 +65,7 @@ - + diff --git a/data/monster/monsters/mazoran.xml b/data/monster/monsters/mazoran.xml index 2f661a17f8..b5a1554c13 100644 --- a/data/monster/monsters/mazoran.xml +++ b/data/monster/monsters/mazoran.xml @@ -34,7 +34,7 @@ - + diff --git a/data/monster/monsters/metal_gargoyle.xml b/data/monster/monsters/metal_gargoyle.xml new file mode 100644 index 0000000000..295e4f8973 --- /dev/null +++ b/data/monster/monsters/metal_gargoyle.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/midnight_asura.xml b/data/monster/monsters/midnight_asura.xml new file mode 100644 index 0000000000..6a2d2c31e1 --- /dev/null +++ b/data/monster/monsters/midnight_asura.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/minotaur_amazon.xml b/data/monster/monsters/minotaur_amazon.xml new file mode 100644 index 0000000000..9c95c536ea --- /dev/null +++ b/data/monster/monsters/minotaur_amazon.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/minotaur_hunter.xml b/data/monster/monsters/minotaur_hunter.xml new file mode 100644 index 0000000000..30ee9cf3ef --- /dev/null +++ b/data/monster/monsters/minotaur_hunter.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/mooh'tah_warrior.xml b/data/monster/monsters/mooh'tah_warrior.xml new file mode 100644 index 0000000000..899d11d6c9 --- /dev/null +++ b/data/monster/monsters/mooh'tah_warrior.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/moohtant.xml b/data/monster/monsters/moohtant.xml new file mode 100644 index 0000000000..e8954cc585 --- /dev/null +++ b/data/monster/monsters/moohtant.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/nomad_blue.xml b/data/monster/monsters/nomad_blue.xml new file mode 100644 index 0000000000..f959cb9dd6 --- /dev/null +++ b/data/monster/monsters/nomad_blue.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/nomad_female.xml b/data/monster/monsters/nomad_female.xml new file mode 100644 index 0000000000..6bb4e0bf52 --- /dev/null +++ b/data/monster/monsters/nomad_female.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/ogre_brute.xml b/data/monster/monsters/ogre_brute.xml new file mode 100644 index 0000000000..a9d270f313 --- /dev/null +++ b/data/monster/monsters/ogre_brute.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/ogre_savage.xml b/data/monster/monsters/ogre_savage.xml new file mode 100644 index 0000000000..2bc0dfbe4a --- /dev/null +++ b/data/monster/monsters/ogre_savage.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/ogre_shaman.xml b/data/monster/monsters/ogre_shaman.xml new file mode 100644 index 0000000000..e295e2f0d2 --- /dev/null +++ b/data/monster/monsters/ogre_shaman.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/orc_berserker.xml b/data/monster/monsters/orc_berserker.xml index f9442a605e..0229c911a8 100644 --- a/data/monster/monsters/orc_berserker.xml +++ b/data/monster/monsters/orc_berserker.xml @@ -43,7 +43,7 @@ - + diff --git a/data/monster/monsters/paladin_familiar.xml b/data/monster/monsters/paladin_familiar.xml new file mode 100644 index 0000000000..864b220969 --- /dev/null +++ b/data/monster/monsters/paladin_familiar.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/plaguesmith.xml b/data/monster/monsters/plaguesmith.xml index 199f1b152e..d1d2ff719d 100644 --- a/data/monster/monsters/plaguesmith.xml +++ b/data/monster/monsters/plaguesmith.xml @@ -76,7 +76,7 @@ - + diff --git a/data/monster/monsters/renegade_knight.xml b/data/monster/monsters/renegade_knight.xml new file mode 100644 index 0000000000..77dda499e7 --- /dev/null +++ b/data/monster/monsters/renegade_knight.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/rift_lord.xml b/data/monster/monsters/rift_lord.xml new file mode 100644 index 0000000000..b44a6b50cc --- /dev/null +++ b/data/monster/monsters/rift_lord.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/rift_phantom.xml b/data/monster/monsters/rift_phantom.xml new file mode 100644 index 0000000000..076da1993b --- /dev/null +++ b/data/monster/monsters/rift_phantom.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/rot_elemental.xml b/data/monster/monsters/rot_elemental.xml new file mode 100644 index 0000000000..e3cd6e646b --- /dev/null +++ b/data/monster/monsters/rot_elemental.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/rukor_zad.xml b/data/monster/monsters/rukor_zad.xml new file mode 100644 index 0000000000..e04604648a --- /dev/null +++ b/data/monster/monsters/rukor_zad.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/rustheap_golem.xml b/data/monster/monsters/rustheap_golem.xml new file mode 100644 index 0000000000..f71f7a1e21 --- /dev/null +++ b/data/monster/monsters/rustheap_golem.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/sir_valorcrest.xml b/data/monster/monsters/sir_valorcrest.xml index c3811b8f3d..975db0caf4 100644 --- a/data/monster/monsters/sir_valorcrest.xml +++ b/data/monster/monsters/sir_valorcrest.xml @@ -52,7 +52,7 @@ - + diff --git a/data/monster/monsters/smuggler_baron_silvertoe.xml b/data/monster/monsters/smuggler_baron_silvertoe.xml new file mode 100644 index 0000000000..d9c1052a74 --- /dev/null +++ b/data/monster/monsters/smuggler_baron_silvertoe.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/sorcerer_familiar.xml b/data/monster/monsters/sorcerer_familiar.xml new file mode 100644 index 0000000000..ee9445d037 --- /dev/null +++ b/data/monster/monsters/sorcerer_familiar.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/teleskor.xml b/data/monster/monsters/teleskor.xml new file mode 100644 index 0000000000..49cc18c226 --- /dev/null +++ b/data/monster/monsters/teleskor.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/the_big_bad_one.xml b/data/monster/monsters/the_big_bad_one.xml new file mode 100644 index 0000000000..47f7770df8 --- /dev/null +++ b/data/monster/monsters/the_big_bad_one.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/the_old_whopper.xml b/data/monster/monsters/the_old_whopper.xml new file mode 100644 index 0000000000..947f544382 --- /dev/null +++ b/data/monster/monsters/the_old_whopper.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/the_pale_count.xml b/data/monster/monsters/the_pale_count.xml index ab524b6980..6bad84b74a 100644 --- a/data/monster/monsters/the_pale_count.xml +++ b/data/monster/monsters/the_pale_count.xml @@ -1,7 +1,7 @@ - + diff --git a/data/monster/monsters/the_snapper.xml b/data/monster/monsters/the_snapper.xml index 3b66506078..094214e4b8 100644 --- a/data/monster/monsters/the_snapper.xml +++ b/data/monster/monsters/the_snapper.xml @@ -35,7 +35,7 @@ - + diff --git a/data/monster/monsters/the_weakened_count.xml b/data/monster/monsters/the_weakened_count.xml new file mode 100644 index 0000000000..8b7603df18 --- /dev/null +++ b/data/monster/monsters/the_weakened_count.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/vampire.xml b/data/monster/monsters/vampire.xml index e850c85fec..b4d3278195 100644 --- a/data/monster/monsters/vampire.xml +++ b/data/monster/monsters/vampire.xml @@ -60,7 +60,7 @@ - + diff --git a/data/monster/monsters/vicious_squire.xml b/data/monster/monsters/vicious_squire.xml new file mode 100644 index 0000000000..1dd1ad9b2e --- /dev/null +++ b/data/monster/monsters/vicious_squire.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/vile_grandmaster.xml b/data/monster/monsters/vile_grandmaster.xml new file mode 100644 index 0000000000..bc0a0eab94 --- /dev/null +++ b/data/monster/monsters/vile_grandmaster.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/worm_priestess.xml b/data/monster/monsters/worm_priestess.xml new file mode 100644 index 0000000000..92c7649cda --- /dev/null +++ b/data/monster/monsters/worm_priestess.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/zarabustor.xml b/data/monster/monsters/zarabustor.xml new file mode 100644 index 0000000000..99098ce048 --- /dev/null +++ b/data/monster/monsters/zarabustor.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/monster/monsters/zevelon_duskbringer.xml b/data/monster/monsters/zevelon_duskbringer.xml index 04d8d4f6b4..af12bce991 100644 --- a/data/monster/monsters/zevelon_duskbringer.xml +++ b/data/monster/monsters/zevelon_duskbringer.xml @@ -56,7 +56,7 @@ - + diff --git a/data/monster/monsters/zombie.xml b/data/monster/monsters/zombie.xml index e1312c00ca..70712e13d4 100644 --- a/data/monster/monsters/zombie.xml +++ b/data/monster/monsters/zombie.xml @@ -49,7 +49,7 @@ - + diff --git a/data/movements/lib/movements.lua b/data/movements/lib/movements.lua index b081a0ffed..585eb19d62 100644 --- a/data/movements/lib/movements.lua +++ b/data/movements/lib/movements.lua @@ -1,2 +1 @@ -- Nothing -- - diff --git a/data/movements/movements.xml b/data/movements/movements.xml index ec17aca74d..3f819fb12d 100644 --- a/data/movements/movements.xml +++ b/data/movements/movements.xml @@ -3,6 +3,7 @@ + @@ -1269,6 +1270,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/movements/scripts/decay.lua b/data/movements/scripts/decay.lua index 9a48822b2f..c0a3b3a237 100644 --- a/data/movements/scripts/decay.lua +++ b/data/movements/scripts/decay.lua @@ -1,4 +1,8 @@ function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() or creature:isInGhostMode() then + return true + end + item:transform(item.itemid + 1) item:decay() return true diff --git a/data/movements/scripts/drowning.lua b/data/movements/scripts/drowning.lua index df48269139..18445d3f3c 100644 --- a/data/movements/scripts/drowning.lua +++ b/data/movements/scripts/drowning.lua @@ -12,7 +12,7 @@ function onStepIn(creature, item, position, fromPosition) end function onStepOut(creature, item, position, fromPosition) - if not creature:isPlayer() then + if creature:isPlayer() then creature:removeCondition(CONDITION_DROWN) end return true diff --git a/data/movements/scripts/level_door.lua b/data/movements/scripts/level_door.lua index a6e938bc6b..c8a7f158ce 100644 --- a/data/movements/scripts/level_door.lua +++ b/data/movements/scripts/level_door.lua @@ -3,7 +3,7 @@ function onStepIn(creature, item, position, fromPosition) return false end - if creature:getLevel() < item.actionid - actionIds.levelDoor then + if creature:getLevel() < item.actionid - actionIds.levelDoor and not creature:getGroup():getAccess() then creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Only the worthy may pass.") creature:teleportTo(fromPosition, true) return false diff --git a/data/movements/scripts/quest_door.lua b/data/movements/scripts/quest_door.lua index 455795d587..07ad2d8876 100644 --- a/data/movements/scripts/quest_door.lua +++ b/data/movements/scripts/quest_door.lua @@ -3,7 +3,7 @@ function onStepIn(creature, item, position, fromPosition) return false end - if creature:getStorageValue(item.actionid) == -1 then + if creature:getStorageValue(item.actionid) == -1 and not creature:getGroup():getAccess() then creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The door seems to be sealed against unwanted intruders.") creature:teleportTo(fromPosition, true) return false diff --git a/data/npc/Captain.xml b/data/npc/Captain.xml new file mode 100644 index 0000000000..1a2f6972ef --- /dev/null +++ b/data/npc/Captain.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/data/npc/lib/npc.lua b/data/npc/lib/npc.lua index a0974c352d..0ca51cfe6f 100644 --- a/data/npc/lib/npc.lua +++ b/data/npc/lib/npc.lua @@ -102,35 +102,11 @@ end function getCount(string) local b, e = string:find("%d+") - return b and e and tonumber(string:sub(b, e)) or -1 -end - -function Player.removeTotalMoney(self, amount) - local moneyCount = self:getMoney() - local bankCount = self:getBankBalance() - - if amount <= moneyCount then - self:removeMoney(amount) - return true - - elseif amount <= (moneyCount + bankCount) then - if moneyCount ~= 0 then - self:removeMoney(moneyCount) - local remains = amount - moneyCount - self:setBankBalance(bankCount - remains) - self:sendTextMessage(MESSAGE_INFO_DESCR, ("Paid %d from inventory and %d gold from bank account. Your account balance is now %d gold."):format(moneyCount, amount - moneyCount, self:getBankBalance())) - return true - else - self:setBankBalance(bankCount - amount) - self:sendTextMessage(MESSAGE_INFO_DESCR, ("Paid %d gold from bank account. Your account balance is now %d gold."):format(amount, self:getBankBalance())) - return true - end + local tonumber = tonumber(string:sub(b, e)) + if tonumber > 2 ^ 32 - 1 then + print("Warning: Casting value to 32bit to prevent crash\n" .. debug.traceback()) end - return false -end - -function Player.getTotalMoney(self) - return self:getMoney() + self:getBankBalance() + return b and e and math.min(2 ^ 32 - 1, tonumber) or -1 end function isValidMoney(money) @@ -139,7 +115,11 @@ end function getMoneyCount(string) local b, e = string:find("%d+") - local money = b and e and tonumber(string:sub(b, e)) or -1 + local tonumber = tonumber(string:sub(b, e)) + if tonumber > 2 ^ 32 - 1 then + print("Warning: Casting value to 32bit to prevent crash\n" .. debug.traceback()) + end + local money = b and e and math.min(2 ^ 32 - 1, tonumber) or -1 if isValidMoney(money) then return money end @@ -147,10 +127,15 @@ function getMoneyCount(string) end function getMoneyWeight(money) - local gold = money - local crystal = math.floor(gold / 10000) - gold = gold - crystal * 10000 - local platinum = math.floor(gold / 100) - gold = gold - platinum * 100 - return (ItemType(ITEM_CRYSTAL_COIN):getWeight() * crystal) + (ItemType(ITEM_PLATINUM_COIN):getWeight() * platinum) + (ItemType(ITEM_GOLD_COIN):getWeight() * gold) + local weight, currencyItems = 0, Game.getCurrencyItems() + for index = #currencyItems, 1, -1 do + local currency = currencyItems[index] + local worth = currency:getWorth() + local currencyCoins = math.floor(money / worth) + if currencyCoins > 0 then + money = money - (currencyCoins * worth) + weight = weight + currency:getWeight(currencyCoins) + end + end + return weight end diff --git a/data/npc/lib/npcsystem/modules.lua b/data/npc/lib/npcsystem/modules.lua index bc29a721bb..726e268bbc 100644 --- a/data/npc/lib/npcsystem/modules.lua +++ b/data/npc/lib/npcsystem/modules.lua @@ -1,6 +1,6 @@ -- Advanced NPC System by Jiddo -if Modules == nil then +if not Modules then -- default words for greeting and ungreeting the npc. Should be a table containing all such words. FOCUS_GREETWORDS = {"hi", "hello"} FOCUS_FAREWELLWORDS = {"bye", "farewell"} @@ -40,10 +40,10 @@ if Modules == nil then -- keywordHandler:addKeyword({"offer"}, StdModule.say, {npcHandler = npcHandler, text = "I sell many powerful melee weapons."}) function StdModule.say(cid, message, keywords, parameters, node) local npcHandler = parameters.npcHandler - if npcHandler == nil then + if not npcHandler then error("StdModule.say called without any npcHandler instance.") end - local onlyFocus = (parameters.onlyFocus == nil or parameters.onlyFocus == true) + local onlyFocus = not parameters.onlyFocus or parameters.onlyFocus if not npcHandler:isFocused(cid) and onlyFocus then return false end @@ -65,7 +65,7 @@ if Modules == nil then -- node1:addChildKeyword({"no"}, StdModule.say, {npcHandler = npcHandler, text = "Allright then. Come back when you are ready."}, reset = true) function StdModule.promotePlayer(cid, message, keywords, parameters, node) local npcHandler = parameters.npcHandler - if npcHandler == nil then + if not npcHandler then error("StdModule.promotePlayer called without any npcHandler instance.") end @@ -96,7 +96,7 @@ if Modules == nil then function StdModule.learnSpell(cid, message, keywords, parameters, node) local npcHandler = parameters.npcHandler - if npcHandler == nil then + if not npcHandler then error("StdModule.learnSpell called without any npcHandler instance.") end @@ -125,7 +125,7 @@ if Modules == nil then function StdModule.bless(cid, message, keywords, parameters, node) local npcHandler = parameters.npcHandler - if npcHandler == nil then + if not npcHandler then error("StdModule.bless called without any npcHandler instance.") end @@ -152,7 +152,7 @@ if Modules == nil then function StdModule.travel(cid, message, keywords, parameters, node) local npcHandler = parameters.npcHandler - if npcHandler == nil then + if not npcHandler then error("StdModule.travel called without any npcHandler instance.") end @@ -229,9 +229,8 @@ if Modules == nil then if parameters.module.npcHandler:isFocused(cid) then parameters.module.npcHandler:onFarewell(cid) return true - else - return false end + return false end -- Custom message matching callback function for greeting messages. @@ -371,7 +370,7 @@ if Modules == nil then end if name and x and y and z and cost then - self:addDestination(name, {x=x, y=y, z=z}, cost, premium) + self:addDestination(name, {x = x, y = y, z = z}, cost, premium) else print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Parameter(s) missing for travel destination:", name, x, y, z, cost, premium) end @@ -397,7 +396,7 @@ if Modules == nil then node:addChildKeywordNode(self.yesNode) node:addChildKeywordNode(self.noNode) - if npcs_loaded_travel[getNpcCid()] == nil then + if not npcs_loaded_travel[getNpcCid()] then npcs_loaded_travel[getNpcCid()] = getNpcCid() self.npcHandler.keywordHandler:addKeyword({'yes'}, TravelModule.onConfirm, {module = self}) self.npcHandler.keywordHandler:addKeyword({'no'}, TravelModule.onDecline, {module = self}) @@ -598,20 +597,24 @@ if Modules == nil then -- invalid item print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Item id missing (or invalid) for parameter item:", item) else - if alreadyParsedIds[itemid] and not it:getFluidSource() then - print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Found duplicated item:", item) + if alreadyParsedIds[itemid] then + if table.contains(alreadyParsedIds[itemid], subType or -1) then + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Found duplicated item:", item) + else + table.insert(alreadyParsedIds[itemid], subType or -1) + end else - alreadyParsedIds[itemid] = true + alreadyParsedIds[itemid] = {subType or -1} end end - if subType == nil and it:getCharges() ~= 0 then + if not subType and it:getCharges() ~= 0 then subType = it:getCharges() end if SHOPMODULE_MODE == SHOPMODULE_MODE_TRADE then if itemid and cost then - if subType == nil and it:isFluidContainer() then + if not subType and it:isFluidContainer() then print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item) else self:addBuyableItem(nil, itemid, cost, subType, realName) @@ -621,7 +624,7 @@ if Modules == nil then end else if name and itemid and cost then - if subType == nil and it:isFluidContainer() then + if not subType and it:isFluidContainer() then print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item) else local names = {} @@ -669,10 +672,14 @@ if Modules == nil then -- invalid item print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Item id missing (or invalid) for parameter item:", item) else - if alreadyParsedIds[itemid] and not it:getFluidSource() then - print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Found duplicated item:", item) + if alreadyParsedIds[itemid] then + if table.contains(alreadyParsedIds[itemid], subType or -1) then + print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "Found duplicated item:", item) + else + table.insert(alreadyParsedIds[itemid], subType or -1) + end else - alreadyParsedIds[itemid] = true + alreadyParsedIds[itemid] = {subType or -1} end end @@ -726,7 +733,7 @@ if Modules == nil then end if name and container and itemid and cost then - if subType == nil and ItemType(itemid):isFluidContainer() then + if not subType and ItemType(itemid):isFluidContainer() then print("[Warning : " .. Npc():getName() .. "] NpcSystem:", "SubType missing for parameter item:", item) else local names = {} @@ -801,15 +808,20 @@ if Modules == nil then -- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (ItemType(itemId):getName() will be used) function ShopModule:addBuyableItem(names, itemid, cost, itemSubType, realName) if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then - if itemSubType == nil then + if not itemSubType then itemSubType = 1 end - - local shopItem = self:getShopItem(itemid, itemSubType) - if shopItem == nil then - self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or ItemType(itemid):getName()} - else - shopItem.buy = cost + local it = ItemType(itemid) + if it:getId() ~= 0 then + local shopItem = self:getShopItem(itemid, itemSubType) + if not shopItem then + self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = cost, sell = -1, subType = itemSubType, name = realName or it:getName()} + else + if cost < shopItem.sell then + print("[Warning : " .. Npc():getName() .. "] NpcSystem: Buy price lower than sell price: (" .. shopItem.name .. ")") + end + shopItem.buy = cost + end end end @@ -833,7 +845,7 @@ if Modules == nil then end end - if npcs_loaded_shop[getNpcCid()] == nil then + if not npcs_loaded_shop[getNpcCid()] then npcs_loaded_shop[getNpcCid()] = getNpcCid() self.npcHandler.keywordHandler:addKeyword({'yes'}, ShopModule.onConfirm, {module = self}) self.npcHandler.keywordHandler:addKeyword({'no'}, ShopModule.onDecline, {module = self}) @@ -896,15 +908,20 @@ if Modules == nil then -- realName - The real, full name for the item. Will be used as ITEMNAME in MESSAGE_ONBUY and MESSAGE_ONSELL if defined. Default value is nil (ItemType(itemId):getName() will be used) function ShopModule:addSellableItem(names, itemid, cost, realName, itemSubType) if SHOPMODULE_MODE ~= SHOPMODULE_MODE_TALK then - if itemSubType == nil then + if not itemSubType then itemSubType = 0 end - - local shopItem = self:getShopItem(itemid, itemSubType) - if shopItem == nil then - self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or ItemType(itemid):getName()} - else - shopItem.sell = cost + local it = ItemType(itemid) + if it:getId() ~= 0 then + local shopItem = self:getShopItem(itemid, itemSubType) + if not shopItem then + self.npcHandler.shopItems[#self.npcHandler.shopItems + 1] = {id = itemid, buy = -1, sell = cost, subType = itemSubType, name = realName or it:getName()} + else + if shopItem.buy > -1 and cost > shopItem.buy then + print("[Warning : " .. Npc():getName() .. "] NpcSystem: Sell price higher than buy price: (" .. shopItem.name .. ")") + end + shopItem.sell = cost + end end end @@ -938,7 +955,7 @@ if Modules == nil then -- Callback onBuy() function. If you wish, you can change certain Npc to use your onBuy(). function ShopModule:callbackOnBuy(cid, itemid, subType, amount, ignoreCap, inBackpacks) local shopItem = self:getShopItem(itemid, subType) - if shopItem == nil then + if not shopItem then error("[ShopModule.onBuy] shopItem == nil") return false end @@ -1005,7 +1022,7 @@ if Modules == nil then -- Callback onSell() function. If you wish, you can change certain Npc to use your onSell(). function ShopModule:callbackOnSell(cid, itemid, subType, amount, ignoreEquipped, _) local shopItem = self:getShopItem(itemid, subType) - if shopItem == nil then + if not shopItem then error("[ShopModule.onSell] items[itemid] == nil") return false end @@ -1034,13 +1051,13 @@ if Modules == nil then player:addMoney(amount * shopItem.sell) self.npcHandler.talkStart[cid] = os.time() return true - else - local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM) - msg = self.npcHandler:parseMessage(msg, parseInfo) - player:sendCancelMessage(msg) - self.npcHandler.talkStart[cid] = os.time() - return false end + + local msg = self.npcHandler:getMessage(MESSAGE_NEEDITEM) + msg = self.npcHandler:parseMessage(msg, parseInfo) + player:sendCancelMessage(msg) + self.npcHandler.talkStart[cid] = os.time() + return false end -- Callback for requesting a trade window with the NPC. @@ -1059,7 +1076,7 @@ if Modules == nil then itemWindow[#itemWindow + 1] = module.npcHandler.shopItems[i] end - if itemWindow[1] == nil then + if not itemWindow[1] then local parseInfo = {[TAG_PLAYERNAME] = Player(cid):getName()} local msg = module.npcHandler:parseMessage(module.npcHandler:getMessage(MESSAGE_NOSHOP), parseInfo) module.npcHandler:say(msg, cid) @@ -1094,7 +1111,7 @@ if Modules == nil then if shop_eventtype[cid] == SHOPMODULE_SELL_ITEM then local ret = doPlayerSellItem(cid, shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid]) - if ret == true then + if ret then local msg = module.npcHandler:getMessage(MESSAGE_ONSELL) msg = module.npcHandler:parseMessage(msg, parseInfo) module.npcHandler:say(msg, cid) @@ -1146,7 +1163,7 @@ if Modules == nil then end elseif shop_eventtype[cid] == SHOPMODULE_BUY_ITEM_CONTAINER then local ret = doPlayerBuyItemContainer(cid, shop_container[cid], shop_itemid[cid], shop_amount[cid], shop_cost[cid] * shop_amount[cid], shop_subtype[cid]) - if ret == true then + if ret then local msg = module.npcHandler:getMessage(MESSAGE_ONBUY) msg = module.npcHandler:parseMessage(msg, parseInfo) module.npcHandler:say(msg, cid) diff --git a/data/npc/lib/npcsystem/npchandler.lua b/data/npc/lib/npcsystem/npchandler.lua index 9e193e9395..19ee6fc115 100644 --- a/data/npc/lib/npcsystem/npchandler.lua +++ b/data/npc/lib/npcsystem/npchandler.lua @@ -1,6 +1,6 @@ -- Advanced NPC System by Jiddo -if NpcHandler == nil then +if not NpcHandler then -- Constant talkdelay behaviors. TALKDELAY_NONE = 0 -- No talkdelay. Npc will reply immedeatly. TALKDELAY_ONTHINK = 1 -- Talkdelay handled through the onThink callback function. (Default) @@ -144,7 +144,7 @@ if NpcHandler == nil then self.focuses[#self.focuses + 1] = newFocus self.topic[newFocus] = 0 local callback = self:getCallback(CALLBACK_ONADDFOCUS) - if callback == nil or callback(newFocus) then + if not callback or callback(newFocus) then self:processModuleCallback(CALLBACK_ONADDFOCUS, newFocus) end self:updateFocus() @@ -152,7 +152,7 @@ if NpcHandler == nil then -- Function used to verify if npc is focused to certain player function NpcHandler:isFocused(focus) - for k,v in pairs(self.focuses) do + for k, v in pairs(self.focuses) do if v == focus then return true end @@ -196,7 +196,7 @@ if NpcHandler == nil then end local pos = nil - for k,v in pairs(self.focuses) do + for k, v in pairs(self.focuses) do if v == focus then pos = k end @@ -209,7 +209,7 @@ if NpcHandler == nil then self.topic[focus] = nil local callback = self:getCallback(CALLBACK_ONRELEASEFOCUS) - if callback == nil or callback(focus) then + if not callback or callback(focus) then self:processModuleCallback(CALLBACK_ONRELEASEFOCUS, focus) end @@ -319,7 +319,7 @@ if NpcHandler == nil then end local callback = self:getCallback(CALLBACK_FAREWELL) - if callback == nil or callback() then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_FAREWELL) then local msg = self:getMessage(MESSAGE_FAREWELL) local player = Player(cid) @@ -337,7 +337,7 @@ if NpcHandler == nil then function NpcHandler:greet(cid) if cid ~= 0 then local callback = self:getCallback(CALLBACK_GREET) - if callback == nil or callback(cid) then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_GREET, cid) then local msg = self:getMessage(MESSAGE_GREET) local player = Player(cid) @@ -369,7 +369,7 @@ if NpcHandler == nil then end local callback = self:getCallback(CALLBACK_CREATURE_APPEAR) - if callback == nil or callback(cid) then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_CREATURE_APPEAR, cid) then -- end @@ -384,7 +384,7 @@ if NpcHandler == nil then end local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR) - if callback == nil or callback(cid) then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then if self:isFocused(cid) then self:unGreet(cid) @@ -397,7 +397,7 @@ if NpcHandler == nil then function NpcHandler:onCreatureSay(creature, msgtype, msg) local cid = creature:getId() local callback = self:getCallback(CALLBACK_CREATURE_SAY) - if callback == nil or callback(cid, msgtype, msg) then + if not callback or callback(cid, msgtype, msg) then if self:processModuleCallback(CALLBACK_CREATURE_SAY, cid, msgtype, msg) then if not self:isInRange(cid) then return @@ -424,7 +424,7 @@ if NpcHandler == nil then function NpcHandler:onPlayerEndTrade(creature) local cid = creature:getId() local callback = self:getCallback(CALLBACK_PLAYER_ENDTRADE) - if callback == nil or callback(cid) then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_PLAYER_ENDTRADE, cid, msgtype, msg) then if self:isFocused(cid) then local player = Player(cid) @@ -441,7 +441,7 @@ if NpcHandler == nil then function NpcHandler:onPlayerCloseChannel(creature) local cid = creature:getId() local callback = self:getCallback(CALLBACK_PLAYER_CLOSECHANNEL) - if callback == nil or callback(cid) then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_PLAYER_CLOSECHANNEL, cid, msgtype, msg) then if self:isFocused(cid) then self:unGreet(cid) @@ -454,7 +454,7 @@ if NpcHandler == nil then function NpcHandler:onBuy(creature, itemid, subType, amount, ignoreCap, inBackpacks) local cid = creature:getId() local callback = self:getCallback(CALLBACK_ONBUY) - if callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks) then + if not callback or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks) then if self:processModuleCallback(CALLBACK_ONBUY, cid, itemid, subType, amount, ignoreCap, inBackpacks) then -- end @@ -465,7 +465,7 @@ if NpcHandler == nil then function NpcHandler:onSell(creature, itemid, subType, amount, ignoreCap, inBackpacks) local cid = creature:getId() local callback = self:getCallback(CALLBACK_ONSELL) - if callback == nil or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks) then + if not callback or callback(cid, itemid, subType, amount, ignoreCap, inBackpacks) then if self:processModuleCallback(CALLBACK_ONSELL, cid, itemid, subType, amount, ignoreCap, inBackpacks) then -- end @@ -475,7 +475,7 @@ if NpcHandler == nil then -- Handles onTradeRequest events. If you wish to handle this yourself, use the CALLBACK_ONTRADEREQUEST callback. function NpcHandler:onTradeRequest(cid) local callback = self:getCallback(CALLBACK_ONTRADEREQUEST) - if callback == nil or callback(cid) then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_ONTRADEREQUEST, cid) then return true end @@ -486,7 +486,7 @@ if NpcHandler == nil then -- Handles onThink events. If you wish to handle this yourself, please use the CALLBACK_ONTHINK callback. function NpcHandler:onThink() local callback = self:getCallback(CALLBACK_ONTHINK) - if callback == nil or callback() then + if not callback or callback() then if NPCHANDLER_TALKDELAY == TALKDELAY_ONTHINK then for cid, talkDelay in pairs(self.talkDelay) do if talkDelay.time and talkDelay.message and os.time() >= talkDelay.time then @@ -531,7 +531,7 @@ if NpcHandler == nil then function NpcHandler:onWalkAway(cid) if self:isFocused(cid) then local callback = self:getCallback(CALLBACK_CREATURE_DISAPPEAR) - if callback == nil or callback() then + if not callback or callback(cid) then if self:processModuleCallback(CALLBACK_CREATURE_DISAPPEAR, cid) then local msg = self:getMessage(MESSAGE_WALKAWAY) @@ -596,7 +596,7 @@ if NpcHandler == nil then local ret = {} for aux = 1, #msgs do self.eventDelayedSay[pcid][aux] = {} - doCreatureSayWithDelay(getNpcCid(), msgs[aux], TALKTYPE_PRIVATE_NP, ((aux-1) * (interval or 4000)) + 700, self.eventDelayedSay[pcid][aux], pcid) + doCreatureSayWithDelay(getNpcCid(), msgs[aux], TALKTYPE_PRIVATE_NP, ((aux - 1) * (interval or 4000)) + 700, self.eventDelayedSay[pcid][aux], pcid) ret[#ret + 1] = self.eventDelayedSay[pcid][aux] end return(ret) @@ -615,7 +615,7 @@ if NpcHandler == nil then end local shallDelay = not shallDelay and true or shallDelay - if NPCHANDLER_TALKDELAY == TALKDELAY_NONE or shallDelay == false then + if NPCHANDLER_TALKDELAY == TALKDELAY_NONE or not shallDelay then selfSay(message, focus, publicize and true or false) return end @@ -623,7 +623,7 @@ if NpcHandler == nil then stopEvent(self.eventSay[focus]) self.eventSay[focus] = addEvent(function(npcId, message, focusId) local npc = Npc(npcId) - if npc == nil then + if not npc then return end local player = Player(focusId) diff --git a/data/npc/lib/npcsystem/npcsystem.lua b/data/npc/lib/npcsystem/npcsystem.lua index c7d7b611b5..fb868308c1 100644 --- a/data/npc/lib/npcsystem/npcsystem.lua +++ b/data/npc/lib/npcsystem/npcsystem.lua @@ -51,11 +51,10 @@ if not NpcSystem then -- Gets an npcparameter with the specified key. Returns nil if no such parameter is found. function NpcSystem.getParameter(key) local ret = getNpcParameter(tostring(key)) - if (type(ret) == 'number' and ret == 0) then + if type(ret) == 'number' and ret == 0 then return nil - else - return ret end + return ret end -- Parses all known parameters for the npc. Also parses parseable modules. diff --git a/data/npc/scripts/The Oracle.lua b/data/npc/scripts/The Oracle.lua index 6619adda16..a3120ad0b6 100644 --- a/data/npc/scripts/The Oracle.lua +++ b/data/npc/scripts/The Oracle.lua @@ -6,10 +6,10 @@ local vocation = {} local town = {} local destination = {} -function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end -function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end -function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end -function onThink() npcHandler:onThink() end +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end local function greetCallback(cid) local player = Player(cid) diff --git a/data/npc/scripts/bank.lua b/data/npc/scripts/bank.lua index 355c64b1f0..2c82dffc02 100644 --- a/data/npc/scripts/bank.lua +++ b/data/npc/scripts/bank.lua @@ -5,10 +5,10 @@ NpcSystem.parseParameters(npcHandler) local count = {} local transfer = {} -function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end -function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end -function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end -function onThink() npcHandler:onThink() end +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end local function greetCallback(cid) count[cid], transfer[cid] = nil, nil @@ -59,10 +59,10 @@ local function creatureSayCallback(cid, type, msg) elseif player:getBankBalance() >= 100000 then npcHandler:say("You certainly have made a pretty penny. Your account balance is " .. player:getBankBalance() .. " gold.", cid) return true - else - npcHandler:say("Your account balance is " .. player:getBankBalance() .. " gold.", cid) - return true end + + npcHandler:say("Your account balance is " .. player:getBankBalance() .. " gold.", cid) + return true elseif msgcontains(msg, "deposit") then count[cid] = player:getMoney() if count[cid] < 1 then @@ -71,12 +71,11 @@ local function creatureSayCallback(cid, type, msg) return false end if msgcontains(msg, "all") then - count[cid] = player:getMoney() npcHandler:say("Would you really like to deposit " .. count[cid] .. " gold?", cid) npcHandler.topic[cid] = topicList.DEPOSIT_CONSENT return true else - if string.match(msg,"%d+") then + if string.match(msg, "%d+") then count[cid] = getMoneyCount(msg) if count[cid] < 1 then npcHandler:say("You do not have enough gold.", cid) @@ -86,11 +85,11 @@ local function creatureSayCallback(cid, type, msg) npcHandler:say("Would you really like to deposit " .. count[cid] .. " gold?", cid) npcHandler.topic[cid] = topicList.DEPOSIT_CONSENT return true - else - npcHandler:say("Please tell me how much gold it is you would like to deposit.", cid) - npcHandler.topic[cid] = topicList.DEPOSIT_GOLD - return true end + + npcHandler:say("Please tell me how much gold it is you would like to deposit.", cid) + npcHandler.topic[cid] = topicList.DEPOSIT_GOLD + return true end if not isValidMoney(count[cid]) then npcHandler:say("Sorry, but you can't deposit that much.", cid) @@ -103,11 +102,11 @@ local function creatureSayCallback(cid, type, msg) npcHandler:say("Would you really like to deposit " .. count[cid] .. " gold?", cid) npcHandler.topic[cid] = topicList.DEPOSIT_CONSENT return true - else - npcHandler:say("You do not have enough gold.", cid) - npcHandler.topic[cid] = topicList.NONE - return true end + + npcHandler:say("You do not have enough gold.", cid) + npcHandler.topic[cid] = topicList.NONE + return true elseif npcHandler.topic[cid] == topicList.DEPOSIT_CONSENT then if msgcontains(msg, "yes") then if player:getMoney() >= tonumber(count[cid]) then @@ -122,7 +121,7 @@ local function creatureSayCallback(cid, type, msg) npcHandler.topic[cid] = topicList.NONE return true elseif msgcontains(msg, "withdraw") then - if string.match(msg,"%d+") then + if string.match(msg, "%d+") then count[cid] = getMoneyCount(msg) if isValidMoney(count[cid]) then npcHandler:say("Are you sure you wish to withdraw " .. count[cid] .. " gold from your bank account?", cid) @@ -132,11 +131,11 @@ local function creatureSayCallback(cid, type, msg) npcHandler.topic[cid] = topicList.NONE end return true - else - npcHandler:say("Please tell me how much gold you would like to withdraw.", cid) - npcHandler.topic[cid] = topicList.WITHDRAW_CONSENT - return true end + + npcHandler:say("Please tell me how much gold you would like to withdraw.", cid) + npcHandler.topic[cid] = topicList.WITHDRAW_CONSENT + return true elseif npcHandler.topic[cid] == topicList.WITHDRAW_CONSENT then count[cid] = getMoneyCount(msg) if isValidMoney(count[cid]) then @@ -234,6 +233,12 @@ local function creatureSayCallback(cid, type, msg) end end elseif npcHandler.topic[cid] == topicList.TRANSFER_PLAYER_GOLD then + local currencyValue = tonumber(msg) + if not currencyValue or currencyValue < 1 then + npcHandler:say("Please tell me the amount of gold you would like to transfer, make sure to specify a number.", cid) + npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_GOLD + return true + end count[cid] = getMoneyCount(msg) if player:getBankBalance() < count[cid] then npcHandler:say("There is not enough gold in your account.", cid) @@ -249,6 +254,11 @@ local function creatureSayCallback(cid, type, msg) end elseif npcHandler.topic[cid] == topicList.TRANSFER_PLAYER_WHO then transfer[cid] = getPlayerDatabaseInfo(msg) + if not transfer[cid] then + npcHandler:say("Hmm, my ledgers have no records of anyone with the name " .. msg .. ". Please ensure the name is correct.", cid) + npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_WHO + return true + end if player:getName() == transfer[cid].name then npcHandler:say("Fill in this field with person who receives your gold!", cid) npcHandler.topic[cid] = topicList.NONE @@ -272,7 +282,7 @@ local function creatureSayCallback(cid, type, msg) if not player:transferMoneyTo(transfer[cid], count[cid]) then npcHandler:say("You cannot transfer money to this account.", cid) else - npcHandler:say("Very well. You have transfered " .. count[cid] .. " gold to " .. transfer[cid].name ..".", cid) + npcHandler:say("Very well. You have transfered " .. count[cid] .. " gold to " .. transfer[cid].name .. ".", cid) transfer[cid] = nil end elseif msgcontains(msg, "no") then @@ -412,7 +422,7 @@ local function creatureSayCallback(cid, type, msg) return true end -keywordHandler:addKeyword({"money"}, StdModule.say,{ +keywordHandler:addKeyword({"money"}, StdModule.say, { npcHandler = npcHandler, text = "We can {change} money for you. You can also access your {bank account}." }) diff --git a/data/npc/scripts/bless.lua b/data/npc/scripts/bless.lua index eb3fc5ac32..3b38fe8ac8 100644 --- a/data/npc/scripts/bless.lua +++ b/data/npc/scripts/bless.lua @@ -2,10 +2,10 @@ local keywordHandler = KeywordHandler:new() local npcHandler = NpcHandler:new(keywordHandler) NpcSystem.parseParameters(npcHandler) -function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end -function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end -function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end -function onThink() npcHandler:onThink() end +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end local node1 = keywordHandler:addKeyword({'first bless'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you want to buy the first blessing for 10000 gold?'}) node1:addChildKeyword({'yes'}, StdModule.bless, {npcHandler = npcHandler, bless = 1, premium = true, cost = 10000}) diff --git a/data/npc/scripts/default.lua b/data/npc/scripts/default.lua index 36d042db20..2a49f4d782 100644 --- a/data/npc/scripts/default.lua +++ b/data/npc/scripts/default.lua @@ -2,9 +2,9 @@ local keywordHandler = KeywordHandler:new() local npcHandler = NpcHandler:new(keywordHandler) NpcSystem.parseParameters(npcHandler) -function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end -function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end -function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end -function onThink() npcHandler:onThink() end +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end npcHandler:addModule(FocusModule:new()) diff --git a/data/npc/scripts/promotion.lua b/data/npc/scripts/promotion.lua index e565d57030..225721657e 100644 --- a/data/npc/scripts/promotion.lua +++ b/data/npc/scripts/promotion.lua @@ -2,13 +2,125 @@ local keywordHandler = KeywordHandler:new() local npcHandler = NpcHandler:new(keywordHandler) NpcSystem.parseParameters(npcHandler) -function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end -function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end -function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end -function onThink() npcHandler:onThink() end +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end local node1 = keywordHandler:addKeyword({'promot'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'I can promote you for 20000 gold coins. Do you want me to promote you?'}) node1:addChildKeyword({'yes'}, StdModule.promotePlayer, {npcHandler = npcHandler, cost = 20000, level = 20, text = 'Congratulations! You are now promoted.'}) node1:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Alright then, come back when you are ready.', reset = true}) +local function creatureSayCallback(cid, type, msg) + if not npcHandler:isFocused(cid) then + return false + end + + local player = Player(cid) + if msgcontains(msg, "outfit") or msgcontains(msg, "addon") then + npcHandler:say("In exchange for a truly generous donation, I will offer a special outfit. Do you want to make a donation?", cid) + npcHandler.topic[cid] = 1 + elseif msgcontains(msg, "yes") then + if npcHandler.topic[cid] == 1 then + npcHandler:say({ + "Excellent! Now, let me explain. If you donate 1.000.000.000 gold pieces, you will be entitled to wear a unique outfit. ...", + "You will be entitled to wear the {armor} for 500.000.000 gold pieces, {helmet} for an additional 250.000.000 and the {boots} for another 250.000.000 gold pieces. ...", + "What will it be?" + }, cid) + npcHandler.topic[cid] = 2 + elseif npcHandler.topic[cid] == 2 then + npcHandler:say("In that case, return to me once you made up your mind.", cid) + npcHandler.topic[cid] = 0 + elseif npcHandler.topic[cid] == 3 then + if player:getStorageValue(PlayerStorageKeys.goldenOutfit) < 1 then + if player:getTotalMoney() >= 500000000 then + local storeInbox = player:getStoreInbox() + if storeInbox then + local item = Game.createItem(ITEM_DECORATION_KIT, 1) + if item then + item:setStoreItem(true) + item:setAttribute("wrapid", 34156) + item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "Unwrap it in your own house to create a " .. ItemType(34156):getName() .. ".") + storeInbox:addItemEx(item) + end + + npcHandler:say("Take this armor as a token of great gratitude. Let us forever remember this day, my friend!", cid) + player:removeTotalMoney(500000000) + player:addOutfit(1211) + player:addOutfit(1210) + player:getPosition():sendMagicEffect(CONST_ME_EARLY_THUNDER) + player:setStorageValue(PlayerStorageKeys.goldenOutfit, 1) + npcHandler.topic[cid] = 2 + end + else + npcHandler:say("You do not have enough money to donate that amount.", cid) + npcHandler.topic[cid] = 2 + end + else + npcHandler:say("You already own this outfit.", cid) + npcHandler.topic[cid] = 0 + end + npcHandler.topic[cid] = 2 + elseif npcHandler.topic[cid] == 4 then + if player:getStorageValue(PlayerStorageKeys.goldenOutfit) == 1 then + if player:getStorageValue(PlayerStorageKeys.goldenOutfit) < 2 then + if player:getTotalMoney() >= 250000000 then + npcHandler:say("Take this helmet as a token of great gratitude. Let us forever remember this day, my friend.", cid) + player:removeTotalMoney(250000000) + player:addOutfitAddon(1210, 2) + player:addOutfitAddon(1211, 2) + player:getPosition():sendMagicEffect(CONST_ME_EARLY_THUNDER) + player:setStorageValue(PlayerStorageKeys.goldenOutfit, 2) + npcHandler.topic[cid] = 2 + else + npcHandler:say("You do not have enough money to donate that amount.", cid) + npcHandler.topic[cid] = 2 + end + else + npcHandler:say("You already own this addon.", cid) + npcHandler.topic[cid] = 2 + end + else + npcHandler:say("You need to donate for the {armor} outfit first.", cid) + npcHandler.topic[cid] = 2 + end + npcHandler.topic[cid] = 2 + elseif npcHandler.topic[cid] == 5 then + if player:getStorageValue(PlayerStorageKeys.goldenOutfit) == 2 then + if player:getStorageValue(PlayerStorageKeys.goldenOutfit) < 3 then + if player:getTotalMoney() >= 250000000 then + npcHandler:say("Take this boots as a token of great gratitude. Let us forever remember this day, my friend.", cid) + player:removeTotalMoney(250000000) + player:addOutfitAddon(1210, 1) + player:addOutfitAddon(1211, 1) + player:getPosition():sendMagicEffect(CONST_ME_EARLY_THUNDER) + player:setStorageValue(PlayerStorageKeys.goldenOutfit, 3) + npcHandler.topic[cid] = 2 + else + npcHandler:say("You do not have enough money to donate that amount.", cid) + npcHandler.topic[cid] = 2 + end + else + npcHandler:say("You already own this addon.", cid) + npcHandler.topic[cid] = 2 + end + else + npcHandler:say("You need to donate for the {helmet} addon first.", cid) + npcHandler.topic[cid] = 2 + end + npcHandler.topic[cid] = 2 + end + elseif msgcontains(msg, "armor") and npcHandler.topic[cid] == 2 then + npcHandler:say("So you would like to donate 500.000.000 gold pieces which in return will entitle you to wear a unique armor?", cid) + npcHandler.topic[cid] = 3 + elseif msgcontains(msg, "helmet") and npcHandler.topic[cid] == 2 then + npcHandler:say("So you would like to donate 250.000.000 gold pieces which in return will entitle you to wear a unique helmet?", cid) + npcHandler.topic[cid] = 4 + elseif msgcontains(msg, "boots") and npcHandler.topic[cid] == 2 then + npcHandler:say("So you would like to donate 250.000.000 gold pieces which in return will entitle you to wear a unique boots?", cid) + npcHandler.topic[cid] = 5 + end +end + +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new()) diff --git a/data/npc/scripts/runes.lua b/data/npc/scripts/runes.lua index 31fcbc60c8..f4ecf5f9f6 100644 --- a/data/npc/scripts/runes.lua +++ b/data/npc/scripts/runes.lua @@ -2,10 +2,10 @@ local keywordHandler = KeywordHandler:new() local npcHandler = NpcHandler:new(keywordHandler) NpcSystem.parseParameters(npcHandler) -function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end -function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end -function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end -function onThink() npcHandler:onThink() end +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end local voices = { {text = "Runes, wands, rods, health and mana potions! Have a look!"} } npcHandler:addModule(VoiceModule:new(voices)) @@ -18,7 +18,7 @@ keywordHandler:addAliasKeyword({'wares'}) keywordHandler:addAliasKeyword({'offer'}) shopModule:addBuyableItem({'spellbook'}, 2175, 150, 'spellbook') -shopModule:addBuyableItem({'magic lightwand'}, 2163, 400, 'magic lightwand') +shopModule:addBuyableItem({'magic lightwand'}, 2162, 400, 'magic lightwand') shopModule:addBuyableItem({'small health'}, 8704, 20, 1, 'small health potion') shopModule:addBuyableItem({'health potion'}, 7618, 45, 1, 'health potion') @@ -31,7 +31,7 @@ shopModule:addBuyableItem({'great spirit'}, 8472, 190, 1, 'great spirit potion') shopModule:addBuyableItem({'ultimate health'}, 8473, 310, 1, 'ultimate health potion') shopModule:addBuyableItem({'antidote potion'}, 8474, 50, 1, 'antidote potion') -shopModule:addSellableItem({'normal potion flask', 'normal flask'}, 7636, 5, 'empty small potion flask') +shopModule:addSellableItem({'empty potion flask', 'empty flask'}, 7636, 5, 'empty small potion flask') shopModule:addSellableItem({'strong potion flask', 'strong flask'}, 7634, 10, 'empty strong potion flask') shopModule:addSellableItem({'great potion flask', 'great flask'}, 7635, 15, 'empty great potion flask') @@ -50,16 +50,16 @@ shopModule:addBuyableItem({'convince creature'}, 2290, 80, 1, 'convince creature shopModule:addBuyableItem({'chameleon'}, 2291, 210, 1, 'chameleon rune') shopModule:addBuyableItem({'disintegrate'}, 2310, 80, 3, 'disintegrate rune') -shopModule:addBuyableItemContainer({'bp ap'}, 2002, 8378, 2000, 1, 'backpack of antidote potions') -shopModule:addBuyableItemContainer({'bp slhp'}, 2000, 8610, 400, 1, 'backpack of small health potions') +shopModule:addBuyableItemContainer({'bp ap'}, 2002, 8474, 2000, 1, 'backpack of antidote potions') +shopModule:addBuyableItemContainer({'bp slhp'}, 2000, 8704, 400, 1, 'backpack of small health potions') shopModule:addBuyableItemContainer({'bp hp'}, 2000, 7618, 900, 1, 'backpack of health potions') shopModule:addBuyableItemContainer({'bp mp'}, 2001, 7620, 1000, 1, 'backpack of mana potions') shopModule:addBuyableItemContainer({'bp shp'}, 2000, 7588, 2000, 1, 'backpack of strong health potions') shopModule:addBuyableItemContainer({'bp smp'}, 2001, 7589, 1600, 1, 'backpack of strong mana potions') shopModule:addBuyableItemContainer({'bp ghp'}, 2000, 7591, 3800, 1, 'backpack of great health potions') shopModule:addBuyableItemContainer({'bp gmp'}, 2001, 7590, 2400, 1, 'backpack of great mana potions') -shopModule:addBuyableItemContainer({'bp gsp'}, 1999, 8376, 3800, 1, 'backpack of great spirit potions') -shopModule:addBuyableItemContainer({'bp uhp'}, 2000, 8377, 6200, 1, 'backpack of ultimate health potions') +shopModule:addBuyableItemContainer({'bp gsp'}, 1999, 8472, 3800, 1, 'backpack of great spirit potions') +shopModule:addBuyableItemContainer({'bp uhp'}, 2000, 8473, 6200, 1, 'backpack of ultimate health potions') shopModule:addBuyableItem({'wand of vortex', 'vortex'}, 2190, 500, 'wand of vortex') shopModule:addBuyableItem({'wand of dragonbreath', 'dragonbreath'}, 2191, 1000, 'wand of dragonbreath') @@ -84,11 +84,11 @@ shopModule:addSellableItem({'wand of dragonbreath', 'dragonbreath'}, 2191, 500, shopModule:addSellableItem({'wand of decay', 'decay'}, 2188, 2500, 'wand of decay') shopModule:addSellableItem({'wand of draconia', 'draconia'}, 8921, 3750, 'wand of draconia') shopModule:addSellableItem({'wand of cosmic energy', 'cosmic energy'}, 2189, 5000, 'wand of cosmic energy') -shopModule:addSellableItem({'wand of inferno', 'inferno'},2187, 7500, 'wand of inferno') +shopModule:addSellableItem({'wand of inferno', 'inferno'}, 2187, 7500, 'wand of inferno') shopModule:addSellableItem({'wand of starstorm', 'starstorm'}, 8920, 9000, 'wand of starstorm') shopModule:addSellableItem({'wand of voodoo', 'voodoo'}, 8922, 11000, 'wand of voodoo') -shopModule:addSellableItem({'snakebite rod', 'snakebite'}, 2182, 250,'snakebite rod') +shopModule:addSellableItem({'snakebite rod', 'snakebite'}, 2182, 250, 'snakebite rod') shopModule:addSellableItem({'moonlight rod', 'moonlight'}, 2186, 500, 'moonlight rod') shopModule:addSellableItem({'necrotic rod', 'necrotic'}, 2185, 2500, 'necrotic rod') shopModule:addSellableItem({'northwind rod', 'northwind'}, 8911, 3750, 'northwind rod') diff --git a/data/npc/scripts/ship.lua b/data/npc/scripts/ship.lua new file mode 100644 index 0000000000..f538d2337b --- /dev/null +++ b/data/npc/scripts/ship.lua @@ -0,0 +1,30 @@ +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) +NpcSystem.parseParameters(npcHandler) + +function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end +function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end +function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end +function onThink() npcHandler:onThink() end + +keywordHandler:addKeyword({'captain'}, StdModule.say, {npcHandler = npcHandler, text = 'I am the captain of this sailing-ship.'}) +keywordHandler:addKeyword({'trip'}, StdModule.say, {npcHandler = npcHandler, text = 'Where do you want to go? To Trekolt, Rhyves, Varak or Saund?'}) +keywordHandler:addKeyword({'ice'}, StdModule.say, {npcHandler = npcHandler, text = 'I\'m sorry, but we don\'t serve the routes to the Ice Islands.'}) + +local node1 = keywordHandler:addKeyword({'trekolt'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you seek a passage to Trekolt for 100 gold?'}) + node1:addChildKeyword({'yes'}, StdModule.travel, {npcHandler = npcHandler, premium = true, cost = 100, destination = math.random(10) == 1 and Position(489, 384, 4) or Position(95, 117, 7)}) + node1:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'We would like to serve you some time.'}) + +local node2 = keywordHandler:addKeyword({'rhyves'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you seek a passage to Rhyves for 120 gold?'}) + node2:addChildKeyword({'yes'}, StdModule.travel, {npcHandler = npcHandler, premium = true, cost = 120, destination = Position(139, 337, 6)}) + node2:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'We would like to serve you some time.'}) + +local node3 = keywordHandler:addKeyword({'varak'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you seek a passage to Varak for 150 gold?'}) + node3:addChildKeyword({'yes'}, StdModule.travel, {npcHandler = npcHandler, premium = true, cost = 160, destination = Position(271, 516, 11)}) + node3:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'We would like to serve you some time.'}) + +local node4 = keywordHandler:addKeyword({'saund'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, text = 'Do you seek a passage to Saund for 150 gold?'}) + node4:addChildKeyword({'yes'}, StdModule.travel, {npcHandler = npcHandler, premium = true, cost = 150, destination = Position(258, 602, 7)}) + node4:addChildKeyword({'no'}, StdModule.say, {npcHandler = npcHandler, onlyFocus = true, reset = true, text = 'We would like to serve you some time.'}) + +npcHandler:addModule(FocusModule:new()) diff --git a/data/scripts/actions/others/blessing_charms.lua b/data/scripts/actions/others/blessing_charms.lua index 650283a629..f5e73debf5 100644 --- a/data/scripts/actions/others/blessing_charms.lua +++ b/data/scripts/actions/others/blessing_charms.lua @@ -1,6 +1,6 @@ local items = { [11260] = {text = "The Spiritual Shielding protects you.", id = 1, effect = CONST_ME_LOSEENERGY}, - [11259] = {text = "The Embrace of Tibia surrounds you.", id = 2, effect = CONST_ME_MAGIC_BLUE}, + [11259] = {text = "The Embrace of the World surrounds you.", id = 2, effect = CONST_ME_MAGIC_BLUE}, [11261] = {text = "The Fire of the Suns engulfs you.", id = 3, effect = CONST_ME_MAGIC_RED}, [11262] = {text = "The Wisdom of Solitude inspires you.", id = 4, effect = CONST_ME_MAGIC_GREEN}, [11258] = {text = "The Spark of the Phoenix emblazes you.", id = 5, effect = CONST_ME_FIREATTACK} @@ -18,7 +18,8 @@ function blessingCharms.onUse(player, item, fromPosition, target, toPosition, is player:addBlessing(blessItem.id) player:say(blessItem.text, TALKTYPE_MONSTER_SAY) player:getPosition():sendMagicEffect(blessItem.effect) - item:remove() + player:sendSupplyUsed(item) + item:remove(1) end return true end diff --git a/data/scripts/actions/others/carpets.lua b/data/scripts/actions/others/carpets.lua index d5c5ac3a5b..2e2d68cf9e 100644 --- a/data/scripts/actions/others/carpets.lua +++ b/data/scripts/actions/others/carpets.lua @@ -19,27 +19,33 @@ function carpets.onUse(player, item, fromPosition, target, toPosition, isHotkey) if not carpet then return false end + if fromPosition.x == CONTAINER_POSITION then player:sendTextMessage(MESSAGE_STATUS_SMALL, "Put the item on the floor first.") return true end + local tile = Tile(item:getPosition()) if not tile:getHouse() then player:sendTextMessage(MESSAGE_STATUS_SMALL, "You may use this only inside a house.") return true end + if tile:getItemByType(ITEM_TYPE_DOOR) then player:sendCancelMessage("You cannot use this item on house doors.") return true end + local carpetStack = 0 for _, carpetId in pairs(transformID) do carpetStack = carpetStack + tile:getItemCountById(carpetId) end - if carpetStack > 1 then + + if carpetStack > 3 then player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) return true end + item:transform(carpet) return true end diff --git a/data/scripts/actions/others/change_gold.lua b/data/scripts/actions/others/change_gold.lua new file mode 100644 index 0000000000..1faee0703b --- /dev/null +++ b/data/scripts/actions/others/change_gold.lua @@ -0,0 +1,27 @@ +local config = {} + +local changeGold = Action() + +function changeGold.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local coin = config[item:getId()] + if coin.changeTo and item.type == 100 then + item:remove() + player:addItem(coin.changeTo, 1) + elseif coin.changeBack then + item:remove(1) + player:addItem(coin.changeBack, 100) + else + return false + end + return true +end + +local currencyItems = Game.getCurrencyItems() +for index, currency in pairs(currencyItems) do + local back, to = currencyItems[index - 1], currencyItems[index + 1] + local currencyId = currency:getId() + config[currencyId] = { changeBack = back and back:getId(), changeTo = to and to:getId() } + changeGold:id(currencyId) +end + +changeGold:register() diff --git a/data/scripts/actions/others/christmas_bundle.lua b/data/scripts/actions/others/christmas_bundle.lua index d858d7cdbe..a862f6ec6d 100644 --- a/data/scripts/actions/others/christmas_bundle.lua +++ b/data/scripts/actions/others/christmas_bundle.lua @@ -66,5 +66,5 @@ function christmasBundle.onUse(player, item, fromPosition, target, toPosition, i return true end -christmasBundle:id(6507,6508,6509) +christmasBundle:id(6507, 6508, 6509) christmasBundle:register() diff --git a/data/scripts/actions/others/clay_lump.lua b/data/scripts/actions/others/clay_lump.lua index 29110f3650..d4f293c563 100644 --- a/data/scripts/actions/others/clay_lump.lua +++ b/data/scripts/actions/others/clay_lump.lua @@ -8,9 +8,9 @@ local config = { local clayLump = Action() function clayLump.onUse(player, item, fromPosition, target, toPosition, isHotkey) - local random, tmpItem = math.random(0, 10000) * 0.01 + local random = math.random(0, 10000) * 0.01 for i = 1, #config do - tmpItem = config[i] + local tmpItem = config[i] if random >= tmpItem.chance[1] and random < tmpItem.chance[2] then item:getPosition():sendMagicEffect(CONST_ME_POFF) diff --git a/data/scripts/actions/others/costume_bag.lua b/data/scripts/actions/others/costume_bag.lua index 6aa86671ce..17175cbb73 100644 --- a/data/scripts/actions/others/costume_bag.lua +++ b/data/scripts/actions/others/costume_bag.lua @@ -1,6 +1,6 @@ local config = { - [7737] = {"orc warrior", "pirate cutthroat", "dworc voodoomaster", "dwarf guard", "minotaur mage"}, -- common - [7739] = {"serpent spawn", "demon", "juggernaut", "behemoth", "ashmunrah"}, -- deluxe + [9075] = {"orc warrior", "pirate cutthroat", "dworc voodoomaster", "dwarf guard", "minotaur mage"}, -- common + [9077] = {"serpent spawn", "demon", "juggernaut", "behemoth", "ashmunrah"}, -- deluxe [9076] = {"quara hydromancer", "diabolic imp", "banshee", "frost giant", "lich"} -- uncommon } @@ -18,7 +18,7 @@ function costumeBag.onUse(player, item, fromPosition, target, toPosition, isHotk return true end -for k,v in pairs(config) do +for k, v in pairs(config) do costumeBag:id(k) end costumeBag:register() diff --git a/data/scripts/actions/others/doors.lua b/data/scripts/actions/others/doors.lua index 23e4194f37..90b569aea2 100644 --- a/data/scripts/actions/others/doors.lua +++ b/data/scripts/actions/others/doors.lua @@ -1,8 +1,8 @@ local positionOffsets = { - Position(1, 0, 0), -- east - Position(0, 1, 0), -- south - Position(-1, 0, 0), -- west - Position(0, -1, 0) -- north + {x = 1, y = 0}, -- east + {x = 0, y = 1}, -- south + {x = -1, y = 0}, -- west + {x = 0, y = -1}, -- north } --[[ @@ -18,7 +18,7 @@ In round 4 it checks if there's a tile blocked by a magic wall or wild growth. local function findPushPosition(creature, round) local pos = creature:getPosition() for _, offset in ipairs(positionOffsets) do - local offsetPosition = pos + offset + local offsetPosition = Position(pos.x + offset.x, pos.y + offset.y, pos.z) local tile = Tile(offsetPosition) if tile then local creatureCount = tile:getCreatureCount() @@ -57,16 +57,16 @@ local door = Action() function door.onUse(player, item, fromPosition, target, toPosition, isHotkey) local itemId = item:getId() - if table.contains(questDoors, itemId) then - if player:getStorageValue(item.actionid) ~= -1 then + if table.contains(closedQuestDoors, itemId) then + if player:getStorageValue(item.actionid) ~= -1 or player:getGroup():getAccess() then item:transform(itemId + 1) player:teleportTo(toPosition, true) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The door seems to be sealed against unwanted intruders.") end return true - elseif table.contains(levelDoors, itemId) then - if item.actionid > 0 and player:getLevel() >= item.actionid - actionIds.levelDoor then + elseif table.contains(closedLevelDoors, itemId) then + if item.actionid > 0 and player:getLevel() >= item.actionid - actionIds.levelDoor or player:getGroup():getAccess() then item:transform(itemId + 1) player:teleportTo(toPosition, true) else @@ -74,18 +74,36 @@ function door.onUse(player, item, fromPosition, target, toPosition, isHotkey) end return true elseif table.contains(keys, itemId) then - if target.actionid > 0 then - if item.actionid == target.actionid and doors[target.itemid] then - target:transform(doors[target.itemid]) - return true - end + local tile = Tile(toPosition) + if not tile then + return false + end + target = tile:getTopVisibleThing() + if target.actionid == 0 then + return false + end + if table.contains(keys, target.itemid) then + return false + end + if not table.contains(openDoors, target.itemid) and not table.contains(closedDoors, target.itemid) and not table.contains(lockedDoors, target.itemid) then + return false + end + if item.actionid ~= target.actionid then player:sendTextMessage(MESSAGE_STATUS_SMALL, "The key does not match.") return true end - return false - end - - if table.contains(horizontalOpenDoors, itemId) or table.contains(verticalOpenDoors, itemId) then + local transformTo = target.itemid + 2 + if table.contains(openDoors, target.itemid) then + transformTo = target.itemid - 2 + elseif table.contains(closedDoors, target.itemid) then + transformTo = target.itemid - 1 + end + target:transform(transformTo) + return true + elseif table.contains(lockedDoors, itemId) then + player:sendTextMessage(MESSAGE_INFO_DESCR, "It is locked.") + return true + elseif table.contains(openDoors, itemId) or table.contains(openExtraDoors, itemId) or table.contains(openHouseDoors, itemId) then local creaturePositionTable = {} local doorCreatures = Tile(toPosition):getCreatures() if doorCreatures and #doorCreatures > 0 then @@ -102,32 +120,19 @@ function door.onUse(player, item, fromPosition, target, toPosition, isHotkey) end end - if not(table.contains(openQuestDoors, itemId)) and not(table.contains(openLevelDoors, itemId)) then - item:transform(itemId - 1) - end + item:transform(itemId - 1) return true - end - - if doors[itemId] then - if item.actionid == 0 then - item:transform(doors[itemId]) - else - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "It is locked.") - end + elseif table.contains(closedDoors, itemId) or table.contains(closedExtraDoors, itemId) or table.contains(closedHouseDoors, itemId) then + item:transform(itemId + 1) return true end return false end -local doorsSet = {} -- unique value set for door ids -for _, d in ipairs(questDoors) do if doorsSet[d] == nil then doorsSet[d] = true end end -for _, d in ipairs(levelDoors) do if doorsSet[d] == nil then doorsSet[d] = true end end -for _, d in ipairs(keys) do if doorsSet[d] == nil then doorsSet[d] = true end end -for _, d in ipairs(horizontalOpenDoors) do if doorsSet[d] == nil then doorsSet[d] = true end end -for _, d in ipairs(verticalOpenDoors) do if doorsSet[d] == nil then doorsSet[d] = true end end -for d, _ in pairs(doors) do if doorsSet[d] == nil then doorsSet[d] = true end end -for i, _ in pairs(doorsSet) do - door:id(i) +local doorTables = {keys, openDoors, closedDoors, lockedDoors, openExtraDoors, closedExtraDoors, openHouseDoors, closedHouseDoors, closedQuestDoors, closedLevelDoors} +for _, doors in pairs(doorTables) do + for _, doorId in pairs(doors) do + door:id(doorId) + end end -doorsSet = nil door:register() diff --git a/data/scripts/actions/others/explosive_present.lua b/data/scripts/actions/others/explosive_present.lua index fbaee2aaa3..c79eac4324 100644 --- a/data/scripts/actions/others/explosive_present.lua +++ b/data/scripts/actions/others/explosive_present.lua @@ -7,5 +7,5 @@ function explosivePresent.onUse(player, item, fromPosition, target, toPosition, return true end -explosivePresent:id(8110) +explosivePresent:id(9074) explosivePresent:register() diff --git a/data/scripts/actions/others/flower_pot.lua b/data/scripts/actions/others/flower_pot.lua index 454913d08e..b8ab4eacee 100644 --- a/data/scripts/actions/others/flower_pot.lua +++ b/data/scripts/actions/others/flower_pot.lua @@ -29,16 +29,16 @@ local flowers = { {itemid = 7677, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 7694}, {itemid = 9984, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 9990}, {itemid = 9985, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 9988}, - {itemid = 7679, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7673, 7670}, chance = 80}, - {itemid = 7681, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7680, 7688}, chance = 80}, - {itemid = 7683, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7682, 7690}, chance = 80}, - {itemid = 7685, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7684, 7692}, chance = 80}, - {itemid = 7687, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7686, 7694}, chance = 80}, - {itemid = 9983, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {9982, 9990}, chance = 80}, - {itemid = 9987, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {9986, 9988}, chance = 80}, - {itemid = 7678, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {7670, 7680, 7682, 7684, 7686, 9982, 9986}, chance = 80}, + {itemid = 7679, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {7673, 7670}, chance = 80}, + {itemid = 7681, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {7680, 7688}, chance = 80}, + {itemid = 7683, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {7682, 7690}, chance = 80}, + {itemid = 7685, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {7684, 7692}, chance = 80}, + {itemid = 7687, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {7686, 7694}, chance = 80}, + {itemid = 9983, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {9982, 9990}, chance = 80}, + {itemid = 9987, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {9986, 9988}, chance = 80}, + {itemid = 7678, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {7670, 7680, 7682, 7684, 7686, 9982, 9986}, chance = 80}, {itemid = 15444, watered = true, advance = false, msg = "You finally remembered to water your plant and it recovered.", after = 15443}, - {itemid = 15442, watered = true, advance = true, msg = {"You watered your plant.","Your plant has grown to the next stage!"}, after = {15443, 15441}, chance = 80}, + {itemid = 15442, watered = true, advance = true, msg = {"You watered your plant.", "Your plant has grown to the next stage!"}, after = {15443, 15441}, chance = 80}, {itemid = 15443, watered = false, advance = false, msg = "Your plant doesn't need water."}, {itemid = 15441, watered = false, advance = false, msg = "This plant can't wither anymore."}, {itemid = 15445, watered = false, advance = false, msg = "This plant can't wither anymore."}, @@ -54,16 +54,16 @@ local flowerPot = Action() function flowerPot.onUse(player, item, fromPosition, target, toPosition, isHotkey) for _, flower in pairs(flowers) do if target.itemid == flower.itemid then - if (flower.watered == false and flower.advance == false) then + if not flower.watered and not flower.advance then player:say(flower.msg, TALKTYPE_MONSTER_SAY) - elseif (flower.watered == true and flower.advance == false) then + elseif flower.watered and not flower.advance then target:transform(flower.after) player:say(flower.msg, TALKTYPE_MONSTER_SAY) toPosition:sendMagicEffect(CONST_ME_LOSEENERGY) target:decay() - elseif (flower.watered == true and flower.advance == true) then + elseif flower.watered and flower.advance then local i = 1 - if (math.random(100) <= flower.chance) then + if math.random(100) <= flower.chance then i = 2 target:transform(flower.after[math.random(2, #flower.after)]) else diff --git a/data/scripts/actions/others/gnomish_voucher_types.lua b/data/scripts/actions/others/gnomish_voucher_types.lua deleted file mode 100644 index 7d7a6459d5..0000000000 --- a/data/scripts/actions/others/gnomish_voucher_types.lua +++ /dev/null @@ -1,45 +0,0 @@ -local config = { - [18517] = {female = 514, male = 516, effect = CONST_ME_GREEN_RINGS}, -- gnomish voucher type MB - [18518] = {female = 514, male = 516, addon = 1, effect = CONST_ME_GREEN_RINGS, achievement = "Funghitastic"}, -- gnomish voucher type MA1 - [18519] = {female = 514, male = 516, addon = 2, effect = CONST_ME_GREEN_RINGS, achievement = "Funghitastic"}, -- gnomish voucher type MA2 - [18520] = {female = 513, male = 512, effect = CONST_ME_GIANTICE}, -- gnomish voucher type CB - [18521] = {female = 513, male = 512, addon = 1, effect = CONST_ME_GIANTICE, achievement = "Crystal Clear"}, -- gnomish voucher type CA1 - [18522] = {female = 513, male = 512, addon = 2, effect = CONST_ME_GIANTICE, achievement = "Crystal Clear"} -- gnomish voucher type CA2 -} - -local gnomishVoucher = Action() - -function gnomishVoucher.onUse(player, item, fromPosition, target, toPosition, isHotkey) - local useItem = config[item.itemid] - local looktype = player:getSex() == PLAYERSEX_FEMALE and useItem.female or useItem.male - if useItem.addon then - if not player:isPremium() - or not player:hasOutfit(looktype) - or player:hasOutfit(looktype, useItem.addon) then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You own no premium account, lack the base outfit or already own this outfit part.") - return true - end - player:addOutfitAddon(useItem.female, useItem.addon) - player:addOutfitAddon(useItem.male, useItem.addon) - player:getPosition():sendMagicEffect(useItem.effect) - if player:hasOutfit(looktype, 3) then - player:addAchievement(useItem.achievement) - end - item:remove(1) - else - if not player:isPremium() or player:hasOutfit(looktype) then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You own no premium account or already own this outfit part.") - return true - end - player:addOutfit(useItem.female) - player:addOutfit(useItem.male) - player:getPosition():sendMagicEffect(useItem.effect) - item:remove(1) - end - return true -end - -for k, v in pairs(config) do - gnomishVoucher:id(k) -end -gnomishVoucher:register() diff --git a/data/scripts/actions/others/golden_outfit_display.lua b/data/scripts/actions/others/golden_outfit_display.lua new file mode 100644 index 0000000000..f81a5441fe --- /dev/null +++ b/data/scripts/actions/others/golden_outfit_display.lua @@ -0,0 +1,42 @@ +local transformDisplay = { + [34165] = 34156, + [34156] = 34161, + [34161] = 34169, + [34169] = 34165, + [34166] = 34158, + [34158] = 34162, + [34162] = 34170, + [34170] = 34166, + [34167] = 34159, + [34159] = 34163, + [34163] = 34171, + [34171] = 34167, + [34168] = 34160, + [34160] = 34164, + [34164] = 34172, + [34172] = 34168 +} + +local goldenOutfitDisplay = Action() + +function goldenOutfitDisplay.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local transformIds = transformDisplay[item:getId()] + if not transformIds then + return false + end + + if player:getStorageValue(PlayerStorageKeys.goldenOutfit) == 3 then + item:transform(transformIds) + item:getPosition():sendMagicEffect(CONST_ME_EARLY_THUNDER) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need Full Golden Outfit to use it.") + item:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return true +end + +for index, value in pairs(transformDisplay) do + goldenOutfitDisplay:id(index) +end + +goldenOutfitDisplay:register() diff --git a/data/scripts/actions/others/golden_outfit_memorial.lua b/data/scripts/actions/others/golden_outfit_memorial.lua new file mode 100644 index 0000000000..a2ea443631 --- /dev/null +++ b/data/scripts/actions/others/golden_outfit_memorial.lua @@ -0,0 +1,38 @@ +local goldenOutfitMemorial = Action() + +function goldenOutfitMemorial.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local resultId = db.storeQuery("SELECT `name`, `value` FROM `player_storage` INNER JOIN `players` as `p` ON `p`.`id` = `player_id` WHERE `key` = " .. PlayerStorageKeys.goldenOutfit .. " AND `value` >= 1;") + if not resultId then + player:showTextDialog(item.itemid, "The Golden Outfit has not been acquired by anyone yet.") + result.free(resultId) + return true + end + + local playerAddons = {[1] = {}, [2] = {}, [3] = {}} + repeat + local addons = result.getNumber(resultId, "value") + local name = result.getString(resultId, "name") + table.insert(playerAddons[addons], "- " .. name) + until not result.next(resultId) + + result.free(resultId) + + local message = "The following characters have spent a fortune on a Golden Outfit:\n" + if #playerAddons[3] > 0 then + message = message .. string.format("\nFull Outfit for 1,000,000,000 gold:\n%s", table.concat(playerAddons[3], "\n")) + end + + if #playerAddons[2] > 0 then + message = message .. string.format("\n\nWith One Addon for 750,000,000 gold:\n%s", table.concat(playerAddons[2], "\n")) + end + + if #playerAddons[1] > 0 then + message = message .. string.format("\n\nBasic Outfit for 500,000,000 gold:\n%s", table.concat(playerAddons[1], "\n")) + end + + player:showTextDialog(item.itemid, message) + return true +end + +goldenOutfitMemorial:id(34174, 34175, 34176, 34177, 34178, 34179) +goldenOutfitMemorial:register() diff --git a/data/scripts/actions/others/muck_remover.lua b/data/scripts/actions/others/muck_remover.lua index a24976bc25..2b12ad7b46 100644 --- a/data/scripts/actions/others/muck_remover.lua +++ b/data/scripts/actions/others/muck_remover.lua @@ -29,6 +29,7 @@ function muckRemover.onUse(player, item, fromPosition, target, toPosition, isHot player:addAchievementProgress("Goo Goo Dancer", 100) target:getPosition():sendMagicEffect(CONST_ME_GREEN_RINGS) target:remove(1) + player:sendSupplyUsed(item) item:remove(1) break end diff --git a/data/scripts/actions/others/spellbook.lua b/data/scripts/actions/others/spellbook.lua index 5f10d83fed..e9eede24d3 100644 --- a/data/scripts/actions/others/spellbook.lua +++ b/data/scripts/actions/others/spellbook.lua @@ -8,6 +8,9 @@ function spellbook.onUse(player, item, fromPosition, target, toPosition, isHotke if spell.manapercent > 0 then spell.mana = spell.manapercent .. "%" end + if spell.params > 0 then + spell.words = spell.words .. " para" + end spells[#spells + 1] = spell end end @@ -18,14 +21,14 @@ function spellbook.onUse(player, item, fromPosition, target, toPosition, isHotke for i, spell in ipairs(spells) do if prevLevel ~= spell.level then if i == 1 then - text[#text == nil and 1 or #text+1] = "Spells for Level " + text[not #text and 1 or #text + 1] = "Spells for Level " else - text[#text+1] = "\nSpells for Level " + text[#text + 1] = "\nSpells for Level " end - text[#text+1] = spell.level .. "\n" + text[#text + 1] = spell.level .. "\n" prevLevel = spell.level end - text[#text+1] = spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" + text[#text + 1] = spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" end player:showTextDialog(item:getId(), table.concat(text)) diff --git a/data/scripts/actions/others/suspicious_surprise_bag.lua b/data/scripts/actions/others/suspicious_surprise_bag.lua index 2ea360e2df..261e8aebcc 100644 --- a/data/scripts/actions/others/suspicious_surprise_bag.lua +++ b/data/scripts/actions/others/suspicious_surprise_bag.lua @@ -6,7 +6,7 @@ local config = { {chanceFrom = 8328, chanceTo = 9141, itemId = 6574}, -- bar of chocolate {chanceFrom = 9142, chanceTo = 9654, itemId = 6394}, -- cream cake {chanceFrom = 9655, chanceTo = 9850, itemId = 7377}, -- ice cream cone - {chanceFrom = 9851, chanceTo = 9986, itemId = 8110}, -- present + {chanceFrom = 9851, chanceTo = 9986, itemId = 9074}, -- explosive present {chanceFrom = 9987, chanceTo = 10000, itemId = 7487} -- toy mouse } diff --git a/data/scripts/actions/others/transforms.lua b/data/scripts/actions/others/transforms.lua index b644a7f5a9..e8086106b4 100644 --- a/data/scripts/actions/others/transforms.lua +++ b/data/scripts/actions/others/transforms.lua @@ -82,6 +82,7 @@ local transformItems = { [26091] = 26093, [26093] = 26091, -- predator lamp [26096] = 26094, [26094] = 26096, -- protectress lamp [26097] = 26095, [26095] = 26097, -- protectress lamp + [38629] = 38630, [38630] = 38629, -- toggle light on podium } local transformTo = Action() diff --git a/data/scripts/actions/others/usable_mount_items.lua b/data/scripts/actions/others/usable_mount_items.lua index 321380add3..3865b7c41f 100644 --- a/data/scripts/actions/others/usable_mount_items.lua +++ b/data/scripts/actions/others/usable_mount_items.lua @@ -17,7 +17,14 @@ local config = { [25521] = { -- mysterious scroll name = "rift runner", mountId = 87, + achievement = "Running the Rift", tameMessage = "You receive the permission to ride a rift runner." + }, + [35285] = { -- spectral scrap of cloth + name = "haze", + mountId = 162, + achievement = "Nothing but Hot Air", + tameMessage = "You are now versed to ride the haze!" } } @@ -42,6 +49,10 @@ function usableItemMounts.onUse(player, item, fromPosition, target, toPosition, end end + if useItem.achievement then + player:addAchievement(useItem.achievement) + end + player:addMount(useItem.mountId) player:addAchievement("Natural Born Cowboy") player:say(useItem.tameMessage, TALKTYPE_MONSTER_SAY) @@ -52,4 +63,5 @@ end for k, v in pairs(config) do usableItemMounts:id(k) end + usableItemMounts:register() diff --git a/data/scripts/actions/others/usable_outfit_items.lua b/data/scripts/actions/others/usable_outfit_items.lua new file mode 100644 index 0000000000..d9da60f2fe --- /dev/null +++ b/data/scripts/actions/others/usable_outfit_items.lua @@ -0,0 +1,108 @@ +local config = { + [18517] = { -- gnomish voucher type MB + female = 514, + male = 516, + effect = CONST_ME_GREEN_RINGS + }, + [18518] = { -- gnomish voucher type MA1 + female = 514, + male = 516, + addon = 1, + effect = CONST_ME_GREEN_RINGS, + achievement = "Funghitastic" + }, + [18519] = { -- gnomish voucher type MA2 + female = 514, + male = 516, + addon = 2, + effect = CONST_ME_GREEN_RINGS, + achievement = "Funghitastic" + }, + [18520] = { -- gnomish voucher type CB + female = 513, + male = 512, + effect = CONST_ME_GIANTICE + }, + [18521] = { -- gnomish voucher type CA1 + female = 513, + male = 512, + addon = 1, + effect = CONST_ME_GIANTICE, + achievement = "Crystal Clear" + }, + [18522] = { -- gnomish voucher type CA2 + female = 513, + male = 512, + addon = 2, + effect = CONST_ME_GIANTICE, + achievement = "Crystal Clear" + }, + [35286] = { -- spooky hood + female = 1271, + male = 1270, + addon = 1, + effect = CONST_ME_GREEN_RINGS, + achievement = "Mainstreet Nightmare" + }, + [35287] = { -- ghost claw + female = 1271, + male = 1270, + addon = 2, + effect = CONST_ME_GREEN_RINGS, + achievement = "Mainstreet Nightmare" + } +} + +local usableOutfitItems = Action() + +function usableOutfitItems.onUse(player, item, fromPosition, target, toPosition, isHotkey) + -- stop if player is not premium + if not player:isPremium() then + player:sendCancelMessage("You need a premium account to use this outfit.") + return true + end + + local outfitConfig = config[item.itemid] + local looktype = player:getSex() == PLAYERSEX_FEMALE and outfitConfig.female or outfitConfig.male + + -- If player is missing the outfit + if not player:hasOutfit(looktype) then + -- And this is an addon item, then stop + if outfitConfig.addon then + player:sendCancelMessage("You need the outfit for this part.") + return true + end + + -- Give outfit + player:addOutfit(outfitConfig.female) + player:addOutfit(outfitConfig.male) + player:getPosition():sendMagicEffect(outfitConfig.effect) + item:remove(1) + return true + end + + -- stop if player already have this addon + if player:hasOutfit(looktype, outfitConfig.addon) then + player:sendCancelMessage("You already own this outfit part.") + return true + end + + -- Give addon + player:addOutfitAddon(outfitConfig.female, outfitConfig.addon) + player:addOutfitAddon(outfitConfig.male, outfitConfig.addon) + player:getPosition():sendMagicEffect(outfitConfig.effect) + item:remove(1) + + -- full set achievement check and give + if player:hasOutfit(looktype, 3) then + player:addAchievement(outfitConfig.achievement) + end + + return true +end + +for k, v in pairs(config) do + usableOutfitItems:id(k) +end + +usableOutfitItems:register() diff --git a/data/scripts/actions/others/windows.lua b/data/scripts/actions/others/windows.lua index 1461fcbfb5..c4a21471aa 100644 --- a/data/scripts/actions/others/windows.lua +++ b/data/scripts/actions/others/windows.lua @@ -79,7 +79,11 @@ local windows = { [20182] = {19974}, -- window [20183] = {19445}, -- window [20184] = {19446}, -- small window - [20185] = {19975} -- window + [20185] = {19975}, -- window + [22813] = {22839}, -- window + [22839] = {22813}, -- window + [22812] = {22838}, -- window + [22838] = {22812}, -- window } local window = Action() diff --git a/data/scripts/actions/tools/check_bless.lua b/data/scripts/actions/tools/check_bless.lua index 59b57c852b..a5165b55d5 100644 --- a/data/scripts/actions/tools/check_bless.lua +++ b/data/scripts/actions/tools/check_bless.lua @@ -1,6 +1,6 @@ local blessings = { "Spiritual Shielding", - "Embrace of Tibia", + "Embrace of the World", "Fire of the Suns", "Spark of the Phoenix", "Wisdom of Solitude", @@ -10,13 +10,14 @@ local blessings = { local checkBless = Action() function checkBless.onUse(player, item, fromPosition, target, toPosition, isHotkey) - local result, bless = "Received blessings:" - for i = 1, #blessings do - bless = blessings[i] - result = player:hasBlessing(i) and result .. "\n" .. bless or result + local message = {"Received blessings:"} + for i, blessing in pairs(blessings) do + if player:hasBlessing(i) then + message[#message + 1] = blessing + end end - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 20 > result:len() and "No blessings received." or result) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, #message == 1 and "No blessings received." or table.concat(message, '\n')) return true end diff --git a/data/scripts/actions/tools/gold_converter.lua b/data/scripts/actions/tools/gold_converter.lua index b366536a81..fad44c91e2 100644 --- a/data/scripts/actions/tools/gold_converter.lua +++ b/data/scripts/actions/tools/gold_converter.lua @@ -1,8 +1,4 @@ -local config = { - [ITEM_GOLD_COIN] = {changeTo = ITEM_PLATINUM_COIN}, - [ITEM_PLATINUM_COIN] = {changeBack = ITEM_GOLD_COIN, changeTo = ITEM_CRYSTAL_COIN}, - [ITEM_CRYSTAL_COIN] = {changeBack = ITEM_PLATINUM_COIN} -} +local config = {} local goldConverter = Action() @@ -32,5 +28,12 @@ function goldConverter.onUse(player, item, fromPosition, target, toPosition, isH return true end +local currencyItems = Game.getCurrencyItems() +for index, currency in pairs(currencyItems) do + local back, to = currencyItems[index - 1], currencyItems[index + 1] + local currencyId = currency:getId() + config[currencyId] = { changeBack = back and back:getId(), changeTo = to and to:getId() } +end + goldConverter:id(26378) goldConverter:register() diff --git a/data/scripts/actions/tools/rust_remover.lua b/data/scripts/actions/tools/rust_remover.lua index 2350d24e29..96ffc4d0c4 100644 --- a/data/scripts/actions/tools/rust_remover.lua +++ b/data/scripts/actions/tools/rust_remover.lua @@ -122,6 +122,7 @@ function rustRemover.onUse(player, item, fromPosition, target, toPosition, isHot target:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) player:addAchievementProgress("Polisher", 1000) end + player:sendSupplyUsed(item) return item:remove(1) end diff --git a/data/scripts/actions/tools/skinning.lua b/data/scripts/actions/tools/skinning.lua index 563c6233a8..262ac4b07b 100644 --- a/data/scripts/actions/tools/skinning.lua +++ b/data/scripts/actions/tools/skinning.lua @@ -176,7 +176,12 @@ function skinning.onUse(player, item, fromPosition, target, toPosition, isHotkey else target:remove() end - if target.itemid == 13583 and player:getStorageValue(PlayerStorageKeys.mutatedPumpkin) <= os.time() then + if target:getId() == 13583 then + if player:getStorageValue(PlayerStorageKeys.mutatedPumpkin) > os.time() then + player:sendCancelMessage("You already used your knife on the corpse.") + return true + end + player:setStorageValue(PlayerStorageKeys.mutatedPumpkin, os.time() + 4 * 60 * 60) player:say("Happy Halloween!", TALKTYPE_MONSTER_SAY) player:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS) @@ -184,9 +189,6 @@ function skinning.onUse(player, item, fromPosition, target, toPosition, isHotkey local reward = math.random(1, #skin) player:addItem(skin[reward].newItem, skin[reward].amount or 1) effect = CONST_ME_HITAREA - else - player:sendCancelMessage("You already used your knife on the corpse.") - return true end if toPosition.x == CONTAINER_POSITION then toPosition = player:getPosition() diff --git a/data/scripts/creaturescripts/gs_wyda_death.lua b/data/scripts/creaturescripts/gs_wyda_death.lua index a51ae5e7bc..35709f6ca3 100644 --- a/data/scripts/creaturescripts/gs_wyda_death.lua +++ b/data/scripts/creaturescripts/gs_wyda_death.lua @@ -2,8 +2,8 @@ local creatureevent = CreatureEvent("GiantSpiderWyda") function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) creature:say("It seems this was just an illusion.", TALKTYPE_MONSTER_SAY) - if mostdamagekiller:isPlayer() then - mostdamagekiller:addAchievement("Someone's Bored") + if mostDamageKiller:isPlayer() then + mostDamageKiller:addAchievement("Someone's Bored") end return true end diff --git a/data/scripts/creaturescripts/whitedeer_scouts.lua b/data/scripts/creaturescripts/white_deer_scouts.lua similarity index 100% rename from data/scripts/creaturescripts/whitedeer_scouts.lua rename to data/scripts/creaturescripts/white_deer_scouts.lua diff --git a/data/scripts/eventcallbacks/monster/default_onDropLoot.lua b/data/scripts/eventcallbacks/monster/default_onDropLoot.lua index 2a2bfbadf1..07c2295e1b 100644 --- a/data/scripts/eventcallbacks/monster/default_onDropLoot.lua +++ b/data/scripts/eventcallbacks/monster/default_onDropLoot.lua @@ -7,26 +7,29 @@ ec.onDropLoot = function(self, corpse) local player = Player(corpse:getCorpseOwner()) local mType = self:getType() + local doCreateLoot = false + if not player or player:getStamina() > 840 then + doCreateLoot = true + end + + if doCreateLoot then local monsterLoot = mType:getLoot() for i = 1, #monsterLoot do local item = corpse:createLootItem(monsterLoot[i]) if not item then - print('[Warning] DropLoot:', 'Could not add loot item to corpse.') + print("[Warning] DropLoot: Could not add loot item to corpse.") end end + end - if player then - local text = ("Loot of %s: %s"):format(mType:getNameDescription(), corpse:getContentDescription()) - local party = player:getParty() - if party then - party:broadcastPartyLoot(text) - else - player:sendTextMessage(MESSAGE_LOOT, text) - end + if player then + local text + if doCreateLoot then + text = ("Loot of %s: %s"):format(mType:getNameDescription(), corpse:getContentDescription()) + else + text = ("Loot of %s: nothing (due to low stamina)"):format(mType:getNameDescription()) end - else - local text = ("Loot of %s: nothing (due to low stamina)"):format(mType:getNameDescription()) local party = player:getParty() if party then party:broadcastPartyLoot(text) diff --git a/data/scripts/eventcallbacks/player/default_onLook.lua b/data/scripts/eventcallbacks/player/default_onLook.lua index f7187b7e3e..22af667f1e 100644 --- a/data/scripts/eventcallbacks/player/default_onLook.lua +++ b/data/scripts/eventcallbacks/player/default_onLook.lua @@ -46,6 +46,7 @@ ec.onLook = function(self, thing, position, distance, description) if thing:isCreature() then if thing:isPlayer() then + description = string.format("%s\nGUID: %s", description, thing:getGuid()) description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp())) end end diff --git a/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua b/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua index 8ec7d21c90..9a573c2118 100644 --- a/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua +++ b/data/scripts/eventcallbacks/player/default_onLookInBattleList.lua @@ -16,6 +16,7 @@ ec.onLookInBattleList = function(self, creature, distance) ) if creature:isPlayer() then + description = string.format("%s\nGUID: %s", description, creature:getGuid()) description = string.format("%s\nIP: %s", description, Game.convertIpToString(creature:getIp())) end end diff --git a/data/scripts/eventcallbacks/player/default_onLookInMarket.lua b/data/scripts/eventcallbacks/player/default_onLookInMarket.lua new file mode 100644 index 0000000000..7942a120cf --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onLookInMarket.lua @@ -0,0 +1,310 @@ +local showAtkWeaponTypes = {WEAPON_CLUB, WEAPON_SWORD, WEAPON_AXE, WEAPON_DISTANCE} +local showDefWeaponTypes = {WEAPON_CLUB, WEAPON_SWORD, WEAPON_AXE, WEAPON_DISTANCE, WEAPON_SHIELD} + +local ec = EventCallback + +ec.onLookInMarket = function(self, itemType) + local response = NetworkMessage() + response:addByte(0xF8) + response:addU16(itemType:getClientId()) + + -- tier label (byte) + do + if itemType:getClassification() > 0 then + response:addByte(0) + end + end + + -- armor + do + local armor = itemType:getArmor() + if armor > 0 then + response:addString(armor) + else + response:addU16(0) + end + end + + -- weapon data (will be reused) + local weaponType = itemType:getWeaponType() + + -- attack + do + local showAtk = table.contains(showAtkWeaponTypes, weaponType) + if showAtk then + local atkAttrs = {} + local atk = itemType:getAttack() + if itemType:isBow() then + if atk ~= 0 then + atkAttrs[#atkAttrs + 1] = string.format("%+d", atk) + end + + local hitChance = itemType:getHitChance() + if hitChance ~= 0 then + atkAttrs[#atkAttrs + 1] = string.format("chance to hit %+d%%", hitChance) + end + + atkAttrs[#atkAttrs + 1] = string.format("%d fields", itemType:getShootRange()) + else + atkAttrs[#atkAttrs + 1] = atk + local elementDmg = itemType:getElementDamage() + if elementDmg ~= 0 then + atkAttrs[#atkAttrs] = string.format("%d physical %+d %s", atkAttrs[#atkAttrs], elementDmg, getCombatName(itemType:getElementType())) + end + end + + response:addString(table.concat(atkAttrs, ", ")) + else + response:addU16(0) + end + end + + -- container slots + do + if itemType:isContainer() then + response:addString(itemType:getCapacity()) + else + response:addU16(0) + end + end + + -- defense + do + local showDef = table.contains(showDefWeaponTypes, weaponType) + if showDef then + local def = itemType:getDefense() + if weaponType == WEAPON_DISTANCE then + -- throwables + if ammoType ~= AMMO_ARROW and ammoType ~= AMMO_BOLT then + response:addString(def) + else + response:addU16(0) + end + else + -- extra def + local xD = itemType:getExtraDefense() + if xD ~= 0 then + def = string.format("%d %+d", def, xD) + end + + response:addString(def) + end + else + response:addU16(0) + end + end + + -- description + do + local desc = itemType:getDescription() + if desc and #desc > 0 then + response:addString(desc:sub(1, -2)) + else + response:addU16(0) + end + end + + -- duration + do + local duration = itemType:getDuration() + if duration == 0 then + local transferType = itemType:getTransformEquipId() + if transferType ~= 0 then + transferType = ItemType(transferType) + duration = transferType and transferType:getDuration() or duration + end + end + + if duration > 0 then + response:addString(Game.getCountdownString(duration, true, true)) + else + response:addU16(0) + end + end + + -- item abilities (will be reused) + local abilities = itemType:getAbilities() + + -- element protections + do + local protections = {} + for element, value in pairs(abilities.absorbPercent) do + if value ~= 0 then + protections[#protections + 1] = string.format("%s %+d%%", getCombatName(2 ^ (element - 1)), value) + end + end + + if #protections > 0 then + response:addString(table.concat(protections, ", ")) + else + response:addU16(0) + end + end + + -- level req + do + local minLevel = itemType:getMinReqLevel() + if minLevel > 0 then + response:addString(minLevel) + else + response:addU16(0) + end + end + + -- magic level req + do + local minMagicLevel = itemType:getMinReqMagicLevel() + if minMagicLevel > 0 then + response:addString(minMagicLevel) + else + response:addU16(0) + end + end + + -- vocation + do + local vocations = itemType:getVocationString() + if vocations and vocations:len() > 0 then + response:addString(vocations) + else + response:addU16(0) + end + end + + -- rune words + do + local spellName = itemType:getRuneSpellName() + if spellName and spellName:len() > 0 then + response:addString(spellName) + else + response:addU16(0) + end + end + + -- "skill boost" category + do + -- atk speed + local atkSpeed = itemType:getAttackSpeed() + if atkSpeed ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("attack speed %0.2f/turn", 2000 / atkSpeed) + end + + -- skill boost + local skillBoosts = {} + if abilities.manaGain > 0 or abilities.healthGain > 0 or abilities.regeneration then + skillBoosts[#skillBoosts + 1] = "faster regeneration" + end + + -- invisibility + if abilities.invisible then + skillBoosts[#skillBoosts + 1] = "invisibility" + end + + -- magic shield (classic) + if abilities.manaShield then + skillBoosts[#skillBoosts + 1] = "magic shield" + end + + -- stats (hp/mp/soul/ml) + for stat, value in pairs(abilities.stats) do + if value ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("%s %+d", getStatName(stat - 1), value) + end + end + + -- stats but in % + for stat, value in pairs(abilities.statsPercent) do + if value ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("%s %+d%%", getStatName(stat - 1), value) + end + end + + -- speed + if abilities.speed ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("speed %+d", math.floor(abilities.speed / 2)) + end + + -- skills + for skill, value in pairs(abilities.skills) do + if value ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("%s %+d", getSkillName(skill - 1), value) + end + end + + -- special skills + for skill, value in pairs(abilities.specialSkills) do + if value ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("%s %+d", getSpecialSkillName[skill - 1], value) + end + end + + -- add to response + if #skillBoosts > 0 then + response:addString(table.concat(skillBoosts, ", ")) + else + response:addU16(0) + end + end + + -- charges + do + if itemType:hasShowCharges() then + response:addString(itemType:getCharges()) + else + response:addU16(0) + end + end + + -- weapon type + do + if itemType:isWeapon() then + response:addString(itemType:getWeaponString()) + else + response:addU16(0) + end + end + + -- weight + response:addString(string.format("%0.2f", itemType:getWeight() / 100)) + + -- to do + response:addU16(0) -- Imbuement Slots + response:addU16(0) -- Magic Shield Capacity + response:addU16(0) -- Cleave + response:addU16(0) -- Damage Reflection + response:addU16(0) -- Perfect Shot + response:addU16(0) -- Classification + response:addU16(0) -- Tier + + -- buy stats + do + local stats = itemType:getMarketBuyStatistics() + if stats then + response:addByte(0x01) + response:addU32(stats.numTransactions) + response:addU64(stats.totalPrice) + response:addU64(stats.highestPrice) + response:addU64(stats.lowestPrice) + else + response:addByte(0x00) + end + end + + -- sell stats + do + local stats = itemType:getMarketSellStatistics() + if stats then + response:addByte(0x01) + response:addU32(stats.numTransactions) + response:addU64(stats.totalPrice) + response:addU64(stats.highestPrice) + response:addU64(stats.lowestPrice) + else + response:addByte(0x00) + end + end + + response:sendToPlayer(self) +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onLookInShop.lua b/data/scripts/eventcallbacks/player/default_onLookInShop.lua new file mode 100644 index 0000000000..8b92392462 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onLookInShop.lua @@ -0,0 +1,25 @@ +local ec = EventCallback + +ec.onLookInShop = function(self, itemType, count, description) + local description = "You see " .. itemType:getItemDescription(distance) + if self:getGroup():getAccess() then + description = string.format("%s\nItem ID: %d", description, itemType:getId()) + description = string.format("%s\nClient ID: %d", description, itemType:getClientId()) + + local transformEquipId = itemType:getTransformEquipId() + local transformDeEquipId = itemType:getTransformDeEquipId() + if transformEquipId ~= 0 then + description = string.format("%s\nTransforms to: %d (onEquip)", description, transformEquipId) + elseif transformDeEquipId ~= 0 then + description = string.format("%s\nTransforms to: %d (onDeEquip)", description, transformDeEquipId) + end + + local decayId = itemType:getDecayId() + if decayId ~= -1 then + description = string.format("%s\nDecays to: %d", description, decayId) + end + end + return description +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onMoveItem.lua b/data/scripts/eventcallbacks/player/default_onMoveItem.lua index f6b6b17a83..4e9a9e6d61 100644 --- a/data/scripts/eventcallbacks/player/default_onMoveItem.lua +++ b/data/scripts/eventcallbacks/player/default_onMoveItem.lua @@ -5,14 +5,13 @@ ec.onMoveItem = function(self, item, count, fromPosition, toPosition, fromCylind local tile = Tile(toPosition) if (fromPosition.x ~= CONTAINER_POSITION and toPosition.x ~= CONTAINER_POSITION) or tile and not tile:getHouse() then if tile and not tile:getHouse() then - self:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) - return false + return RETURNVALUE_NOTPOSSIBLE end end end if toPosition.x ~= CONTAINER_POSITION then - return true + return RETURNVALUE_NOERROR end if item:getTopParent() == self and bit.band(toPosition.y, 0x40) == 0 then @@ -22,22 +21,20 @@ ec.onMoveItem = function(self, item, count, fromPosition, toPosition, fromCylind elseif itemType:getWeaponType() == WEAPON_SHIELD and toPosition.y == CONST_SLOT_RIGHT then moveItem = self:getSlotItem(CONST_SLOT_LEFT) if moveItem and bit.band(ItemType(moveItem:getId()):getSlotPosition(), SLOTP_TWO_HAND) == 0 then - return true + return RETURNVALUE_NOERROR end end if moveItem then local parent = item:getParent() if parent:isContainer() and parent:getSize() == parent:getCapacity() then - self:sendTextMessage(MESSAGE_STATUS_SMALL, Game.getReturnMessage(RETURNVALUE_CONTAINERNOTENOUGHROOM)) - return false - else - return moveItem:moveTo(parent) + return RETURNVALUE_CONTAINERNOTENOUGHROOM end + return moveItem:moveTo(parent) and RETURNVALUE_NOERROR or RETURNVALUE_NOTPOSSIBLE end end - return true + return RETURNVALUE_NOERROR end ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua b/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua index 99fd078e4c..b2b1db35df 100644 --- a/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua +++ b/data/scripts/eventcallbacks/player/default_onReportRuleViolation.lua @@ -3,9 +3,8 @@ local function hasPendingReport(name, targetName, reportType) if f then io.close(f) return true - else - return false end + return false end local ec = EventCallback diff --git a/data/scripts/lib/event_callbacks.lua b/data/scripts/lib/event_callbacks.lua index 49e1958c6c..bafb90eba4 100644 --- a/data/scripts/lib/event_callbacks.lua +++ b/data/scripts/lib/event_callbacks.lua @@ -1,139 +1,161 @@ +local unpack = unpack +local pack = table.pack + +local EventCallbackData, callbacks, updateableParameters, autoID = {}, {}, {}, 0 +-- This metatable creates an auto-configuration mechanism to create new types of EventCallbacks +local ec = setmetatable({}, { __newindex = function(self, key, value) + autoID = autoID + 1 + callbacks[key] = autoID + local info, update = {}, {} + for k, v in pairs(value) do + if type(k) == "string" then + info[k] = v + else + update[k] = v + end + end + updateableParameters[autoID] = update + callbacks[autoID] = info + EventCallbackData[autoID] = {maxn = 0} + EVENT_CALLBACK_LAST = autoID +end}) + +--@ Definitions of valid EventCallback types to hook according to the given field name +--@ The fields within the assigned table, allow to save arbitrary information -- Creature -EVENT_CALLBACK_ONCHANGEOUTFIT = 1 -EVENT_CALLBACK_ONCHANGEMOUNT = 2 -EVENT_CALLBACK_ONAREACOMBAT = 3 -EVENT_CALLBACK_ONTARGETCOMBAT = 4 -EVENT_CALLBACK_ONHEAR = 5 +ec.onChangeOutfit = {} +ec.onChangeMount = {} +ec.onAreaCombat = {returnValue=true} +ec.onTargetCombat = {returnValue=true} +ec.onHear = {} -- Party -EVENT_CALLBACK_ONJOIN = 6 -EVENT_CALLBACK_ONLEAVE = 7 -EVENT_CALLBACK_ONDISBAND = 8 -EVENT_CALLBACK_ONSHAREEXPERIENCE = 9 +ec.onJoin = {} +ec.onLeave = {} +ec.onDisband = {} +ec.onShareExperience = {} -- Player -EVENT_CALLBACK_ONBROWSEFIELD = 10 -EVENT_CALLBACK_ONLOOK = 11 -EVENT_CALLBACK_ONLOOKINBATTLELIST = 12 -EVENT_CALLBACK_ONLOOKINTRADE = 13 -EVENT_CALLBACK_ONLOOKINSHOP = 14 -EVENT_CALLBACK_ONTRADEREQUEST = 15 -EVENT_CALLBACK_ONTRADEACCEPT = 16 -EVENT_CALLBACK_ONTRADECOMPLETED = 17 -EVENT_CALLBACK_ONMOVEITEM = 18 -EVENT_CALLBACK_ONITEMMOVED = 19 -EVENT_CALLBACK_ONMOVECREATURE = 20 -EVENT_CALLBACK_ONREPORTRULEVIOLATION = 21 -EVENT_CALLBACK_ONREPORTBUG = 22 -EVENT_CALLBACK_ONTURN = 23 -EVENT_CALLBACK_ONGAINEXPERIENCE = 24 -EVENT_CALLBACK_ONLOSEEXPERIENCE = 25 -EVENT_CALLBACK_ONGAINSKILLTRIES = 26 -EVENT_CALLBACK_ONWRAPITEM = 27 +ec.onBrowseField = {} +ec.onLook = {[5] = 1} +ec.onLookInBattleList = {[4] = 1} +ec.onLookInTrade = {[5] = 1} +ec.onLookInShop = {[4] = 1} +ec.onLookInMarket = {} +ec.onTradeRequest = {} +ec.onTradeAccept = {} +ec.onTradeCompleted = {} +ec.onMoveItem = {} +ec.onItemMoved = {} +ec.onMoveCreature = {} +ec.onReportRuleViolation = {} +ec.onReportBug = {} +ec.onTurn = {} +ec.onGainExperience = {[3] = 1} +ec.onLoseExperience = {[2] = 1} +ec.onGainSkillTries = {[3] = 1} +ec.onWrapItem = {} +ec.onInventoryUpdate = {} -- Monster -EVENT_CALLBACK_ONDROPLOOT = 28 -EVENT_CALLBACK_ONSPAWN = 29 --- last (for correct table counting) -EVENT_CALLBACK_LAST = EVENT_CALLBACK_ONSPAWN - -local callbacks = { - -- Creature - ["onChangeOutfit"] = EVENT_CALLBACK_ONCHANGEOUTFIT, - ["onChangeMount"] = EVENT_CALLBACK_ONCHANGEMOUNT, - ["onAreaCombat"] = EVENT_CALLBACK_ONAREACOMBAT, - ["onTargetCombat"] = EVENT_CALLBACK_ONTARGETCOMBAT, - ["onHear"] = EVENT_CALLBACK_ONHEAR, - -- Party - ["onJoin"] = EVENT_CALLBACK_ONJOIN, - ["onLeave"] = EVENT_CALLBACK_ONLEAVE, - ["onDisband"] = EVENT_CALLBACK_ONDISBAND, - ["onShareExperience"] = EVENT_CALLBACK_ONSHAREEXPERIENCE, - -- Player - ["onBrowseField"] = EVENT_CALLBACK_ONBROWSEFIELD, - ["onLook"] = EVENT_CALLBACK_ONLOOK, - ["onLookInBattleList"] = EVENT_CALLBACK_ONLOOKINBATTLELIST, - ["onLookInTrade"] = EVENT_CALLBACK_ONLOOKINTRADE, - ["onLookInShop"] = EVENT_CALLBACK_ONLOOKINSHOP, - ["onTradeRequest"] = EVENT_CALLBACK_ONTRADEREQUEST, - ["onTradeAccept"] = EVENT_CALLBACK_ONTRADEACCEPT, - ["onTradeCompleted"] = EVENT_CALLBACK_ONTRADECOMPLETED, - ["onMoveItem"] = EVENT_CALLBACK_ONMOVEITEM, - ["onItemMoved"] = EVENT_CALLBACK_ONITEMMOVED, - ["onMoveCreature"] = EVENT_CALLBACK_ONMOVECREATURE, - ["onReportRuleViolation"] = EVENT_CALLBACK_ONREPORTRULEVIOLATION, - ["onReportBug"] = EVENT_CALLBACK_ONREPORTBUG, - ["onTurn"] = EVENT_CALLBACK_ONTURN, - ["onGainExperience"] = EVENT_CALLBACK_ONGAINEXPERIENCE, - ["onLoseExperience"] = EVENT_CALLBACK_ONLOSEEXPERIENCE, - ["onGainSkillTries"] = EVENT_CALLBACK_ONGAINSKILLTRIES, - ["onWrapItem"] = EVENT_CALLBACK_ONWRAPITEM, - -- Monster - ["onDropLoot"] = EVENT_CALLBACK_ONDROPLOOT, - ["onSpawn"] = EVENT_CALLBACK_ONSPAWN -} - -local auxargs = { - [EVENT_CALLBACK_ONGAINEXPERIENCE] = {[3] = 1}, - [EVENT_CALLBACK_ONLOSEEXPERIENCE] = {[2] = 1}, - [EVENT_CALLBACK_ONGAINSKILLTRIES] = {[3] = 1} -} - -EventCallbackData = {} -hasEventCallback = function (type) - return #EventCallbackData[type] > 0 -end +ec.onDropLoot = {} +ec.onSpawn = {} EventCallback = { - register = function (self, index) + register = function(self, triggerIndex) if isScriptsInterface() then - local type, call = rawget(self, "type"), rawget(self, "call") - if type and call then - index = tonumber(index) - EventCallbackData[type][#EventCallbackData[type] + 1] = {call, index or -math.huge} - if index then - table.sort(EventCallbackData[type], function (a, b) return a[2] < b[2] end) - end - return rawset(self, "type", nil) and rawset(self, "call", nil) - else - debugPrint("[Warning - EventCallback::register] is need to set up a callback before register.") + local eventType = rawget(self, 'eventType') + local callback = rawget(self, 'callback') + if not eventType or not callback then + debugPrint("[Warning - EventCallback::register] need to setup a callback before you can register.") + return end + + local eventData = EventCallbackData[eventType] + eventData.maxn = #eventData + 1 + eventData[eventData.maxn] = { + callback = callback, + triggerIndex = tonumber(triggerIndex) or 0 + } + table.sort(eventData, function(ecl, ecr) return ecl.triggerIndex < ecr.triggerIndex end) + self.eventType = nil + self.callback = nil end end, - clear = function (self) + + clear = function(self) EventCallbackData = {} for i = 1, EVENT_CALLBACK_LAST do - EventCallbackData[i] = {} + EventCallbackData[i] = {maxn = 0} end end } setmetatable(EventCallback, { - __index = function (self) return self end, - __newindex = function (self, k, v) - if isScriptsInterface() then - local ecType = callbacks[k] - if ecType then - if type(v) == "function" then - return rawset(self, "type", ecType) and rawset(self, "call", v) - end - debugPrint(string.format("[Warning - EventCallback::%s] a function is expected.", k)) - else - debugPrint(string.format("[Warning - EventCallback::%s] is not a valid callback.", k)) - end + __newindex = function(self, key, callback) + if not isScriptsInterface() then + return + end + + local eventType = callbacks[key] + if not eventType then + debugPrint(string.format("[Warning - EventCallback::%s] is not a valid callback.", key)) + return end + + if type(callback) ~= "function" then + debugPrint(string.format("[Warning - EventCallback::%s] a function is expected.", key)) + return + end + + rawset(self, 'eventType', eventType) + rawset(self, 'callback', callback) end, - __call = function (self, type, ...) - local eventTable, ret = EventCallbackData[type] - local args, events = table.pack(...), #eventTable - for k, ev in pairs(eventTable) do - ret = {ev[1](unpack(args))} - if k == events or (ret[1] ~= nil and (ret[1] == false or table.contains({EVENT_CALLBACK_ONAREACOMBAT, EVENT_CALLBACK_ONTARGETCOMBAT}, type) and ret[1] ~= RETURNVALUE_NOERROR)) then - return unpack(ret) + + __index = function(self, key) + local callback = callbacks[key] + if not callback then + if not isScriptsInterface() then + return end - for k, v in pairs(auxargs[type] or {}) do - args[k] = ret[v] + + return rawget(self, key) + end + + local eventData = EventCallbackData[callback] + if eventData.maxn == 0 then + return + end + + return function(...) + local results, args, info = {}, pack(...), callbacks[callback] + for index = 1, eventData.maxn do + local event = eventData[index] + repeat + results = {event.callback(unpack(args))} + local output = results[1] + -- If the call returns nil then we continue with the next call + if output == nil then + break + end + -- If the call returns false then we exit the loop + if not output then + return false + end + -- If the call of type returnvalue returns noerror then we continue the loop + if info.returnValue then + if output == RETURNVALUE_NOERROR then + break + end + return output + end + -- We left the loop why have we reached the end + if index == eventData.maxn then + return unpack(results) + end + until true + -- Update the results for the next call + for index, value in pairs(updateableParameters[callback]) do + args[index] = results[value] + end end end end - }) - --- can't be overwritten on reloads -EventCallback:clear() +}) diff --git a/data/scripts/lib/register_monster_type.lua b/data/scripts/lib/register_monster_type.lua index 08f15ef345..74bc2b4ba0 100644 --- a/data/scripts/lib/register_monster_type.lua +++ b/data/scripts/lib/register_monster_type.lua @@ -3,7 +3,7 @@ setmetatable(registerMonsterType, { __call = function(self, mtype, mask) - for _,parse in pairs(self) do + for _, parse in pairs(self) do parse(mtype, mask) end end @@ -13,6 +13,11 @@ MonsterType.register = function(self, mask) return registerMonsterType(self, mask) end +registerMonsterType.name = function(mtype, mask) + if mask.name then + mtype:name(mask.name) + end +end registerMonsterType.description = function(mtype, mask) if mask.description then mtype:nameDescription(mask.description) @@ -77,31 +82,40 @@ registerMonsterType.corpse = function(mtype, mask) end registerMonsterType.flags = function(mtype, mask) if mask.flags then - if mask.flags.attackable ~= nil then + if mask.flags.attackable then mtype:isAttackable(mask.flags.attackable) end - if mask.flags.healthHidden ~= nil then + if mask.flags.healthHidden then mtype:isHealthHidden(mask.flags.healthHidden) end - if mask.flags.boss ~= nil then + if mask.flags.boss then mtype:isBoss(mask.flags.boss) end - if mask.flags.convinceable ~= nil then + if mask.flags.challengeable then + mtype:isChallengeable(mask.flags.challengeable) + end + if mask.flags.convinceable then mtype:isConvinceable(mask.flags.convinceable) end - if mask.flags.illusionable ~= nil then + if mask.flags.summonable then + mtype:isSummonable(mask.flags.summonable) + end + if mask.flags.ignoreSpawnBlock then + mtype:isIgnoringSpawnBlock(mask.flags.ignoreSpawnBlock) + end + if mask.flags.illusionable then mtype:isIllusionable(mask.flags.illusionable) end - if mask.flags.hostile ~= nil then + if mask.flags.hostile then mtype:isHostile(mask.flags.hostile) end - if mask.flags.pushable ~= nil then + if mask.flags.pushable then mtype:isPushable(mask.flags.pushable) end - if mask.flags.canPushItems ~= nil then + if mask.flags.canPushItems then mtype:canPushItems(mask.flags.canPushItems) end - if mask.flags.canPushCreatures ~= nil then + if mask.flags.canPushCreatures then mtype:canPushCreatures(mask.flags.canPushCreatures) end -- if a monster can push creatures, @@ -115,16 +129,20 @@ registerMonsterType.flags = function(mtype, mask) if mask.flags.staticAttackChance then mtype:staticAttackChance(mask.flags.staticAttackChance) end + if mask.flags.canWalkOnEnergy then + mtype:canWalkOnEnergy(mask.flags.canWalkOnEnergy) + end + if mask.flags.canWalkOnFire then + mtype:canWalkOnFire(mask.flags.canWalkOnFire) + end + if mask.flags.canWalkOnPoison then + mtype:canWalkOnPoison(mask.flags.canWalkOnPoison) + end end end registerMonsterType.light = function(mtype, mask) if mask.light then - if mask.light.color then - local color = mask.light.color - end - if mask.light.level then - mtype:light(color, mask.light.level) - end + mtype:light(mask.light.color or 0, mask.light.level or 0) end end registerMonsterType.changeTarget = function(mtype, mask) @@ -156,7 +174,7 @@ end registerMonsterType.summons = function(mtype, mask) if type(mask.summons) == "table" then for k, v in pairs(mask.summons) do - mtype:addSummon(v.name, v.interval, v.chance) + mtype:addSummon(v.name, v.interval, v.chance, v.max or -1) end end end @@ -184,8 +202,11 @@ registerMonsterType.loot = function(mtype, mask) if loot.aid or loot.actionId then parent:setActionId(loot.aid or loot.actionId) end + local charges = loot.charges or ItemType(loot.id):getCharges() if loot.subType or loot.charges then - parent:setSubType(loot.subType or loot.charges) + parent:setSubType(loot.subType or charges) + else + parent:setSubType(charges) end if loot.text or loot.description then parent:setDescription(loot.text or loot.description) @@ -205,8 +226,11 @@ registerMonsterType.loot = function(mtype, mask) if children.aid or children.actionId then child:setActionId(children.aid or children.actionId) end + local charges = children.charges or ItemType(children.id):getCharges() if children.subType or children.charges then - child:setSubType(children.subType or children.charges) + child:setSubType(children.subType or charges) + else + child:setSubType(charges) end if children.text or children.description then child:setDescription(children.text or children.description) @@ -217,7 +241,7 @@ registerMonsterType.loot = function(mtype, mask) mtype:addLoot(parent) end if lootError then - print("[Warning - end] Monster: \"".. mtype:name() .. "\" loot could not correctly be load.") + print("[Warning - end] Monster: \"" .. mtype:name() .. "\" loot could not correctly be load.") end end end @@ -242,115 +266,126 @@ registerMonsterType.immunities = function(mtype, mask) end end end -registerMonsterType.attacks = function(mtype, mask) - if type(mask.attacks) == "table" then - for _, attack in pairs(mask.attacks) do - local spell = MonsterSpell() - if attack.name then - if attack.name == "melee" then - spell:setType("melee") - if attack.attack and attack.skill then - spell:setAttackValue(attack.attack, attack.skill) - end - if attack.interval then - spell:setInterval(attack.interval) - end - if attack.effect then - spell:setCombatEffect(attack.effect) - end - if attack.condition then - if attack.condition.type then - spell:setConditionType(attack.condition.type) - end - local startDamnage = 0 - if attack.condition.startDamage then - startDamage = attack.condition.startDamage - end - if attack.condition.minDamage and attack.condition.maxDamage then - spell:setConditionDamage(attack.condition.minDamage, attack.condition.maxDamage, startDamage) - end - if attack.condition.duration then - spell:setConditionDuration(attack.condition.duration) - end - if attack.condition.interval then - spell:setConditionTickInterval(attack.condition.interval) - end - end - else - spell:setType(attack.name) - if attack.type then - if attack.name == "combat" then - spell:setCombatType(attack.type) - else - spell:setConditionType(attack.type) - end - end - if attack.interval then - spell:setInterval(attack.interval) - end - if attack.chance then - spell:setChance(attack.chance) - end - if attack.range then - spell:setRange(attack.range) - end - if attack.duration then - spell:setConditionDuration(attack.duration) - end - if attack.speed then - if type(attack.speed) ~= "table" then - spell:setConditionSpeedChange(attack.speed) - elseif type(attack.speed) == "table" then - if attack.speed.min and attack.speed.max then - spell:setConditionSpeedChange(attack.speed.min, attack.speed.max) - end - end - end - if attack.target then - spell:setNeedTarget(attack.target) - end - if attack.length then - spell:setCombatLength(attack.length) - end - if attack.spread then - spell:setCombatSpread(attack.spread) - end - if attack.radius then - spell:setCombatRadius(attack.radius) - end - if attack.minDamage and attack.maxDamage then - if attack.name == "combat" then - spell:setCombatValue(attack.minDamage, attack.maxDamage) - else - local startDamage = 0 - if attack.startDamage then - startDamage = attack.startDamage - end - spell:setConditionDamage(attack.minDamage, attack.maxDamage, startDamage) - end - end - if attack.effect then - spell:setCombatEffect(attack.effect) - end - if attack.shootEffect then - spell:setCombatShootEffect(attack.shootEffect) +local function AbilityTableToSpell(ability) + local spell = MonsterSpell() + if ability.name then + if ability.name == "melee" then + spell:setType("melee") + if ability.attack and ability.skill then + spell:setAttackValue(ability.attack, ability.skill) + end + if ability.minDamage and ability.maxDamage then + spell:setCombatValue(ability.minDamage, ability.maxDamage) + end + if ability.interval then + spell:setInterval(ability.interval) + end + if ability.effect then + spell:setCombatEffect(ability.effect) + end + else + spell:setType(ability.name) + if ability.type then + spell:setCombatType(ability.type) + end + if ability.interval then + spell:setInterval(ability.interval) + end + if ability.chance then + spell:setChance(ability.chance) + end + if ability.range then + spell:setRange(ability.range) + end + if ability.duration then + spell:setConditionDuration(ability.duration) + end + if ability.speed then + if type(ability.speed) ~= "table" then + spell:setConditionSpeedChange(ability.speed) + elseif type(ability.speed) == "table" then + if ability.speed.min and ability.speed.max then + spell:setConditionSpeedChange(ability.speed.min, ability.speed.max) end end - elseif attack.script then - spell:setScriptName(attack.script) - if attack.interval then - spell:setInterval(attack.interval) - end - if attack.chance then - spell:setChance(attack.chance) - end - if attack.minDamage and attack.maxDamage then - spell:setCombatValue(attack.minDamage, attack.maxDamage) - end - if attack.target then - spell:setNeedTarget(attack.target) + end + if ability.target then + spell:setNeedTarget(ability.target) + end + if ability.length then + spell:setCombatLength(ability.length) + end + if ability.spread then + spell:setCombatSpread(ability.spread) + end + if ability.radius then + spell:setCombatRadius(ability.radius) + end + if ability.ring then + spell:setCombatRing(ability.ring) + end + if ability.minDamage and ability.maxDamage then + spell:setCombatValue(ability.minDamage, ability.maxDamage) + end + if ability.effect then + spell:setCombatEffect(ability.effect) + end + if ability.shootEffect then + spell:setCombatShootEffect(ability.shootEffect) + end + local outfit = ability.outfit or ability.monster or ability.item + if outfit then + spell:setOutfit(outfit) + end + if ability.name == "drunk" then + spell:setConditionType(CONDITION_DRUNK) + if ability.drunkenness then + spell:setConditionDrunkenness(ability.drunkenness) end end + end + if ability.condition then + if ability.condition.type then + spell:setConditionType(ability.condition.type) + end + local startDamage = 0 + if ability.condition.startDamage then + startDamage = ability.condition.startDamage + end + if ability.condition.minDamage and ability.condition.maxDamage then + spell:setConditionDamage(ability.condition.minDamage, ability.condition.maxDamage, startDamage) + end + if ability.condition.duration then + spell:setConditionDuration(ability.condition.duration) + end + if ability.condition.interval then + spell:setConditionTickInterval(ability.condition.interval) + end + end + elseif ability.script then + spell:setScriptName(ability.script) + if ability.interval then + spell:setInterval(ability.interval) + end + if ability.chance then + spell:setChance(ability.chance) + end + if ability.minDamage and ability.maxDamage then + spell:setCombatValue(ability.minDamage, ability.maxDamage) + end + if ability.target then + spell:setNeedTarget(ability.target) + end + if ability.direction then + spell:setNeedDirection(ability.direction) + end + end + return spell +end +registerMonsterType.attacks = function(mtype, mask) + if type(mask.attacks) == "table" then + for _, attack in pairs(mask.attacks) do + local spell = AbilityTableToSpell(attack) mtype:addAttack(spell) end end @@ -365,112 +400,7 @@ registerMonsterType.defenses = function(mtype, mask) end for _, defense in pairs(mask.defenses) do if type(defense) == "table" then - local spell = MonsterSpell() - if defense.name then - if defense.name == "melee" then - spell:setType("melee") - if defense.attack and defense.skill then - spell:setAttackValue(defense.attack, defense.skill) - end - if defense.interval then - spell:setInterval(defense.interval) - end - if defense.effect then - spell:setCombatEffect(defense.effect) - end - if defense.condition then - if defense.condition.type then - spell:setConditionType(defense.condition.type) - end - local startDamnage = 0 - if defense.condition.startDamage then - startDamage = defense.condition.startDamage - end - if defense.condition.minDamage and defense.condition.maxDamage then - spell:setConditionDamage(defense.condition.minDamage, defense.condition.maxDamage, startDamage) - end - if defense.condition.duration then - spell:setConditionDuration(defense.condition.duration) - end - if defense.condition.interval then - spell:setConditionTickInterval(defense.condition.interval) - end - end - else - spell:setType(defense.name) - if defense.type then - if defense.name == "combat" then - spell:setCombatType(defense.type) - else - spell:setConditionType(defense.type) - end - end - if defense.interval then - spell:setInterval(defense.interval) - end - if defense.chance then - spell:setChance(defense.chance) - end - if defense.range then - spell:setRange(defense.range) - end - if defense.duration then - spell:setConditionDuration(defense.duration) - end - if defense.speed then - if type(defense.speed) ~= "table" then - spell:setConditionSpeedChange(defense.speed) - elseif type(defense.speed) == "table" then - if defense.speed.min and defense.speed.max then - spell:setConditionSpeedChange(defense.speed.min, defense.speed.max) - end - end - end - if defense.target then - spell:setNeedTarget(defense.target) - end - if defense.length then - spell:setCombatLength(defense.length) - end - if defense.spread then - spell:setCombatSpread(defense.spread) - end - if defense.radius then - spell:setCombatRadius(defense.radius) - end - if defense.minDamage and defense.maxDamage then - if defense.name == "combat" then - spell:setCombatValue(defense.minDamage, defense.maxDamage) - else - local startDamage = 0 - if defense.startDamage then - startDamage = defense.startDamage - end - spell:setConditionDamage(defense.minDamage, defense.maxDamage, startDamage) - end - end - if defense.effect then - spell:setCombatEffect(defense.effect) - end - if defense.shootEffect then - spell:setCombatShootEffect(defense.shootEffect) - end - end - elseif defense.script then - spell:setScriptName(defense.script) - if defense.interval then - spell:setInterval(defense.interval) - end - if defense.chance then - spell:setChance(defense.chance) - end - if defense.minDamage and defense.maxDamage then - spell:setCombatValue(defense.minDamage, defense.maxDamage) - end - if defense.target then - spell:setNeedTarget(defense.target) - end - end + local spell = AbilityTableToSpell(defense) mtype:addDefense(spell) end end diff --git a/data/scripts/monsters/monster_storages.lua b/data/scripts/monsters/monster_storages.lua new file mode 100644 index 0000000000..029eab988a --- /dev/null +++ b/data/scripts/monsters/monster_storages.lua @@ -0,0 +1,33 @@ +if not MonsterStorages then + MonsterStorages = setmetatable({}, { + __index = function(storage, cid) + local self = Monster(cid) + if not self then + return + end + + if self:registerEvent("MonsterStorages") then + storage[cid] = {} + return storage[cid] + end + end + }) +end + +local creatureEvent = CreatureEvent("MonsterStorages") + +function creatureEvent.onDeath(self) + MonsterStorages[self:getId()] = nil + return true +end + +creatureEvent:register() + +function Monster:getStorageValue(key) + return MonsterStorages[self:getId()][key] or -1 +end + +function Monster:setStorageValue(key, value) + MonsterStorages[self:getId()][key] = value + return true +end diff --git a/data/scripts/movements/claw_of_the_noxious_spawn.lua b/data/scripts/movements/claw_of_the_noxious_spawn.lua index 0cdfd4f017..8b3486b359 100644 --- a/data/scripts/movements/claw_of_the_noxious_spawn.lua +++ b/data/scripts/movements/claw_of_the_noxious_spawn.lua @@ -1,7 +1,7 @@ local clawOfTheNoxiousSpawn = MoveEvent() function clawOfTheNoxiousSpawn.onEquip(player, item, slot, isCheck) - if isCheck == false then + if not isCheck then if not Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then doTargetCombat(0, player, COMBAT_PHYSICALDAMAGE, -150, -200, CONST_ME_DRAWBLOOD) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Ouch! The serpent claw stabbed you.") diff --git a/data/spells/lib/spells.lua b/data/spells/lib/spells.lua index 021d47ca20..d6d7ee2ca8 100644 --- a/data/spells/lib/spells.lua +++ b/data/spells/lib/spells.lua @@ -211,7 +211,7 @@ AREADIAGONAL_WALLFIELD = { } -- This array contains all destroyable field items -FIELDS = {1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1500,1501,1502,1503,1504} +FIELDS = {1487, 1488, 1489, 1490, 1491, 1492, 1493, 1494, 1495, 1496, 1500, 1501, 1502, 1503, 1504} function Player:addPartyCondition(combat, variant, condition, baseMana) local party = self:getParty() diff --git a/data/spells/scripts/healing/mass_healing.lua b/data/spells/scripts/healing/mass_healing.lua index 3ac122adc5..72dad78f2f 100644 --- a/data/spells/scripts/healing/mass_healing.lua +++ b/data/spells/scripts/healing/mass_healing.lua @@ -10,7 +10,7 @@ function onCastSpell(creature, variant) for _, target in ipairs(combat:getTargets(creature, variant)) do local master = target:getMaster() if target:isPlayer() or master and master:isPlayer() then - doTargetCombat(0, target, COMBAT_HEALING, min, max) + doTargetCombat(creature, target, COMBAT_HEALING, min, max) end end return true diff --git a/data/spells/scripts/house/edit_door_list.lua b/data/spells/scripts/house/edit_door.lua similarity index 100% rename from data/spells/scripts/house/edit_door_list.lua rename to data/spells/scripts/house/edit_door.lua diff --git a/data/spells/scripts/house/edit_guest_list.lua b/data/spells/scripts/house/invite_guests.lua similarity index 100% rename from data/spells/scripts/house/edit_guest_list.lua rename to data/spells/scripts/house/invite_guests.lua diff --git a/data/spells/scripts/house/edit_subowner_list.lua b/data/spells/scripts/house/invite_subowners.lua similarity index 100% rename from data/spells/scripts/house/edit_subowner_list.lua rename to data/spells/scripts/house/invite_subowners.lua diff --git a/data/spells/scripts/house/kick.lua b/data/spells/scripts/house/kick_guest.lua similarity index 100% rename from data/spells/scripts/house/kick.lua rename to data/spells/scripts/house/kick_guest.lua diff --git a/data/spells/scripts/monster/root_branchy.lua b/data/spells/scripts/monster/root_branchy.lua new file mode 100644 index 0000000000..f89976c032 --- /dev/null +++ b/data/spells/scripts/monster/root_branchy.lua @@ -0,0 +1,11 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ROOTING) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_LEAFSTAR) + +local condition = Condition(CONDITION_ROOT) +condition:setParameter(CONDITION_PARAM_TICKS, 3000) +combat:addCondition(condition) + +function onCastSpell(creature, var) + return combat:execute(creature, var) +end diff --git a/data/spells/scripts/monster/summon_challenge.lua b/data/spells/scripts/monster/summon_challenge.lua new file mode 100644 index 0000000000..cbd3643b3e --- /dev/null +++ b/data/spells/scripts/monster/summon_challenge.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setArea(createCombatArea(AREA_CIRCLE2X2)) + +function onTargetCreature(creature, target) + return doChallengeCreature(creature, target) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end diff --git a/data/spells/scripts/support/disintegrate_rune.lua b/data/spells/scripts/support/disintegrate_rune.lua index e54ae1165f..d53ec50309 100644 --- a/data/spells/scripts/support/disintegrate_rune.lua +++ b/data/spells/scripts/support/disintegrate_rune.lua @@ -5,10 +5,14 @@ function onCastSpell(creature, variant, isHotkey) local position = variant:getPosition() local tile = Tile(position) if tile then + if tile:getHouse() then + position:sendMagicEffect(CONST_ME_POFF) + return true + end local items = tile:getItems() if items then for i, item in ipairs(items) do - if item:getType():isMovable() and item:getUniqueId() > 65535 and item:getActionId() == 0 and not table.contains(corpseIds, item:getId()) then + if item:getType():isMovable() and item:getUniqueId() > 65535 and item:getActionId() == 0 and not table.contains(corpseIds, item:getId()) and not item:isPodium() then item:remove() end diff --git a/data/spells/scripts/support/find_person.lua b/data/spells/scripts/support/find_person.lua index f2984c0cab..b9fe2d1b0e 100644 --- a/data/spells/scripts/support/find_person.lua +++ b/data/spells/scripts/support/find_person.lua @@ -62,7 +62,7 @@ function onCastSpell(creature, variant) end local level = positionDifference.z > 0 and LEVEL_HIGHER or positionDifference.z < 0 and LEVEL_LOWER or LEVEL_SAME - local distance = maxPositionDifference < 5 and DISTANCE_BESIDE or maxPositionDifference < 101 and DISTANCE_CLOSE or maxPositionDifference < 275 and DISTANCE_FAR or DISTANCE_VERYFAR + local distance = maxPositionDifference < 5 and DISTANCE_BESIDE or maxPositionDifference < 101 and DISTANCE_CLOSE or maxPositionDifference < 250 and DISTANCE_FAR or DISTANCE_VERYFAR local description = descriptions[distance][level] or descriptions[distance] if distance ~= DISTANCE_BESIDE then description = description .. " " .. directions[direction] diff --git a/data/spells/scripts/support/food.lua b/data/spells/scripts/support/food.lua index b1c81d0139..149d9ebd62 100644 --- a/data/spells/scripts/support/food.lua +++ b/data/spells/scripts/support/food.lua @@ -5,7 +5,7 @@ local foods = { 2674, -- apple 2689, -- bread 2690, -- roll - 2696 -- cheese + 2696 -- cheese } function onCastSpell(creature, variant) diff --git a/data/spells/scripts/support/levitate.lua b/data/spells/scripts/support/levitate.lua index 6cf401f0ce..6e24cd4f51 100644 --- a/data/spells/scripts/support/levitate.lua +++ b/data/spells/scripts/support/levitate.lua @@ -1,16 +1,24 @@ local function levitate(creature, parameter) local fromPosition = creature:getPosition() + parameter = parameter:trim():lower() if parameter == "up" and fromPosition.z ~= 8 or parameter == "down" and fromPosition.z ~= 7 then local toPosition = creature:getPosition() toPosition:getNextPosition(creature:getDirection()) local tile = Tile(parameter == "up" and Position(fromPosition.x, fromPosition.y, fromPosition.z - 1) or toPosition) - if not tile or not tile:getGround() and not tile:hasFlag(parameter == "up" and TILESTATE_IMMOVABLEBLOCKSOLID or TILESTATE_BLOCKSOLID) then + if not tile or not tile:getGround() and not tile:hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) then tile = Tile(toPosition.x, toPosition.y, toPosition.z + (parameter == "up" and -1 or 1)) - if tile and tile:getGround() and not tile:hasFlag(bit.bor(TILESTATE_IMMOVABLEBLOCKSOLID, TILESTATE_FLOORCHANGE)) then - return creature:move(tile, bit.bor(FLAG_IGNOREBLOCKITEM, FLAG_IGNOREBLOCKCREATURE)) + if tile and tile:getGround() and not tile:hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) then + local fromPos = creature:getPosition() + local moved = creature:move(tile, bit.bor(FLAG_IGNOREBLOCKITEM, FLAG_IGNOREBLOCKCREATURE)) + + if moved == RETURNVALUE_NOERROR then + fromPos:sendMagicEffect(CONST_ME_TELEPORT) + end + + return moved end end end @@ -25,6 +33,5 @@ function onCastSpell(creature, variant) return false end - creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT) return true end diff --git a/data/spells/spells.xml b/data/spells/spells.xml index 4bf79aee8f..7b9e5404a2 100644 --- a/data/spells/spells.xml +++ b/data/spells/spells.xml @@ -487,7 +487,7 @@ - + @@ -744,10 +744,10 @@ - - - - + + + + @@ -801,4 +801,6 @@ + + diff --git a/data/talkactions/scripts/add_skill.lua b/data/talkactions/scripts/add_skill.lua index a841b78986..e2af99e6b4 100644 --- a/data/talkactions/scripts/add_skill.lua +++ b/data/talkactions/scripts/add_skill.lua @@ -11,9 +11,15 @@ local function getSkillId(skillName) return SKILL_SHIELD elseif skillName:sub(1, 4) == "fish" then return SKILL_FISHING - else + elseif skillName:sub(1, 4) == "fist" then return SKILL_FIST + elseif skillName:sub(1, 1) == "m" then + return SKILL_MAGLEVEL + elseif skillName == "level" or skillName:sub(1, 1) == "l" or skillName:sub(1, 1) == "e" then + return SKILL_LEVEL end + + return nil end function onSay(player, words, param) @@ -42,16 +48,12 @@ function onSay(player, words, param) count = tonumber(split[3]) end - local ch = split[2]:sub(1, 1) - for i = 1, count do - if ch == "l" or ch == "e" then - target:addExperience(getExperienceForLevel(target:getLevel() + 1) - target:getExperience(), false) - elseif ch == "m" then - target:addManaSpent(target:getVocation():getRequiredManaSpent(target:getBaseMagicLevel() + 1) - target:getManaSpent()) - else - local skillId = getSkillId(split[2]) - target:addSkillTries(skillId, target:getVocation():getRequiredSkillTries(skillId, target:getSkillLevel(skillId) + 1) - target:getSkillTries(skillId)) - end + local skillId = getSkillId(split[2]) + if not skillId then + player:sendCancelMessage("Unknown skill.") + return false end + + target:addSkill(skillId, count) return false end diff --git a/data/talkactions/scripts/add_tutor.lua b/data/talkactions/scripts/add_tutor.lua index c2dcde7afb..782904ef9a 100644 --- a/data/talkactions/scripts/add_tutor.lua +++ b/data/talkactions/scripts/add_tutor.lua @@ -1,5 +1,5 @@ function onSay(player, words, param) - if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + if player:getAccountType() <= ACCOUNT_TYPE_SENIORTUTOR then return true end diff --git a/data/talkactions/scripts/attributes.lua b/data/talkactions/scripts/attributes.lua new file mode 100644 index 0000000000..ec47cfa568 --- /dev/null +++ b/data/talkactions/scripts/attributes.lua @@ -0,0 +1,48 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position:getNextPosition(player:getDirection()) + + local tile = Tile(position) + if not tile then + player:sendTextMessage(MESSAGE_INFO_DESCR, "There is no tile in front of you.") + return false + end + + local thing = tile:getTopVisibleThing(player) + if not thing then + player:sendTextMessage(MESSAGE_INFO_DESCR, "There is an empty tile in front of you.") + return false + end + + local separatorPos = param:find(',') + if not separatorPos then + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Usage: %s attribute, value.", words)) + return false + end + + local attribute = string.trim(param:sub(0, separatorPos - 1)) + local value = string.trim(param:sub(separatorPos + 1)) + + if thing:isItem() then + local attributeId = Game.getItemAttributeByName(attribute) + if attributeId == ITEM_ATTRIBUTE_NONE then + player:sendTextMessage(MESSAGE_INFO_DESCR, "Invalid attribute name.") + return false + end + + if not thing:setAttribute(attribute, value) then + player:sendTextMessage(MESSAGE_INFO_DESCR, "Could not set attribute.") + return false + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Attribute %s set to: %s", attribute, thing:getAttribute(attributeId))) + position:sendMagicEffect(CONST_ME_MAGIC_GREEN) + else + player:sendTextMessage(MESSAGE_INFO_DESCR, "Thing in front of you is not supported.") + return false + end +end diff --git a/data/talkactions/scripts/ban.lua b/data/talkactions/scripts/ban.lua index 1b4283f782..44b9231456 100644 --- a/data/talkactions/scripts/ban.lua +++ b/data/talkactions/scripts/ban.lua @@ -20,7 +20,7 @@ function onSay(player, words, param) end local resultId = db.storeQuery("SELECT 1 FROM `account_bans` WHERE `account_id` = " .. accountId) - if resultId ~= false then + if resultId then result.free(resultId) return false end diff --git a/data/talkactions/scripts/buyhouse.lua b/data/talkactions/scripts/buyhouse.lua index 3320556fce..8d146c3c4e 100644 --- a/data/talkactions/scripts/buyhouse.lua +++ b/data/talkactions/scripts/buyhouse.lua @@ -1,10 +1,20 @@ +local config = { + level = 1, + onlyPremium = true +} + function onSay(player, words, param) local housePrice = configManager.getNumber(configKeys.HOUSE_PRICE) if housePrice == -1 then return true end - if not player:isPremium() then + if player:getLevel() < config.level then + player:sendCancelMessage("You need level " .. config.level .. " or higher to buy a house.") + return false + end + + if config.onlyPremium and not player:isPremium() then player:sendCancelMessage("You need a premium account.") return false end diff --git a/data/talkactions/scripts/buyprem.lua b/data/talkactions/scripts/buyprem.lua index efb23a4082..821f758e72 100644 --- a/data/talkactions/scripts/buyprem.lua +++ b/data/talkactions/scripts/buyprem.lua @@ -12,7 +12,7 @@ function onSay(player, words, param) if player:getPremiumDays() <= config.maxDays then if player:removeTotalMoney(config.price) then player:addPremiumDays(config.days) - player:sendTextMessage(MESSAGE_INFO_DESCR, "You have bought " .. config.days .." days of premium account.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have bought " .. config.days .. " days of premium account.") else player:sendCancelMessage("You don't have enough money, " .. config.maxDays .. " days premium account costs " .. config.price .. " gold coins.") player:getPosition():sendMagicEffect(CONST_ME_POFF) diff --git a/data/talkactions/scripts/closeserver.lua b/data/talkactions/scripts/closeserver.lua index 2f7c95ec3e..b09af02f2e 100644 --- a/data/talkactions/scripts/closeserver.lua +++ b/data/talkactions/scripts/closeserver.lua @@ -11,7 +11,7 @@ function onSay(player, words, param) Game.setGameState(GAME_STATE_SHUTDOWN) else Game.setGameState(GAME_STATE_CLOSED) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now closed.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Server is now closed.") end return false end diff --git a/data/talkactions/scripts/create_item.lua b/data/talkactions/scripts/create_item.lua index 4b9a39e34d..6bf1a779cd 100644 --- a/data/talkactions/scripts/create_item.lua +++ b/data/talkactions/scripts/create_item.lua @@ -26,10 +26,14 @@ function onSay(player, words, param) return false end + local keyNumber = 0 local count = tonumber(split[2]) if count then if itemType:isStackable() then count = math.min(10000, math.max(1, count)) + elseif itemType:isKey() then + keyNumber = count + count = 1 elseif not itemType:isFluidContainer() then count = math.min(100, math.max(1, count)) else @@ -51,6 +55,9 @@ function onSay(player, words, param) item:decay() end else + if itemType:isKey() then + result:setAttribute(ITEM_ATTRIBUTE_ACTIONID, keyNumber) + end result:decay() end end diff --git a/data/talkactions/scripts/deathlist.lua b/data/talkactions/scripts/deathlist.lua index 3996b44a1d..c2d96cbc03 100644 --- a/data/talkactions/scripts/deathlist.lua +++ b/data/talkactions/scripts/deathlist.lua @@ -9,9 +9,8 @@ local function getMonthDayEnding(day) return "nd" elseif day == "03" or day == "23" then return "rd" - else - return "th" end + return "th" end local function getMonthString(m) @@ -20,7 +19,7 @@ end function onSay(player, words, param) local resultId = db.storeQuery("SELECT `id`, `name` FROM `players` WHERE `name` = " .. db.escapeString(param)) - if resultId ~= false then + if resultId then local targetGUID = result.getNumber(resultId, "id") local targetName = result.getString(resultId, "name") result.free(resultId) @@ -28,7 +27,7 @@ function onSay(player, words, param) local breakline = "" local resultId = db.storeQuery("SELECT `time`, `level`, `killed_by`, `is_player` FROM `player_deaths` WHERE `player_id` = " .. targetGUID .. " ORDER BY `time` DESC") - if resultId ~= false then + if resultId then repeat if str ~= "" then breakline = "\n" diff --git a/data/talkactions/scripts/force_raid.lua b/data/talkactions/scripts/force_raid.lua index ebdf16230d..99dd2e9a4f 100644 --- a/data/talkactions/scripts/force_raid.lua +++ b/data/talkactions/scripts/force_raid.lua @@ -11,9 +11,9 @@ function onSay(player, words, param) local returnValue = Game.startRaid(param) if returnValue ~= RETURNVALUE_NOERROR then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, Game.getReturnMessage(returnValue)) + player:sendTextMessage(MESSAGE_INFO_DESCR, Game.getReturnMessage(returnValue)) else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid started.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Raid started.") end return false diff --git a/data/talkactions/scripts/info.lua b/data/talkactions/scripts/info.lua index 8969b1cae8..f311bc6009 100644 --- a/data/talkactions/scripts/info.lua +++ b/data/talkactions/scripts/info.lua @@ -15,13 +15,13 @@ function onSay(player, words, param) end local targetIp = target:getIp() - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Name: " .. target:getName()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Access: " .. (target:getGroup():getAccess() and "1" or "0")) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Level: " .. target:getLevel()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Magic Level: " .. target:getMagicLevel()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Speed: " .. target:getSpeed()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Position: " .. string.format("(%0.5d / %0.5d / %0.3d)", target:getPosition().x, target:getPosition().y, target:getPosition().z)) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "IP: " .. Game.convertIpToString(targetIp)) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Name: " .. target:getName()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Access: " .. (target:getGroup():getAccess() and "1" or "0")) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Level: " .. target:getLevel()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Magic Level: " .. target:getMagicLevel()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Speed: " .. target:getSpeed()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Position: " .. string.format("(%0.5d / %0.5d / %0.3d)", target:getPosition().x, target:getPosition().y, target:getPosition().z)) + player:sendTextMessage(MESSAGE_INFO_DESCR, "IP: " .. Game.convertIpToString(targetIp)) local players = {} for _, targetPlayer in ipairs(Game.getPlayers()) do @@ -31,7 +31,7 @@ function onSay(player, words, param) end if #players > 0 then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Other players on same IP: " .. table.concat(players, ", ") .. ".") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Other players on same IP: " .. table.concat(players, ", ") .. ".") end return false end diff --git a/data/talkactions/scripts/ipban.lua b/data/talkactions/scripts/ipban.lua index 42a78fc86f..4d920d13a7 100644 --- a/data/talkactions/scripts/ipban.lua +++ b/data/talkactions/scripts/ipban.lua @@ -6,7 +6,7 @@ function onSay(player, words, param) end local resultId = db.storeQuery("SELECT `name`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param)) - if resultId == false then + if not resultId then return false end @@ -25,8 +25,8 @@ function onSay(player, words, param) end resultId = db.storeQuery("SELECT 1 FROM `ip_bans` WHERE `ip` = " .. targetIp) - if resultId ~= false then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, targetName .. " is already IP banned.") + if resultId then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, targetName .. " is already IP banned.") result.free(resultId) return false end @@ -34,6 +34,6 @@ function onSay(player, words, param) local timeNow = os.time() db.query("INSERT INTO `ip_bans` (`ip`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (" .. targetIp .. ", '', " .. timeNow .. ", " .. timeNow + (ipBanDays * 86400) .. ", " .. player:getGuid() .. ")") - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, targetName .. " has been IP banned.") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, targetName .. " has been IP banned.") return false end diff --git a/data/talkactions/scripts/kills.lua b/data/talkactions/scripts/kills.lua index 360bf62d6e..d04df77d08 100644 --- a/data/talkactions/scripts/kills.lua +++ b/data/talkactions/scripts/kills.lua @@ -1,13 +1,13 @@ function onSay(player, words, param) local fragTime = configManager.getNumber(configKeys.FRAG_TIME) if fragTime <= 0 then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You do not have any unjustified kill.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "You do not have any unjustified kill.") return false end local skullTime = player:getSkullTime() if skullTime <= 0 then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You do not have any unjustified kill.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "You do not have any unjustified kill.") return false end @@ -41,6 +41,6 @@ function onSay(player, words, param) message = message .. seconds .. " seconds." end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, message) + player:sendTextMessage(MESSAGE_INFO_DESCR, message) return false end diff --git a/data/talkactions/scripts/looktype.lua b/data/talkactions/scripts/looktype.lua index 05d677d4ba..d5c749d69e 100644 --- a/data/talkactions/scripts/looktype.lua +++ b/data/talkactions/scripts/looktype.lua @@ -14,7 +14,18 @@ local invalidTypes = { 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 847, 864, 865, 866, 867, 871, 872, 880, 891, 892, 893, - 894, 895, 896, 897, 898 + 894, 895, 896, 897, 898, 911, 912, 917, 930, 941, 942, 946, 953, 954, 983, + 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, + 1009, 1010, 1012, 1014, 1015, 1022, 1028, 1074, 1075, 1080, 1081, 1082, 1083, + 1084, 1085, 1086, 1087, 1089, 1090, 1096, 1097, 1098, 1099, 1100, 1141, 1145, + 1153, 1154, 1155, 1156, 1160, 1170, 1171, 1172, 1176, 1177, 1178, 1182, 1192, + 1193, 1194, 1198, 1215, 1216, 1225, 1226, 1227, 1228, 1235, 1236, 1237, 1238, + 1239, 1240, 1241, 1242, 1250, 1254, 1263, 1267, 1273, 1274, 1287, 1302, 1318, + 1319, 1320, 1327, 1328, 1329, 1330, 1340, 1343, 1345, 1347, 1348, 1349, 1350, + 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1368, + 1369, 1370, 1374, 1375, 1376, 1388, 1392, 1395, 1400, 1402, 1404, 1409, 1410, + 1411, 1420, 1421, 1427, 1429, 1432, 1433, 1434, 1435, 1438, 1442, 1443, 1451, + 1452, 1458, 1462 } function onSay(player, words, param) @@ -23,7 +34,7 @@ function onSay(player, words, param) end local lookType = tonumber(param) - if lookType >= 0 and lookType < 903 and not table.contains(invalidTypes, lookType) then + if lookType >= 0 and lookType < 1469 and not table.contains(invalidTypes, lookType) then local playerOutfit = player:getOutfit() playerOutfit.lookType = lookType player:setOutfit(playerOutfit) diff --git a/data/talkactions/scripts/magiceffect.lua b/data/talkactions/scripts/magiceffect.lua index 51972c228c..0eb873c36c 100644 --- a/data/talkactions/scripts/magiceffect.lua +++ b/data/talkactions/scripts/magiceffect.lua @@ -4,7 +4,7 @@ function onSay(player, words, param) end local effect = tonumber(param) - if(effect ~= nil and effect > 0) then + if effect and effect > 0 then player:getPosition():sendMagicEffect(effect) end diff --git a/data/talkactions/scripts/mccheck.lua b/data/talkactions/scripts/mccheck.lua index 13ffbc970e..6da986ced5 100644 --- a/data/talkactions/scripts/mccheck.lua +++ b/data/talkactions/scripts/mccheck.lua @@ -7,7 +7,7 @@ function onSay(player, words, param) return false end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Multiclient Check List:") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Multiclient Check List:") local ipList = {} local players = Game.getPlayers() @@ -33,7 +33,7 @@ function onSay(player, words, param) tmpPlayer = list[i] message = ("%s, %s [%d]"):format(message, tmpPlayer:getName(), tmpPlayer:getLevel()) end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, message .. ".") + player:sendTextMessage(MESSAGE_INFO_DESCR, message .. ".") end end return false diff --git a/data/talkactions/scripts/online.lua b/data/talkactions/scripts/online.lua index 56879faae0..09c79c9dbf 100644 --- a/data/talkactions/scripts/online.lua +++ b/data/talkactions/scripts/online.lua @@ -1,23 +1,22 @@ local maxPlayersPerMessage = 10 function onSay(player, words, param) - local hasAccess = player:getGroup():getAccess() local players = Game.getPlayers() local onlineList = {} for _, targetPlayer in ipairs(players) do - if hasAccess or not targetPlayer:isInGhostMode() then + if player:canSeeCreature(targetPlayer) then table.insert(onlineList, ("%s [%d]"):format(targetPlayer:getName(), targetPlayer:getLevel())) end end local playersOnline = #onlineList - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, ("%d players online."):format(playersOnline)) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, ("%d players online."):format(playersOnline)) for i = 1, playersOnline, maxPlayersPerMessage do local j = math.min(i + maxPlayersPerMessage - 1, playersOnline) local msg = table.concat(onlineList, ", ", i, j) .. "." - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, msg) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, msg) end return false end diff --git a/data/talkactions/scripts/openserver.lua b/data/talkactions/scripts/openserver.lua index c3896e7280..c9e0a27e86 100644 --- a/data/talkactions/scripts/openserver.lua +++ b/data/talkactions/scripts/openserver.lua @@ -8,6 +8,6 @@ function onSay(player, words, param) end Game.setGameState(GAME_STATE_NORMAL) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now open.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Server is now open.") return false end diff --git a/data/talkactions/scripts/position.lua b/data/talkactions/scripts/position.lua index 299ce6e566..b5f373ecc9 100644 --- a/data/talkactions/scripts/position.lua +++ b/data/talkactions/scripts/position.lua @@ -4,7 +4,7 @@ function onSay(player, words, param) player:teleportTo(Position(split[1], split[2], split[3])) else local position = player:getPosition() - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") end return false end diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua index f11e9d51e8..ae889a88db 100644 --- a/data/talkactions/scripts/reload.lua +++ b/data/talkactions/scripts/reload.lua @@ -43,7 +43,7 @@ local reloadTypes = { ["raids"] = RELOAD_TYPE_RAIDS, ["spell"] = RELOAD_TYPE_SPELLS, - ["spells"] = RELOAD_TYPE_SPELLS, + ["spells"] = RELOAD_TYPE_SPELLS, ["talk"] = RELOAD_TYPE_TALKACTIONS, ["talkaction"] = RELOAD_TYPE_TALKACTIONS, @@ -69,7 +69,7 @@ function onSay(player, words, param) local reloadType = reloadTypes[param:lower()] if not reloadType then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Reload type not found.") return false end @@ -79,6 +79,6 @@ function onSay(player, words, param) end Game.reload(reloadType) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Reloaded %s.", param:lower())) + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Reloaded %s.", param:lower())) return false end diff --git a/data/talkactions/scripts/remove_tutor.lua b/data/talkactions/scripts/remove_tutor.lua index c233fbc904..033eaf5ec2 100644 --- a/data/talkactions/scripts/remove_tutor.lua +++ b/data/talkactions/scripts/remove_tutor.lua @@ -1,10 +1,10 @@ function onSay(player, words, param) - if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + if player:getAccountType() <= ACCOUNT_TYPE_SENIORTUTOR then return true end local resultId = db.storeQuery("SELECT `name`, `account_id`, (SELECT `type` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `account_type` FROM `players` WHERE `name` = " .. db.escapeString(param)) - if resultId == false then + if not resultId then player:sendCancelMessage("A player with that name does not exist.") return false end diff --git a/data/talkactions/scripts/serverinfo.lua b/data/talkactions/scripts/serverinfo.lua index 6b26b9814d..fc6cc7a713 100644 --- a/data/talkactions/scripts/serverinfo.lua +++ b/data/talkactions/scripts/serverinfo.lua @@ -1,5 +1,5 @@ function onSay(player, words, param) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server Info:" + player:sendTextMessage(MESSAGE_INFO_DESCR, "Server Info:" .. "\nExp rate: " .. Game.getExperienceStage(player:getLevel()) .. "\nSkill rate: " .. configManager.getNumber(configKeys.RATE_SKILL) .. "\nMagic rate: " .. configManager.getNumber(configKeys.RATE_MAGIC) diff --git a/data/talkactions/scripts/unban.lua b/data/talkactions/scripts/unban.lua index f0cbffa062..7e47f4d347 100644 --- a/data/talkactions/scripts/unban.lua +++ b/data/talkactions/scripts/unban.lua @@ -4,7 +4,7 @@ function onSay(player, words, param) end local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param)) - if resultId == false then + if not resultId then return false end diff --git a/data/talkactions/scripts/uptime.lua b/data/talkactions/scripts/uptime.lua index 7c0e291ede..b77f76daac 100644 --- a/data/talkactions/scripts/uptime.lua +++ b/data/talkactions/scripts/uptime.lua @@ -3,6 +3,6 @@ function onSay(player, words, param) local hours = math.floor(uptime / 3600) local minutes = math.floor((uptime - (3600 * hours)) / 60) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") return false end diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml index e357119534..6c4896a11a 100644 --- a/data/talkactions/talkactions.xml +++ b/data/talkactions/talkactions.xml @@ -1,6 +1,7 @@ + @@ -41,7 +42,7 @@ - + diff --git a/data/weapons/scripts/diamond_arrow.lua b/data/weapons/scripts/diamond_arrow.lua new file mode 100644 index 0000000000..f7ed524e59 --- /dev/null +++ b/data/weapons/scripts/diamond_arrow.lua @@ -0,0 +1,19 @@ +local area = createCombatArea({ + {0, 1, 1, 1, 0}, + {1, 1, 1, 1, 1}, + {1, 1, 3, 1, 1}, + {1, 1, 1, 1, 1}, + {0, 1, 1, 1, 0}, +}) + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_DIAMONDARROW) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) +combat:setFormula(COMBAT_FORMULA_SKILL, 0, 0, 1, 0) +combat:setArea(area) + +function onUseWeapon(player, variant) + return combat:execute(player, variant) +end diff --git a/data/weapons/weapons.xml b/data/weapons/weapons.xml index eaea81541b..7173ae1395 100644 --- a/data/weapons/weapons.xml +++ b/data/weapons/weapons.xml @@ -40,6 +40,9 @@ + + + @@ -90,6 +93,9 @@ + + + @@ -587,13 +593,13 @@ - - - - - - - + + + + + + + @@ -612,12 +618,18 @@ - + - + + + + + + + diff --git a/data/world/forgotten-house.xml b/data/world/forgotten-house.xml index 6ff8612506..0d8d4ac45f 100644 --- a/data/world/forgotten-house.xml +++ b/data/world/forgotten-house.xml @@ -52,7 +52,7 @@ - + diff --git a/data/world/forgotten-spawn.xml b/data/world/forgotten-spawn.xml index 5c9e2ece50..f7a2d8cb61 100644 --- a/data/world/forgotten-spawn.xml +++ b/data/world/forgotten-spawn.xml @@ -122,12 +122,10 @@ - - - - - - + + + + @@ -379,7 +377,10 @@ - + + + + @@ -744,7 +745,7 @@ - + diff --git a/data/world/forgotten.otbm b/data/world/forgotten.otbm index f08ab6e479..6457d721a1 100644 Binary files a/data/world/forgotten.otbm and b/data/world/forgotten.otbm differ diff --git a/schema.sql b/schema.sql index eb1c9d963f..718dc6b89d 100644 --- a/schema.sql +++ b/schema.sql @@ -27,6 +27,11 @@ CREATE TABLE IF NOT EXISTS `players` ( `looklegs` int NOT NULL DEFAULT '0', `looktype` int NOT NULL DEFAULT '136', `lookaddons` int NOT NULL DEFAULT '0', + `lookmount` int NOT NULL DEFAULT '0', + `lookmounthead` int NOT NULL DEFAULT '0', + `lookmountbody` int NOT NULL DEFAULT '0', + `lookmountlegs` int NOT NULL DEFAULT '0', + `lookmountfeet` int NOT NULL DEFAULT '0', `direction` tinyint unsigned NOT NULL DEFAULT '2', `maglevel` int NOT NULL DEFAULT '0', `mana` int NOT NULL DEFAULT '0', @@ -37,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `players` ( `posx` int NOT NULL DEFAULT '0', `posy` int NOT NULL DEFAULT '0', `posz` int NOT NULL DEFAULT '0', - `conditions` blob NOT NULL, + `conditions` blob DEFAULT NULL, `cap` int NOT NULL DEFAULT '400', `sex` int NOT NULL DEFAULT '0', `lastlogin` bigint unsigned NOT NULL DEFAULT '0', @@ -96,6 +101,14 @@ CREATE TABLE IF NOT EXISTS `account_ban_history` ( FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; +CREATE TABLE IF NOT EXISTS `account_storage` ( + `account_id` int NOT NULL, + `key` int unsigned NOT NULL, + `value` int NOT NULL, + PRIMARY KEY (`account_id`, `key`), + FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; + CREATE TABLE IF NOT EXISTS `ip_bans` ( `ip` int unsigned NOT NULL, `reason` varchar(255) NOT NULL, @@ -225,7 +238,7 @@ CREATE TABLE IF NOT EXISTS `market_history` ( `sale` tinyint NOT NULL DEFAULT '0', `itemtype` smallint unsigned NOT NULL, `amount` smallint unsigned NOT NULL, - `price` int unsigned NOT NULL DEFAULT '0', + `price` bigint unsigned NOT NULL DEFAULT '0', `expires_at` bigint unsigned NOT NULL, `inserted` bigint unsigned NOT NULL, `state` tinyint unsigned NOT NULL, @@ -242,7 +255,7 @@ CREATE TABLE IF NOT EXISTS `market_offers` ( `amount` smallint unsigned NOT NULL, `created` bigint unsigned NOT NULL, `anonymous` tinyint NOT NULL DEFAULT '0', - `price` int unsigned NOT NULL DEFAULT '0', + `price` bigint unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `sale` (`sale`,`itemtype`), KEY `created` (`created`), @@ -349,7 +362,7 @@ CREATE TABLE IF NOT EXISTS `towns` ( UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '28'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '32'), ('players_record', '0'); DROP TRIGGER IF EXISTS `ondelete_players`; DROP TRIGGER IF EXISTS `oncreate_guilds`; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5540fa584a..a9da43e2c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,12 +22,11 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/fileloader.cpp ${CMAKE_CURRENT_LIST_DIR}/game.cpp ${CMAKE_CURRENT_LIST_DIR}/globalevent.cpp - ${CMAKE_CURRENT_LIST_DIR}/guild.cpp ${CMAKE_CURRENT_LIST_DIR}/groups.cpp + ${CMAKE_CURRENT_LIST_DIR}/guild.cpp ${CMAKE_CURRENT_LIST_DIR}/house.cpp ${CMAKE_CURRENT_LIST_DIR}/housetile.cpp ${CMAKE_CURRENT_LIST_DIR}/inbox.cpp - ${CMAKE_CURRENT_LIST_DIR}/ioguild.cpp ${CMAKE_CURRENT_LIST_DIR}/iologindata.cpp ${CMAKE_CURRENT_LIST_DIR}/iomap.cpp ${CMAKE_CURRENT_LIST_DIR}/iomapserialize.cpp @@ -48,6 +47,7 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/party.cpp ${CMAKE_CURRENT_LIST_DIR}/player.cpp + ${CMAKE_CURRENT_LIST_DIR}/podium.cpp ${CMAKE_CURRENT_LIST_DIR}/position.cpp ${CMAKE_CURRENT_LIST_DIR}/protocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp @@ -58,8 +58,8 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/raids.cpp ${CMAKE_CURRENT_LIST_DIR}/rsa.cpp ${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp - ${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp ${CMAKE_CURRENT_LIST_DIR}/script.cpp + ${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp ${CMAKE_CURRENT_LIST_DIR}/server.cpp ${CMAKE_CURRENT_LIST_DIR}/signals.cpp ${CMAKE_CURRENT_LIST_DIR}/spawn.cpp @@ -78,3 +78,94 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/xtea.cpp PARENT_SCOPE) +set(tfs_HDR + ${CMAKE_CURRENT_LIST_DIR}/otpch.h + ${CMAKE_CURRENT_LIST_DIR}/account.h + ${CMAKE_CURRENT_LIST_DIR}/actions.h + ${CMAKE_CURRENT_LIST_DIR}/ban.h + ${CMAKE_CURRENT_LIST_DIR}/baseevents.h + ${CMAKE_CURRENT_LIST_DIR}/bed.h + ${CMAKE_CURRENT_LIST_DIR}/chat.h + ${CMAKE_CURRENT_LIST_DIR}/combat.h + ${CMAKE_CURRENT_LIST_DIR}/condition.h + ${CMAKE_CURRENT_LIST_DIR}/configmanager.h + ${CMAKE_CURRENT_LIST_DIR}/connection.h + ${CMAKE_CURRENT_LIST_DIR}/const.h + ${CMAKE_CURRENT_LIST_DIR}/container.h + ${CMAKE_CURRENT_LIST_DIR}/creatureevent.h + ${CMAKE_CURRENT_LIST_DIR}/creature.h + ${CMAKE_CURRENT_LIST_DIR}/cylinder.h + ${CMAKE_CURRENT_LIST_DIR}/database.h + ${CMAKE_CURRENT_LIST_DIR}/databasemanager.h + ${CMAKE_CURRENT_LIST_DIR}/databasetasks.h + ${CMAKE_CURRENT_LIST_DIR}/definitions.h + ${CMAKE_CURRENT_LIST_DIR}/depotchest.h + ${CMAKE_CURRENT_LIST_DIR}/depotlocker.h + ${CMAKE_CURRENT_LIST_DIR}/enums.h + ${CMAKE_CURRENT_LIST_DIR}/events.h + ${CMAKE_CURRENT_LIST_DIR}/fileloader.h + ${CMAKE_CURRENT_LIST_DIR}/game.h + ${CMAKE_CURRENT_LIST_DIR}/globalevent.h + ${CMAKE_CURRENT_LIST_DIR}/groups.h + ${CMAKE_CURRENT_LIST_DIR}/guild.h + ${CMAKE_CURRENT_LIST_DIR}/house.h + ${CMAKE_CURRENT_LIST_DIR}/housetile.h + ${CMAKE_CURRENT_LIST_DIR}/inbox.h + ${CMAKE_CURRENT_LIST_DIR}/iologindata.h + ${CMAKE_CURRENT_LIST_DIR}/iomap.h + ${CMAKE_CURRENT_LIST_DIR}/iomapserialize.h + ${CMAKE_CURRENT_LIST_DIR}/iomarket.h + ${CMAKE_CURRENT_LIST_DIR}/item.h + ${CMAKE_CURRENT_LIST_DIR}/itemloader.h + ${CMAKE_CURRENT_LIST_DIR}/items.h + ${CMAKE_CURRENT_LIST_DIR}/lockfree.h + ${CMAKE_CURRENT_LIST_DIR}/luascript.h + ${CMAKE_CURRENT_LIST_DIR}/luavariant.h + ${CMAKE_CURRENT_LIST_DIR}/mailbox.h + ${CMAKE_CURRENT_LIST_DIR}/map.h + ${CMAKE_CURRENT_LIST_DIR}/monster.h + ${CMAKE_CURRENT_LIST_DIR}/monsters.h + ${CMAKE_CURRENT_LIST_DIR}/mounts.h + ${CMAKE_CURRENT_LIST_DIR}/movement.h + ${CMAKE_CURRENT_LIST_DIR}/networkmessage.h + ${CMAKE_CURRENT_LIST_DIR}/npc.h + ${CMAKE_CURRENT_LIST_DIR}/outfit.h + ${CMAKE_CURRENT_LIST_DIR}/outputmessage.h + ${CMAKE_CURRENT_LIST_DIR}/party.h + ${CMAKE_CURRENT_LIST_DIR}/player.h + ${CMAKE_CURRENT_LIST_DIR}/podium.h + ${CMAKE_CURRENT_LIST_DIR}/position.h + ${CMAKE_CURRENT_LIST_DIR}/protocolgame.h + ${CMAKE_CURRENT_LIST_DIR}/protocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocollogin.h + ${CMAKE_CURRENT_LIST_DIR}/protocolold.h + ${CMAKE_CURRENT_LIST_DIR}/protocolstatus.h + ${CMAKE_CURRENT_LIST_DIR}/pugicast.h + ${CMAKE_CURRENT_LIST_DIR}/quests.h + ${CMAKE_CURRENT_LIST_DIR}/raids.h + ${CMAKE_CURRENT_LIST_DIR}/rsa.h + ${CMAKE_CURRENT_LIST_DIR}/scheduler.h + ${CMAKE_CURRENT_LIST_DIR}/script.h + ${CMAKE_CURRENT_LIST_DIR}/scriptmanager.h + ${CMAKE_CURRENT_LIST_DIR}/server.h + ${CMAKE_CURRENT_LIST_DIR}/signals.h + ${CMAKE_CURRENT_LIST_DIR}/spawn.h + ${CMAKE_CURRENT_LIST_DIR}/spectators.h + ${CMAKE_CURRENT_LIST_DIR}/spells.h + ${CMAKE_CURRENT_LIST_DIR}/storeinbox.h + ${CMAKE_CURRENT_LIST_DIR}/talkaction.h + ${CMAKE_CURRENT_LIST_DIR}/tasks.h + ${CMAKE_CURRENT_LIST_DIR}/teleport.h + ${CMAKE_CURRENT_LIST_DIR}/thing.h + ${CMAKE_CURRENT_LIST_DIR}/thread_holder_base.h + ${CMAKE_CURRENT_LIST_DIR}/tile.h + ${CMAKE_CURRENT_LIST_DIR}/tools.h + ${CMAKE_CURRENT_LIST_DIR}/town.h + ${CMAKE_CURRENT_LIST_DIR}/trashholder.h + ${CMAKE_CURRENT_LIST_DIR}/vocation.h + ${CMAKE_CURRENT_LIST_DIR}/weapons.h + ${CMAKE_CURRENT_LIST_DIR}/wildcardtree.h + ${CMAKE_CURRENT_LIST_DIR}/xtea.h + ) + +add_custom_target(format COMMAND /usr/bin/clang-format -style=file -i ${tfs_HDR} ${tfs_SRC}) diff --git a/src/account.h b/src/account.h index 2c4901ed69..c3818b1e4f 100644 --- a/src/account.h +++ b/src/account.h @@ -1,28 +1,13 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F -#define FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F +#ifndef FS_ACCOUNT_H +#define FS_ACCOUNT_H #include "enums.h" -struct Account { +struct Account +{ std::vector characters; std::string name; std::string key; @@ -33,4 +18,4 @@ struct Account { Account() = default; }; -#endif +#endif // FS_ACCOUNT_H diff --git a/src/actions.cpp b/src/actions.cpp index af7b493c51..2114b2952e 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1,29 +1,15 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "actions.h" + #include "bed.h" #include "configmanager.h" #include "container.h" #include "game.h" +#include "housetile.h" #include "pugicast.h" #include "spells.h" @@ -32,20 +18,13 @@ extern Spells* g_spells; extern Actions* g_actions; extern ConfigManager g_config; -Actions::Actions() : - scriptInterface("Action Interface") -{ - scriptInterface.initState(); -} +Actions::Actions() : scriptInterface("Action Interface") { scriptInterface.initState(); } -Actions::~Actions() -{ - clear(false); -} +Actions::~Actions() { clear(false); } void Actions::clearMap(ActionUseMap& map, bool fromLua) { - for (auto it = map.begin(); it != map.end(); ) { + for (auto it = map.begin(); it != map.end();) { if (fromLua == it->second.fromLua) { it = map.erase(it); } else { @@ -63,19 +42,13 @@ void Actions::clear(bool fromLua) reInitState(fromLua); } -LuaScriptInterface& Actions::getScriptInterface() -{ - return scriptInterface; -} +LuaScriptInterface& Actions::getScriptInterface() { return scriptInterface; } -std::string Actions::getScriptBaseName() const -{ - return "actions"; -} +std::string Actions::getScriptBaseName() const { return "actions"; } Event_ptr Actions::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "action") != 0) { + if (!caseInsensitiveEqual(nodeName, "action")) { return nullptr; } return Event_ptr(new Action(&scriptInterface)); @@ -83,7 +56,7 @@ Event_ptr Actions::getEvent(const std::string& nodeName) bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) { - Action_ptr action{static_cast(event.release())}; //event is guaranteed to be an Action + Action_ptr action{static_cast(event.release())}; // event is guaranteed to be an Action pugi::xml_attribute attr; if ((attr = node.attribute("itemid"))) { @@ -93,7 +66,8 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) for (const auto& id : idList) { auto result = useItemMap.emplace(id, std::move(*action)); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id + << std::endl; success = false; } } @@ -112,14 +86,16 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) auto result = useItemMap.emplace(iterId, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId + << " in fromid: " << fromId << ", toid: " << toId << std::endl; } bool success = result.second; while (++iterId <= toId) { result = useItemMap.emplace(iterId, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId + << " in fromid: " << fromId << ", toid: " << toId << std::endl; continue; } success = true; @@ -132,7 +108,8 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) for (const auto& uid : uidList) { auto result = uniqueItemMap.emplace(uid, std::move(*action)); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << uid << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << uid + << std::endl; success = false; } } @@ -141,7 +118,8 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) } else if ((attr = node.attribute("fromuid"))) { pugi::xml_attribute toUidAttribute = node.attribute("touid"); if (!toUidAttribute) { - std::cout << "[Warning - Actions::registerEvent] Missing touid in fromuid: " << attr.as_string() << std::endl; + std::cout << "[Warning - Actions::registerEvent] Missing touid in fromuid: " << attr.as_string() + << std::endl; return false; } @@ -151,14 +129,16 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) auto result = uniqueItemMap.emplace(iterUid, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid + << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; } bool success = result.second; while (++iterUid <= toUid) { result = uniqueItemMap.emplace(iterUid, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid + << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; continue; } success = true; @@ -171,7 +151,8 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) for (const auto& aid : aidList) { auto result = actionItemMap.emplace(aid, std::move(*action)); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid + << std::endl; success = false; } } @@ -180,7 +161,8 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) } else if ((attr = node.attribute("fromaid"))) { pugi::xml_attribute toAidAttribute = node.attribute("toaid"); if (!toAidAttribute) { - std::cout << "[Warning - Actions::registerEvent] Missing toaid in fromaid: " << attr.as_string() << std::endl; + std::cout << "[Warning - Actions::registerEvent] Missing toaid in fromaid: " << attr.as_string() + << std::endl; return false; } @@ -190,14 +172,16 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) auto result = actionItemMap.emplace(iterAid, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid + << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; } bool success = result.second; while (++iterAid <= toAid) { result = actionItemMap.emplace(iterAid, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid + << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; continue; } success = true; @@ -209,68 +193,41 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) bool Actions::registerLuaEvent(Action* event) { - Action_ptr action{ event }; - if (action->getItemIdRange().size() > 0) { - if (action->getItemIdRange().size() == 1) { - auto id = action->getItemIdRange().front(); - auto result = useItemMap.emplace(id, std::move(*action)); + Action_ptr action{event}; + if (!action->getItemIdRange().empty()) { + const auto& range = action->getItemIdRange(); + for (auto id : range) { + auto result = useItemMap.emplace(id, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << id << std::endl; - } - return result.second; - } else { - auto v = action->getItemIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - auto result = useItemMap.emplace(*i, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << *i << " in range from id: " << v.front() << ", to id: " << v.at(v.size() - 1) << std::endl; - continue; - } + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << id + << " in range from id: " << range.front() << ", to id: " << range.back() << std::endl; } - return true; } - } else if (action->getUniqueIdRange().size() > 0) { - if (action->getUniqueIdRange().size() == 1) { - auto uid = action->getUniqueIdRange().front(); - auto result = uniqueItemMap.emplace(uid, std::move(*action)); + return true; + } else if (!action->getUniqueIdRange().empty()) { + const auto& range = action->getUniqueIdRange(); + for (auto id : range) { + auto result = uniqueItemMap.emplace(id, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << uid << std::endl; + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << id + << " in range from uid: " << range.front() << ", to uid: " << range.back() << std::endl; } - return result.second; - } else { - auto v = action->getUniqueIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - auto result = uniqueItemMap.emplace(*i, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << *i << " in range from uid: " << v.front() << ", to uid: " << v.at(v.size() - 1) << std::endl; - continue; - } - } - return true; } - } else if (action->getActionIdRange().size() > 0) { - if (action->getActionIdRange().size() == 1) { - auto aid = action->getActionIdRange().front(); - auto result = actionItemMap.emplace(aid, std::move(*action)); + return true; + } else if (!action->getActionIdRange().empty()) { + const auto& range = action->getActionIdRange(); + for (auto id : range) { + auto result = actionItemMap.emplace(id, *action); if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << aid << std::endl; - } - return result.second; - } else { - auto v = action->getActionIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - auto result = actionItemMap.emplace(*i, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << *i << " in range from aid: " << v.front() << ", to aid: " << v.at(v.size() - 1) << std::endl; - continue; - } + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << id + << " in range from aid: " << range.front() << ", to aid: " << range.back() << std::endl; } - return true; } - } else { - std::cout << "[Warning - Actions::registerLuaEvent] There is no id / aid / uid set for this event" << std::endl; - return false; + return true; } + + std::cout << "[Warning - Actions::registerLuaEvent] There is no id / aid / uid set for this event" << std::endl; + return false; } ReturnValue Actions::canUse(const Player* player, const Position& pos) @@ -308,11 +265,11 @@ ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, return creaturePos.z > toPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS; } - if (!Position::areInRange<7, 5>(toPos, creaturePos)) { + if (!Position::areInRange(toPos, creaturePos)) { return RETURNVALUE_TOOFARAWAY; } - if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos)) { + if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos, checkLineOfSight, checkFloor)) { return RETURNVALUE_CANNOTTHROW; } @@ -340,7 +297,7 @@ Action* Actions::getAction(const Item* item) return &it->second; } - //rune items + // rune items return g_spells->getRuneSpell(item->getID()); } @@ -348,7 +305,7 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ { if (Door* door = item->getDoor()) { if (!door->canUse(player)) { - return RETURNVALUE_CANNOTUSETHISOBJECT; + return RETURNVALUE_NOTPOSSIBLE; } } @@ -362,15 +319,20 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ if (item->isRemoved()) { return RETURNVALUE_CANNOTUSETHISOBJECT; } - } else if (action->function) { - if (action->function(player, item, pos, nullptr, pos, isHotkey)) { - return RETURNVALUE_NOERROR; - } + } else if (action->function && action->function(player, item, pos, nullptr, pos, isHotkey)) { + return RETURNVALUE_NOERROR; } } if (BedItem* bed = item->getBed()) { if (!bed->canUse(player)) { + if (!bed->getHouse()) { + return RETURNVALUE_YOUCANNOTUSETHISBED; + } + + if (!player->isPremium()) { + return RETURNVALUE_YOUNEEDPREMIUMACCOUNT; + } return RETURNVALUE_CANNOTUSETHISOBJECT; } @@ -385,12 +347,11 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ if (Container* container = item->getContainer()) { Container* openContainer; - //depot container + // depot container if (DepotLocker* depot = container->getDepotLocker()) { - DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId()); - myDepotLocker->setParent(depot->getParent()->getTile()); - openContainer = myDepotLocker; - player->setLastDepotId(depot->getDepotId()); + DepotLocker& myDepotLocker = player->getDepotLocker(); + myDepotLocker.setParent(depot->getParent()->getTile()); + openContainer = &myDepotLocker; } else { openContainer = container; } @@ -400,14 +361,14 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ return RETURNVALUE_YOUARENOTTHEOWNER; } - //open/close container + // open/close container int32_t oldContainerId = player->getContainerID(openContainer); - if (oldContainerId != -1) { - player->onCloseContainer(openContainer); - player->closeContainer(oldContainerId); - } else { + if (oldContainerId == -1) { player->addContainer(index, openContainer); player->onSendContainer(openContainer); + } else { + player->onCloseContainer(openContainer); + player->closeContainer(oldContainerId); } return RETURNVALUE_NOERROR; @@ -429,29 +390,63 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ return RETURNVALUE_CANNOTUSETHISOBJECT; } +static void showUseHotkeyMessage(Player* player, const Item* item, uint32_t count) +{ + const ItemType& it = Item::items[item->getID()]; + if (!it.showCount) { + player->sendTextMessage(MESSAGE_HOTKEY_PRESSED, fmt::format("Using one of {:s}...", item->getName())); + } else if (count == 1) { + player->sendTextMessage(MESSAGE_HOTKEY_PRESSED, fmt::format("Using the last {:s}...", item->getName())); + } else { + player->sendTextMessage(MESSAGE_HOTKEY_PRESSED, + fmt::format("Using one of {:d} {:s}...", count, item->getPluralName())); + } +} + bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey) { - player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL)); - player->stopWalk(); + int32_t cooldown = g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL); + player->setNextAction(OTSYS_TIME() + cooldown); + player->sendUseItemCooldown(cooldown); + if (item->isSupply()) { + player->sendSupplyUsed(item->getClientID()); + } if (isHotkey) { uint16_t subType = item->getSubType(); - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), subType != item->getItemCount() ? subType : -1)); + showUseHotkeyMessage(player, item, + player->getItemTypeCount(item->getID(), subType != item->getItemCount() ? subType : -1)); + } + + if (g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { + if (const HouseTile* const houseTile = dynamic_cast(item->getTile())) { + if (!item->getTopParent()->getCreature() && !houseTile->getHouse()->isInvited(player)) { + player->sendCancelMessage(RETURNVALUE_PLAYERISNOTINVITED); + return false; + } + } } ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey); + if (ret == RETURNVALUE_YOUCANNOTUSETHISBED) { + g_game.internalCreatureSay(player, TALKTYPE_MONSTER_SAY, getReturnMessage(ret), false); + return false; + } + if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return false; } + return true; } -bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos, - uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature/* = nullptr*/) +bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, + bool isHotkey, Creature* creature /* = nullptr*/) { - player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL)); - player->stopWalk(); + int32_t cooldown = g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL); + player->setNextAction(OTSYS_TIME() + cooldown); + player->sendUseItemCooldown(cooldown); Action* action = getAction(item); if (!action) { @@ -467,35 +462,34 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& if (isHotkey) { uint16_t subType = item->getSubType(); - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), subType != item->getItemCount() ? subType : -1)); + showUseHotkeyMessage(player, item, + player->getItemTypeCount(item->getID(), subType != item->getItemCount() ? subType : -1)); } - if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) { - if (!action->hasOwnErrorHandler()) { - player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + if (g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { + if (const HouseTile* const houseTile = dynamic_cast(item->getTile())) { + if (!item->getTopParent()->getCreature() && !houseTile->getHouse()->isInvited(player)) { + player->sendCancelMessage(RETURNVALUE_PLAYERISNOTINVITED); + return false; + } } - return false; } - return true; -} -void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t count) -{ - std::ostringstream ss; + if (action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, + isHotkey)) { + return true; + } - const ItemType& it = Item::items[item->getID()]; - if (!it.showCount) { - ss << "Using one of " << item->getName() << "..."; - } else if (count == 1) { - ss << "Using the last " << item->getName() << "..."; - } else { - ss << "Using one of " << count << ' ' << item->getPluralName() << "..."; + if (!action->hasOwnErrorHandler()) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); } - player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + return false; } Action::Action(LuaScriptInterface* interface) : - Event(interface), function(nullptr), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {} + Event(interface), function(nullptr), allowFarUse(false), checkFloor(true), checkLineOfSight(true) +{} bool Action::configureEvent(const pugi::xml_node& node) { @@ -521,24 +515,21 @@ namespace { bool enterMarket(Player* player, Item*, const Position&, Thing*, const Position&, bool) { - if (player->getLastDepotId() == -1) { - return false; - } - - player->sendMarketEnter(player->getLastDepotId()); + player->sendMarketEnter(); return true; } -} +} // namespace bool Action::loadFunction(const pugi::xml_attribute& attr, bool isScripted) { const char* functionName = attr.as_string(); - if (strcasecmp(functionName, "market") == 0) { + if (caseInsensitiveEqual(functionName, "market")) { function = enterMarket; } else { if (!isScripted) { - std::cout << "[Warning - Action::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + std::cout << "[Warning - Action::loadFunction] Function \"" << functionName << "\" does not exist." + << std::endl; return false; } } @@ -549,18 +540,14 @@ bool Action::loadFunction(const pugi::xml_attribute& attr, bool isScripted) return true; } -std::string Action::getScriptEventName() const -{ - return "onUse"; -} +std::string Action::getScriptEventName() const { return "onUse"; } ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos) { - if (!allowFarUse) { - return g_actions->canUse(player, toPos); - } else { + if (allowFarUse) { return g_actions->canUseFar(player, toPos, checkLineOfSight, checkFloor); } + return g_actions->canUse(player, toPos); } Thing* Action::getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const @@ -571,9 +558,10 @@ Thing* Action::getTarget(Player* player, Creature* targetCreature, const Positio return g_game.internalGetThing(player, toPosition, toStackPos, 0, STACKPOS_USETARGET); } -bool Action::executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) +bool Action::executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, + const Position& toPosition, bool isHotkey) { - //onUse(player, item, fromPosition, target, toPosition, isHotkey) + // onUse(player, item, fromPosition, target, toPosition, isHotkey) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl; return false; diff --git a/src/actions.h b/src/actions.h index 69ca1a9950..cd4b08c97b 100644 --- a/src/actions.h +++ b/src/actions.h @@ -1,24 +1,8 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6 -#define FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_ACTIONS_H +#define FS_ACTIONS_H #include "baseevents.h" #include "enums.h" @@ -26,119 +10,98 @@ class Action; using Action_ptr = std::unique_ptr; -using ActionFunction = std::function; +using ActionFunction = std::function; class Action : public Event { - public: - explicit Action(LuaScriptInterface* interface); - - bool configureEvent(const pugi::xml_node& node) override; - bool loadFunction(const pugi::xml_attribute& attr, bool isScripted) override; - - //scripting - virtual bool executeUse(Player* player, Item* item, const Position& fromPosition, - Thing* target, const Position& toPosition, bool isHotkey); - - bool getAllowFarUse() const { - return allowFarUse; - } - void setAllowFarUse(bool v) { - allowFarUse = v; - } - - bool getCheckLineOfSight() const { - return checkLineOfSight; - } - void setCheckLineOfSight(bool v) { - checkLineOfSight = v; - } - - bool getCheckFloor() const { - return checkFloor; - } - void setCheckFloor(bool v) { - checkFloor = v; - } - - std::vector getItemIdRange() { - return ids; - } - void addItemId(uint16_t id) { - ids.emplace_back(id); - } - - std::vector getUniqueIdRange() { - return uids; - } - void addUniqueId(uint16_t id) { - uids.emplace_back(id); - } - - std::vector getActionIdRange() { - return aids; - } - void addActionId(uint16_t id) { - aids.emplace_back(id); - } - - virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); - virtual bool hasOwnErrorHandler() { - return false; - } - virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const; - - ActionFunction function; - - private: - std::string getScriptEventName() const override; - - bool allowFarUse = false; - bool checkFloor = true; - bool checkLineOfSight = true; - std::vector ids; - std::vector uids; - std::vector aids; +public: + explicit Action(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute& attr, bool isScripted) override; + + // scripting + virtual bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, + const Position& toPosition, bool isHotkey); + + bool getAllowFarUse() const { return allowFarUse; } + void setAllowFarUse(bool v) { allowFarUse = v; } + + bool getCheckLineOfSight() const { return checkLineOfSight; } + void setCheckLineOfSight(bool v) { checkLineOfSight = v; } + + bool getCheckFloor() const { return checkFloor; } + void setCheckFloor(bool v) { checkFloor = v; } + + void clearItemIdRange() { return ids.clear(); } + const std::vector& getItemIdRange() const { return ids; } + void addItemId(uint16_t id) { ids.emplace_back(id); } + + void clearUniqueIdRange() { return uids.clear(); } + const std::vector& getUniqueIdRange() const { return uids; } + void addUniqueId(uint16_t id) { uids.emplace_back(id); } + + void clearActionIdRange() { return aids.clear(); } + const std::vector& getActionIdRange() const { return aids; } + void addActionId(uint16_t id) { aids.emplace_back(id); } + + virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); + virtual bool hasOwnErrorHandler() { return false; } + virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, + uint8_t toStackPos) const; + + ActionFunction function; + +private: + std::string getScriptEventName() const override; + + bool allowFarUse = false; + bool checkFloor = true; + bool checkLineOfSight = true; + std::vector ids; + std::vector uids; + std::vector aids; }; class Actions final : public BaseEvents { - public: - Actions(); - ~Actions(); +public: + Actions(); + ~Actions(); - // non-copyable - Actions(const Actions&) = delete; - Actions& operator=(const Actions&) = delete; + // non-copyable + Actions(const Actions&) = delete; + Actions& operator=(const Actions&) = delete; - bool useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); - bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature = nullptr); + bool useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); + bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, + bool isHotkey, Creature* creature = nullptr); - ReturnValue canUse(const Player* player, const Position& pos); - ReturnValue canUse(const Player* player, const Position& pos, const Item* item); - ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor); + ReturnValue canUse(const Player* player, const Position& pos); + ReturnValue canUse(const Player* player, const Position& pos, const Item* item); + ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor); - bool registerLuaEvent(Action* event); - void clear(bool fromLua) override final; + bool registerLuaEvent(Action* event); + void clear(bool fromLua) override final; - private: - ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); - static void showUseHotkeyMessage(Player* player, const Item* item, uint32_t count); +private: + ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - using ActionUseMap = std::map; - ActionUseMap useItemMap; - ActionUseMap uniqueItemMap; - ActionUseMap actionItemMap; + using ActionUseMap = std::map; + ActionUseMap useItemMap; + ActionUseMap uniqueItemMap; + ActionUseMap actionItemMap; - Action* getAction(const Item* item); - void clearMap(ActionUseMap& map, bool fromLua); + Action* getAction(const Item* item); + void clearMap(ActionUseMap& map, bool fromLua); - LuaScriptInterface scriptInterface; + LuaScriptInterface scriptInterface; }; -#endif +#endif // FS_ACTIONS_H diff --git a/src/ban.cpp b/src/ban.cpp index 0b75cebbab..efa2838ad5 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "ban.h" + #include "database.h" #include "databasetasks.h" #include "tools.h" @@ -62,10 +47,9 @@ bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `reason`, `expires_at`, `banned_at`, `banned_by`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `account_bans` WHERE `account_id` = " << accountId; - - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `reason`, `expires_at`, `banned_at`, `banned_by`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `account_bans` WHERE `account_id` = {:d}", + accountId)); if (!result) { return false; } @@ -73,13 +57,11 @@ bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) int64_t expiresAt = result->getNumber("expires_at"); if (expiresAt != 0 && time(nullptr) > expiresAt) { // Move the ban to history if it has expired - query.str(std::string()); - query << "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << accountId << ',' << db.escapeString(result->getString("reason")) << ',' << result->getNumber("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; - g_databaseTasks.addTask(query.str()); - - query.str(std::string()); - query << "DELETE FROM `account_bans` WHERE `account_id` = " << accountId; - g_databaseTasks.addTask(query.str()); + g_databaseTasks.addTask(fmt::format( + "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES ({:d}, {:s}, {:d}, {:d}, {:d})", + accountId, db.escapeString(result->getString("reason")), result->getNumber("banned_at"), expiresAt, + result->getNumber("banned_by"))); + g_databaseTasks.addTask(fmt::format("DELETE FROM `account_bans` WHERE `account_id` = {:d}", accountId)); return false; } @@ -97,19 +79,16 @@ bool IOBan::isIpBanned(uint32_t clientIP, BanInfo& banInfo) Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `reason`, `expires_at`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `ip_bans` WHERE `ip` = " << clientIP; - - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `reason`, `expires_at`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `ip_bans` WHERE `ip` = {:d}", + clientIP)); if (!result) { return false; } int64_t expiresAt = result->getNumber("expires_at"); if (expiresAt != 0 && time(nullptr) > expiresAt) { - query.str(std::string()); - query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientIP; - g_databaseTasks.addTask(query.str()); + g_databaseTasks.addTask(fmt::format("DELETE FROM `ip_bans` WHERE `ip` = {:d}", clientIP)); return false; } @@ -121,7 +100,7 @@ bool IOBan::isIpBanned(uint32_t clientIP, BanInfo& banInfo) bool IOBan::isPlayerNamelocked(uint32_t playerId) { - std::ostringstream query; - query << "SELECT 1 FROM `player_namelocks` WHERE `player_id` = " << playerId; - return Database::getInstance().storeQuery(query.str()).get() != nullptr; + return Database::getInstance() + .storeQuery(fmt::format("SELECT 1 FROM `player_namelocks` WHERE `player_id` = {:d}", playerId)) + .get(); } diff --git a/src/ban.h b/src/ban.h index 850d7cca8b..0818e4c881 100644 --- a/src/ban.h +++ b/src/ban.h @@ -1,34 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_BAN_H_CADB975222D745F0BDA12D982F1006E3 -#define FS_BAN_H_CADB975222D745F0BDA12D982F1006E3 - -struct BanInfo { +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_BAN_H +#define FS_BAN_H + +struct BanInfo +{ std::string bannedBy; std::string reason; time_t expiresAt; }; -struct ConnectBlock { +struct ConnectBlock +{ constexpr ConnectBlock(uint64_t lastAttempt, uint64_t blockTime, uint32_t count) : - lastAttempt(lastAttempt), blockTime(blockTime), count(count) {} + lastAttempt(lastAttempt), blockTime(blockTime), count(count) + {} uint64_t lastAttempt; uint64_t blockTime; @@ -39,20 +26,20 @@ using IpConnectMap = std::map; class Ban { - public: - bool acceptConnection(uint32_t clientIP); +public: + bool acceptConnection(uint32_t clientIP); - private: - IpConnectMap ipConnectMap; - std::recursive_mutex lock; +private: + IpConnectMap ipConnectMap; + std::recursive_mutex lock; }; class IOBan { - public: - static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo); - static bool isIpBanned(uint32_t clientIP, BanInfo& banInfo); - static bool isPlayerNamelocked(uint32_t playerId); +public: + static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo); + static bool isIpBanned(uint32_t clientIP, BanInfo& banInfo); + static bool isPlayerNamelocked(uint32_t playerId); }; -#endif +#endif // FS_BAN_H diff --git a/src/baseevents.cpp b/src/baseevents.cpp index 4dc648cd3e..c75a861c57 100644 --- a/src/baseevents.cpp +++ b/src/baseevents.cpp @@ -1,27 +1,11 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "baseevents.h" -#include "pugicast.h" +#include "luascript.h" #include "tools.h" extern LuaEnvironment g_luaEnvironment; @@ -36,7 +20,8 @@ bool BaseEvents::loadFromXml() std::string scriptsName = getScriptBaseName(); std::string basePath = "data/" + scriptsName + "/"; if (getScriptInterface().loadFile(basePath + "lib/" + scriptsName + ".lua") == -1) { - std::cout << "[Warning - BaseEvents::loadFromXml] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + std::cout << "[Warning - BaseEvents::loadFromXml] Can not load " << scriptsName << " lib/" << scriptsName + << ".lua" << std::endl; } std::string filename = basePath + scriptsName + ".xml"; @@ -97,13 +82,15 @@ void BaseEvents::reInitState(bool fromLua) Event::Event(LuaScriptInterface* interface) : scriptInterface(interface) {} -bool Event::checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const +bool Event::checkScript(const std::string& basePath, const std::string& scriptsName, + const std::string& scriptFile) const { LuaScriptInterface* testInterface = g_luaEnvironment.getTestInterface(); testInterface->reInitState(); if (testInterface->loadFile(std::string(basePath + "lib/" + scriptsName + ".lua")) == -1) { - std::cout << "[Warning - Event::checkScript] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + std::cout << "[Warning - Event::checkScript] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" + << std::endl; } if (scriptId != 0) { @@ -119,7 +106,8 @@ bool Event::checkScript(const std::string& basePath, const std::string& scriptsN int32_t id = testInterface->getEvent(getScriptEventName()); if (id == -1) { - std::cout << "[Warning - Event::checkScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + std::cout << "[Warning - Event::checkScript] Event " << getScriptEventName() << " not found. " << scriptFile + << std::endl; return false; } return true; @@ -140,7 +128,8 @@ bool Event::loadScript(const std::string& scriptFile) int32_t id = scriptInterface->getEvent(getScriptEventName()); if (id == -1) { - std::cout << "[Warning - Event::loadScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + std::cout << "[Warning - Event::loadScript] Event " << getScriptEventName() << " not found. " << scriptFile + << std::endl; return false; } diff --git a/src/baseevents.h b/src/baseevents.h index 451bf066c7..f11b44367a 100644 --- a/src/baseevents.h +++ b/src/baseevents.h @@ -1,99 +1,75 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A -#define FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A - -#include "luascript.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_BASEEVENTS_H +#define FS_BASEEVENTS_H + +class LuaScriptInterface; class Event; using Event_ptr = std::unique_ptr; class Event { - public: - explicit Event(LuaScriptInterface* interface); - virtual ~Event() = default; +public: + explicit Event(LuaScriptInterface* interface); + virtual ~Event() = default; - virtual bool configureEvent(const pugi::xml_node& node) = 0; + virtual bool configureEvent(const pugi::xml_node& node) = 0; - bool checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const; - bool loadScript(const std::string& scriptFile); - bool loadCallback(); - virtual bool loadFunction(const pugi::xml_attribute&, bool) { - return false; - } + bool checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const; + bool loadScript(const std::string& scriptFile); + bool loadCallback(); + virtual bool loadFunction(const pugi::xml_attribute&, bool) { return false; } - bool isScripted() const { - return scripted; - } + bool isScripted() const { return scripted; } - bool scripted = false; - bool fromLua = false; + bool scripted = false; + bool fromLua = false; - int32_t getScriptId() { - return scriptId; - } + int32_t getScriptId() { return scriptId; } - protected: - virtual std::string getScriptEventName() const = 0; +protected: + virtual std::string getScriptEventName() const = 0; - int32_t scriptId = 0; - LuaScriptInterface* scriptInterface = nullptr; + int32_t scriptId = 0; + LuaScriptInterface* scriptInterface = nullptr; }; class BaseEvents { - public: - constexpr BaseEvents() = default; - virtual ~BaseEvents() = default; - - bool loadFromXml(); - bool reload(); - bool isLoaded() const { - return loaded; - } - void reInitState(bool fromLua); - - private: - virtual LuaScriptInterface& getScriptInterface() = 0; - virtual std::string getScriptBaseName() const = 0; - virtual Event_ptr getEvent(const std::string& nodeName) = 0; - virtual bool registerEvent(Event_ptr event, const pugi::xml_node& node) = 0; - virtual void clear(bool) = 0; - - bool loaded = false; +public: + constexpr BaseEvents() = default; + virtual ~BaseEvents() = default; + + bool loadFromXml(); + bool reload(); + bool isLoaded() const { return loaded; } + void reInitState(bool fromLua); + +private: + virtual LuaScriptInterface& getScriptInterface() = 0; + virtual std::string getScriptBaseName() const = 0; + virtual Event_ptr getEvent(const std::string& nodeName) = 0; + virtual bool registerEvent(Event_ptr event, const pugi::xml_node& node) = 0; + virtual void clear(bool) = 0; + + bool loaded = false; }; class CallBack { - public: - CallBack() = default; +public: + CallBack() = default; - bool loadCallBack(LuaScriptInterface* interface, const std::string& name); + bool loadCallBack(LuaScriptInterface* interface, const std::string& name); - protected: - int32_t scriptId = 0; - LuaScriptInterface* scriptInterface = nullptr; +protected: + int32_t scriptId = 0; + LuaScriptInterface* scriptInterface = nullptr; - private: - bool loaded = false; +private: + bool loaded = false; }; -#endif +#endif // FS_BASEEVENTS_H diff --git a/src/bed.cpp b/src/bed.cpp index a66a573772..c2918d34e8 100644 --- a/src/bed.cpp +++ b/src/bed.cpp @@ -1,35 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "bed.h" + +#include "condition.h" #include "game.h" #include "iologindata.h" #include "scheduler.h" extern Game g_game; -BedItem::BedItem(uint16_t id) : Item(id) -{ - internalRemoveSleeper(); -} +BedItem::BedItem(uint16_t id) : Item(id) { internalRemoveSleeper(); } Attr_ReadValue BedItem::readAttr(AttrTypes_t attr, PropStream& propStream) { @@ -95,7 +78,7 @@ BedItem* BedItem::getNextBedItem() const bool BedItem::canUse(Player* player) { - if (!player || !house || !player->isPremium()) { + if (!player || !house || !player->isPremium() || player->getZone() != ZONE_PROTECTION) { return false; } @@ -163,8 +146,8 @@ bool BedItem::sleep(Player* player) g_game.addMagicEffect(player->getPosition(), CONST_ME_SLEEP); // kick player after he sees himself walk onto the bed and it change id - uint32_t playerId = player->getID(); - g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&Game::kickPlayer, &g_game, playerId, false))); + g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, + [playerID = player->getID()]() { g_game.kickPlayer(playerID, false); })); // change self and partner's appearance updateAppearance(player); diff --git a/src/bed.h b/src/bed.h index ccc37cc295..f58feab248 100644 --- a/src/bed.h +++ b/src/bed.h @@ -1,24 +1,8 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_BED_H_84DE19758D424C6C9789189231946BFF -#define FS_BED_H_84DE19758D424C6C9789189231946BFF +#ifndef FS_BED_H +#define FS_BED_H #include "item.h" @@ -27,48 +11,39 @@ class Player; class BedItem final : public Item { - public: - explicit BedItem(uint16_t id); +public: + explicit BedItem(uint16_t id); - BedItem* getBed() override { - return this; - } - const BedItem* getBed() const override { - return this; - } + BedItem* getBed() override { return this; } + const BedItem* getBed() const override { return this; } - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - void serializeAttr(PropWriteStream& propWriteStream) const override; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream& propWriteStream) const override; - bool canRemove() const override { - return house == nullptr; - } + bool canRemove() const override { return !house; } - uint32_t getSleeper() const { - return sleeperGUID; - } + uint32_t getSleeper() const { return sleeperGUID; } - void setHouse(House* h) { - house = h; - } + House* getHouse() const { return house; } + void setHouse(House* h) { house = h; } - bool canUse(Player* player); + bool canUse(Player* player); - bool trySleep(Player* player); - bool sleep(Player* player); - void wakeUp(Player* player); + bool trySleep(Player* player); + bool sleep(Player* player); + void wakeUp(Player* player); - BedItem* getNextBedItem() const; + BedItem* getNextBedItem() const; - private: - void updateAppearance(const Player* player); - void regeneratePlayer(Player* player) const; - void internalSetSleeper(const Player* player); - void internalRemoveSleeper(); +private: + void updateAppearance(const Player* player); + void regeneratePlayer(Player* player) const; + void internalSetSleeper(const Player* player); + void internalRemoveSleeper(); - House* house = nullptr; - uint64_t sleepStart; - uint32_t sleeperGUID; + House* house = nullptr; + uint64_t sleepStart; + uint32_t sleeperGUID; }; -#endif +#endif // FS_BED_H diff --git a/src/chat.cpp b/src/chat.cpp index 42050a7c60..4a181f5b4f 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "chat.h" + #include "game.h" #include "pugicast.h" #include "scheduler.h" @@ -35,10 +20,7 @@ bool PrivateChatChannel::isInvited(uint32_t guid) const return invites.find(guid) != invites.end(); } -bool PrivateChatChannel::removeInvite(uint32_t guid) -{ - return invites.erase(guid) != 0; -} +bool PrivateChatChannel::removeInvite(uint32_t guid) { return invites.erase(guid) != 0; } void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer) { @@ -47,13 +29,11 @@ void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer return; } - std::ostringstream ss; - ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel."; - invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, + fmt::format("{:s} invites you to {:s} private chat channel.", player.getName(), + player.getSex() == PLAYERSEX_FEMALE ? "her" : "his")); - ss.str(std::string()); - ss << invitePlayer.getName() << " has been invited."; - player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + player.sendTextMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has been invited.", invitePlayer.getName())); for (const auto& it : users) { it.second->sendChannelEvent(id, invitePlayer.getName(), CHANNELEVENT_INVITE); @@ -68,9 +48,7 @@ void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlay removeUser(excludePlayer); - std::ostringstream ss; - ss << excludePlayer.getName() << " has been excluded."; - player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + player.sendTextMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has been excluded.", excludePlayer.getName())); excludePlayer.sendClosePrivate(id); @@ -100,7 +78,8 @@ bool ChatChannel::addUser(Player& player) if (id == CHANNEL_GUILD) { Guild* guild = player.getGuild(); if (guild && !guild->getMotd().empty()) { - g_scheduler.addEvent(createSchedulerTask(150, std::bind(&Game::sendGuildMotd, &g_game, player.getID()))); + g_scheduler.addEvent( + createSchedulerTask(150, [playerID = player.getID()]() { g_game.sendGuildMotd(playerID); })); } } @@ -133,9 +112,7 @@ bool ChatChannel::removeUser(const Player& player) return true; } -bool ChatChannel::hasUser(const Player& player) { - return users.find(player.getID()) != users.end(); -} +bool ChatChannel::hasUser(const Player& player) { return users.find(player.getID()) != users.end(); } void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) const { @@ -162,7 +139,7 @@ bool ChatChannel::executeCanJoinEvent(const Player& player) return true; } - //canJoin(player) + // canJoin(player) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl; @@ -187,7 +164,7 @@ bool ChatChannel::executeOnJoinEvent(const Player& player) return true; } - //onJoin(player) + // onJoin(player) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl; @@ -212,7 +189,7 @@ bool ChatChannel::executeOnLeaveEvent(const Player& player) return true; } - //onLeave(player) + // onLeave(player) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl; @@ -237,7 +214,7 @@ bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, return true; } - //onSpeak(player, type, message) + // onSpeak(player, type, message) LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl; @@ -278,9 +255,7 @@ bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, return result; } -Chat::Chat(): - scriptInterface("Chat Interface"), - dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") +Chat::Chat() : scriptInterface("Chat Interface"), dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") { scriptInterface.initState(); } @@ -307,13 +282,15 @@ bool Chat::load() channel.name = channelName; if (scriptAttribute) { - if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { + if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == + 0) { channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); channel.canJoinEvent = scriptInterface.getEvent("canJoin"); channel.onJoinEvent = scriptInterface.getEvent("onJoin"); channel.onLeaveEvent = scriptInterface.getEvent("onLeave"); } else { - std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl; + std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() + << std::endl; } } @@ -328,7 +305,8 @@ bool Chat::load() channel.publicChannel = isPublic; if (scriptAttribute) { - if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { + if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == + 0) { channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); channel.canJoinEvent = scriptInterface.getEvent("canJoin"); channel.onJoinEvent = scriptInterface.getEvent("onJoin"); @@ -353,7 +331,8 @@ ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId) case CHANNEL_GUILD: { Guild* guild = player.getGuild(); if (guild) { - auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName()))); + auto ret = + guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName()))); return &ret.first->second; } break; @@ -369,15 +348,16 @@ ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId) } case CHANNEL_PRIVATE: { - //only 1 private channel for each premium player + // only 1 private channel for each premium player if (!player.isPremium() || getPrivateChannel(player)) { return nullptr; } - //find a free private channel slot + // find a free private channel slot for (uint16_t i = 100; i < 10000; ++i) { - auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel"))); - if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map + auto ret = + privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel"))); + if (ret.second) { // second is a bool that indicates that a new channel has been placed in the map auto& newChannel = (*ret.first).second; newChannel.setOwner(player.getGUID()); return &newChannel; diff --git a/src/chat.h b/src/chat.h index 2f4c6037e9..8ebc6fa9c0 100644 --- a/src/chat.h +++ b/src/chat.h @@ -1,24 +1,8 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628 -#define FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CHAT_H +#define FS_CHAT_H #include "const.h" #include "luascript.h" @@ -31,134 +15,115 @@ using InvitedMap = std::map; class ChatChannel { - public: - ChatChannel() = default; - ChatChannel(uint16_t channelId, std::string channelName): - id{channelId}, name{std::move(channelName)} {} +public: + ChatChannel() = default; + ChatChannel(uint16_t channelId, std::string channelName) : id{channelId}, name{std::move(channelName)} {} - virtual ~ChatChannel() = default; + virtual ~ChatChannel() = default; - bool addUser(Player& player); - bool removeUser(const Player& player); - bool hasUser(const Player& player); + bool addUser(Player& player); + bool removeUser(const Player& player); + bool hasUser(const Player& player); - bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text); - void sendToAll(const std::string& message, SpeakClasses type) const; + bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text); + void sendToAll(const std::string& message, SpeakClasses type) const; - const std::string& getName() const { - return name; - } - uint16_t getId() const { - return id; - } - const UsersMap& getUsers() const { - return users; - } - virtual const InvitedMap* getInvitedUsers() const { - return nullptr; - } + const std::string& getName() const { return name; } + uint16_t getId() const { return id; } + const UsersMap& getUsers() const { return users; } + virtual const InvitedMap* getInvitedUsers() const { return nullptr; } - virtual uint32_t getOwner() const { - return 0; - } + virtual uint32_t getOwner() const { return 0; } - bool isPublicChannel() const { return publicChannel; } + bool isPublicChannel() const { return publicChannel; } - bool executeOnJoinEvent(const Player& player); - bool executeCanJoinEvent(const Player& player); - bool executeOnLeaveEvent(const Player& player); - bool executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message); + bool executeOnJoinEvent(const Player& player); + bool executeCanJoinEvent(const Player& player); + bool executeOnLeaveEvent(const Player& player); + bool executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message); - protected: - UsersMap users; +protected: + UsersMap users; - uint16_t id; + uint16_t id; - private: - std::string name; +private: + std::string name; - int32_t canJoinEvent = -1; - int32_t onJoinEvent = -1; - int32_t onLeaveEvent = -1; - int32_t onSpeakEvent = -1; + int32_t canJoinEvent = -1; + int32_t onJoinEvent = -1; + int32_t onLeaveEvent = -1; + int32_t onSpeakEvent = -1; - bool publicChannel = false; + bool publicChannel = false; friend class Chat; }; class PrivateChatChannel final : public ChatChannel { - public: - PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {} +public: + PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {} - uint32_t getOwner() const override { - return owner; - } - void setOwner(uint32_t owner) { - this->owner = owner; - } + uint32_t getOwner() const override { return owner; } + void setOwner(uint32_t owner) { this->owner = owner; } - bool isInvited(uint32_t guid) const; + bool isInvited(uint32_t guid) const; - void invitePlayer(const Player& player, Player& invitePlayer); - void excludePlayer(const Player& player, Player& excludePlayer); + void invitePlayer(const Player& player, Player& invitePlayer); + void excludePlayer(const Player& player, Player& excludePlayer); - bool removeInvite(uint32_t guid); + bool removeInvite(uint32_t guid); - void closeChannel() const; + void closeChannel() const; - const InvitedMap* getInvitedUsers() const override { - return &invites; - } + const InvitedMap* getInvitedUsers() const override { return &invites; } - private: - InvitedMap invites; - uint32_t owner = 0; +private: + InvitedMap invites; + uint32_t owner = 0; }; using ChannelList = std::list; class Chat { - public: - Chat(); +public: + Chat(); - // non-copyable - Chat(const Chat&) = delete; - Chat& operator=(const Chat&) = delete; + // non-copyable + Chat(const Chat&) = delete; + Chat& operator=(const Chat&) = delete; - bool load(); + bool load(); - ChatChannel* createChannel(const Player& player, uint16_t channelId); - bool deleteChannel(const Player& player, uint16_t channelId); + ChatChannel* createChannel(const Player& player, uint16_t channelId); + bool deleteChannel(const Player& player, uint16_t channelId); - ChatChannel* addUserToChannel(Player& player, uint16_t channelId); - bool removeUserFromChannel(const Player& player, uint16_t channelId); - void removeUserFromAllChannels(const Player& player); + ChatChannel* addUserToChannel(Player& player, uint16_t channelId); + bool removeUserFromChannel(const Player& player, uint16_t channelId); + void removeUserFromAllChannels(const Player& player); - bool talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId); + bool talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId); - ChannelList getChannelList(const Player& player); + ChannelList getChannelList(const Player& player); - ChatChannel* getChannel(const Player& player, uint16_t channelId); - ChatChannel* getChannelById(uint16_t channelId); - ChatChannel* getGuildChannelById(uint32_t guildId); - PrivateChatChannel* getPrivateChannel(const Player& player); + ChatChannel* getChannel(const Player& player, uint16_t channelId); + ChatChannel* getChannelById(uint16_t channelId); + ChatChannel* getGuildChannelById(uint32_t guildId); + PrivateChatChannel* getPrivateChannel(const Player& player); - LuaScriptInterface* getScriptInterface() { - return &scriptInterface; - } + LuaScriptInterface* getScriptInterface() { return &scriptInterface; } - private: - std::map normalChannels; - std::map privateChannels; - std::map partyChannels; - std::map guildChannels; +private: + std::map normalChannels; + std::map privateChannels; + std::map partyChannels; + std::map guildChannels; - LuaScriptInterface scriptInterface; + LuaScriptInterface scriptInterface; - PrivateChatChannel dummyPrivate; + PrivateChatChannel dummyPrivate; }; -#endif +#endif // FS_CHAT_H diff --git a/src/combat.cpp b/src/combat.cpp index 3564207d1e..a4dd02c664 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -1,30 +1,15 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "combat.h" -#include "game.h" -#include "weapons.h" #include "configmanager.h" #include "events.h" +#include "game.h" +#include "spectators.h" +#include "weapons.h" extern Game g_game; extern Weapons* g_weapons; @@ -66,8 +51,10 @@ MatrixArea createArea(const std::vector& vec, uint32_t rows) return area; } -std::vector getList(const MatrixArea& area, const Position& targetPos) +std::vector getList(const MatrixArea& area, const Position& targetPos, const Direction dir) { + auto casterPos = getNextPosition(dir, targetPos); + std::vector vec; auto center = area.getCenter(); @@ -76,7 +63,7 @@ std::vector getList(const MatrixArea& area, const Position& targetPos) for (uint32_t row = 0; row < area.getRows(); ++row, ++tmpPos.y) { for (uint32_t col = 0; col < area.getCols(); ++col, ++tmpPos.x) { if (area(row, col)) { - if (g_game.isSightClear(targetPos, tmpPos, true)) { + if (g_game.isSightClear(casterPos, tmpPos, true)) { Tile* tile = g_game.map.getTile(tmpPos); if (!tile) { tile = new StaticTile(tmpPos.x, tmpPos.y, tmpPos.z); @@ -98,7 +85,7 @@ std::vector getCombatArea(const Position& centerPos, const Position& targ } if (area) { - return getList(area->getArea(centerPos, targetPos), targetPos); + return getList(area->getArea(centerPos, targetPos), targetPos, getDirectionTo(targetPos, centerPos)); } Tile* tile = g_game.map.getTile(targetPos); @@ -109,7 +96,7 @@ std::vector getCombatArea(const Position& centerPos, const Position& targ return {tile}; } -} +} // namespace CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const { @@ -117,10 +104,7 @@ CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const damage.origin = params.origin; damage.primary.type = params.combatType; if (formulaType == COMBAT_FORMULA_DAMAGE) { - damage.primary.value = normal_random( - static_cast(mina), - static_cast(maxa) - ); + damage.primary.value = normal_random(static_cast(mina), static_cast(maxa)); } else if (creature) { int32_t min, max; if (creature->getCombatValues(min, max)) { @@ -129,13 +113,17 @@ CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const if (params.valueCallback) { params.valueCallback->getMinMaxValues(player, damage); } else if (formulaType == COMBAT_FORMULA_LEVELMAGIC) { - int32_t levelFormula = player->getLevel() * 2 + player->getMagicLevel() * 3; - damage.primary.value = normal_random(std::fma(levelFormula, mina, minb), std::fma(levelFormula, maxa, maxb)); + int32_t levelFormula = + player->getLevel() * 2 + + (player->getMagicLevel() + player->getSpecialMagicLevel(damage.primary.type)) * 3; + damage.primary.value = + normal_random(std::fma(levelFormula, mina, minb), std::fma(levelFormula, maxa, maxb)); } else if (formulaType == COMBAT_FORMULA_SKILL) { Item* tool = player->getWeapon(); const Weapon* weapon = g_weapons->getWeapon(tool); if (weapon) { - damage.primary.value = normal_random(minb, std::fma(weapon->getWeaponDamage(player, target, tool, true), maxa, maxb)); + damage.primary.value = + normal_random(minb, std::fma(weapon->getWeaponDamage(player, target, tool, true), maxa, maxb)); damage.secondary.type = weapon->getElementType(); damage.secondary.value = weapon->getElementDamage(player, target, tool); } else { @@ -233,16 +221,16 @@ ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) } if (!attacker->hasFlag(PlayerFlag_IgnoreProtectionZone)) { - //pz-zone + // pz-zone if (attacker->getZone() == ZONE_PROTECTION) { - return RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE; + return RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE; } if (target->getZone() == ZONE_PROTECTION) { - return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + return RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE; } - //nopvp-zone + // nopvp-zone if (isPlayerCombat(target)) { if (attacker->getZone() == ZONE_NOPVP) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; @@ -257,9 +245,8 @@ ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) if (attacker->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { if (target->getPlayer()) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; - } else { - return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; } + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; } if (target->getPlayer()) { @@ -267,7 +254,8 @@ ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } - if (attacker->hasSecureMode() && !Combat::isInPvpZone(attacker, target) && attacker->getSkullClient(target->getPlayer()) == SKULL_NONE) { + if (attacker->hasSecureMode() && !Combat::isInPvpZone(attacker, target) && + attacker->getSkullClient(target->getPlayer()) == SKULL_NONE) { return RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS; } } @@ -305,7 +293,7 @@ ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) } } - //pz-zone + // pz-zone if (aggressive && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE; } @@ -325,7 +313,7 @@ bool Combat::isProtected(const Player* attacker, const Player* target) return true; } - if (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE) { + if (!attacker->getVocation()->allowsPvp() || !target->getVocation()->allowsPvp()) { return true; } @@ -356,11 +344,12 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } - //nopvp-zone + // nopvp-zone const Tile* targetPlayerTile = targetPlayer->getTile(); if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; - } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { + } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && + !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } } @@ -485,6 +474,44 @@ bool Combat::setParam(CombatParam_t param, uint32_t value) return false; } +int32_t Combat::getParam(CombatParam_t param) +{ + switch (param) { + case COMBAT_PARAM_TYPE: + return static_cast(params.combatType); + + case COMBAT_PARAM_EFFECT: + return static_cast(params.impactEffect); + + case COMBAT_PARAM_DISTANCEEFFECT: + return static_cast(params.distanceEffect); + + case COMBAT_PARAM_BLOCKARMOR: + return params.blockedByArmor ? 1 : 0; + + case COMBAT_PARAM_BLOCKSHIELD: + return params.blockedByShield ? 1 : 0; + + case COMBAT_PARAM_TARGETCASTERORTOPMOST: + return params.targetCasterOrTopMost ? 1 : 0; + + case COMBAT_PARAM_CREATEITEM: + return params.itemId; + + case COMBAT_PARAM_AGGRESSIVE: + return params.aggressive ? 1 : 0; + + case COMBAT_PARAM_DISPEL: + return static_cast(params.dispelType); + + case COMBAT_PARAM_USECHARGES: + return params.useCharges ? 1 : 0; + + default: + return std::numeric_limits().max(); + } +} + bool Combat::setCallback(CallBackParam_t key) { switch (key) { @@ -588,7 +615,8 @@ void Combat::combatTileEffects(const SpectatorVec& spectators, Creature* caster, } else if (itemId == ITEM_WILDGROWTH) { itemId = ITEM_WILDGROWTH_NOPVP; } - } else if (itemId == ITEM_FIREFIELD_PVP_FULL || itemId == ITEM_POISONFIELD_PVP || itemId == ITEM_ENERGYFIELD_PVP) { + } else if (itemId == ITEM_FIREFIELD_PVP_FULL || itemId == ITEM_POISONFIELD_PVP || + itemId == ITEM_ENERGYFIELD_PVP) { casterPlayer->addInFightTicks(); } } @@ -658,11 +686,12 @@ void Combat::addDistanceEffect(Creature* caster, const Position& fromPos, const void Combat::doCombat(Creature* caster, Creature* target) const { - //target combat callback function + // target combat callback function if (params.combatType != COMBAT_NONE) { CombatDamage damage = getCombatDamage(caster, target); - bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + bool canCombat = + !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { g_game.addMagicEffect(target->getPosition(), params.impactEffect); } @@ -699,7 +728,8 @@ void Combat::doCombat(Creature* caster, Creature* target) const /* if (params.impactEffect != CONST_ME_NONE) { - g_game.addMagicEffect(target->getPosition(), params.impactEffect); + g_game.addMagicEffect(target->getPosition(), + params.impactEffect); } */ @@ -712,18 +742,19 @@ void Combat::doCombat(Creature* caster, Creature* target) const void Combat::doCombat(Creature* caster, const Position& position) const { - //area combat callback function + // area combat callback function if (params.combatType != COMBAT_NONE) { CombatDamage damage = getCombatDamage(caster, nullptr); doAreaCombat(caster, position, area.get(), damage, params); } else { - auto tiles = caster ? getCombatArea(caster->getPosition(), position, area.get()) : getCombatArea(position, position, area.get()); + auto tiles = caster ? getCombatArea(caster->getPosition(), position, area.get()) + : getCombatArea(position, position, area.get()); SpectatorVec spectators; uint32_t maxX = 0; uint32_t maxY = 0; - //calculate the max viewable range + // calculate the max viewable range for (Tile* tile : tiles) { const Position& tilePos = tile->getPosition(); @@ -764,7 +795,8 @@ void Combat::doCombat(Creature* caster, const Position& position) const } } - if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + if (!params.aggressive || + (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { for (const auto& condition : params.conditionList) { if (caster == creature || !creature->isImmune(condition->getType())) { Condition* conditionCopy = condition->clone(); @@ -772,7 +804,7 @@ void Combat::doCombat(Creature* caster, const Position& position) const conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); } - //TODO: infight condition until all aggressive conditions has ended + // TODO: infight condition until all aggressive conditions has ended creature->addCombatCondition(conditionCopy); } } @@ -807,13 +839,15 @@ void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& da bool success = false; if (damage.primary.type != COMBAT_MANADRAIN) { - if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { + if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, + params.itemId != 0, params.ignoreResistances)) { return; } if (casterPlayer) { Player* targetPlayer = target ? target->getPlayer() : nullptr; - if (targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) { + if (targetPlayer && casterPlayer != targetPlayer && targetPlayer->getSkull() != SKULL_BLACK && + damage.primary.type != COMBAT_HEALING) { damage.primary.value /= 2; damage.secondary.value /= 2; } @@ -843,7 +877,7 @@ void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& da conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); } - //TODO: infight condition until all aggressive conditions has ended + // TODO: infight condition until all aggressive conditions has ended target->addCombatCondition(conditionCopy); } } @@ -853,7 +887,8 @@ void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& da g_game.addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); } - if (!damage.leeched && damage.primary.type != COMBAT_HEALING && casterPlayer && damage.origin != ORIGIN_CONDITION) { + if (!damage.leeched && damage.primary.type != COMBAT_HEALING && casterPlayer && + damage.origin != ORIGIN_CONDITION) { CombatDamage leechCombat; leechCombat.origin = ORIGIN_NONE; leechCombat.leeched = true; @@ -893,14 +928,17 @@ void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& da } } -void Combat::doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) +void Combat::doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, + const CombatParams& params) { - auto tiles = caster ? getCombatArea(caster->getPosition(), position, area) : getCombatArea(position, position, area); + auto tiles = + caster ? getCombatArea(caster->getPosition(), position, area) : getCombatArea(position, position, area); Player* casterPlayer = caster ? caster->getPlayer() : nullptr; int32_t criticalPrimary = 0; int32_t criticalSecondary = 0; - if (!damage.critical && damage.primary.type != COMBAT_HEALING && casterPlayer && damage.origin != ORIGIN_CONDITION) { + if (!damage.critical && damage.primary.type != COMBAT_HEALING && casterPlayer && + damage.origin != ORIGIN_CONDITION) { uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT); if (chance > 0 && skill > 0 && uniform_random(1, 100) <= chance) { @@ -913,7 +951,7 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area uint32_t maxX = 0; uint32_t maxY = 0; - //calculate the max viewable range + // calculate the max viewable range for (Tile* tile : tiles) { const Position& tilePos = tile->getPosition(); @@ -959,7 +997,8 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area } } - if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + if (!params.aggressive || + (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { toDamageCreatures.push_back(creature); if (params.targetCasterOrTopMost) { @@ -975,16 +1014,16 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area leechCombat.leeched = true; for (Creature* creature : toDamageCreatures) { - CombatDamage damageCopy = damage; // we cannot avoid copying here, because we don't know if it's player combat or not, so we can't modify the initial damage. + CombatDamage damageCopy = damage; // we cannot avoid copying here, because we don't know if it's player combat + // or not, so we can't modify the initial damage. bool playerCombatReduced = false; if ((damageCopy.primary.value < 0 || damageCopy.secondary.value < 0) && caster) { Player* targetPlayer = creature->getPlayer(); - if (casterPlayer) { - if (targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) { - damageCopy.primary.value /= 2; - damageCopy.secondary.value /= 2; - playerCombatReduced = true; - } + if (casterPlayer && targetPlayer && casterPlayer != targetPlayer && + targetPlayer->getSkull() != SKULL_BLACK) { + damageCopy.primary.value /= 2; + damageCopy.secondary.value /= 2; + playerCombatReduced = true; } } @@ -996,7 +1035,8 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area bool success = false; if (damageCopy.primary.type != COMBAT_MANADRAIN) { - if (g_game.combatBlockHit(damageCopy, caster, creature, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { + if (g_game.combatBlockHit(damageCopy, caster, creature, params.blockedByShield, params.blockedByArmor, + params.itemId != 0, params.ignoreResistances)) { continue; } success = g_game.combatChangeHealth(caster, creature, damageCopy); @@ -1013,7 +1053,7 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); } - //TODO: infight condition until all aggressive conditions has ended + // TODO: infight condition until all aggressive conditions has ended creature->addCombatCondition(conditionCopy); } } @@ -1021,14 +1061,17 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area int32_t totalDamage = std::abs(damageCopy.primary.value + damageCopy.secondary.value); - if (casterPlayer && !damage.leeched && damage.primary.type != COMBAT_HEALING && damage.origin != ORIGIN_CONDITION) { + if (casterPlayer && !damage.leeched && damage.primary.type != COMBAT_HEALING && + damage.origin != ORIGIN_CONDITION) { int32_t targetsCount = toDamageCreatures.size(); if (casterPlayer->getHealth() < casterPlayer->getMaxHealth()) { uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE); uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT); if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - leechCombat.primary.value = std::ceil(totalDamage * ((skill / 100.) + ((targetsCount - 1) * ((skill / 100.) / 10.))) / targetsCount); + leechCombat.primary.value = + std::ceil(totalDamage * ((skill / 100.) + ((targetsCount - 1) * ((skill / 100.) / 10.))) / + targetsCount); g_game.combatChangeHealth(nullptr, casterPlayer, leechCombat); casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_RED); } @@ -1038,7 +1081,9 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE); uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT); if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - leechCombat.primary.value = std::ceil(totalDamage * ((skill / 100.) + ((targetsCount - 1) * ((skill / 100.) / 10.))) / targetsCount); + leechCombat.primary.value = + std::ceil(totalDamage * ((skill / 100.) + ((targetsCount - 1) * ((skill / 100.) / 10.))) / + targetsCount); g_game.combatChangeMana(nullptr, casterPlayer, leechCombat); casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_BLUE); } @@ -1062,7 +1107,7 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const { - //onGetPlayerMinMaxValues(...) + // onGetPlayerMinMaxValues(...) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - ValueCallback::getMinMaxValues] Call stack overflow" << std::endl; return; @@ -1084,15 +1129,15 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const int parameters = 1; switch (type) { case COMBAT_FORMULA_LEVELMAGIC: { - //onGetPlayerMinMaxValues(player, level, maglevel) + // onGetPlayerMinMaxValues(player, level, maglevel) lua_pushnumber(L, player->getLevel()); - lua_pushnumber(L, player->getMagicLevel()); + lua_pushnumber(L, player->getMagicLevel() + player->getSpecialMagicLevel(damage.primary.type)); parameters += 2; break; } case COMBAT_FORMULA_SKILL: { - //onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor) + // onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor) Item* tool = player->getWeapon(); const Weapon* weapon = g_weapons->getWeapon(tool); Item* item = nullptr; @@ -1129,10 +1174,8 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const if (lua_pcall(L, parameters, 2, 0) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); } else { - damage.primary.value = normal_random( - LuaScriptInterface::getNumber(L, -2), - LuaScriptInterface::getNumber(L, -1) - ); + damage.primary.value = + normal_random(LuaScriptInterface::getNumber(L, -2), LuaScriptInterface::getNumber(L, -1)); lua_pop(L, 2); } @@ -1147,7 +1190,7 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const void TileCallback::onTileCombat(Creature* creature, Tile* tile) const { - //onTileCombat(creature, pos) + // onTileCombat(creature, pos) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - TileCallback::onTileCombat] Call stack overflow" << std::endl; return; @@ -1177,7 +1220,7 @@ void TileCallback::onTileCombat(Creature* creature, Tile* tile) const void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const { - //onTargetCombat(creature, target) + // onTargetCombat(creature, target) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - TargetCallback::onTargetCombat] Call stack overflow" << std::endl; return; @@ -1222,7 +1265,8 @@ void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const //**********************************************************// -MatrixArea MatrixArea::flip() const { +MatrixArea MatrixArea::flip() const +{ Container newArr(arr.size()); for (uint32_t i = 0; i < rows; ++i) { // assign rows, top to bottom, to the current rows, bottom to top @@ -1231,7 +1275,8 @@ MatrixArea MatrixArea::flip() const { return {{cols - center.first - 1, center.second}, rows, cols, std::move(newArr)}; } -MatrixArea MatrixArea::mirror() const { +MatrixArea MatrixArea::mirror() const +{ Container newArr(arr.size()); for (uint32_t i = 0; i < cols; ++i) { // assign cols, left to right, to the current rows, right to left @@ -1240,11 +1285,13 @@ MatrixArea MatrixArea::mirror() const { return {{center.first, rows - center.second - 1}, rows, cols, std::move(newArr)}; } -MatrixArea MatrixArea::transpose() const { +MatrixArea MatrixArea::transpose() const +{ return {{center.second, center.first}, rows, cols, arr[std::gslice(0, {cols, rows}, {1, cols})]}; } -MatrixArea MatrixArea::rotate90() const { +MatrixArea MatrixArea::rotate90() const +{ Container newArr(arr.size()); for (uint32_t i = 0; i < rows; ++i) { // assign rows, top to bottom, to the current cols, right to left @@ -1253,13 +1300,15 @@ MatrixArea MatrixArea::rotate90() const { return {{rows - center.second - 1, center.first}, cols, rows, std::move(newArr)}; } -MatrixArea MatrixArea::rotate180() const { +MatrixArea MatrixArea::rotate180() const +{ Container newArr(arr.size()); std::reverse_copy(std::begin(arr), std::end(arr), std::begin(newArr)); return {{cols - center.first - 1, rows - center.second - 1}, rows, cols, std::move(newArr)}; } -MatrixArea MatrixArea::rotate270() const { +MatrixArea MatrixArea::rotate270() const +{ Container newArr(arr.size()); for (uint32_t i = 0; i < cols; ++i) { // assign cols, left to right, to the current rows, bottom to top @@ -1268,7 +1317,8 @@ MatrixArea MatrixArea::rotate270() const { return {{center.second, cols - center.first - 1}, cols, rows, std::move(newArr)}; } -const MatrixArea& AreaCombat::getArea(const Position& centerPos, const Position& targetPos) const { +const MatrixArea& AreaCombat::getArea(const Position& centerPos, const Position& targetPos) const +{ int32_t dx = Position::getOffsetX(targetPos, centerPos); int32_t dy = Position::getOffsetY(targetPos, centerPos); @@ -1306,7 +1356,10 @@ const MatrixArea& AreaCombat::getArea(const Position& centerPos, const Position& void AreaCombat::setupArea(const std::vector& vec, uint32_t rows) { auto area = createArea(vec, rows); - areas.resize(4); + if (areas.size() == 0) { + areas.resize(4); + } + areas[DIRECTION_EAST] = area.rotate90(); areas[DIRECTION_SOUTH] = area.rotate180(); areas[DIRECTION_WEST] = area.rotate270(); @@ -1350,21 +1403,13 @@ void AreaCombat::setupArea(int32_t length, int32_t spread) void AreaCombat::setupArea(int32_t radius) { - int32_t area[13][13] = { - {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, - {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, - {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, - {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, - {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, - {8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8}, - {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, - {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, - {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, - {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, - {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0} - }; + int32_t area[13][13] = {{0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8}, {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}}; std::vector vec; vec.reserve(13 * 13); @@ -1383,6 +1428,33 @@ void AreaCombat::setupArea(int32_t radius) setupArea(vec, 13); } +void AreaCombat::setupAreaRing(int32_t ring) +{ + int32_t area[13][13] = {{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 6, 6, 6, 7, 0, 0, 0, 0}, + {0, 0, 0, 7, 6, 5, 5, 5, 6, 7, 0, 0, 0}, {0, 0, 7, 6, 5, 4, 4, 4, 5, 6, 7, 0, 0}, + {0, 7, 6, 5, 4, 3, 3, 3, 4, 5, 6, 7, 0}, {7, 6, 5, 4, 3, 2, 0, 2, 3, 4, 5, 6, 7}, + {7, 6, 5, 4, 3, 0, 1, 0, 3, 4, 5, 6, 7}, {7, 6, 5, 4, 3, 2, 0, 2, 3, 4, 5, 6, 7}, + {0, 7, 6, 5, 4, 3, 3, 3, 4, 5, 6, 7, 0}, {0, 0, 7, 6, 5, 4, 4, 4, 5, 6, 7, 0, 0}, + {0, 0, 0, 7, 6, 5, 5, 5, 6, 7, 0, 0, 0}, {0, 0, 0, 0, 7, 6, 6, 6, 7, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}}; + + std::vector vec; + vec.reserve(13 * 13); + for (auto& row : area) { + for (int cell : row) { + if (cell == 1) { + vec.push_back(3); + } else if (cell > 0 && cell == ring) { + vec.push_back(1); + } else { + vec.push_back(0); + } + } + } + + setupArea(vec, 13); +} + void AreaCombat::setupExtArea(const std::vector& vec, uint32_t rows) { if (vec.empty()) { @@ -1402,8 +1474,8 @@ void AreaCombat::setupExtArea(const std::vector& vec, uint32_t rows) void MagicField::onStepInField(Creature* creature) { - //remove magic walls/wild growth - if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE || isBlocking()) { + // remove magic walls/wild growth + if (id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE || isBlocking()) { if (!creature->isInGhostMode()) { g_game.internalRemoveItem(this, 1); } @@ -1411,7 +1483,7 @@ void MagicField::onStepInField(Creature* creature) return; } - //remove magic walls/wild growth (only nopvp tiles/world) + // remove magic walls/wild growth (only nopvp tiles/world) if (id == ITEM_MAGICWALL_NOPVP || id == ITEM_WILDGROWTH_NOPVP) { if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || getTile()->hasFlag(TILESTATE_NOPVPZONE)) { g_game.internalRemoveItem(this, 1); diff --git a/src/combat.h b/src/combat.h index 42fa81a106..42eac17450 100644 --- a/src/combat.h +++ b/src/combat.h @@ -1,63 +1,45 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC -#define FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC - -#include "thing.h" -#include "condition.h" -#include "map.h" -#include "baseevents.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#include -#include +#ifndef FS_COMBAT_H +#define FS_COMBAT_H -class Condition; -class Creature; -class Item; +#include "baseevents.h" +#include "condition.h" +#include "item.h" +#include "tools.h" +class Creature; +class Player; struct Position; +class SpectatorVec; +class Tile; -//for luascript callback +// for luascript callback class ValueCallback final : public CallBack { - public: - explicit ValueCallback(formulaType_t type): type(type) {} - void getMinMaxValues(Player* player, CombatDamage& damage) const; +public: + explicit ValueCallback(formulaType_t type) : type(type) {} + void getMinMaxValues(Player* player, CombatDamage& damage) const; - private: - formulaType_t type; +private: + formulaType_t type; }; class TileCallback final : public CallBack { - public: - void onTileCombat(Creature* creature, Tile* tile) const; +public: + void onTileCombat(Creature* creature, Tile* tile) const; }; class TargetCallback final : public CallBack { - public: - void onTargetCombat(Creature* creature, Creature* target) const; +public: + void onTargetCombat(Creature* creature, Creature* target) const; }; -struct CombatParams { +struct CombatParams +{ std::forward_list> conditionList; std::unique_ptr valueCallback; @@ -86,150 +68,140 @@ class MatrixArea using Center = std::pair; using Container = std::valarray; - public: - MatrixArea() = default; - MatrixArea(uint32_t rows, uint32_t cols): arr(rows * cols), rows{rows}, cols{cols} {} +public: + MatrixArea() = default; + MatrixArea(uint32_t rows, uint32_t cols) : arr(rows * cols), rows{rows}, cols{cols} {} - bool operator()(uint32_t row, uint32_t col) const { return arr[row * cols + col]; } - bool& operator()(uint32_t row, uint32_t col) { return arr[row * cols + col]; } + bool operator()(uint32_t row, uint32_t col) const { return arr[row * cols + col]; } + bool& operator()(uint32_t row, uint32_t col) { return arr[row * cols + col]; } - void setCenter(uint32_t y, uint32_t x) { center = std::make_pair(x, y); } - const Center& getCenter() const { return center; } + void setCenter(uint32_t y, uint32_t x) { center = std::make_pair(x, y); } + const Center& getCenter() const { return center; } - uint32_t getRows() const { return rows; } - uint32_t getCols() const { return cols; } + uint32_t getRows() const { return rows; } + uint32_t getCols() const { return cols; } - MatrixArea flip() const; - MatrixArea mirror() const; - MatrixArea transpose() const; - MatrixArea rotate90() const; - MatrixArea rotate180() const; - MatrixArea rotate270() const; + MatrixArea flip() const; + MatrixArea mirror() const; + MatrixArea transpose() const; + MatrixArea rotate90() const; + MatrixArea rotate180() const; + MatrixArea rotate270() const; - operator bool() const { return rows == 0 || cols == 0; } + operator bool() const { return rows == 0 || cols == 0; } - private: - MatrixArea(Center center, uint32_t rows, uint32_t cols, Container&& arr): - arr{std::move(arr)}, center{std::move(center)}, rows{rows}, cols{cols} {} +private: + MatrixArea(Center center, uint32_t rows, uint32_t cols, Container&& arr) : + arr{std::move(arr)}, center{std::move(center)}, rows{rows}, cols{cols} + {} - Container arr = {}; - Center center = {}; - uint32_t rows = 0, cols = 0; + Container arr = {}; + Center center = {}; + uint32_t rows = 0, cols = 0; }; class AreaCombat { - public: - void setupArea(const std::vector& vec, uint32_t rows); - void setupArea(int32_t length, int32_t spread); - void setupArea(int32_t radius); - void setupExtArea(const std::vector& vec, uint32_t rows); - const MatrixArea& getArea(const Position& centerPos, const Position& targetPos) const; - - private: - std::vector areas; - bool hasExtArea = false; +public: + void setupArea(const std::vector& vec, uint32_t rows); + void setupArea(int32_t length, int32_t spread); + void setupArea(int32_t radius); + void setupAreaRing(int32_t ring); + void setupExtArea(const std::vector& vec, uint32_t rows); + const MatrixArea& getArea(const Position& centerPos, const Position& targetPos) const; + +private: + std::vector areas; + bool hasExtArea = false; }; class Combat { - public: - Combat() = default; - - // non-copyable - Combat(const Combat&) = delete; - Combat& operator=(const Combat&) = delete; - - static bool isInPvpZone(const Creature* attacker, const Creature* target); - static bool isProtected(const Player* attacker, const Player* target); - static bool isPlayerCombat(const Creature* target); - static CombatType_t ConditionToDamageType(ConditionType_t type); - static ConditionType_t DamageToConditionType(CombatType_t type); - static ReturnValue canTargetCreature(Player* attacker, Creature* target); - static ReturnValue canDoCombat(Creature* caster, Tile* tile, bool aggressive); - static ReturnValue canDoCombat(Creature* attacker, Creature* target); - static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params); - - static void addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect); - - void doCombat(Creature* caster, Creature* target) const; - void doCombat(Creature* caster, const Position& position) const; - - static void doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); - static void doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); - - bool setCallback(CallBackParam_t key); - CallBack* getCallback(CallBackParam_t key); - - bool setParam(CombatParam_t param, uint32_t value); - void setArea(AreaCombat* area) { - this->area.reset(area); - } - bool hasArea() const { - return area != nullptr; - } - void addCondition(const Condition* condition) { - params.conditionList.emplace_front(condition); - } - void clearConditions() { - params.conditionList.clear(); - } - void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb); - void postCombatEffects(Creature* caster, const Position& pos) const { - postCombatEffects(caster, pos, params); - } - - void setOrigin(CombatOrigin origin) { - params.origin = origin; - } - - private: - static void combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, const CombatParams& params); - CombatDamage getCombatDamage(Creature* creature, Creature* target) const; - - //configurable - CombatParams params; - - //formula variables - formulaType_t formulaType = COMBAT_FORMULA_UNDEFINED; - double mina = 0.0; - double minb = 0.0; - double maxa = 0.0; - double maxb = 0.0; - - std::unique_ptr area; +public: + Combat() = default; + + // non-copyable + Combat(const Combat&) = delete; + Combat& operator=(const Combat&) = delete; + + static bool isInPvpZone(const Creature* attacker, const Creature* target); + static bool isProtected(const Player* attacker, const Player* target); + static bool isPlayerCombat(const Creature* target); + static CombatType_t ConditionToDamageType(ConditionType_t type); + static ConditionType_t DamageToConditionType(CombatType_t type); + static ReturnValue canTargetCreature(Player* attacker, Creature* target); + static ReturnValue canDoCombat(Creature* caster, Tile* tile, bool aggressive); + static ReturnValue canDoCombat(Creature* attacker, Creature* target); + static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params); + + static void addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect); + + void doCombat(Creature* caster, Creature* target) const; + void doCombat(Creature* caster, const Position& position) const; + + static void doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + static void doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, + const CombatParams& params); + + bool setCallback(CallBackParam_t key); + CallBack* getCallback(CallBackParam_t key); + + bool setParam(CombatParam_t param, uint32_t value); + int32_t getParam(CombatParam_t param); + + void setArea(AreaCombat* area) { this->area.reset(area); } + bool hasArea() const { return area != nullptr; } + void addCondition(const Condition* condition) { params.conditionList.emplace_front(condition); } + void clearConditions() { params.conditionList.clear(); } + void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb); + void postCombatEffects(Creature* caster, const Position& pos) const { postCombatEffects(caster, pos, params); } + + void setOrigin(CombatOrigin origin) { params.origin = origin; } + +private: + static void combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, + const CombatParams& params); + CombatDamage getCombatDamage(Creature* creature, Creature* target) const; + + // configurable + CombatParams params; + + // formula variables + formulaType_t formulaType = COMBAT_FORMULA_UNDEFINED; + double mina = 0.0; + double minb = 0.0; + double maxa = 0.0; + double maxb = 0.0; + + std::unique_ptr area; }; class MagicField final : public Item { - public: - explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {} - - MagicField* getMagicField() override { - return this; - } - const MagicField* getMagicField() const override { - return this; - } - - bool isReplaceable() const { - return Item::items[getID()].replaceable; - } - CombatType_t getCombatType() const { - const ItemType& it = items[getID()]; - return it.combatType; - } - int32_t getDamage() const { - const ItemType& it = items[getID()]; - if (it.conditionDamage) { - return it.conditionDamage->getTotalDamage(); - } - return 0; +public: + explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {} + + MagicField* getMagicField() override { return this; } + const MagicField* getMagicField() const override { return this; } + + bool isReplaceable() const { return Item::items[getID()].replaceable; } + CombatType_t getCombatType() const + { + const ItemType& it = items[getID()]; + return it.combatType; + } + int32_t getDamage() const + { + const ItemType& it = items[getID()]; + if (it.conditionDamage) { + return it.conditionDamage->getTotalDamage(); } - void onStepInField(Creature* creature); + return 0; + } + void onStepInField(Creature* creature); - private: - int64_t createTime; +private: + int64_t createTime; }; -#endif +#endif // FS_COMBAT_H diff --git a/src/condition.cpp b/src/condition.cpp index 241aa1ad49..a3c789e3cf 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -1,26 +1,13 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "condition.h" + +#include "combat.h" #include "game.h" +#include "spectators.h" extern Game g_game; @@ -53,6 +40,23 @@ bool Condition::setParam(ConditionParam_t param, int32_t value) } } +int32_t Condition::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_TICKS: + return ticks; + + case CONDITION_PARAM_BUFF_SPELL: + return isBuff ? 1 : 0; + + case CONDITION_PARAM_SUBID: + return subId; + + default: + return std::numeric_limits().max(); + } +} + bool Condition::unserialize(PropStream& propStream) { uint8_t attr_type; @@ -156,12 +160,13 @@ bool Condition::executeCondition(Creature*, int32_t interval) return true; } - //Not using set ticks here since it would reset endTime + // Not using set ticks here since it would reset endTime ticks = std::max(0, ticks - interval); return getEndTime() >= OTSYS_TIME(); } -Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, bool buff/* = false*/, uint32_t subId/* = 0*/, bool aggressive/* = false */) +Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param /* = 0*/, + bool buff /* = false*/, uint32_t subId /* = 0*/, bool aggressive /* = false */) { switch (type) { case CONDITION_POISON: @@ -202,8 +207,10 @@ Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, in case CONDITION_SPELLGROUPCOOLDOWN: return new ConditionSpellGroupCooldown(id, type, ticks, buff, subId, aggressive); - case CONDITION_INFIGHT: case CONDITION_DRUNK: + return new ConditionDrunk(id, type, ticks, buff, subId, param, aggressive); + + case CONDITION_INFIGHT: case CONDITION_EXHAUST_WEAPON: case CONDITION_EXHAUST_COMBAT: case CONDITION_EXHAUST_HEAL: @@ -214,6 +221,9 @@ Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, in case CONDITION_MANASHIELD: return new ConditionGeneric(id, type, ticks, buff, subId, aggressive); + case CONDITION_ROOT: + return new ConditionGeneric(id, type, ticks, buff, subId, aggressive); + default: return nullptr; } @@ -276,7 +286,8 @@ Condition* Condition::createCondition(PropStream& propStream) return nullptr; } - return createCondition(static_cast(id), static_cast(type), ticks, 0, buff != 0, subId, aggressive); + return createCondition(static_cast(id), static_cast(type), ticks, 0, buff != 0, + subId, aggressive); } bool Condition::startCondition(Creature*) @@ -300,10 +311,7 @@ bool Condition::isPersistent() const return true; } -uint32_t Condition::getIcons() const -{ - return isBuff ? ICON_PARTY_BUFF : 0; -} +uint32_t Condition::getIcons() const { return isBuff ? ICON_PARTY_BUFF : 0; } bool Condition::updateCondition(const Condition* addCondition) { @@ -322,10 +330,7 @@ bool Condition::updateCondition(const Condition* addCondition) return true; } -bool ConditionGeneric::startCondition(Creature* creature) -{ - return Condition::startCondition(creature); -} +bool ConditionGeneric::startCondition(Creature* creature) { return Condition::startCondition(creature); } bool ConditionGeneric::executeCondition(Creature* creature, int32_t interval) { @@ -357,8 +362,8 @@ uint32_t ConditionGeneric::getIcons() const icons |= ICON_SWORDS; break; - case CONDITION_DRUNK: - icons |= ICON_DRUNK; + case CONDITION_ROOT: + icons |= ICON_ROOT; break; default: @@ -374,10 +379,10 @@ void ConditionAttributes::addCondition(Creature* creature, const Condition* cond setTicks(condition->getTicks()); const ConditionAttributes& conditionAttrs = static_cast(*condition); - //Remove the old condition + // Remove the old condition endCondition(creature); - //Apply the new one + // Apply the new one memcpy(skills, conditionAttrs.skills, sizeof(skills)); memcpy(specialSkills, conditionAttrs.specialSkills, sizeof(specialSkills)); memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); @@ -398,6 +403,8 @@ bool ConditionAttributes::unserializeProp(ConditionAttr_t attr, PropStream& prop { if (attr == CONDITIONATTR_SKILLS) { return propStream.read(skills[currentSkill++]); + } else if (attr == CONDITIONATTR_SPECIALSKILLS) { + return propStream.read(specialSkills[currentSpecialSkill++]); } else if (attr == CONDITIONATTR_STATS) { return propStream.read(stats[currentStat++]); } else if (attr == CONDITIONATTR_DISABLEDEFENSE) { @@ -422,6 +429,11 @@ void ConditionAttributes::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_DISABLEDEFENSE); propWriteStream.write(disableDefense); + + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { + propWriteStream.write(CONDITIONATTR_SPECIALSKILLS); + propWriteStream.write(specialSkills[i]); + } } bool ConditionAttributes::startCondition(Creature* creature) @@ -478,6 +490,7 @@ void ConditionAttributes::updateStats(Player* player) if (needUpdateStats) { player->sendStats(); + player->sendSkills(); } } @@ -556,6 +569,7 @@ void ConditionAttributes::endCondition(Creature* creature) if (needUpdateStats) { player->sendStats(); + player->sendSkills(); } } @@ -728,6 +742,95 @@ bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) } } +int32_t ConditionAttributes::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_SKILL_FIST: + return skills[SKILL_FIST]; + + case CONDITION_PARAM_SKILL_FISTPERCENT: + return skillsPercent[SKILL_FIST]; + + case CONDITION_PARAM_SKILL_CLUB: + return skills[SKILL_CLUB]; + + case CONDITION_PARAM_SKILL_CLUBPERCENT: + return skillsPercent[SKILL_CLUB]; + + case CONDITION_PARAM_SKILL_SWORD: + return skills[SKILL_SWORD]; + + case CONDITION_PARAM_SKILL_SWORDPERCENT: + return skillsPercent[SKILL_SWORD]; + + case CONDITION_PARAM_SKILL_AXE: + return skills[SKILL_AXE]; + + case CONDITION_PARAM_SKILL_AXEPERCENT: + return skillsPercent[SKILL_AXE]; + + case CONDITION_PARAM_SKILL_DISTANCE: + return skills[SKILL_DISTANCE]; + + case CONDITION_PARAM_SKILL_DISTANCEPERCENT: + return skillsPercent[SKILL_DISTANCE]; + + case CONDITION_PARAM_SKILL_SHIELD: + return skills[SKILL_SHIELD]; + + case CONDITION_PARAM_SKILL_SHIELDPERCENT: + return skillsPercent[SKILL_SHIELD]; + + case CONDITION_PARAM_SKILL_FISHING: + return skills[SKILL_FISHING]; + + case CONDITION_PARAM_SKILL_FISHINGPERCENT: + return skillsPercent[SKILL_FISHING]; + + case CONDITION_PARAM_STAT_MAXHITPOINTS: + return stats[STAT_MAXHITPOINTS]; + + case CONDITION_PARAM_STAT_MAXMANAPOINTS: + return stats[STAT_MAXMANAPOINTS]; + + case CONDITION_PARAM_STAT_MAGICPOINTS: + return stats[STAT_MAGICPOINTS]; + + case CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT: + return statsPercent[STAT_MAXHITPOINTS]; + + case CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT: + return statsPercent[STAT_MAXMANAPOINTS]; + + case CONDITION_PARAM_STAT_MAGICPOINTSPERCENT: + return statsPercent[STAT_MAGICPOINTS]; + + case CONDITION_PARAM_DISABLE_DEFENSE: + return disableDefense ? 1 : 0; + + case CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE: + return specialSkills[SPECIALSKILL_CRITICALHITCHANCE]; + + case CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT: + return specialSkills[SPECIALSKILL_CRITICALHITAMOUNT]; + + case CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE: + return specialSkills[SPECIALSKILL_LIFELEECHCHANCE]; + + case CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT: + return specialSkills[SPECIALSKILL_LIFELEECHAMOUNT]; + + case CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE: + return specialSkills[SPECIALSKILL_MANALEECHCHANCE]; + + case CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT: + return specialSkills[SPECIALSKILL_MANALEECHAMOUNT]; + + default: + return ConditionGeneric::getParam(param); + } +} + void ConditionRegeneration::addCondition(Creature*, const Condition* condition) { if (updateCondition(condition)) { @@ -793,7 +896,8 @@ bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interva if (isBuff && realHealthGain > 0) { Player* player = creature->getPlayer(); if (player) { - std::string healString = std::to_string(realHealthGain) + (realHealthGain != 1 ? " hitpoints." : " hitpoint."); + std::string healString = + std::to_string(realHealthGain) + (realHealthGain != 1 ? " hitpoints." : " hitpoint."); TextMessage message(MESSAGE_HEALED, "You were healed for " + healString); message.position = player->getPosition(); @@ -875,6 +979,26 @@ bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) } } +int32_t ConditionRegeneration::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_HEALTHGAIN: + return healthGain; + + case CONDITION_PARAM_HEALTHTICKS: + return healthTicks; + + case CONDITION_PARAM_MANAGAIN: + return manaGain; + + case CONDITION_PARAM_MANATICKS: + return manaTicks; + + default: + return ConditionGeneric::getParam(param); + } +} + void ConditionSoul::addCondition(Creature*, const Condition* condition) { if (updateCondition(condition)) { @@ -941,6 +1065,20 @@ bool ConditionSoul::setParam(ConditionParam_t param, int32_t value) } } +int32_t ConditionSoul::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_SOULGAIN: + return soulGain; + + case CONDITION_PARAM_SOULTICKS: + return soulTicks; + + default: + return ConditionGeneric::getParam(param); + } +} + bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) { bool ret = Condition::setParam(param, value); @@ -989,6 +1127,41 @@ bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) return ret; } +int32_t ConditionDamage::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_OWNER: + return owner; + + case CONDITION_PARAM_FORCEUPDATE: + return forceUpdate ? 1 : 0; + + case CONDITION_PARAM_DELAYED: + return delayed ? 1 : 0; + + case CONDITION_PARAM_MAXVALUE: + return maxDamage; + + case CONDITION_PARAM_MINVALUE: + return minDamage; + + case CONDITION_PARAM_STARTVALUE: + return startDamage; + + case CONDITION_PARAM_TICKINTERVAL: + return tickInterval; + + case CONDITION_PARAM_PERIODICDAMAGE: + return periodDamage; + + case CONDITION_PARAM_FIELD: + return field ? 1 : 0; + + default: + return Condition::getParam(param); + } +} + bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { if (attr == CONDITIONATTR_DELAYED) { @@ -1052,7 +1225,7 @@ bool ConditionDamage::addDamage(int32_t rounds, int32_t time, int32_t value) { time = std::max(time, EVENT_CREATURE_THINK_INTERVAL); if (rounds == -1) { - //periodic damage + // periodic damage periodDamage = value; setParam(CONDITION_PARAM_TICKINTERVAL, time); setParam(CONDITION_PARAM_TICKS, -1); @@ -1063,7 +1236,7 @@ bool ConditionDamage::addDamage(int32_t rounds, int32_t time, int32_t value) return false; } - //rounds, time, damage + // rounds, time, damage for (int32_t i = 0; i < rounds; ++i) { IntervalInfo damageInfo; damageInfo.interval = time; @@ -1241,7 +1414,7 @@ void ConditionDamage::addCondition(Creature* creature, const Condition* conditio int32_t nextTimeLeft = tickInterval; if (!damageList.empty()) { - //save previous timeLeft + // save previous timeLeft IntervalInfo& damageInfo = damageList.front(); nextTimeLeft = damageInfo.timeLeft; damageList.clear(); @@ -1251,7 +1424,7 @@ void ConditionDamage::addCondition(Creature* creature, const Condition* conditio if (init()) { if (!damageList.empty()) { - //restore last timeLeft + // restore last timeLeft IntervalInfo& damageInfo = damageList.front(); damageInfo.timeLeft = nextTimeLeft; } @@ -1366,6 +1539,17 @@ bool ConditionSpeed::setParam(ConditionParam_t param, int32_t value) return true; } +int32_t ConditionSpeed::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_SPEED: + return speedDelta; + + default: + return Condition::getParam(param); + } +} + bool ConditionSpeed::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { if (attr == CONDITIONATTR_SPEEDDELTA) { @@ -1427,10 +1611,7 @@ bool ConditionSpeed::executeCondition(Creature* creature, int32_t interval) return Condition::executeCondition(creature, interval); } -void ConditionSpeed::endCondition(Creature* creature) -{ - g_game.changeSpeed(creature, -speedDelta); -} +void ConditionSpeed::endCondition(Creature* creature) { g_game.changeSpeed(creature, -speedDelta); } void ConditionSpeed::addCondition(Creature* creature, const Condition* condition) { @@ -1487,21 +1668,20 @@ bool ConditionInvisible::startCondition(Creature* creature) return false; } - g_game.internalCreatureChangeVisible(creature, false); + if (!creature->isInGhostMode()) { + g_game.internalCreatureChangeVisible(creature, false); + } return true; } void ConditionInvisible::endCondition(Creature* creature) { - if (!creature->isInvisible()) { + if (!creature->isInGhostMode() && !creature->isInvisible()) { g_game.internalCreatureChangeVisible(creature, true); } } -void ConditionOutfit::setOutfit(const Outfit_t& outfit) -{ - this->outfit = outfit; -} +void ConditionOutfit::setOutfit(const Outfit_t& outfit) { this->outfit = outfit; } bool ConditionOutfit::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { @@ -1624,6 +1804,20 @@ bool ConditionLight::setParam(ConditionParam_t param, int32_t value) } } +int32_t ConditionLight::getParam(ConditionParam_t param) +{ + switch (param) { + case CONDITION_PARAM_LIGHT_LEVEL: + return lightInfo.level; + + case CONDITION_PARAM_LIGHT_COLOR: + return lightInfo.color; + + default: + return Condition::getParam(param); + } +} + bool ConditionLight::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { if (attr == CONDITIONATTR_LIGHTCOLOR) { @@ -1654,9 +1848,8 @@ void ConditionLight::serialize(PropWriteStream& propWriteStream) { Condition::serialize(propWriteStream); - // TODO: color and level could be serialized as 8-bit if we can retain backwards - // compatibility, but perhaps we should keep it like this in case they increase - // in the future... + // TODO: color and level could be serialized as 8-bit if we can retain backwards compatibility, but perhaps we + // should keep it like this in case they increase in the future... propWriteStream.write(CONDITIONATTR_LIGHTCOLOR); propWriteStream.write(lightInfo.color); @@ -1727,3 +1920,50 @@ bool ConditionSpellGroupCooldown::startCondition(Creature* creature) } return true; } + +bool ConditionDrunk::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + creature->setDrunkenness(drunkenness); + return true; +} + +bool ConditionDrunk::updateCondition(const Condition* addCondition) +{ + const ConditionDrunk* conditionDrunk = static_cast(addCondition); + return conditionDrunk->drunkenness > drunkenness; +} + +void ConditionDrunk::addCondition(Creature* creature, const Condition* condition) +{ + if (!updateCondition(condition)) { + return; + } + + const ConditionDrunk* conditionDrunk = static_cast(condition); + setTicks(conditionDrunk->getTicks()); + creature->setDrunkenness(conditionDrunk->drunkenness); +} + +void ConditionDrunk::endCondition(Creature* creature) { creature->setDrunkenness(0); } + +uint32_t ConditionDrunk::getIcons() const { return ICON_DRUNK; } + +bool ConditionDrunk::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = Condition::setParam(param, value); + + switch (param) { + case CONDITION_PARAM_DRUNKENNESS: { + drunkenness = value; + return true; + } + + default: { + return ret; + } + } +} diff --git a/src/condition.h b/src/condition.h index a6fcf93b68..5aaf968704 100644 --- a/src/condition.h +++ b/src/condition.h @@ -1,33 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086 -#define FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086 - -#include "fileloader.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CONDITION_H +#define FS_CONDITION_H + #include "enums.h" class Creature; class Player; class PropStream; +class PropWriteStream; -enum ConditionAttr_t { +enum ConditionAttr_t +{ CONDITIONATTR_TYPE = 1, CONDITIONATTR_ID, CONDITIONATTR_TICKS, @@ -57,12 +42,14 @@ enum ConditionAttr_t { CONDITIONATTR_SUBID, CONDITIONATTR_ISAGGRESSIVE, CONDITIONATTR_DISABLEDEFENSE, + CONDITIONATTR_SPECIALSKILLS, - //reserved for serialization + // reserved for serialization CONDITIONATTR_END = 254, }; -struct IntervalInfo { +struct IntervalInfo +{ int32_t timeLeft; int32_t value; int32_t interval; @@ -70,361 +57,387 @@ struct IntervalInfo { class Condition { - public: - Condition() = default; - Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - endTime(ticks == -1 ? std::numeric_limits::max() : 0), - subId(subId), ticks(ticks), conditionType(type), isBuff(buff), aggressive(aggressive), id(id) {} - virtual ~Condition() = default; - - virtual bool startCondition(Creature* creature); - virtual bool executeCondition(Creature* creature, int32_t interval); - virtual void endCondition(Creature* creature) = 0; - virtual void addCondition(Creature* creature, const Condition* condition) = 0; - virtual uint32_t getIcons() const; - ConditionId_t getId() const { - return id; - } - uint32_t getSubId() const { - return subId; - } - - virtual Condition* clone() const = 0; - - ConditionType_t getType() const { - return conditionType; - } - int64_t getEndTime() const { - return endTime; - } - int32_t getTicks() const { - return ticks; - } - void setTicks(int32_t newTicks); - bool isAggressive() const { - return aggressive; - } - - static Condition* createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, bool buff = false, uint32_t subId = 0, bool aggressive = false); - static Condition* createCondition(PropStream& propStream); - - virtual bool setParam(ConditionParam_t param, int32_t value); - - //serialization - bool unserialize(PropStream& propStream); - virtual void serialize(PropWriteStream& propWriteStream); - virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); - - bool isPersistent() const; - - protected: - virtual bool updateCondition(const Condition* addCondition); - - int64_t endTime; - uint32_t subId; - int32_t ticks; - ConditionType_t conditionType; - bool isBuff; - bool aggressive; - - private: - ConditionId_t id; +public: + Condition() = default; + Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + endTime(ticks == -1 ? std::numeric_limits::max() : 0), + subId(subId), + ticks(ticks), + conditionType(type), + isBuff(buff), + aggressive(aggressive), + id(id) + {} + virtual ~Condition() = default; + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature) = 0; + virtual void addCondition(Creature* creature, const Condition* condition) = 0; + virtual uint32_t getIcons() const; + ConditionId_t getId() const { return id; } + uint32_t getSubId() const { return subId; } + + virtual Condition* clone() const = 0; + + ConditionType_t getType() const { return conditionType; } + int64_t getEndTime() const { return endTime; } + int32_t getTicks() const { return ticks; } + void setTicks(int32_t newTicks); + bool isAggressive() const { return aggressive; } + + static Condition* createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, + bool buff = false, uint32_t subId = 0, bool aggressive = false); + static Condition* createCondition(PropStream& propStream); + + virtual bool setParam(ConditionParam_t param, int32_t value); + virtual int32_t getParam(ConditionParam_t param); + + // serialization + bool unserialize(PropStream& propStream); + virtual void serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + bool isPersistent() const; + +protected: + virtual bool updateCondition(const Condition* addCondition); + + int64_t endTime; + uint32_t subId; + int32_t ticks; + ConditionType_t conditionType; + bool isBuff; + bool aggressive; + +private: + ConditionId_t id; }; class ConditionGeneric : public Condition { - public: - ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false): - Condition(id, type, ticks, buff, subId, aggressive) {} - - bool startCondition(Creature* creature) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - uint32_t getIcons() const override; - - ConditionGeneric* clone() const override { - return new ConditionGeneric(*this); - } +public: + ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + Condition(id, type, ticks, buff, subId, aggressive) + {} + + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + uint32_t getIcons() const override; + + ConditionGeneric* clone() const override { return new ConditionGeneric(*this); } }; class ConditionAttributes final : public ConditionGeneric { - public: - ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - ConditionGeneric(id, type, ticks, buff, subId, aggressive) {} - - bool startCondition(Creature* creature) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - - bool setParam(ConditionParam_t param, int32_t value) override; - - ConditionAttributes* clone() const override { - return new ConditionAttributes(*this); - } - - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - - private: - int32_t skills[SKILL_LAST + 1] = {}; - int32_t skillsPercent[SKILL_LAST + 1] = {}; - int32_t specialSkills[SPECIALSKILL_LAST + 1] = {}; - int32_t stats[STAT_LAST + 1] = {}; - int32_t statsPercent[STAT_LAST + 1] = {}; - int32_t currentSkill = 0; - int32_t currentStat = 0; - - bool disableDefense = false; - - void updatePercentStats(Player* player); - void updateStats(Player* player); - void updatePercentSkills(Player* player); - void updateSkills(Player* player); +public: + ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + ConditionGeneric(id, type, ticks, buff, subId, aggressive) + {} + + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + + bool setParam(ConditionParam_t param, int32_t value) override; + int32_t getParam(ConditionParam_t param) override; + + ConditionAttributes* clone() const override { return new ConditionAttributes(*this); } + + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + +private: + int32_t skills[SKILL_LAST + 1] = {}; + int32_t skillsPercent[SKILL_LAST + 1] = {}; + int32_t specialSkills[SPECIALSKILL_LAST + 1] = {}; + int32_t stats[STAT_LAST + 1] = {}; + int32_t statsPercent[STAT_LAST + 1] = {}; + int32_t currentSkill = 0; + int32_t currentSpecialSkill = 0; + int32_t currentStat = 0; + + bool disableDefense = false; + + void updatePercentStats(Player* player); + void updateStats(Player* player); + void updatePercentSkills(Player* player); + void updateSkills(Player* player); }; class ConditionRegeneration final : public ConditionGeneric { - public: - ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false): - ConditionGeneric(id, type, ticks, buff, subId, aggressive) {} +public: + ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + ConditionGeneric(id, type, ticks, buff, subId, aggressive) + {} - void addCondition(Creature* creature, const Condition* condition) override; - bool executeCondition(Creature* creature, int32_t interval) override; + void addCondition(Creature* creature, const Condition* condition) override; + bool executeCondition(Creature* creature, int32_t interval) override; - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) override; + int32_t getParam(ConditionParam_t param) override; - ConditionRegeneration* clone() const override { - return new ConditionRegeneration(*this); - } + ConditionRegeneration* clone() const override { return new ConditionRegeneration(*this); } - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - private: - uint32_t internalHealthTicks = 0; - uint32_t internalManaTicks = 0; +private: + uint32_t internalHealthTicks = 0; + uint32_t internalManaTicks = 0; - uint32_t healthTicks = 1000; - uint32_t manaTicks = 1000; - uint32_t healthGain = 0; - uint32_t manaGain = 0; + uint32_t healthTicks = 1000; + uint32_t manaTicks = 1000; + uint32_t healthGain = 0; + uint32_t manaGain = 0; }; class ConditionSoul final : public ConditionGeneric { - public: - ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - ConditionGeneric(id, type, ticks, buff, subId, aggressive) {} +public: + ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + ConditionGeneric(id, type, ticks, buff, subId, aggressive) + {} - void addCondition(Creature* creature, const Condition* condition) override; - bool executeCondition(Creature* creature, int32_t interval) override; + void addCondition(Creature* creature, const Condition* condition) override; + bool executeCondition(Creature* creature, int32_t interval) override; - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) override; + int32_t getParam(ConditionParam_t param) override; - ConditionSoul* clone() const override { - return new ConditionSoul(*this); - } + ConditionSoul* clone() const override { return new ConditionSoul(*this); } - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - private: - uint32_t internalSoulTicks = 0; - uint32_t soulTicks = 0; - uint32_t soulGain = 0; +private: + uint32_t internalSoulTicks = 0; + uint32_t soulTicks = 0; + uint32_t soulGain = 0; }; class ConditionInvisible final : public ConditionGeneric { - public: - ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - ConditionGeneric(id, type, ticks, buff, subId, aggressive) {} +public: + ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + ConditionGeneric(id, type, ticks, buff, subId, aggressive) + {} - bool startCondition(Creature* creature) override; - void endCondition(Creature* creature) override; + bool startCondition(Creature* creature) override; + void endCondition(Creature* creature) override; - ConditionInvisible* clone() const override { - return new ConditionInvisible(*this); - } + ConditionInvisible* clone() const override { return new ConditionInvisible(*this); } }; class ConditionDamage final : public Condition { - public: - ConditionDamage() = default; - ConditionDamage(ConditionId_t id, ConditionType_t type, bool buff = false, uint32_t subId = 0, bool aggressive = true) : - Condition(id, type, 0, buff, subId, aggressive) {} +public: + ConditionDamage() = default; + ConditionDamage(ConditionId_t id, ConditionType_t type, bool buff = false, uint32_t subId = 0, + bool aggressive = true) : + Condition(id, type, 0, buff, subId, aggressive) + {} - static void generateDamageList(int32_t amount, int32_t start, std::list& list); + static void generateDamageList(int32_t amount, int32_t start, std::list& list); - bool startCondition(Creature* creature) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - uint32_t getIcons() const override; + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + uint32_t getIcons() const override; - ConditionDamage* clone() const override { - return new ConditionDamage(*this); - } + ConditionDamage* clone() const override { return new ConditionDamage(*this); } - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) override; + int32_t getParam(ConditionParam_t param) override; - bool addDamage(int32_t rounds, int32_t time, int32_t value); - bool doForceUpdate() const { - return forceUpdate; - } - int32_t getTotalDamage() const; + bool addDamage(int32_t rounds, int32_t time, int32_t value); + bool doForceUpdate() const { return forceUpdate; } + int32_t getTotalDamage() const; - void setInitDamage(int32_t initDamage) { - this->initDamage = initDamage; - } + void setInitDamage(int32_t initDamage) { this->initDamage = initDamage; } - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - private: - int32_t maxDamage = 0; - int32_t minDamage = 0; - int32_t startDamage = 0; - int32_t periodDamage = 0; - int32_t periodDamageTick = 0; - int32_t tickInterval = 2000; - int32_t initDamage = 0; +private: + int32_t maxDamage = 0; + int32_t minDamage = 0; + int32_t startDamage = 0; + int32_t periodDamage = 0; + int32_t periodDamageTick = 0; + int32_t tickInterval = 2000; + int32_t initDamage = 0; - bool forceUpdate = false; - bool delayed = false; - bool field = false; - uint32_t owner = 0; + bool forceUpdate = false; + bool delayed = false; + bool field = false; + uint32_t owner = 0; - bool init(); + bool init(); - std::list damageList; + std::list damageList; - bool getNextDamage(int32_t& damage); - bool doDamage(Creature* creature, int32_t healthChange); + bool getNextDamage(int32_t& damage); + bool doDamage(Creature* creature, int32_t healthChange); - bool updateCondition(const Condition* addCondition) override; + bool updateCondition(const Condition* addCondition) override; }; class ConditionSpeed final : public Condition { - public: - ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, int32_t changeSpeed, bool aggressive = false) : - Condition(id, type, ticks, buff, subId, aggressive), speedDelta(changeSpeed) {} - - bool startCondition(Creature* creature) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - uint32_t getIcons() const override; - - ConditionSpeed* clone() const override { - return new ConditionSpeed(*this); - } +public: + ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, + int32_t changeSpeed, bool aggressive = false) : + Condition(id, type, ticks, buff, subId, aggressive), speedDelta(changeSpeed) + {} + + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + uint32_t getIcons() const override; + + ConditionSpeed* clone() const override { return new ConditionSpeed(*this); } - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) override; + int32_t getParam(ConditionParam_t param) override; - void setFormulaVars(float mina, float minb, float maxa, float maxb); + void setFormulaVars(float mina, float minb, float maxa, float maxb); - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - private: - int32_t speedDelta; +private: + int32_t speedDelta; - //formula variables - float mina = 0.0f; - float minb = 0.0f; - float maxa = 0.0f; - float maxb = 0.0f; + // formula variables + float mina = 0.0f; + float minb = 0.0f; + float maxa = 0.0f; + float maxb = 0.0f; }; class ConditionOutfit final : public Condition { - public: - ConditionOutfit(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - Condition(id, type, ticks, buff, subId, aggressive) {} +public: + ConditionOutfit(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + Condition(id, type, ticks, buff, subId, aggressive) + {} - bool startCondition(Creature* creature) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; - ConditionOutfit* clone() const override { - return new ConditionOutfit(*this); - } + ConditionOutfit* clone() const override { return new ConditionOutfit(*this); } - void setOutfit(const Outfit_t& outfit); + void setOutfit(const Outfit_t& outfit); - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - private: - Outfit_t outfit; +private: + Outfit_t outfit; }; class ConditionLight final : public Condition { - public: - ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, uint8_t lightlevel, uint8_t lightcolor, bool aggressive = false) : - Condition(id, type, ticks, buff, subId, aggressive), lightInfo(lightlevel, lightcolor) {} - - bool startCondition(Creature* creature) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - - ConditionLight* clone() const override { - return new ConditionLight(*this); - } - - bool setParam(ConditionParam_t param, int32_t value) override; - - //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - - private: - LightInfo lightInfo; - uint32_t internalLightTicks = 0; - uint32_t lightChangeInterval = 0; +public: + ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, uint8_t lightlevel, + uint8_t lightcolor, bool aggressive = false) : + Condition(id, type, ticks, buff, subId, aggressive), lightInfo(lightlevel, lightcolor) + {} + + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + + ConditionLight* clone() const override { return new ConditionLight(*this); } + + bool setParam(ConditionParam_t param, int32_t value) override; + int32_t getParam(ConditionParam_t param) override; + + // serialization + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + +private: + LightInfo lightInfo; + uint32_t internalLightTicks = 0; + uint32_t lightChangeInterval = 0; }; class ConditionSpellCooldown final : public ConditionGeneric { - public: - ConditionSpellCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - ConditionGeneric(id, type, ticks, buff, subId, aggressive) {} +public: + ConditionSpellCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, + bool aggressive = false) : + ConditionGeneric(id, type, ticks, buff, subId, aggressive) + {} - bool startCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; + bool startCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; - ConditionSpellCooldown* clone() const override { - return new ConditionSpellCooldown(*this); - } + ConditionSpellCooldown* clone() const override { return new ConditionSpellCooldown(*this); } }; class ConditionSpellGroupCooldown final : public ConditionGeneric { - public: - ConditionSpellGroupCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0, bool aggressive = false) : - ConditionGeneric(id, type, ticks, buff, subId, aggressive) {} +public: + ConditionSpellGroupCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, + uint32_t subId = 0, bool aggressive = false) : + ConditionGeneric(id, type, ticks, buff, subId, aggressive) + {} + + bool startCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; - bool startCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; + ConditionSpellGroupCooldown* clone() const override { return new ConditionSpellGroupCooldown(*this); } +}; - ConditionSpellGroupCooldown* clone() const override { - return new ConditionSpellGroupCooldown(*this); +class ConditionDrunk final : public Condition +{ +public: + ConditionDrunk(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, + uint8_t drunkenness, bool aggressive = false) : + Condition(id, type, ticks, buff, subId, aggressive) + { + if (drunkenness != 0) { + this->drunkenness = drunkenness; } + } + + uint32_t getIcons() const override; + void endCondition(Creature* creature) override; + bool startCondition(Creature* creature) override; + bool setParam(ConditionParam_t param, int32_t value) override; + void addCondition(Creature* creature, const Condition* condition) override; + + ConditionDrunk* clone() const override { return new ConditionDrunk(*this); } + +private: + uint8_t drunkenness = 25; + + bool updateCondition(const Condition* addCondition) override; }; -#endif +#endif // FS_CONDITION_H diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 6ed4424bdb..93d8ebef8f 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -1,35 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found +// in the LICENSE file. #include "otpch.h" -#include +#include "configmanager.h" + +#include "game.h" +#include "monster.h" +#include "pugicast.h" + #if __has_include("luajit/lua.hpp") #include #else #include #endif -#include "configmanager.h" -#include "game.h" -#include "pugicast.h" - #if LUA_VERSION_NUM >= 502 #undef lua_strlen #define lua_strlen lua_rawlen @@ -86,12 +72,9 @@ bool getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultVa return val != 0; } -} +} // namespace -ConfigManager::ConfigManager() -{ - string[CONFIG_FILE] = "config.lua"; -} +ConfigManager::ConfigManager() { string[CONFIG_FILE] = "config.lua"; } namespace { @@ -130,7 +113,7 @@ ExperienceStages loadXMLStages() ExperienceStages stages; for (auto stageNode : doc.child("stages").children()) { - if (strcasecmp(stageNode.name(), "config") == 0) { + if (caseInsensitiveEqual(stageNode.name(), "config")) { if (!stageNode.attribute("enabled").as_bool()) { return {}; } @@ -161,7 +144,7 @@ ExperienceStages loadXMLStages() return stages; } -} +} // namespace bool ConfigManager::load() { @@ -178,8 +161,8 @@ bool ConfigManager::load() return false; } - //parse config - if (!loaded) { //info that must be loaded one time (unless we reset the modules involved) + // parse config + if (!loaded) { // info that must be loaded one time (unless we reset the modules involved) boolean[BIND_ONLY_GLOBAL_ADDRESS] = getGlobalBoolean(L, "bindOnlyGlobalAddress", false); boolean[OPTIMIZE_DATABASE] = getGlobalBoolean(L, "startupDatabaseOptimization", true); @@ -237,14 +220,16 @@ bool ConfigManager::load() boolean[SERVER_SAVE_SHUTDOWN] = getGlobalBoolean(L, "serverSaveShutdown", true); boolean[ONLINE_OFFLINE_CHARLIST] = getGlobalBoolean(L, "showOnlineStatusInCharlist", false); boolean[YELL_ALLOW_PREMIUM] = getGlobalBoolean(L, "yellAlwaysAllowPremium", false); + boolean[PREMIUM_TO_SEND_PRIVATE] = getGlobalBoolean(L, "premiumToSendPrivate", false); boolean[FORCE_MONSTERTYPE_LOAD] = getGlobalBoolean(L, "forceMonsterTypesOnLoad", true); boolean[DEFAULT_WORLD_LIGHT] = getGlobalBoolean(L, "defaultWorldLight", true); boolean[HOUSE_OWNED_BY_ACCOUNT] = getGlobalBoolean(L, "houseOwnedByAccount", false); - boolean[LUA_ITEM_DESC] = getGlobalBoolean(L, "luaItemDesc", false); boolean[CLEAN_PROTECTION_ZONES] = getGlobalBoolean(L, "cleanProtectionZones", false); boolean[HOUSE_DOOR_SHOW_PRICE] = getGlobalBoolean(L, "houseDoorShowPrice", true); boolean[ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS] = getGlobalBoolean(L, "onlyInvitedCanMoveHouseItems", true); boolean[REMOVE_ON_DESPAWN] = getGlobalBoolean(L, "removeOnDespawn", true); + boolean[PLAYER_CONSOLE_LOGS] = getGlobalBoolean(L, "showPlayerLogInConsole", true); + boolean[TWO_FACTOR_AUTH] = getGlobalBoolean(L, "enableTwoFactorAuth", true); string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); string[SERVER_NAME] = getGlobalString(L, "serverName", ""); @@ -252,13 +237,13 @@ bool ConfigManager::load() string[OWNER_EMAIL] = getGlobalString(L, "ownerEmail", ""); string[URL] = getGlobalString(L, "url", ""); string[LOCATION] = getGlobalString(L, "location", ""); - string[MOTD] = getGlobalString(L, "motd", ""); string[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp"); integer[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers"); integer[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 60000); - integer[DEFAULT_DESPAWNRANGE] = getGlobalNumber(L, "deSpawnRange", 2); - integer[DEFAULT_DESPAWNRADIUS] = getGlobalNumber(L, "deSpawnRadius", 50); + integer[DEFAULT_DESPAWNRANGE] = Monster::despawnRange = getGlobalNumber(L, "deSpawnRange", 2); + integer[DEFAULT_DESPAWNRADIUS] = Monster::despawnRadius = getGlobalNumber(L, "deSpawnRadius", 50); + integer[DEFAULT_WALKTOSPAWNRADIUS] = getGlobalNumber(L, "walkToSpawnRadius", 15); integer[RATE_EXPERIENCE] = getGlobalNumber(L, "rateExp", 5); integer[RATE_SKILL] = getGlobalNumber(L, "rateSkill", 3); integer[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 2); @@ -288,29 +273,28 @@ bool ConfigManager::load() integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); integer[SERVER_SAVE_NOTIFY_DURATION] = getGlobalNumber(L, "serverSaveNotifyDuration", 5); integer[YELL_MINIMUM_LEVEL] = getGlobalNumber(L, "yellMinimumLevel", 2); + integer[MINIMUM_LEVEL_TO_SEND_PRIVATE] = getGlobalNumber(L, "minimumLevelToSendPrivate", 1); integer[VIP_FREE_LIMIT] = getGlobalNumber(L, "vipFreeLimit", 20); integer[VIP_PREMIUM_LIMIT] = getGlobalNumber(L, "vipPremiumLimit", 100); + integer[DEPOT_FREE_LIMIT] = getGlobalNumber(L, "depotFreeLimit", 2000); + integer[DEPOT_PREMIUM_LIMIT] = getGlobalNumber(L, "depotPremiumLimit", 15000); + integer[QUEST_TRACKER_FREE_LIMIT] = getGlobalNumber(L, "questTrackerFreeLimit", 10); + integer[QUEST_TRACKER_PREMIUM_LIMIT] = getGlobalNumber(L, "questTrackerPremiumLimit", 15); expStages = loadXMLStages(); if (expStages.empty()) { expStages = loadLuaStages(L); } else { - std::cout << "[Warning - ConfigManager::load] XML stages are deprecated, consider moving to config.lua." << std::endl; + std::cout << "[Warning - ConfigManager::load] XML stages are deprecated, " + "consider moving to config.lua." + << std::endl; } expStages.shrink_to_fit(); loaded = true; lua_close(L); - return true; -} -bool ConfigManager::reload() -{ - bool result = load(); - if (transformToSHA1(getString(ConfigManager::MOTD)) != g_game.getMotdHash()) { - g_game.incrementMotdNum(); - } - return result; + return true; } static std::string dummyStr; diff --git a/src/configmanager.h b/src/configmanager.h index e2a3f2d48f..4f1be213f6 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -1,164 +1,154 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 -#define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 - -#include -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CONFIGMANAGER_H +#define FS_CONFIGMANAGER_H using ExperienceStages = std::vector>; class ConfigManager { - public: - ConfigManager(); - - enum boolean_config_t { - ALLOW_CHANGEOUTFIT, - ONE_PLAYER_ON_ACCOUNT, - AIMBOT_HOTKEY_ENABLED, - REMOVE_RUNE_CHARGES, - REMOVE_WEAPON_AMMO, - REMOVE_WEAPON_CHARGES, - REMOVE_POTION_CHARGES, - EXPERIENCE_FROM_PLAYERS, - FREE_PREMIUM, - REPLACE_KICK_ON_LOGIN, - ALLOW_CLONES, - ALLOW_WALKTHROUGH, - BIND_ONLY_GLOBAL_ADDRESS, - OPTIMIZE_DATABASE, - MARKET_PREMIUM, - EMOTE_SPELLS, - STAMINA_SYSTEM, - WARN_UNSAFE_SCRIPTS, - CONVERT_UNSAFE_SCRIPTS, - CLASSIC_EQUIPMENT_SLOTS, - CLASSIC_ATTACK_SPEED, - SCRIPTS_CONSOLE_LOGS, - SERVER_SAVE_NOTIFY_MESSAGE, - SERVER_SAVE_CLEAN_MAP, - SERVER_SAVE_CLOSE, - SERVER_SAVE_SHUTDOWN, - ONLINE_OFFLINE_CHARLIST, - YELL_ALLOW_PREMIUM, - FORCE_MONSTERTYPE_LOAD, - DEFAULT_WORLD_LIGHT, - HOUSE_OWNED_BY_ACCOUNT, - LUA_ITEM_DESC, - CLEAN_PROTECTION_ZONES, - HOUSE_DOOR_SHOW_PRICE, - ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, - REMOVE_ON_DESPAWN, - - LAST_BOOLEAN_CONFIG /* this must be the last one */ - }; - - enum string_config_t { - MAP_NAME, - HOUSE_RENT_PERIOD, - SERVER_NAME, - OWNER_NAME, - OWNER_EMAIL, - URL, - LOCATION, - IP, - MOTD, - WORLD_TYPE, - MYSQL_HOST, - MYSQL_USER, - MYSQL_PASS, - MYSQL_DB, - MYSQL_SOCK, - DEFAULT_PRIORITY, - MAP_AUTHOR, - CONFIG_FILE, - - LAST_STRING_CONFIG /* this must be the last one */ - }; - - enum integer_config_t { - SQL_PORT, - MAX_PLAYERS, - PZ_LOCKED, - DEFAULT_DESPAWNRANGE, - DEFAULT_DESPAWNRADIUS, - RATE_EXPERIENCE, - RATE_SKILL, - RATE_LOOT, - RATE_MAGIC, - RATE_SPAWN, - HOUSE_PRICE, - RED_DAILY_LIMIT, - RED_WEEKLY_LIMIT, - RED_MONTHLY_LIMIT, - RED_SKULL_LENGTH, - BLACK_DAILY_LIMIT, - BLACK_WEEKLY_LIMIT, - BLACK_MONTHLY_LIMIT, - BLACK_SKULL_LENGTH, - MAX_MESSAGEBUFFER, - ACTIONS_DELAY_INTERVAL, - EX_ACTIONS_DELAY_INTERVAL, - KICK_AFTER_MINUTES, - PROTECTION_LEVEL, - DEATH_LOSE_PERCENT, - STATUSQUERY_TIMEOUT, - WHITE_SKULL_TIME, - GAME_PORT, - LOGIN_PORT, - STATUS_PORT, - STAIRHOP_DELAY, - MARKET_OFFER_DURATION, - CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, - MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, - EXP_FROM_PLAYERS_LEVEL_RANGE, - MAX_PACKETS_PER_SECOND, - SERVER_SAVE_NOTIFY_DURATION, - YELL_MINIMUM_LEVEL, - VIP_FREE_LIMIT, - VIP_PREMIUM_LIMIT, - - LAST_INTEGER_CONFIG /* this must be the last one */ - }; - - bool load(); - bool reload(); - - const std::string& getString(string_config_t what) const; - int32_t getNumber(integer_config_t what) const; - bool getBoolean(boolean_config_t what) const; - float getExperienceStage(uint32_t level) const; - - bool setString(string_config_t what, const std::string& value); - bool setNumber(integer_config_t what, int32_t value); - bool setBoolean(boolean_config_t what, bool value); - - private: - std::string string[LAST_STRING_CONFIG] = {}; - int32_t integer[LAST_INTEGER_CONFIG] = {}; - bool boolean[LAST_BOOLEAN_CONFIG] = {}; - - ExperienceStages expStages = {}; - - bool loaded = false; +public: + ConfigManager(); + + enum boolean_config_t + { + ALLOW_CHANGEOUTFIT, + ONE_PLAYER_ON_ACCOUNT, + AIMBOT_HOTKEY_ENABLED, + REMOVE_RUNE_CHARGES, + REMOVE_WEAPON_AMMO, + REMOVE_WEAPON_CHARGES, + REMOVE_POTION_CHARGES, + EXPERIENCE_FROM_PLAYERS, + FREE_PREMIUM, + REPLACE_KICK_ON_LOGIN, + ALLOW_CLONES, + ALLOW_WALKTHROUGH, + BIND_ONLY_GLOBAL_ADDRESS, + OPTIMIZE_DATABASE, + MARKET_PREMIUM, + EMOTE_SPELLS, + STAMINA_SYSTEM, + WARN_UNSAFE_SCRIPTS, + CONVERT_UNSAFE_SCRIPTS, + CLASSIC_EQUIPMENT_SLOTS, + CLASSIC_ATTACK_SPEED, + SCRIPTS_CONSOLE_LOGS, + SERVER_SAVE_NOTIFY_MESSAGE, + SERVER_SAVE_CLEAN_MAP, + SERVER_SAVE_CLOSE, + SERVER_SAVE_SHUTDOWN, + ONLINE_OFFLINE_CHARLIST, + YELL_ALLOW_PREMIUM, + PREMIUM_TO_SEND_PRIVATE, + FORCE_MONSTERTYPE_LOAD, + DEFAULT_WORLD_LIGHT, + HOUSE_OWNED_BY_ACCOUNT, + CLEAN_PROTECTION_ZONES, + HOUSE_DOOR_SHOW_PRICE, + ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, + REMOVE_ON_DESPAWN, + PLAYER_CONSOLE_LOGS, + TWO_FACTOR_AUTH, + + LAST_BOOLEAN_CONFIG /* this must be the last one */ + }; + + enum string_config_t + { + MAP_NAME, + HOUSE_RENT_PERIOD, + SERVER_NAME, + OWNER_NAME, + OWNER_EMAIL, + URL, + LOCATION, + IP, + WORLD_TYPE, + MYSQL_HOST, + MYSQL_USER, + MYSQL_PASS, + MYSQL_DB, + MYSQL_SOCK, + DEFAULT_PRIORITY, + MAP_AUTHOR, + CONFIG_FILE, + + LAST_STRING_CONFIG /* this must be the last one */ + }; + + enum integer_config_t + { + SQL_PORT, + MAX_PLAYERS, + PZ_LOCKED, + DEFAULT_DESPAWNRANGE, + DEFAULT_DESPAWNRADIUS, + DEFAULT_WALKTOSPAWNRADIUS, + RATE_EXPERIENCE, + RATE_SKILL, + RATE_LOOT, + RATE_MAGIC, + RATE_SPAWN, + HOUSE_PRICE, + RED_DAILY_LIMIT, + RED_WEEKLY_LIMIT, + RED_MONTHLY_LIMIT, + RED_SKULL_LENGTH, + BLACK_DAILY_LIMIT, + BLACK_WEEKLY_LIMIT, + BLACK_MONTHLY_LIMIT, + BLACK_SKULL_LENGTH, + MAX_MESSAGEBUFFER, + ACTIONS_DELAY_INTERVAL, + EX_ACTIONS_DELAY_INTERVAL, + KICK_AFTER_MINUTES, + PROTECTION_LEVEL, + DEATH_LOSE_PERCENT, + STATUSQUERY_TIMEOUT, + WHITE_SKULL_TIME, + GAME_PORT, + LOGIN_PORT, + STATUS_PORT, + STAIRHOP_DELAY, + MARKET_OFFER_DURATION, + CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, + MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, + EXP_FROM_PLAYERS_LEVEL_RANGE, + MAX_PACKETS_PER_SECOND, + SERVER_SAVE_NOTIFY_DURATION, + YELL_MINIMUM_LEVEL, + MINIMUM_LEVEL_TO_SEND_PRIVATE, + VIP_FREE_LIMIT, + VIP_PREMIUM_LIMIT, + DEPOT_FREE_LIMIT, + DEPOT_PREMIUM_LIMIT, + QUEST_TRACKER_FREE_LIMIT, + QUEST_TRACKER_PREMIUM_LIMIT, + + LAST_INTEGER_CONFIG /* this must be the last one */ + }; + + bool load(); + + const std::string& getString(string_config_t what) const; + int32_t getNumber(integer_config_t what) const; + bool getBoolean(boolean_config_t what) const; + float getExperienceStage(uint32_t level) const; + + bool setString(string_config_t what, const std::string& value); + bool setNumber(integer_config_t what, int32_t value); + bool setBoolean(boolean_config_t what, bool value); + +private: + std::string string[LAST_STRING_CONFIG] = {}; + int32_t integer[LAST_INTEGER_CONFIG] = {}; + bool boolean[LAST_BOOLEAN_CONFIG] = {}; + + ExperienceStages expStages = {}; + + bool loaded = false; }; -#endif +#endif // FS_CONFIGMANAGER_H diff --git a/src/connection.cpp b/src/connection.cpp index a2d294e14f..29d20da3be 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -1,34 +1,20 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "configmanager.h" #include "connection.h" + +#include "configmanager.h" #include "outputmessage.h" #include "protocol.h" -#include "scheduler.h" #include "server.h" +#include "tasks.h" extern ConfigManager g_config; -Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort) +Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, + ConstServicePort_ptr servicePort) { std::lock_guard lockClass(connectionManagerLock); @@ -63,24 +49,20 @@ void ConnectionManager::closeAll() void Connection::close(bool force) { - //any thread + // any thread ConnectionManager::getInstance().releaseConnection(shared_from_this()); std::lock_guard lockClass(connectionLock); - if (closed) { - return; - } - closed = true; + connectionState = CONNECTION_STATE_DISCONNECTED; if (protocol) { - g_dispatcher.addTask( - createTask(std::bind(&Protocol::release, protocol))); + g_dispatcher.addTask(createTask([protocol = protocol]() { protocol->release(); })); } if (messageQueue.empty() || force) { closeSocket(); } else { - //will be closed by the destructor or onWriteOperation + // will be closed by the destructor or onWriteOperation } } @@ -99,30 +81,39 @@ void Connection::closeSocket() } } -Connection::~Connection() -{ - closeSocket(); -} +Connection::~Connection() { closeSocket(); } void Connection::accept(Protocol_ptr protocol) { this->protocol = protocol; - g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol))); - + g_dispatcher.addTask(createTask([=]() { protocol->onConnect(); })); + connectionState = CONNECTION_STATE_GAMEWORLD_AUTH; accept(); } void Connection::accept() { + if (connectionState == CONNECTION_STATE_PENDING) { + connectionState = CONNECTION_STATE_REQUEST_CHARLIST; + } + std::lock_guard lockClass(connectionLock); try { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); - readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + readTimer.async_wait( + [thisPtr = std::weak_ptr(shared_from_this())](const boost::system::error_code& error) { + Connection::handleTimeout(thisPtr, error); + }); // Read size of the first packet - boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + auto bufferLength = !receivedLastChar && receivedName && connectionState == CONNECTION_STATE_GAMEWORLD_AUTH + ? 1 + : NetworkMessage::HEADER_LENGTH; + boost::asio::async_read( + socket, boost::asio::buffer(msg.getBuffer(), bufferLength), + [thisPtr = shared_from_this()](const boost::system::error_code& error, auto /*bytes_transferred*/) { + thisPtr->parseHeader(error); + }); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::accept] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -137,17 +128,44 @@ void Connection::parseHeader(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (closed) { + } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { return; } uint32_t timePassed = std::max(1, (time(nullptr) - timeConnected) + 1); - if ((++packetsSent / timePassed) > static_cast(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) { + if ((++packetsSent / timePassed) > + static_cast(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) { std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl; close(); return; } + if (!receivedLastChar && connectionState == CONNECTION_STATE_GAMEWORLD_AUTH) { + uint8_t* msgBuffer = msg.getBuffer(); + + if (!receivedName && msgBuffer[1] == 0x00) { + receivedLastChar = true; + } else { + if (!receivedName) { + receivedName = true; + + accept(); + return; + } + + if (msgBuffer[0] == 0x0A) { + receivedLastChar = true; + } + + accept(); + return; + } + } + + if (receivedLastChar && connectionState == CONNECTION_STATE_GAMEWORLD_AUTH) { + connectionState = CONNECTION_STATE_GAME; + } + if (timePassed > 2) { timeConnected = time(nullptr); packetsSent = 0; @@ -161,13 +179,18 @@ void Connection::parseHeader(const boost::system::error_code& error) try { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); - readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + readTimer.async_wait( + [thisPtr = std::weak_ptr(shared_from_this())](const boost::system::error_code& error) { + Connection::handleTimeout(thisPtr, error); + }); // Read packet content msg.setLength(size + NetworkMessage::HEADER_LENGTH); - boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size), - std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); + boost::asio::async_read( + socket, boost::asio::buffer(msg.getBodyBuffer(), size), + [thisPtr = shared_from_this()](const boost::system::error_code& error, auto /*bytes_transferred*/) { + thisPtr->parsePacket(error); + }); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -182,32 +205,25 @@ void Connection::parsePacket(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (closed) { + } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { return; } - //Check packet checksum - uint32_t checksum; - int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH; - if (len > 0) { - checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len); - } else { - checksum = 0; - } - - uint32_t recvChecksum = msg.get(); - if (recvChecksum != checksum) { - // it might not have been the checksum, step back - msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH); - } + // Read potential checksum bytes + msg.get(); if (!receivedFirst) { - // First message received receivedFirst = true; if (!protocol) { + // Skip deprecated checksum bytes (with clients that aren't using it in mind) + uint16_t len = msg.getLength(); + if (len < 280 && len != 151) { + msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH); + } + // Game protocol has already been created at this point - protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this()); + protocol = service_port->make_protocol(msg, shared_from_this()); if (!protocol) { close(FORCE_CLOSE); return; @@ -223,13 +239,17 @@ void Connection::parsePacket(const boost::system::error_code& error) try { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); - readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + readTimer.async_wait( + [thisPtr = std::weak_ptr(shared_from_this())](const boost::system::error_code& error) { + Connection::handleTimeout(thisPtr, error); + }); // Wait to the next packet - boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + boost::asio::async_read( + socket, boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), + [thisPtr = shared_from_this()](const boost::system::error_code& error, auto /*bytes_transferred*/) { + thisPtr->parseHeader(error); + }); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -239,7 +259,7 @@ void Connection::parsePacket(const boost::system::error_code& error) void Connection::send(const OutputMessage_ptr& msg) { std::lock_guard lockClass(connectionLock); - if (closed) { + if (connectionState == CONNECTION_STATE_DISCONNECTED) { return; } @@ -255,12 +275,16 @@ void Connection::internalSend(const OutputMessage_ptr& msg) protocol->onSendMessage(msg); try { writeTimer.expires_from_now(std::chrono::seconds(CONNECTION_WRITE_TIMEOUT)); - writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); - - boost::asio::async_write(socket, - boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()), - std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + writeTimer.async_wait( + [thisPtr = std::weak_ptr(shared_from_this())](const boost::system::error_code& error) { + Connection::handleTimeout(thisPtr, error); + }); + + boost::asio::async_write( + socket, boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()), + [thisPtr = shared_from_this()](const boost::system::error_code& error, auto /*bytes_transferred*/) { + thisPtr->onWriteOperation(error); + }); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -295,7 +319,7 @@ void Connection::onWriteOperation(const boost::system::error_code& error) if (!messageQueue.empty()) { internalSend(messageQueue.front()); - } else if (closed) { + } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { closeSocket(); } } @@ -303,7 +327,7 @@ void Connection::onWriteOperation(const boost::system::error_code& error) void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error) { if (error == boost::asio::error::operation_aborted) { - //The timer has been manually canceled + // The timer has been cancelled manually return; } diff --git a/src/connection.h b/src/connection.h index 6b847cfaf9..169060a81b 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,29 +1,27 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348 -#define FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348 - -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CONNECTION_H +#define FS_CONNECTION_H #include "networkmessage.h" +enum ConnectionState_t +{ + CONNECTION_STATE_DISCONNECTED, + CONNECTION_STATE_REQUEST_CHARLIST, + CONNECTION_STATE_GAMEWORLD_AUTH, + CONNECTION_STATE_GAME, + CONNECTION_STATE_PENDING +}; + +enum checksumMode_t +{ + CHECKSUM_DISABLED, + CHECKSUM_ADLER, + CHECKSUM_SEQUENCE +}; + static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30; static constexpr int32_t CONNECTION_READ_TIMEOUT = 30; @@ -42,87 +40,91 @@ using ConstServicePort_ptr = std::shared_ptr; class ConnectionManager { - public: - static ConnectionManager& getInstance() { - static ConnectionManager instance; - return instance; - } - - Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort); - void releaseConnection(const Connection_ptr& connection); - void closeAll(); - - private: - ConnectionManager() = default; - - std::unordered_set connections; - std::mutex connectionManagerLock; +public: + static ConnectionManager& getInstance() + { + static ConnectionManager instance; + return instance; + } + + Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort); + void releaseConnection(const Connection_ptr& connection); + void closeAll(); + +private: + ConnectionManager() = default; + + std::unordered_set connections; + std::mutex connectionManagerLock; }; class Connection : public std::enable_shared_from_this { - public: - // non-copyable - Connection(const Connection&) = delete; - Connection& operator=(const Connection&) = delete; +public: + // non-copyable + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; - enum { FORCE_CLOSE = true }; + enum + { + FORCE_CLOSE = true + }; - Connection(boost::asio::io_service& io_service, - ConstServicePort_ptr service_port) : - readTimer(io_service), - writeTimer(io_service), - service_port(std::move(service_port)), - socket(io_service), - timeConnected(time(nullptr)) {} - ~Connection(); + Connection(boost::asio::io_service& io_service, ConstServicePort_ptr service_port) : + readTimer(io_service), + writeTimer(io_service), + service_port(std::move(service_port)), + socket(io_service), + timeConnected(time(nullptr)) + {} + ~Connection(); - friend class ConnectionManager; + friend class ConnectionManager; - void close(bool force = false); - // Used by protocols that require server to send first - void accept(Protocol_ptr protocol); - void accept(); + void close(bool force = false); + // Used by protocols that require server to send first + void accept(Protocol_ptr protocol); + void accept(); - void send(const OutputMessage_ptr& msg); + void send(const OutputMessage_ptr& msg); - uint32_t getIP(); + uint32_t getIP(); - private: - void parseHeader(const boost::system::error_code& error); - void parsePacket(const boost::system::error_code& error); +private: + void parseHeader(const boost::system::error_code& error); + void parsePacket(const boost::system::error_code& error); - void onWriteOperation(const boost::system::error_code& error); + void onWriteOperation(const boost::system::error_code& error); - static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error); + static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error); - void closeSocket(); - void internalSend(const OutputMessage_ptr& msg); + void closeSocket(); + void internalSend(const OutputMessage_ptr& msg); - boost::asio::ip::tcp::socket& getSocket() { - return socket; - } - friend class ServicePort; + boost::asio::ip::tcp::socket& getSocket() { return socket; } + friend class ServicePort; - NetworkMessage msg; + NetworkMessage msg; - boost::asio::steady_timer readTimer; - boost::asio::steady_timer writeTimer; + boost::asio::steady_timer readTimer; + boost::asio::steady_timer writeTimer; - std::recursive_mutex connectionLock; + std::recursive_mutex connectionLock; - std::list messageQueue; + std::list messageQueue; - ConstServicePort_ptr service_port; - Protocol_ptr protocol; + ConstServicePort_ptr service_port; + Protocol_ptr protocol; - boost::asio::ip::tcp::socket socket; + boost::asio::ip::tcp::socket socket; - time_t timeConnected; - uint32_t packetsSent = 0; + time_t timeConnected; + uint32_t packetsSent = 0; - bool closed = false; - bool receivedFirst = false; + ConnectionState_t connectionState = CONNECTION_STATE_PENDING; + bool receivedFirst = false; + bool receivedName = false; + bool receivedLastChar = false; }; -#endif +#endif // FS_CONNECTION_H diff --git a/src/const.h b/src/const.h index 7e41643bec..ba70dcdb98 100644 --- a/src/const.h +++ b/src/const.h @@ -1,28 +1,25 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B -#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CONST_H +#define FS_CONST_H static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590; +static constexpr int32_t MIN_MARKET_FEE = 20; +static constexpr int32_t MAX_MARKET_FEE = 100000; + +enum MagicEffectsType_t : uint8_t +{ + MAGIC_EFFECTS_END_LOOP = 0, // ends the magic effect loop + MAGIC_EFFECTS_DELTA = 1, // needs uint8_t delta after type to adjust position + MAGIC_EFFECTS_DELAY = 2, // needs uint16_t delay after type to delay in miliseconds effect display + MAGIC_EFFECTS_CREATE_EFFECT = 3, // needs uint8_t effectid after type + MAGIC_EFFECTS_CREATE_DISTANCEEFFECT = 4, // needs uint8_t and deltaX(int8_t), deltaY(int8_t) after type + MAGIC_EFFECTS_CREATE_DISTANCEEFFECT_REVERSED = 5, // needs uint8_t and deltaX(int8_t), deltaY(int8_t) after type +}; -enum MagicEffectClasses : uint8_t { +enum MagicEffectClasses : uint8_t +{ CONST_ME_NONE, CONST_ME_DRAWBLOOD = 1, @@ -113,9 +110,71 @@ enum MagicEffectClasses : uint8_t { CONST_ME_CRITICAL_DAMAGE = 173, // 174 is empty CONST_ME_PLUNGING_FISH = 175, + CONST_ME_BLUECHAIN = 176, + CONST_ME_ORANGECHAIN = 177, + CONST_ME_GREENCHAIN = 178, + CONST_ME_PURPLECHAIN = 179, + CONST_ME_GREYCHAIN = 180, + CONST_ME_YELLOWCHAIN = 181, + CONST_ME_YELLOWSPARKLES = 182, + // 183 is empty + CONST_ME_FAEEXPLOSION = 184, + CONST_ME_FAECOMING = 185, + CONST_ME_FAEGOING = 186, + // 187 is empty + CONST_ME_BIGCLOUDSSINGLESPACE = 188, + CONST_ME_STONESSINGLESPACE = 189, + // 190 is empty + CONST_ME_BLUEGHOST = 191, + // 192 is empty + CONST_ME_POINTOFINTEREST = 193, + CONST_ME_MAPEFFECT = 194, + CONST_ME_PINKSPARK = 195, + CONST_ME_FIREWORK_GREEN = 196, + CONST_ME_FIREWORK_ORANGE = 197, + CONST_ME_FIREWORK_PURPLE = 198, + CONST_ME_FIREWORK_TURQUOISE = 199, + // 200 is empty + CONST_ME_THECUBE = 201, + CONST_ME_DRAWINK = 202, + CONST_ME_PRISMATICSPARKLES = 203, + CONST_ME_THAIAN = 204, + CONST_ME_THAIANGHOST = 205, + CONST_ME_GHOSTSMOKE = 206, + // 207 is empty + CONST_ME_FLOATINGBLOCK = 208, + CONST_ME_BLOCK = 209, + CONST_ME_ROOTING = 210, + // 211-212 are empty + CONST_ME_GHOSTLYSCRATCH = 213, + CONST_ME_GHOSTLYBITE = 214, + CONST_ME_BIGSCRATCHING = 215, + CONST_ME_SLASH = 216, + CONST_ME_BITE = 217, + // 218 is empty + CONST_ME_CHIVALRIOUSCHALLENGE = 219, + CONST_ME_DIVINEDAZZLE = 220, + CONST_ME_ELECTRICALSPARK = 221, + CONST_ME_PURPLETELEPORT = 222, + CONST_ME_REDTELEPORT = 223, + CONST_ME_ORANGETELEPORT = 224, + CONST_ME_GREYTELEPORT = 225, + CONST_ME_LIGHTBLUETELEPORT = 226, + // 227-229 are empty + CONST_ME_FATAL = 230, + CONST_ME_DODGE = 231, + CONST_ME_HOURGLASS = 232, + // 233-234 are empty + CONST_ME_FERUMBRAS_1 = 235, + CONST_ME_GAZHARAGOTH = 236, + CONST_ME_MAD_MAGE = 237, + CONST_ME_HORESTIS = 238, + CONST_ME_DEVOVORGA = 239, + CONST_ME_FERUMBRAS_2 = 240, }; -enum ShootType_t : uint8_t { +enum ShootType_t : uint8_t +{ CONST_ANI_NONE, CONST_ANI_SPEAR = 1, @@ -172,39 +231,48 @@ enum ShootType_t : uint8_t { CONST_ANI_GLOOTHSPEAR = 53, CONST_ANI_SIMPLEARROW = 54, + CONST_ANI_LEAFSTAR = 56, + CONST_ANI_DIAMONDARROW = 57, + CONST_ANI_SPECTRALBOLT = 58, + CONST_ANI_ROYALSTAR = 59, + // for internal use, don't send to client CONST_ANI_WEAPONTYPE = 0xFE, // 254 }; -enum SpeakClasses : uint8_t { +enum SpeakClasses : uint8_t +{ TALKTYPE_SAY = 1, TALKTYPE_WHISPER = 2, TALKTYPE_YELL = 3, - TALKTYPE_PRIVATE_FROM = 4, - TALKTYPE_PRIVATE_TO = 5, + TALKTYPE_PRIVATE_FROM = 4, // Received private message + TALKTYPE_PRIVATE_TO = 5, // Sent private message + // TALKTYPE_CHANNEL_M = 6 // not working (?) TALKTYPE_CHANNEL_Y = 7, TALKTYPE_CHANNEL_O = 8, - TALKTYPE_PRIVATE_NP = 10, - TALKTYPE_PRIVATE_PN = 12, + TALKTYPE_SPELL = 9, // Like SAY but with "casts" instead of "says" + TALKTYPE_PRIVATE_NP = 10, // NPC speaking to player + TALKTYPE_PRIVATE_NP_CONSOLE = 11, // NPC channel message, no text on game screen, for sendPrivateMessage use only + TALKTYPE_PRIVATE_PN = 12, // Player speaking to NPC TALKTYPE_BROADCAST = 13, - TALKTYPE_CHANNEL_R1 = 14, //red - #c text - TALKTYPE_PRIVATE_RED_FROM = 15, //@name@text - TALKTYPE_PRIVATE_RED_TO = 16, //@name@text + TALKTYPE_CHANNEL_R1 = 14, // red - #c text + TALKTYPE_PRIVATE_RED_FROM = 15, // @name@text + TALKTYPE_PRIVATE_RED_TO = 16, // @name@text TALKTYPE_MONSTER_SAY = 36, TALKTYPE_MONSTER_YELL = 37, + TALKTYPE_POTION = 52, // Like MONSTER_SAY but can be disabled in client settings }; -enum MessageClasses : uint8_t { - MESSAGE_STATUS_CONSOLE_BLUE = 4, /*FIXME Blue message in the console*/ - - MESSAGE_STATUS_CONSOLE_RED = 13, /*Red message in the console*/ +enum MessageClasses : uint8_t +{ + MESSAGE_STATUS_DEFAULT = 17, // White, bottom + console + MESSAGE_STATUS_WARNING = 18, // Red, over player + console + MESSAGE_EVENT_ADVANCE = 19, // White, over player + console + MESSAGE_STATUS_WARNING2 = 20, // Red, over player + console + MESSAGE_STATUS_SMALL = 21, // White, bottom of the screen + MESSAGE_INFO_DESCR = 22, // Green, over player + console - MESSAGE_STATUS_DEFAULT = 17, /*White message at the bottom of the game window and in the console*/ - MESSAGE_STATUS_WARNING = 18, /*Red message in game window and in the console*/ - MESSAGE_EVENT_ADVANCE = 19, /*White message in game window and in the console*/ - - MESSAGE_STATUS_SMALL = 21, /*White message at the bottom of the game window"*/ - MESSAGE_INFO_DESCR = 22, /*Green message in game window and in the console*/ + // White, console MESSAGE_DAMAGE_DEALT = 23, MESSAGE_DAMAGE_RECEIVED = 24, MESSAGE_HEALED = 25, @@ -212,17 +280,34 @@ enum MessageClasses : uint8_t { MESSAGE_DAMAGE_OTHERS = 27, MESSAGE_HEALED_OTHERS = 28, MESSAGE_EXPERIENCE_OTHERS = 29, - MESSAGE_EVENT_DEFAULT = 30, /*White message at the bottom of the game window and in the console*/ - MESSAGE_LOOT = 31, - - MESSAGE_GUILD = 33, /*White message in channel (+ channelId)*/ - MESSAGE_PARTY_MANAGEMENT = 34, /*White message in channel (+ channelId)*/ - MESSAGE_PARTY = 35, /*White message in channel (+ channelId)*/ - MESSAGE_EVENT_ORANGE = 36, /*Orange message in the console*/ - MESSAGE_STATUS_CONSOLE_ORANGE = 37, /*Orange message in the console*/ + + MESSAGE_EVENT_DEFAULT = 30, // White, bottom + console + MESSAGE_LOOT = 31, // White, over player + console, supports colors as {text|itemClientId} + MESSAGE_TRADE = 32, // Green, over player + console + + // White, in channel (needs channel Id) + MESSAGE_GUILD = 33, + MESSAGE_PARTY_MANAGEMENT = 34, + MESSAGE_PARTY = 35, + + MESSAGE_REPORT = 38, // White, over player + conosle + MESSAGE_HOTKEY_PRESSED = 39, // Green, over player + console + // MESSAGE_TUTORIAL_HINT = 40, // not working (?) + // MESSAGE_THANK_YOU = 41, // not working (?) + MESSAGE_MARKET = 42, // Window "Market Message" + "Ok" button + // MESSAGE_MANA = 43, // not working (?) + MESSAGE_BEYOND_LAST = 44, // White, console only + MESSAGE_TOURNAMENT_INFO = 45, // Window "Tournament" + "Ok" button + // unused 46? + // unused 47? + MESSAGE_ATTENTION = 48, // White, console only + MESSAGE_BOOSTED_CREATURE = 49, // White, console only + MESSAGE_OFFLINE_TRAINING = 50, // White, over player + console + MESSAGE_TRANSACTION = 51, // White, console only }; -enum FluidColors_t : uint8_t { +enum FluidColors_t : uint8_t +{ FLUID_EMPTY, FLUID_BLUE, FLUID_RED, @@ -231,9 +316,11 @@ enum FluidColors_t : uint8_t { FLUID_YELLOW, FLUID_WHITE, FLUID_PURPLE, + FLUID_BLACK, }; -enum FluidTypes_t : uint8_t { +enum FluidTypes_t : uint8_t +{ FLUID_NONE = FLUID_EMPTY, FLUID_WATER = FLUID_BLUE, FLUID_BLOOD = FLUID_RED, @@ -242,6 +329,7 @@ enum FluidTypes_t : uint8_t { FLUID_LEMONADE = FLUID_YELLOW, FLUID_MILK = FLUID_WHITE, FLUID_MANA = FLUID_PURPLE, + FLUID_INK = FLUID_BLACK, FLUID_LIFE = FLUID_RED + 8, FLUID_OIL = FLUID_BROWN + 8, @@ -262,40 +350,18 @@ enum FluidTypes_t : uint8_t { }; const uint8_t reverseFluidMap[] = { - FLUID_EMPTY, - FLUID_WATER, - FLUID_MANA, - FLUID_BEER, - FLUID_EMPTY, - FLUID_BLOOD, - FLUID_SLIME, - FLUID_EMPTY, - FLUID_LEMONADE, - FLUID_MILK, + FLUID_EMPTY, FLUID_WATER, FLUID_MANA, FLUID_BEER, FLUID_EMPTY, FLUID_BLOOD, + FLUID_SLIME, FLUID_EMPTY, FLUID_LEMONADE, FLUID_MILK, FLUID_INK, }; const uint8_t clientToServerFluidMap[] = { - FLUID_EMPTY, - FLUID_WATER, - FLUID_MANA, - FLUID_BEER, - FLUID_MUD, - FLUID_BLOOD, - FLUID_SLIME, - FLUID_RUM, - FLUID_LEMONADE, - FLUID_MILK, - FLUID_WINE, - FLUID_LIFE, - FLUID_URINE, - FLUID_OIL, - FLUID_FRUITJUICE, - FLUID_COCONUTMILK, - FLUID_TEA, - FLUID_MEAD, + FLUID_EMPTY, FLUID_WATER, FLUID_MANA, FLUID_BEER, FLUID_MUD, FLUID_BLOOD, FLUID_SLIME, + FLUID_RUM, FLUID_LEMONADE, FLUID_MILK, FLUID_WINE, FLUID_LIFE, FLUID_URINE, FLUID_OIL, + FLUID_FRUITJUICE, FLUID_COCONUTMILK, FLUID_TEA, FLUID_MEAD, FLUID_INK, }; -enum ClientFluidTypes_t : uint8_t { +enum ClientFluidTypes_t : uint8_t +{ CLIENTFLUID_EMPTY = 0, CLIENTFLUID_BLUE = 1, CLIENTFLUID_PURPLE = 2, @@ -306,27 +372,25 @@ enum ClientFluidTypes_t : uint8_t { CLIENTFLUID_BROWN = 7, CLIENTFLUID_YELLOW = 8, CLIENTFLUID_WHITE = 9, + CLIENTFLUID_BLACK = 18, }; const uint8_t fluidMap[] = { - CLIENTFLUID_EMPTY, - CLIENTFLUID_BLUE, - CLIENTFLUID_RED, - CLIENTFLUID_BROWN_1, - CLIENTFLUID_GREEN, - CLIENTFLUID_YELLOW, - CLIENTFLUID_WHITE, - CLIENTFLUID_PURPLE, + CLIENTFLUID_EMPTY, CLIENTFLUID_BLUE, CLIENTFLUID_RED, CLIENTFLUID_BROWN_1, CLIENTFLUID_GREEN, + CLIENTFLUID_YELLOW, CLIENTFLUID_WHITE, CLIENTFLUID_PURPLE, CLIENTFLUID_BLACK, }; -enum SquareColor_t : uint8_t { +enum SquareColor_t : uint8_t +{ SQ_COLOR_BLACK = 0, }; -enum TextColor_t : uint8_t { +enum TextColor_t : uint8_t +{ TEXTCOLOR_BLUE = 5, TEXTCOLOR_LIGHTGREEN = 30, TEXTCOLOR_LIGHTBLUE = 35, + TEXTCOLOR_DARKGREY = 86, TEXTCOLOR_MAYABLUE = 95, TEXTCOLOR_DARKRED = 108, TEXTCOLOR_LIGHTGREY = 129, @@ -341,10 +405,11 @@ enum TextColor_t : uint8_t { TEXTCOLOR_NONE = 255, }; -enum Icons_t { +enum Icons_t +{ ICON_POISON = 1 << 0, ICON_BURN = 1 << 1, - ICON_ENERGY = 1 << 2, + ICON_ENERGY = 1 << 2, ICON_DRUNK = 1 << 3, ICON_MANASHIELD = 1 << 4, ICON_PARALYZE = 1 << 5, @@ -358,9 +423,21 @@ enum Icons_t { ICON_REDSWORDS = 1 << 13, ICON_PIGEON = 1 << 14, ICON_BLEEDING = 1 << 15, + ICON_LESSERHEX = 1 << 16, + ICON_INTENSEHEX = 1 << 17, + ICON_GREATERHEX = 1 << 18, + ICON_ROOT = 1 << 19, + ICON_FEAR = 1 << 20, + ICON_GOSHNAR1 = 1 << 21, + ICON_GOSHNAR2 = 1 << 22, + ICON_GOSHNAR3 = 1 << 23, + ICON_GOSHNAR4 = 1 << 24, + ICON_GOSHNAR5 = 1 << 25, + ICON_MANASHIELD_BREAKABLE = 1 << 26, }; -enum WeaponType_t : uint8_t { +enum WeaponType_t : uint8_t +{ WEAPON_NONE, WEAPON_SWORD, WEAPON_CLUB, @@ -371,7 +448,8 @@ enum WeaponType_t : uint8_t { WEAPON_AMMO, }; -enum Ammo_t : uint8_t { +enum Ammo_t : uint8_t +{ AMMO_NONE, AMMO_BOLT, AMMO_ARROW, @@ -382,14 +460,16 @@ enum Ammo_t : uint8_t { AMMO_SNOWBALL, }; -enum WeaponAction_t : uint8_t { +enum WeaponAction_t : uint8_t +{ WEAPONACTION_NONE, WEAPONACTION_REMOVECOUNT, WEAPONACTION_REMOVECHARGE, WEAPONACTION_MOVE, }; -enum WieldInfo_t { +enum WieldInfo_t +{ WIELDINFO_NONE = 0 << 0, WIELDINFO_LEVEL = 1 << 0, WIELDINFO_MAGLV = 1 << 1, @@ -397,7 +477,8 @@ enum WieldInfo_t { WIELDINFO_PREMIUM = 1 << 3, }; -enum Skulls_t : uint8_t { +enum Skulls_t : uint8_t +{ SKULL_NONE = 0, SKULL_YELLOW = 1, SKULL_GREEN = 2, @@ -407,7 +488,8 @@ enum Skulls_t : uint8_t { SKULL_ORANGE = 6, }; -enum PartyShields_t : uint8_t { +enum PartyShields_t : uint8_t +{ SHIELD_NONE = 0, SHIELD_WHITEYELLOW = 1, SHIELD_WHITEBLUE = 2, @@ -422,7 +504,8 @@ enum PartyShields_t : uint8_t { SHIELD_GRAY = 11, }; -enum GuildEmblems_t : uint8_t { +enum GuildEmblems_t : uint8_t +{ GUILDEMBLEM_NONE = 0, GUILDEMBLEM_ALLY = 1, GUILDEMBLEM_ENEMY = 2, @@ -431,8 +514,10 @@ enum GuildEmblems_t : uint8_t { GUILDEMBLEM_OTHER = 5, }; -enum item_t : uint16_t { +enum item_t : uint16_t +{ ITEM_BROWSEFIELD = 460, // for internal use + ITEM_DECORATION_KIT = 26054, ITEM_FIREFIELD_PVP_FULL = 1487, ITEM_FIREFIELD_PVP_MEDIUM = 1488, @@ -469,10 +554,12 @@ enum item_t : uint16_t { ITEM_STORE_COIN = 24774, // in-game store currency ITEM_DEPOT = 2594, - ITEM_LOCKER1 = 2589, + ITEM_LOCKER = 2589, ITEM_INBOX = 14404, ITEM_MARKET = 14405, ITEM_STORE_INBOX = 26052, + + // move to separate enum class? ITEM_DEPOT_BOX_I = 25453, ITEM_DEPOT_BOX_II = 25454, ITEM_DEPOT_BOX_III = 25455, @@ -490,6 +577,7 @@ enum item_t : uint16_t { ITEM_DEPOT_BOX_XV = 25467, ITEM_DEPOT_BOX_XVI = 25468, ITEM_DEPOT_BOX_XVII = 25469, + ITEM_DEPOT_BOX_XVIII = 34571, ITEM_MALE_CORPSE = 3058, ITEM_FEMALE_CORPSE = 3065, @@ -504,10 +592,20 @@ enum item_t : uint16_t { ITEM_AMULETOFLOSS = 2173, - ITEM_DOCUMENT_RO = 1968, //read-only + ITEM_DOCUMENT_RO = 1968, // read-only }; -enum PlayerFlags : uint64_t { +enum ResourceTypes_t : uint8_t +{ + RESOURCE_BANK_BALANCE = 0x00, + RESOURCE_GOLD_EQUIPPED = 0x01, + RESOURCE_PREY_WILDCARDS = 0x0A, + RESOURCE_DAILYREWARD_STREAK = 0x14, + RESOURCE_DAILYREWARD_JOKERS = 0x15, +}; + +enum PlayerFlags : uint64_t +{ PlayerFlag_CannotUseCombat = 1 << 0, PlayerFlag_CannotAttackPlayer = 1 << 1, PlayerFlag_CannotAttackMonster = 1 << 2, @@ -546,9 +644,19 @@ enum PlayerFlags : uint64_t { PlayerFlag_IgnoreWeaponCheck = static_cast(1) << 35, PlayerFlag_CannotBeMuted = static_cast(1) << 36, PlayerFlag_IsAlwaysPremium = static_cast(1) << 37, + PlayerFlag_IgnoreYellCheck = static_cast(1) << 38, + PlayerFlag_IgnoreSendPrivateCheck = static_cast(1) << 39, +}; + +enum PodiumFlags : uint8_t +{ + PODIUM_SHOW_PLATFORM = 0, // show the platform below the outfit + PODIUM_SHOW_OUTFIT = 1, // show outfit + PODIUM_SHOW_MOUNT = 2 // show mount }; -enum ReloadTypes_t : uint8_t { +enum ReloadTypes_t : uint8_t +{ RELOAD_TYPE_ALL, RELOAD_TYPE_ACTIONS, RELOAD_TYPE_CHAT, @@ -574,7 +682,7 @@ static constexpr int32_t CHANNEL_GUILD = 0x00; static constexpr int32_t CHANNEL_PARTY = 0x01; static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; -//Reserved player storage key ranges; +// Reserved player storage key ranges; //[10000000 - 20000000]; static constexpr int32_t PSTRG_RESERVED_RANGE_START = 10000000; static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000; @@ -586,6 +694,7 @@ static constexpr int32_t PSTRG_MOUNTS_RANGE_START = (PSTRG_RESERVED_RANGE_START static constexpr int32_t PSTRG_MOUNTS_RANGE_SIZE = 10; static constexpr int32_t PSTRG_MOUNTS_CURRENTMOUNT = (PSTRG_MOUNTS_RANGE_START + 10); -#define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE)) +#define IS_IN_KEYRANGE(key, range) \ + (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE)) -#endif +#endif // FS_CONST_H diff --git a/src/container.cpp b/src/container.cpp index 2f9c9f80aa..0366df2e61 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -1,38 +1,24 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "container.h" -#include "iomap.h" + +#include "depotchest.h" #include "game.h" +#include "housetile.h" +#include "inbox.h" +#include "iomap.h" +#include "spectators.h" +#include "storeinbox.h" extern Game g_game; -Container::Container(uint16_t type) : - Container(type, items[type].maxItems) {} +Container::Container(uint16_t type) : Container(type, items[type].maxItems) {} Container::Container(uint16_t type, uint16_t size, bool unlocked /*= true*/, bool pagination /*= false*/) : - Item(type), - maxSize(size), - unlocked(unlocked), - pagination(pagination) + Item(type), maxSize(size), unlocked(unlocked), pagination(pagination) {} Container::Container(Tile* tile) : Container(ITEM_BROWSEFIELD, 30, false, true) @@ -40,7 +26,8 @@ Container::Container(Tile* tile) : Container(ITEM_BROWSEFIELD, 30, false, true) TileItemVector* itemVector = tile->getItemList(); if (itemVector) { for (Item* item : *itemVector) { - if ((item->getContainer() || item->hasProperty(CONST_PROP_MOVEABLE)) && !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if ((item->getContainer() || item->hasProperty(CONST_PROP_MOVEABLE)) && + !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { itemlist.push_front(item); item->setParent(this); } @@ -85,15 +72,13 @@ Container* Container::getParentContainer() return thing->getContainer(); } -std::string Container::getName(bool addArticle /* = false*/) const { +std::string Container::getName(bool addArticle /* = false*/) const +{ const ItemType& it = items[id]; return getNameDescription(it, this, -1, addArticle); } -bool Container::hasParent() const -{ - return getID() != ITEM_BROWSEFIELD && dynamic_cast(getParent()) == nullptr; -} +bool Container::hasParent() const { return getID() != ITEM_BROWSEFIELD && !dynamic_cast(getParent()); } void Container::addItem(Item* item) { @@ -120,7 +105,7 @@ bool Container::unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, } for (auto& itemNode : node.children) { - //load container items + // load container items if (itemNode.type != OTBM_ITEM) { // unknown type return false; @@ -154,42 +139,7 @@ void Container::updateItemWeight(int32_t diff) } } -uint32_t Container::getWeight() const -{ - return Item::getWeight() + totalWeight; -} - -std::string Container::getContentDescription() const -{ - std::ostringstream os; - return getContentDescription(os).str(); -} - -std::ostringstream& Container::getContentDescription(std::ostringstream& os) const -{ - bool firstitem = true; - for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { - Item* item = *it; - - Container* container = item->getContainer(); - if (container && !container->empty()) { - continue; - } - - if (firstitem) { - firstitem = false; - } else { - os << ", "; - } - - os << item->getNameDescription(); - } - - if (firstitem) { - os << "nothing"; - } - return os; -} +uint32_t Container::getWeight() const { return Item::getWeight() + totalWeight; } Item* Container::getItemByIndex(size_t index) const { @@ -221,14 +171,14 @@ bool Container::isHoldingItem(const Item* item) const void Container::onAddContainerItem(Item* item) { SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + g_game.map.getSpectators(spectators, getPosition(), false, true, 1, 1, 1, 1); - //send to client + // send to client for (Creature* spectator : spectators) { spectator->getPlayer()->sendAddContainerItem(this, item); } - //event methods + // event methods for (Creature* spectator : spectators) { spectator->getPlayer()->onAddContainerItem(item); } @@ -237,14 +187,14 @@ void Container::onAddContainerItem(Item* item) void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem) { SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + g_game.map.getSpectators(spectators, getPosition(), false, true, 1, 1, 1, 1); - //send to client + // send to client for (Creature* spectator : spectators) { spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem); } - //event methods + // event methods for (Creature* spectator : spectators) { spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem); } @@ -253,26 +203,26 @@ void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newIt void Container::onRemoveContainerItem(uint32_t index, Item* item) { SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + g_game.map.getSpectators(spectators, getPosition(), false, true, 1, 1, 1, 1); - //send change to client + // send change to client for (Creature* spectator : spectators) { spectator->getPlayer()->sendRemoveContainerItem(this, index); } - //event methods + // event methods for (Creature* spectator : spectators) { spectator->getPlayer()->onRemoveContainerItem(this, item); } } -ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor/* = nullptr*/) const +ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor /* = nullptr*/) const { bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); if (childIsOwner) { - //a child container is querying, since we are the top container (not carried by a player) - //just return with no error. + // a child container is querying, since we are the top container (not carried by a player) just return with no + // error. return RETURNVALUE_NOERROR; } @@ -281,7 +231,7 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun } const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -322,7 +272,7 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun cylinder = cylinder->getParent(); } - if (index == INDEX_WHEREEVER && size() >= capacity()) { + if (index == INDEX_WHEREEVER && size() >= capacity() && !hasPagination()) { return RETURNVALUE_CONTAINERNOTENOUGHROOM; } } else { @@ -335,24 +285,31 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun } } - const Cylinder* topParent = getTopParent(); + const Cylinder* const topParent = getTopParent(); + if (actor && g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { + if (const HouseTile* const houseTile = dynamic_cast(topParent->getTile())) { + if (!topParent->getCreature() && !houseTile->getHouse()->isInvited(actor->getPlayer())) { + return RETURNVALUE_PLAYERISNOTINVITED; + } + } + } + if (topParent != this) { return topParent->queryAdd(INDEX_WHEREEVER, *item, count, flags | FLAG_CHILDISOWNER, actor); - } else { - return RETURNVALUE_NOERROR; } + return RETURNVALUE_NOERROR; } -ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, - uint32_t& maxQueryCount, uint32_t flags) const +ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const { const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { maxQueryCount = 0; return RETURNVALUE_NOTPOSSIBLE; } - if (hasBitSet(FLAG_NOLIMIT, flags)) { + if (hasBitSet(FLAG_NOLIMIT, flags) || hasPagination()) { maxQueryCount = std::max(1, count); return RETURNVALUE_NOERROR; } @@ -363,7 +320,7 @@ ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t uint32_t n = 0; if (index == INDEX_WHEREEVER) { - //Iterate through every item and check how much free stackable slots there is. + // Iterate through every item and check how much free stackable slots there is. uint32_t slotIndex = 0; for (Item* containerItem : itemlist) { if (containerItem != item && containerItem->equals(item) && containerItem->getItemCount() < 100) { @@ -394,7 +351,8 @@ ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t return RETURNVALUE_NOERROR; } -ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor /*= nullptr */) const +ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor /*= nullptr */) const { int32_t index = getThingIndex(&thing); if (index == -1) { @@ -402,7 +360,7 @@ ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t } const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -414,16 +372,19 @@ ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t return RETURNVALUE_NOTMOVEABLE; } - const HouseTile* houseTile = dynamic_cast(getTopParent()); - if (houseTile) { - return houseTile->queryRemove(thing, count, flags, actor); + if (actor && g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { + const Cylinder* const topParent = getTopParent(); + if (const HouseTile* const houseTile = dynamic_cast(topParent->getTile())) { + if (!topParent->getCreature() && !houseTile->getHouse()->isInvited(actor->getPlayer())) { + return RETURNVALUE_PLAYERISNOTINVITED; + } + } } return RETURNVALUE_NOERROR; } -Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) +Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) { if (!unlocked) { *destItem = nullptr; @@ -447,10 +408,10 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** } else if (index >= static_cast(capacity())) { /* if you have a container, maximize it to show all 20 slots - then you open a bag that is inside the container you will have a bag with 8 slots - and a "grey" area where the other 12 slots where from the container - if you drop the item on that grey area - the client calculates the slot position as if the bag has 20 slots + then you open a bag that is inside the container you will have a bag + with 8 slots and a "grey" area where the other 12 slots where from the + container if you drop the item on that grey area the client calculates + the slot position as if the bag has 20 slots */ index = INDEX_WHEREEVER; *destItem = nullptr; @@ -481,7 +442,7 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** return this; } - //try find a suitable item to stack with + // try find a suitable item to stack with uint32_t n = 0; for (Item* listItem : itemlist) { if (listItem != item && listItem->equals(item) && listItem->getItemCount() < 100) { @@ -495,10 +456,7 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** return this; } -void Container::addThing(Thing* thing) -{ - return addThing(0, thing); -} +void Container::addThing(Thing* thing) { return addThing(0, thing); } void Container::addThing(int32_t index, Thing* thing) { @@ -507,7 +465,7 @@ void Container::addThing(int32_t index, Thing* thing) } Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } @@ -515,7 +473,7 @@ void Container::addThing(int32_t index, Thing* thing) itemlist.push_front(item); updateItemWeight(item->getWeight()); - //send change to client + // send change to client if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { onAddContainerItem(item); } @@ -526,7 +484,7 @@ void Container::addItemBack(Item* item) addItem(item); updateItemWeight(item->getWeight()); - //send change to client + // send change to client if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { onAddContainerItem(item); } @@ -540,7 +498,7 @@ void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count) } Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } @@ -549,7 +507,7 @@ void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count) item->setSubType(count); updateItemWeight(-oldWeight + item->getWeight()); - //send change to client + // send change to client if (getParent()) { onUpdateContainerItem(index, item, item); } @@ -571,7 +529,7 @@ void Container::replaceThing(uint32_t index, Thing* thing) item->setParent(this); updateItemWeight(-static_cast(replacedItem->getWeight()) + item->getWeight()); - //send change to client + // send change to client if (getParent()) { onUpdateContainerItem(index, replacedItem, item); } @@ -582,7 +540,7 @@ void Container::replaceThing(uint32_t index, Thing* thing) void Container::removeThing(Thing* thing, uint32_t count) { Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } @@ -597,14 +555,14 @@ void Container::removeThing(Thing* thing, uint32_t count) item->setItemCount(newCount); updateItemWeight(-oldWeight + item->getWeight()); - //send change to client + // send change to client if (getParent()) { onUpdateContainerItem(index, item, item); } } else { updateItemWeight(-static_cast(item->getWeight())); - //send change to client + // send change to client if (getParent()) { onRemoveContainerItem(index, item); } @@ -626,17 +584,11 @@ int32_t Container::getThingIndex(const Thing* thing) const return -1; } -size_t Container::getFirstIndex() const -{ - return 0; -} +size_t Container::getFirstIndex() const { return 0; } -size_t Container::getLastIndex() const -{ - return size(); -} +size_t Container::getLastIndex() const { return size(); } -uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType/* = -1*/) const +uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType /* = -1*/) const { uint32_t count = 0; for (Item* item : itemlist) { @@ -670,10 +622,7 @@ ItemVector Container::getItems(bool recursive /*= false*/) return containerItems; } -Thing* Container::getThing(size_t index) const -{ - return getItemByIndex(index); -} +Thing* Container::getThing(size_t index) const { return getItemByIndex(index); } void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) { @@ -681,7 +630,7 @@ void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int if (topParent->getCreature()) { topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT); } else if (topParent == this) { - //let the tile class notify surrounding players + // let the tile class notify surrounding players if (topParent->getParent()) { topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR); } @@ -696,7 +645,7 @@ void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, if (topParent->getCreature()) { topParent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT); } else if (topParent == this) { - //let the tile class notify surrounding players + // let the tile class notify surrounding players if (topParent->getParent()) { topParent->getParent()->postRemoveNotification(thing, newParent, index, LINK_NEAR); } @@ -705,15 +654,12 @@ void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, } } -void Container::internalAddThing(Thing* thing) -{ - internalAddThing(0, thing); -} +void Container::internalAddThing(Thing* thing) { internalAddThing(0, thing); } void Container::internalAddThing(uint32_t, Thing* thing) { Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return; } @@ -724,6 +670,8 @@ void Container::internalAddThing(uint32_t, Thing* thing) void Container::startDecaying() { + Item::startDecaying(); + for (Item* item : itemlist) { item->startDecaying(); } @@ -739,10 +687,7 @@ ContainerIterator Container::iterator() const return cit; } -Item* ContainerIterator::operator*() -{ - return *cur; -} +Item* ContainerIterator::operator*() { return *cur; } void ContainerIterator::advance() { diff --git a/src/container.h b/src/container.h index cfa69219ec..65d50cb5a7 100644 --- a/src/container.h +++ b/src/container.h @@ -1,188 +1,138 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC -#define FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC - -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CONTAINER_H +#define FS_CONTAINER_H #include "cylinder.h" #include "item.h" #include "tile.h" class Container; -class DepotChest; class DepotLocker; class StoreInbox; class ContainerIterator { - public: - bool hasNext() const { - return !over.empty(); - } +public: + bool hasNext() const { return !over.empty(); } - void advance(); - Item* operator*(); + void advance(); + Item* operator*(); - private: - std::list over; - ItemDeque::const_iterator cur; +private: + std::list over; + ItemDeque::const_iterator cur; - friend class Container; + friend class Container; }; class Container : public Item, public Cylinder { - public: - explicit Container(uint16_t type); - Container(uint16_t type, uint16_t size, bool unlocked = true, bool pagination = false); - explicit Container(Tile* tile); - ~Container(); - - // non-copyable - Container(const Container&) = delete; - Container& operator=(const Container&) = delete; - - Item* clone() const override final; - - Container* getContainer() override final { - return this; - } - const Container* getContainer() const override final { - return this; - } - - virtual DepotLocker* getDepotLocker() { - return nullptr; - } - virtual const DepotLocker* getDepotLocker() const { - return nullptr; - } - - virtual StoreInbox* getStoreInbox() { - return nullptr; - } - virtual const StoreInbox* getStoreInbox() const { - return nullptr; - } - - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - bool unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream) override; - std::string getContentDescription() const; - - size_t size() const { - return itemlist.size(); - } - bool empty() const { - return itemlist.empty(); - } - uint32_t capacity() const { - return maxSize; - } - - ContainerIterator iterator() const; - - const ItemDeque& getItemList() const { - return itemlist; - } - - ItemDeque::const_reverse_iterator getReversedItems() const { - return itemlist.rbegin(); - } - ItemDeque::const_reverse_iterator getReversedEnd() const { - return itemlist.rend(); - } - - std::string getName(bool addArticle = false) const; - - bool hasParent() const; - void addItem(Item* item); - Item* getItemByIndex(size_t index) const; - bool isHoldingItem(const Item* item) const; - - uint32_t getItemHoldingCount() const; - uint32_t getWeight() const override final; - - bool isUnlocked() const { - return unlocked; - } - bool hasPagination() const { - return pagination; - } - - //cylinder implementations - virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, - uint32_t flags) const override final; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override final; - Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) override final; - - void addThing(Thing* thing) override final; - void addThing(int32_t index, Thing* thing) override final; - void addItemBack(Item* item); - - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; - void replaceThing(uint32_t index, Thing* thing) override final; - - void removeThing(Thing* thing, uint32_t count) override final; - - int32_t getThingIndex(const Thing* thing) const override final; - size_t getFirstIndex() const override final; - size_t getLastIndex() const override final; - uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; - std::map& getAllItemTypeCount(std::map& countMap) const override final; - Thing* getThing(size_t index) const override final; - - ItemVector getItems(bool recursive = false); - - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - - void internalAddThing(Thing* thing) override final; - void internalAddThing(uint32_t index, Thing* thing) override final; - void startDecaying() override final; - - protected: - ItemDeque itemlist; - - private: - std::ostringstream& getContentDescription(std::ostringstream& os) const; - - uint32_t maxSize; - uint32_t totalWeight = 0; - uint32_t serializationCount = 0; - - bool unlocked; - bool pagination; - - void onAddContainerItem(Item* item); - void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem); - void onRemoveContainerItem(uint32_t index, Item* item); - - Container* getParentContainer(); - void updateItemWeight(int32_t diff); - - friend class ContainerIterator; - friend class IOMapSerialize; +public: + explicit Container(uint16_t type); + Container(uint16_t type, uint16_t size, bool unlocked = true, bool pagination = false); + explicit Container(Tile* tile); + ~Container(); + + // non-copyable + Container(const Container&) = delete; + Container& operator=(const Container&) = delete; + + Item* clone() const override final; + + Container* getContainer() override final { return this; } + const Container* getContainer() const override final { return this; } + + virtual DepotLocker* getDepotLocker() { return nullptr; } + virtual const DepotLocker* getDepotLocker() const { return nullptr; } + + virtual StoreInbox* getStoreInbox() { return nullptr; } + virtual const StoreInbox* getStoreInbox() const { return nullptr; } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + bool unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream) override; + + size_t size() const { return itemlist.size(); } + bool empty() const { return itemlist.empty(); } + uint32_t capacity() const { return maxSize; } + + ContainerIterator iterator() const; + + const ItemDeque& getItemList() const { return itemlist; } + + ItemDeque::const_reverse_iterator getReversedItems() const { return itemlist.rbegin(); } + ItemDeque::const_reverse_iterator getReversedEnd() const { return itemlist.rend(); } + + std::string getName(bool addArticle = false) const; + + bool hasParent() const; + void addItem(Item* item); + Item* getItemByIndex(size_t index) const; + bool isHoldingItem(const Item* item) const; + + uint32_t getItemHoldingCount() const; + uint32_t getWeight() const override final; + + bool isUnlocked() const { return unlocked; } + bool hasPagination() const { return pagination; } + + // cylinder implementations + virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const override final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override final; + + void addThing(Thing* thing) override final; + void addThing(int32_t index, Thing* thing) override final; + void addItemBack(Item* item); + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; + void replaceThing(uint32_t index, Thing* thing) override final; + + void removeThing(Thing* thing, uint32_t count) override final; + + int32_t getThingIndex(const Thing* thing) const override final; + size_t getFirstIndex() const override final; + size_t getLastIndex() const override final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; + std::map& getAllItemTypeCount(std::map& countMap) const override final; + Thing* getThing(size_t index) const override final; + + ItemVector getItems(bool recursive = false); + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + + void internalAddThing(Thing* thing) override final; + void internalAddThing(uint32_t index, Thing* thing) override final; + void startDecaying() override final; + +protected: + ItemDeque itemlist; + +private: + uint32_t maxSize; + uint32_t totalWeight = 0; + uint32_t serializationCount = 0; + + bool unlocked; + bool pagination; + + void onAddContainerItem(Item* item); + void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem); + void onRemoveContainerItem(uint32_t index, Item* item); + + Container* getParentContainer(); + void updateItemWeight(int32_t diff); + + friend class ContainerIterator; + friend class IOMapSerialize; }; -#endif +#endif // FS_CONTAINER_H diff --git a/src/creature.cpp b/src/creature.cpp index 9e7fb9cb50..ed3a44d21b 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -1,29 +1,17 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "creature.h" + +#include "combat.h" +#include "configmanager.h" #include "game.h" #include "monster.h" -#include "configmanager.h" +#include "party.h" #include "scheduler.h" +#include "spectators.h" double Creature::speedA = 857.36; double Creature::speedB = 261.29; @@ -33,10 +21,7 @@ extern Game g_game; extern ConfigManager g_config; extern CreatureEvents* g_creatureEvents; -Creature::Creature() -{ - onIdleStatus(); -} +Creature::Creature() { onIdleStatus(); } Creature::~Creature() { @@ -73,8 +58,8 @@ bool Creature::canSee(const Position& myPos, const Position& pos, int32_t viewRa } const int_fast32_t offsetz = myPos.getZ() - pos.getZ(); - return (pos.getX() >= myPos.getX() - viewRangeX + offsetz) && (pos.getX() <= myPos.getX() + viewRangeX + offsetz) - && (pos.getY() >= myPos.getY() - viewRangeY + offsetz) && (pos.getY() <= myPos.getY() + viewRangeY + offsetz); + return (pos.getX() >= myPos.getX() - viewRangeX + offsetz) && (pos.getX() <= myPos.getX() + viewRangeX + offsetz) && + (pos.getY() >= myPos.getY() - viewRangeY + offsetz) && (pos.getY() <= myPos.getY() + viewRangeY + offsetz); } bool Creature::canSee(const Position& pos) const @@ -84,6 +69,10 @@ bool Creature::canSee(const Position& pos) const bool Creature::canSeeCreature(const Creature* creature) const { + if (!canSeeGhostMode(creature) && creature->isInGhostMode()) { + return false; + } + if (!canSeeInvisibility() && creature->isInvisible()) { return false; } @@ -117,7 +106,7 @@ int32_t Creature::getWalkDelay(Direction dir) const int32_t Creature::getWalkDelay() const { - //Used for auto-walking + // Used for auto-walking if (lastStep == 0) { return 0; } @@ -162,7 +151,7 @@ void Creature::onThink(uint32_t interval) goToFollowCreature(); } - //scripting event - onThink + // scripting event - onThink const CreatureEventList& thinkEvents = getCreatureEvents(CREATURE_EVENT_THINK); for (CreatureEvent* thinkEvent : thinkEvents) { thinkEvent->executeOnThink(this, interval); @@ -207,11 +196,11 @@ void Creature::onWalk() forceUpdateFollowPath = true; } } else { + stopEventWalk(); + if (listWalkDir.empty()) { onWalkComplete(); } - - stopEventWalk(); } } @@ -229,15 +218,17 @@ void Creature::onWalk() void Creature::onWalk(Direction& dir) { - if (hasCondition(CONDITION_DRUNK)) { - uint32_t r = uniform_random(0, 20); - if (r <= DIRECTION_DIAGONAL_MASK) { - if (r < DIRECTION_DIAGONAL_MASK) { - dir = static_cast(r); - } - g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, "Hicks!", false); - } + if (!hasCondition(CONDITION_DRUNK)) { + return; } + + uint16_t rand = uniform_random(0, 399); + if (rand / 4 > getDrunkenness()) { + return; + } + + dir = static_cast(rand % 4); + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, "Hicks!", false); } bool Creature::getNextStep(Direction& dir, uint32_t&) @@ -278,6 +269,10 @@ void Creature::startAutoWalk(Direction direction) void Creature::startAutoWalk(const std::vector& listDir) { + if (hasCondition(CONDITION_ROOT)) { + return; + } + Player* player = getPlayer(); if (player && player->isMovementBlocked()) { player->sendCancelWalk(); @@ -310,7 +305,7 @@ void Creature::addEventWalk(bool firstStep) g_game.checkCreatureWalk(getID()); } - eventWalk = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Game::checkCreatureWalk, &g_game, getID()))); + eventWalk = g_scheduler.addEvent(createSchedulerTask(ticks, [id = getID()]() { g_game.checkCreatureWalk(id); })); } void Creature::stopEventWalk() @@ -340,7 +335,8 @@ void Creature::updateMapCache() void Creature::updateTileCache(const Tile* tile, int32_t dx, int32_t dy) { if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) { - localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) == RETURNVALUE_NOERROR; + localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = + tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) == RETURNVALUE_NOERROR; } } @@ -375,13 +371,12 @@ int32_t Creature::getWalkCache(const Position& pos) const if (std::abs(dy) <= maxWalkCacheHeight) { if (localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx]) { return 1; - } else { - return 0; } + return 0; } } - //out of range + // out of range return 2; } @@ -392,8 +387,8 @@ void Creature::onAddTileItem(const Tile* tile, const Position& pos) } } -void Creature::onUpdateTileItem(const Tile* tile, const Position& pos, const Item*, - const ItemType& oldType, const Item*, const ItemType& newType) +void Creature::onUpdateTileItem(const Tile* tile, const Position& pos, const Item*, const ItemType& oldType, + const Item*, const ItemType& newType) { if (!isMapLoaded) { return; @@ -440,11 +435,7 @@ void Creature::onCreatureAppear(Creature* creature, bool isLogin) void Creature::onRemoveCreature(Creature* creature, bool) { onCreatureDisappear(creature, true); - if (creature == this) { - if (master && !master->isRemoved()) { - setMaster(nullptr); - } - } else if (isMapLoaded) { + if (creature != this && isMapLoaded) { if (creature->getPosition().z == getPosition().z) { updateTileCache(creature->getTile(), creature->getPosition()); } @@ -478,8 +469,8 @@ void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) } } -void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) +void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) { if (creature == this) { lastStep = OTSYS_TIME(); @@ -487,10 +478,10 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos if (!teleport) { if (oldPos.z != newPos.z) { - //floor change extra cost + // floor change extra cost lastStepCost = 2; } else if (Position::getDistanceX(newPos, oldPos) >= 1 && Position::getDistanceY(newPos, oldPos) >= 1) { - //diagonal extra cost + // diagonal extra cost lastStepCost = 3; } } else { @@ -498,11 +489,13 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos } if (!summons.empty()) { - //check if any of our summons is out of range (+/- 2 floors or 30 tiles away) + // check if any of our summons is out of range (+/- 2 floors or 30 tiles away) std::forward_list despawnList; for (Creature* summon : summons) { const Position& pos = summon->getPosition(); - if (Position::getDistanceZ(newPos, pos) > 2 || (std::max(Position::getDistanceX(newPos, pos), Position::getDistanceY(newPos, pos)) > 30)) { + if (Position::getDistanceZ(newPos, pos) > 2 || + (std::max(Position::getDistanceX(newPos, pos), Position::getDistanceY(newPos, pos)) > + 30)) { despawnList.push_front(summon); } } @@ -516,39 +509,41 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos onChangeZone(getZone()); } - //update map cache + // update map cache if (isMapLoaded) { if (teleport || oldPos.z != newPos.z) { updateMapCache(); } else { const Position& myPos = getPosition(); - if (oldPos.y > newPos.y) { //north - //shift y south + if (oldPos.y > newPos.y) { // north + // shift y south for (int32_t y = mapWalkHeight - 1; --y >= 0;) { memcpy(localMapCache[y + 1], localMapCache[y], sizeof(localMapCache[y])); } - //update 0 + // update 0 for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { - Tile* cacheTile = g_game.map.getTile(myPos.getX() + x, myPos.getY() - maxWalkCacheHeight, myPos.z); + Tile* cacheTile = + g_game.map.getTile(myPos.getX() + x, myPos.getY() - maxWalkCacheHeight, myPos.z); updateTileCache(cacheTile, x, -maxWalkCacheHeight); } } else if (oldPos.y < newPos.y) { // south - //shift y north + // shift y north for (int32_t y = 0; y <= mapWalkHeight - 2; ++y) { memcpy(localMapCache[y], localMapCache[y + 1], sizeof(localMapCache[y])); } - //update mapWalkHeight - 1 + // update mapWalkHeight - 1 for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { - Tile* cacheTile = g_game.map.getTile(myPos.getX() + x, myPos.getY() + maxWalkCacheHeight, myPos.z); + Tile* cacheTile = + g_game.map.getTile(myPos.getX() + x, myPos.getY() + maxWalkCacheHeight, myPos.z); updateTileCache(cacheTile, x, maxWalkCacheHeight); } } if (oldPos.x < newPos.x) { // east - //shift y west + // shift y west int32_t starty = 0; int32_t endy = mapWalkHeight - 1; int32_t dy = Position::getDistanceY(oldPos, newPos); @@ -565,13 +560,13 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos } } - //update mapWalkWidth - 1 + // update mapWalkWidth - 1 for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { Tile* cacheTile = g_game.map.getTile(myPos.x + maxWalkCacheWidth, myPos.y + y, myPos.z); updateTileCache(cacheTile, maxWalkCacheWidth, y); } } else if (oldPos.x > newPos.x) { // west - //shift y east + // shift y east int32_t starty = 0; int32_t endy = mapWalkHeight - 1; int32_t dy = Position::getDistanceY(oldPos, newPos); @@ -588,7 +583,7 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos } } - //update 0 + // update 0 for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { Tile* cacheTile = g_game.map.getTile(myPos.x - maxWalkCacheWidth, myPos.y + y, myPos.z); updateTileCache(cacheTile, -maxWalkCacheWidth, y); @@ -627,8 +622,8 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos onCreatureDisappear(attackedCreature, false); } else { if (hasExtraSwing()) { - //our target is moving lets see if we can get in hit - g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + // our target is moving lets see if we can get in hit + g_dispatcher.addTask(createTask([id = getID()]() { g_game.checkCreatureAttack(id); })); } if (newTile->getZone() != oldTile->getZone()) { @@ -638,6 +633,20 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos } } +CreatureVector Creature::getKillers() +{ + CreatureVector killers; + const int64_t timeNow = OTSYS_TIME(); + const uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED); + for (const auto& it : damageMap) { + Creature* attacker = g_game.getCreatureByID(it.first); + if (attacker && attacker != this && timeNow - it.second.ticks <= inFightTicks) { + killers.push_back(attacker); + } + } + return killers; +} + void Creature::onDeath() { bool lastHitUnjustified = false; @@ -671,7 +680,8 @@ void Creature::onDeath() attackerPlayer->removeAttacked(getPlayer()); Party* party = attackerPlayer->getParty(); - if (party && party->getLeader() && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + if (party && party->getLeader() && party->isSharedExperienceActive() && + party->isSharedExperienceEnabled()) { attacker = party->getLeader(); } } @@ -693,7 +703,8 @@ void Creature::onDeath() if (mostDamageCreature) { if (mostDamageCreature != lastHitCreature && mostDamageCreature != lastHitCreatureMaster) { Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); - if (lastHitCreature != mostDamageCreatureMaster && (lastHitCreatureMaster == nullptr || mostDamageCreatureMaster != lastHitCreatureMaster)) { + if (lastHitCreature != mostDamageCreatureMaster && + (!lastHitCreatureMaster || mostDamageCreatureMaster != lastHitCreatureMaster)) { mostDamageUnjustified = mostDamageCreature->onKilledCreature(this, false); } } @@ -711,14 +722,16 @@ void Creature::onDeath() } } -bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) +bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, + bool mostDamageUnjustified) { if (!lootDrop && getMonster()) { if (master) { - //scripting event - onDeath + // scripting event - onDeath const CreatureEventList& deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH); for (CreatureEvent* deathEvent : deathEvents) { - deathEvent->executeOnDeath(this, nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + deathEvent->executeOnDeath(this, nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, + mostDamageUnjustified); } } @@ -734,6 +747,10 @@ bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreatur splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); break; + case RACE_INK: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_INK); + break; + default: splash = nullptr; break; @@ -752,9 +769,10 @@ bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreatur g_game.startDecay(corpse); } - //scripting event - onDeath + // scripting event - onDeath for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { - deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, + mostDamageUnjustified); } if (corpse) { @@ -774,12 +792,9 @@ bool Creature::hasBeenAttacked(uint32_t attackerId) return (OTSYS_TIME() - it->second.ticks) <= g_config.getNumber(ConfigManager::PZ_LOCKED); } -Item* Creature::getCorpse(Creature*, Creature*) -{ - return Item::CreateItem(getLookCorpse()); -} +Item* Creature::getCorpse(Creature*, Creature*) { return Item::CreateItem(getLookCorpse()); } -void Creature::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +void Creature::changeHealth(int32_t healthChange, bool sendHealthChange /* = true*/) { int32_t oldHealth = health; @@ -794,7 +809,7 @@ void Creature::changeHealth(int32_t healthChange, bool sendHealthChange/* = true } if (health <= 0) { - g_dispatcher.addTask(createTask(std::bind(&Game::executeDeath, &g_game, getID()))); + g_dispatcher.addTask(createTask([id = getID()]() { g_game.executeDeath(id); })); } } @@ -812,11 +827,14 @@ void Creature::drainHealth(Creature* attacker, int32_t damage) if (attacker) { attacker->onAttackedCreatureDrainHealth(this, damage); + } else { + lastHitCreatureId = 0; } } BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense /* = false */, bool checkArmor /* = false */, bool /* field = false */, bool /* ignoreResistances = false */) + bool checkDefense /* = false */, bool checkArmor /* = false */, bool /* field = false */, + bool /* ignoreResistances = false */) { BlockType_t blockType = BLOCK_NONE; @@ -861,6 +879,29 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 } if (attacker) { + if (Player* attackerPlayer = attacker->getPlayer()) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + if (!attackerPlayer->isItemAbilityEnabled(static_cast(slot))) { + continue; + } + + Item* item = attackerPlayer->getInventoryItem(static_cast(slot)); + if (!item) { + continue; + } + + const uint16_t boostPercent = item->getBoostPercent(combatType); + if (boostPercent != 0) { + damage += std::round(damage * (boostPercent / 100.)); + } + } + } + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + attacker->onAttackedCreature(this); attacker->onAttackedCreatureBlockHit(blockType); } @@ -1011,16 +1052,9 @@ void Creature::addDamagePoints(Creature* attacker, int32_t damagePoints) uint32_t attackerId = attacker->id; - auto it = damageMap.find(attackerId); - if (it == damageMap.end()) { - CountBlock_t cb; - cb.ticks = OTSYS_TIME(); - cb.total = damagePoints; - damageMap[attackerId] = cb; - } else { - it->second.total += damagePoints; - it->second.ticks = OTSYS_TIME(); - } + auto& cb = damageMap[attackerId]; + cb.ticks = OTSYS_TIME(); + cb.total += damagePoints; lastHitCreatureId = attackerId; } @@ -1081,10 +1115,7 @@ void Creature::onTickCondition(ConditionType_t type, bool& bRemove) } } -void Creature::onCombatRemoveCondition(Condition* condition) -{ - removeCondition(condition); -} +void Creature::onCombatRemoveCondition(Condition* condition) { removeCondition(condition); } void Creature::onAttacked() { @@ -1102,7 +1133,7 @@ bool Creature::onKilledCreature(Creature* target, bool) master->onKilledCreature(target); } - //scripting event - onKill + // scripting event - onKill const CreatureEventList& killEvents = getCreatureEvents(CREATURE_EVENT_KILL); for (CreatureEvent* killEvent : killEvents) { killEvent->executeOnKill(this, target); @@ -1125,7 +1156,9 @@ void Creature::onGainExperience(uint64_t gainExp, Creature* target) return; } - TextMessage message(MESSAGE_EXPERIENCE_OTHERS, ucfirst(getNameDescription()) + " gained " + std::to_string(gainExp) + (gainExp != 1 ? " experience points." : " experience point.")); + TextMessage message(MESSAGE_EXPERIENCE_OTHERS, ucfirst(getNameDescription()) + " gained " + + std::to_string(gainExp) + + (gainExp != 1 ? " experience points." : " experience point.")); message.position = position; message.primary.color = TEXTCOLOR_WHITE_EXP; message.primary.value = gainExp; @@ -1135,7 +1168,8 @@ void Creature::onGainExperience(uint64_t gainExp, Creature* target) } } -bool Creature::setMaster(Creature* newMaster) { +bool Creature::setMaster(Creature* newMaster) +{ if (!newMaster && !master) { return false; } @@ -1158,16 +1192,17 @@ bool Creature::setMaster(Creature* newMaster) { return true; } -bool Creature::addCondition(Condition* condition, bool force/* = false*/) +bool Creature::addCondition(Condition* condition, bool force /* = false*/) { - if (condition == nullptr) { + if (!condition) { return false; } if (!force && condition->getType() == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { int64_t walkDelay = getWalkDelay(); if (walkDelay > 0) { - g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceAddCondition, &g_game, getID(), condition))); + g_scheduler.addEvent( + createSchedulerTask(walkDelay, [=, id = getID()]() { g_game.forceAddCondition(id, condition); })); return false; } } @@ -1191,7 +1226,7 @@ bool Creature::addCondition(Condition* condition, bool force/* = false*/) bool Creature::addCombatCondition(Condition* condition) { - //Caution: condition variable could be deleted after the call to addCondition + // Caution: condition variable could be deleted after the call to addCondition ConditionType_t type = condition->getType(); if (!addCondition(condition)) { @@ -1202,7 +1237,7 @@ bool Creature::addCombatCondition(Condition* condition) return true; } -void Creature::removeCondition(ConditionType_t type, bool force/* = false*/) +void Creature::removeCondition(ConditionType_t type, bool force /* = false*/) { auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -1215,7 +1250,8 @@ void Creature::removeCondition(ConditionType_t type, bool force/* = false*/) if (!force && type == CONDITION_PARALYZE) { int64_t walkDelay = getWalkDelay(); if (walkDelay > 0) { - g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + g_scheduler.addEvent( + createSchedulerTask(walkDelay, [=, id = getID()]() { g_game.forceRemoveCondition(id, type); })); return; } } @@ -1229,7 +1265,7 @@ void Creature::removeCondition(ConditionType_t type, bool force/* = false*/) } } -void Creature::removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force/* = false*/) +void Creature::removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force /* = false*/) { auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -1242,7 +1278,8 @@ void Creature::removeCondition(ConditionType_t type, ConditionId_t conditionId, if (!force && type == CONDITION_PARALYZE) { int64_t walkDelay = getWalkDelay(); if (walkDelay > 0) { - g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + g_scheduler.addEvent( + createSchedulerTask(walkDelay, [=, id = getID()]() { g_game.forceRemoveCondition(id, type); })); return; } } @@ -1270,7 +1307,7 @@ void Creature::removeCombatCondition(ConditionType_t type) } } -void Creature::removeCondition(Condition* condition, bool force/* = false*/) +void Creature::removeCondition(Condition* condition, bool force /* = false*/) { auto it = std::find(conditions.begin(), conditions.end(), condition); if (it == conditions.end()) { @@ -1280,7 +1317,8 @@ void Creature::removeCondition(Condition* condition, bool force/* = false*/) if (!force && condition->getType() == CONDITION_PARALYZE) { int64_t walkDelay = getWalkDelay(); if (walkDelay > 0) { - g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), condition->getType()))); + g_scheduler.addEvent(createSchedulerTask( + walkDelay, [id = getID(), type = condition->getType()]() { g_game.forceRemoveCondition(id, type); })); return; } } @@ -1302,7 +1340,7 @@ Condition* Creature::getCondition(ConditionType_t type) const return nullptr; } -Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId/* = 0*/) const +Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId /* = 0*/) const { for (Condition* condition : conditions) { if (condition->getType() == type && condition->getId() == conditionId && condition->getSubId() == subId) { @@ -1314,7 +1352,7 @@ Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionI void Creature::executeConditions(uint32_t interval) { - ConditionList tempConditions{ conditions }; + ConditionList tempConditions{conditions}; for (Condition* condition : tempConditions) { auto it = std::find(conditions.begin(), conditions.end(), condition); if (it == conditions.end()) { @@ -1333,7 +1371,7 @@ void Creature::executeConditions(uint32_t interval) } } -bool Creature::hasCondition(ConditionType_t type, uint32_t subId/* = 0*/) const +bool Creature::hasCondition(ConditionType_t type, uint32_t subId /* = 0*/) const { if (isSuppress(type)) { return false; @@ -1387,7 +1425,8 @@ int64_t Creature::getStepDuration() const int32_t stepSpeed = getStepSpeed(); if (stepSpeed > -Creature::speedB) { - calculatedStepSpeed = floor((Creature::speedA * log((stepSpeed / 2) + Creature::speedB) + Creature::speedC) + 0.5); + calculatedStepSpeed = + floor((Creature::speedA * log((stepSpeed / 2) + Creature::speedB) + Creature::speedC) + 0.5); if (calculatedStepSpeed == 0) { calculatedStepSpeed = 1; } @@ -1430,19 +1469,11 @@ int64_t Creature::getEventStepTicks(bool onlyDelay) const return ret; } -LightInfo Creature::getCreatureLight() const -{ - return internalLight; -} +LightInfo Creature::getCreatureLight() const { return internalLight; } -void Creature::setCreatureLight(LightInfo lightInfo) { - internalLight = std::move(lightInfo); -} +void Creature::setCreatureLight(LightInfo lightInfo) { internalLight = std::move(lightInfo); } -void Creature::setNormalCreatureLight() -{ - internalLight = {}; -} +void Creature::setNormalCreatureLight() { internalLight = {}; } bool Creature::registerCreatureEvent(const std::string& name) { @@ -1522,7 +1553,7 @@ CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) } bool FrozenPathingConditionCall::isInRange(const Position& startPos, const Position& testPos, - const FindPathParams& fpp) const + const FindPathParams& fpp) const { if (fpp.fullPathSearch) { if (testPos.x > targetPos.x + fpp.maxTargetDist) { @@ -1569,7 +1600,7 @@ bool FrozenPathingConditionCall::isInRange(const Position& startPos, const Posit } bool FrozenPathingConditionCall::operator()(const Position& startPos, const Position& testPos, - const FindPathParams& fpp, int32_t& bestMatchDist) const + const FindPathParams& fpp, int32_t& bestMatchDist) const { if (!isInRange(startPos, testPos, fpp)) { return false; @@ -1579,7 +1610,8 @@ bool FrozenPathingConditionCall::operator()(const Position& startPos, const Posi return false; } - int32_t testDist = std::max(Position::getDistanceX(targetPos, testPos), Position::getDistanceY(targetPos, testPos)); + int32_t testDist = + std::max(Position::getDistanceX(targetPos, testPos), Position::getDistanceY(targetPos, testPos)); if (fpp.maxTargetDist == 1) { if (testDist < fpp.minTargetDist || testDist > fpp.maxTargetDist) { return false; @@ -1595,7 +1627,7 @@ bool FrozenPathingConditionCall::operator()(const Position& startPos, const Posi bestMatchDist = 0; return true; } else if (testDist > bestMatchDist) { - //not quite what we want, but the best so far + // not quite what we want, but the best so far bestMatchDist = testDist; return true; } @@ -1605,9 +1637,9 @@ bool FrozenPathingConditionCall::operator()(const Position& startPos, const Posi bool Creature::isInvisible() const { - return std::find_if(conditions.begin(), conditions.end(), [] (const Condition* condition) { - return condition->getType() == CONDITION_INVISIBLE; - }) != conditions.end(); + return std::find_if(conditions.begin(), conditions.end(), [](const Condition* condition) { + return condition->getType() == CONDITION_INVISIBLE; + }) != conditions.end(); } bool Creature::getPathTo(const Position& targetPos, std::vector& dirList, const FindPathParams& fpp) const @@ -1615,7 +1647,9 @@ bool Creature::getPathTo(const Position& targetPos, std::vector& dirL return g_game.map.getPathMatching(*this, dirList, FrozenPathingConditionCall(targetPos), fpp); } -bool Creature::getPathTo(const Position& targetPos, std::vector& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch /*= true*/, bool clearSight /*= true*/, int32_t maxSearchDist /*= 0*/) const +bool Creature::getPathTo(const Position& targetPos, std::vector& dirList, int32_t minTargetDist, + int32_t maxTargetDist, bool fullPathSearch /*= true*/, bool clearSight /*= true*/, + int32_t maxSearchDist /*= 0*/) const { FindPathParams fpp; fpp.fullPathSearch = fullPathSearch; diff --git a/src/creature.h b/src/creature.h index d412062dbf..d599818e92 100644 --- a/src/creature.h +++ b/src/creature.h @@ -1,37 +1,28 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CREATURE_H_5363C04015254E298F84E6D59A139508 -#define FS_CREATURE_H_5363C04015254E298F84E6D59A139508 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. +#ifndef FS_CREATURE_H +#define FS_CREATURE_H + +#include "const.h" +#include "creatureevent.h" +#include "enums.h" #include "map.h" #include "position.h" -#include "condition.h" -#include "const.h" #include "tile.h" -#include "enums.h" -#include "creatureevent.h" + +class Condition; +class Container; +class Item; +class Monster; +class Npc; +class Player; using ConditionList = std::list; using CreatureEventList = std::list; -enum slots_t : uint8_t { +enum slots_t : uint8_t +{ CONST_SLOT_WHEREEVER = 0, CONST_SLOT_HEAD = 1, CONST_SLOT_NECKLACE = 2, @@ -49,7 +40,8 @@ enum slots_t : uint8_t { CONST_SLOT_LAST = CONST_SLOT_AMMO, }; -struct FindPathParams { +struct FindPathParams +{ bool fullPathSearch = true; bool clearSight = true; bool allowDiagonal = true; @@ -59,32 +51,24 @@ struct FindPathParams { int32_t maxTargetDist = -1; }; -class Map; -class Thing; -class Container; -class Player; -class Monster; -class Npc; -class Item; -class Tile; - static constexpr int32_t EVENT_CREATURECOUNT = 10; static constexpr int32_t EVENT_CREATURE_THINK_INTERVAL = 1000; static constexpr int32_t EVENT_CHECK_CREATURE_INTERVAL = (EVENT_CREATURE_THINK_INTERVAL / EVENT_CREATURECOUNT); +static constexpr uint32_t CREATURE_ID_MIN = 0x10000000; +static constexpr uint32_t CREATURE_ID_MAX = std::numeric_limits::max(); class FrozenPathingConditionCall { - public: - explicit FrozenPathingConditionCall(Position targetPos) : targetPos(std::move(targetPos)) {} +public: + explicit FrozenPathingConditionCall(Position targetPos) : targetPos(std::move(targetPos)) {} - bool operator()(const Position& startPos, const Position& testPos, - const FindPathParams& fpp, int32_t& bestMatchDist) const; + bool operator()(const Position& startPos, const Position& testPos, const FindPathParams& fpp, + int32_t& bestMatchDist) const; - bool isInRange(const Position& startPos, const Position& testPos, - const FindPathParams& fpp) const; + bool isInRange(const Position& startPos, const Position& testPos, const FindPathParams& fpp) const; - private: - Position targetPos; +private: + Position targetPos; }; ////////////////////////////////////////////////////////////////////// @@ -93,480 +77,368 @@ class FrozenPathingConditionCall class Creature : virtual public Thing { - protected: - Creature(); - - public: - static double speedA, speedB, speedC; - - virtual ~Creature(); - - // non-copyable - Creature(const Creature&) = delete; - Creature& operator=(const Creature&) = delete; - - Creature* getCreature() override final { - return this; - } - const Creature* getCreature() const override final { - return this; - } - virtual Player* getPlayer() { - return nullptr; - } - virtual const Player* getPlayer() const { - return nullptr; - } - virtual Npc* getNpc() { - return nullptr; - } - virtual const Npc* getNpc() const { - return nullptr; - } - virtual Monster* getMonster() { - return nullptr; - } - virtual const Monster* getMonster() const { - return nullptr; - } - - virtual const std::string& getName() const = 0; - virtual const std::string& getNameDescription() const = 0; - - virtual CreatureType_t getType() const = 0; - - virtual void setID() = 0; - void setRemoved() { - isInternalRemoved = true; - } - - uint32_t getID() const { - return id; - } - virtual void removeList() = 0; - virtual void addList() = 0; - - virtual bool canSee(const Position& pos) const; - virtual bool canSeeCreature(const Creature* creature) const; - - virtual RaceType_t getRace() const { - return RACE_NONE; - } - virtual Skulls_t getSkull() const { - return skull; - } - virtual Skulls_t getSkullClient(const Creature* creature) const { - return creature->getSkull(); - } - void setSkull(Skulls_t newSkull); - Direction getDirection() const { - return direction; - } - void setDirection(Direction dir) { - direction = dir; - } - - bool isHealthHidden() const { - return hiddenHealth; - } - void setHiddenHealth(bool b) { - hiddenHealth = b; - } - - int32_t getThrowRange() const override final { - return 1; - } - bool isPushable() const override { - return getWalkDelay() <= 0; - } - bool isRemoved() const override final { - return isInternalRemoved; - } - virtual bool canSeeInvisibility() const { - return false; - } - virtual bool isInGhostMode() const { - return false; - } - - int32_t getWalkDelay(Direction dir) const; - int32_t getWalkDelay() const; - int64_t getTimeSinceLastMove() const; - - int64_t getEventStepTicks(bool onlyDelay = false) const; - int64_t getStepDuration(Direction dir) const; - int64_t getStepDuration() const; - virtual int32_t getStepSpeed() const { - return getSpeed(); - } - int32_t getSpeed() const { - return baseSpeed + varSpeed; - } - void setSpeed(int32_t varSpeedDelta) { - int32_t oldSpeed = getSpeed(); - varSpeed = varSpeedDelta; - - if (getSpeed() <= 0) { - stopEventWalk(); - cancelNextWalk = true; - } else if (oldSpeed <= 0 && !listWalkDir.empty()) { - addEventWalk(); - } - } - - void setBaseSpeed(uint32_t newBaseSpeed) { - baseSpeed = newBaseSpeed; - } - uint32_t getBaseSpeed() const { - return baseSpeed; - } - - int32_t getHealth() const { - return health; - } - virtual int32_t getMaxHealth() const { - return healthMax; - } - - const Outfit_t getCurrentOutfit() const { - return currentOutfit; - } - void setCurrentOutfit(Outfit_t outfit) { - currentOutfit = outfit; - } - const Outfit_t getDefaultOutfit() const { - return defaultOutfit; - } - bool isInvisible() const; - ZoneType_t getZone() const { - return getTile()->getZone(); - } - - //walk functions - void startAutoWalk(); - void startAutoWalk(Direction direction); - void startAutoWalk(const std::vector& listDir); - void addEventWalk(bool firstStep = false); - void stopEventWalk(); - virtual void goToFollowCreature(); - - //walk events - virtual void onWalk(Direction& dir); - virtual void onWalkAborted() {} - virtual void onWalkComplete() {} - - //follow functions - Creature* getFollowCreature() const { - return followCreature; - } - virtual bool setFollowCreature(Creature* creature); - - //follow events - virtual void onFollowCreature(const Creature*) {} - virtual void onFollowCreatureComplete(const Creature*) {} - - //combat functions - Creature* getAttackedCreature() { - return attackedCreature; - } - virtual bool setAttackedCreature(Creature* creature); - virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false, bool ignoreResistances = false); - - bool setMaster(Creature* newMaster); - - void removeMaster() { - if (master) { - master = nullptr; - decrementReferenceCounter(); - } - } - - bool isSummon() const { - return master != nullptr; - } - Creature* getMaster() const { - return master; - } - - const std::list& getSummons() const { - return summons; - } - - virtual int32_t getArmor() const { - return 0; - } - virtual int32_t getDefense() const { - return 0; - } - virtual float getAttackFactor() const { - return 1.0f; - } - virtual float getDefenseFactor() const { - return 1.0f; - } - - virtual uint8_t getSpeechBubble() const { - return SPEECHBUBBLE_NONE; - } - - bool addCondition(Condition* condition, bool force = false); - bool addCombatCondition(Condition* condition); - void removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force = false); - void removeCondition(ConditionType_t type, bool force = false); - void removeCondition(Condition* condition, bool force = false); - void removeCombatCondition(ConditionType_t type); - Condition* getCondition(ConditionType_t type) const; - Condition* getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId = 0) const; - void executeConditions(uint32_t interval); - bool hasCondition(ConditionType_t type, uint32_t subId = 0) const; - virtual bool isImmune(ConditionType_t type) const; - virtual bool isImmune(CombatType_t type) const; - virtual bool isSuppress(ConditionType_t type) const; - virtual uint32_t getDamageImmunities() const { - return 0; - } - virtual uint32_t getConditionImmunities() const { - return 0; - } - virtual uint32_t getConditionSuppressions() const { - return 0; - } - virtual bool isAttackable() const { - return true; - } - - virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); - - void gainHealth(Creature* healer, int32_t healthGain); - virtual void drainHealth(Creature* attacker, int32_t damage); - - virtual bool challengeCreature(Creature*) { - return false; - } - - void onDeath(); - virtual uint64_t getGainedExperience(Creature* attacker) const; - void addDamagePoints(Creature* attacker, int32_t damagePoints); - bool hasBeenAttacked(uint32_t attackerId); - - //combat event functions - virtual void onAddCondition(ConditionType_t type); - virtual void onAddCombatCondition(ConditionType_t type); - virtual void onEndCondition(ConditionType_t type); - void onTickCondition(ConditionType_t type, bool& bRemove); - virtual void onCombatRemoveCondition(Condition* condition); - virtual void onAttackedCreature(Creature*, bool = true) {} - virtual void onAttacked(); - virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points); - virtual void onTargetCreatureGainHealth(Creature*, int32_t) {} - virtual bool onKilledCreature(Creature* target, bool lastHit = true); - virtual void onGainExperience(uint64_t gainExp, Creature* target); - virtual void onAttackedCreatureBlockHit(BlockType_t) {} - virtual void onBlockHit() {} - virtual void onChangeZone(ZoneType_t zone); - virtual void onAttackedCreatureChangeZone(ZoneType_t zone); - virtual void onIdleStatus(); - - virtual LightInfo getCreatureLight() const; - virtual void setNormalCreatureLight(); - void setCreatureLight(LightInfo lightInfo); - - virtual void onThink(uint32_t interval); - void onAttacking(uint32_t interval); - virtual void onWalk(); - virtual bool getNextStep(Direction& dir, uint32_t& flags); - - void onAddTileItem(const Tile* tile, const Position& pos); - virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType); - virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item); - - virtual void onCreatureAppear(Creature* creature, bool isLogin); - virtual void onRemoveCreature(Creature* creature, bool isLogout); - virtual void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport); - - virtual void onAttackedCreatureDisappear(bool) {} - virtual void onFollowCreatureDisappear(bool) {} - - virtual void onCreatureSay(Creature*, SpeakClasses, const std::string&) {} - - virtual void onPlacedCreature() {} - - virtual bool getCombatValues(int32_t&, int32_t&) { - return false; - } - - size_t getSummonCount() const { - return summons.size(); - } - void setDropLoot(bool lootDrop) { - this->lootDrop = lootDrop; - } - void setSkillLoss(bool skillLoss) { - this->skillLoss = skillLoss; - } - void setUseDefense(bool useDefense) { - canUseDefense = useDefense; - } - void setMovementBlocked(bool state) { - movementBlocked = state; +protected: + Creature(); + +public: + static double speedA, speedB, speedC; + + virtual ~Creature(); + + // non-copyable + Creature(const Creature&) = delete; + Creature& operator=(const Creature&) = delete; + + Creature* getCreature() override final { return this; } + const Creature* getCreature() const override final { return this; } + virtual Player* getPlayer() { return nullptr; } + virtual const Player* getPlayer() const { return nullptr; } + virtual Npc* getNpc() { return nullptr; } + virtual const Npc* getNpc() const { return nullptr; } + virtual Monster* getMonster() { return nullptr; } + virtual const Monster* getMonster() const { return nullptr; } + + virtual const std::string& getName() const = 0; + virtual const std::string& getNameDescription() const = 0; + + virtual CreatureType_t getType() const = 0; + + virtual void setID() = 0; + void setRemoved() { isInternalRemoved = true; } + + uint32_t getID() const { return id; } + virtual void removeList() = 0; + virtual void addList() = 0; + + virtual bool canSee(const Position& pos) const; + virtual bool canSeeCreature(const Creature* creature) const; + + virtual RaceType_t getRace() const { return RACE_NONE; } + virtual Skulls_t getSkull() const { return skull; } + virtual Skulls_t getSkullClient(const Creature* creature) const { return creature->getSkull(); } + void setSkull(Skulls_t newSkull); + Direction getDirection() const { return direction; } + void setDirection(Direction dir) { direction = dir; } + + bool isHealthHidden() const { return hiddenHealth; } + void setHiddenHealth(bool b) { hiddenHealth = b; } + + int32_t getThrowRange() const override final { return 1; } + bool isPushable() const override { return getWalkDelay() <= 0; } + bool isRemoved() const override final { return isInternalRemoved; } + virtual bool canSeeInvisibility() const { return false; } + virtual bool isInGhostMode() const { return false; } + virtual bool canSeeGhostMode(const Creature*) const { return false; } + + int32_t getWalkDelay(Direction dir) const; + int32_t getWalkDelay() const; + int64_t getTimeSinceLastMove() const; + + int64_t getEventStepTicks(bool onlyDelay = false) const; + int64_t getStepDuration(Direction dir) const; + int64_t getStepDuration() const; + virtual int32_t getStepSpeed() const { return getSpeed(); } + int32_t getSpeed() const { return baseSpeed + varSpeed; } + void setSpeed(int32_t varSpeedDelta) + { + int32_t oldSpeed = getSpeed(); + varSpeed = varSpeedDelta; + + if (getSpeed() <= 0) { + stopEventWalk(); cancelNextWalk = true; - } - bool isMovementBlocked() const { - return movementBlocked; - } - - //creature script events - bool registerCreatureEvent(const std::string& name); - bool unregisterCreatureEvent(const std::string& name); - - Cylinder* getParent() const override final { - return tile; - } - void setParent(Cylinder* cylinder) override final { - tile = static_cast(cylinder); - position = tile->getPosition(); - } - - const Position& getPosition() const override final { - return position; - } - - Tile* getTile() override final { - return tile; - } - const Tile* getTile() const override final { - return tile; - } - - int32_t getWalkCache(const Position& pos) const; - - const Position& getLastPosition() const { - return lastPosition; - } - void setLastPosition(Position newLastPos) { - lastPosition = newLastPos; - } - - static bool canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY); - - double getDamageRatio(Creature* attacker) const; - - bool getPathTo(const Position& targetPos, std::vector& dirList, const FindPathParams& fpp) const; - bool getPathTo(const Position& targetPos, std::vector& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch = true, bool clearSight = true, int32_t maxSearchDist = 0) const; - - void incrementReferenceCounter() { - ++referenceCounter; - } - void decrementReferenceCounter() { - if (--referenceCounter == 0) { - delete this; - } - } - - protected: - virtual bool useCacheMap() const { - return false; - } - - struct CountBlock_t { - int32_t total; - int64_t ticks; - }; - - static constexpr int32_t mapWalkWidth = Map::maxViewportX * 2 + 1; - static constexpr int32_t mapWalkHeight = Map::maxViewportY * 2 + 1; - static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; - static constexpr int32_t maxWalkCacheHeight = (mapWalkHeight - 1) / 2; - - Position position; - - using CountMap = std::map; - CountMap damageMap; - - std::list summons; - CreatureEventList eventsList; - ConditionList conditions; - - std::vector listWalkDir; - - Tile* tile = nullptr; - Creature* attackedCreature = nullptr; - Creature* master = nullptr; - Creature* followCreature = nullptr; - - uint64_t lastStep = 0; - uint32_t referenceCounter = 0; - uint32_t id = 0; - uint32_t scriptEventsBitField = 0; - uint32_t eventWalk = 0; - uint32_t walkUpdateTicks = 0; - uint32_t lastHitCreatureId = 0; - uint32_t blockCount = 0; - uint32_t blockTicks = 0; - uint32_t lastStepCost = 1; - uint32_t baseSpeed = 220; - int32_t varSpeed = 0; - int32_t health = 1000; - int32_t healthMax = 1000; - - Outfit_t currentOutfit; - Outfit_t defaultOutfit; - - Position lastPosition; - LightInfo internalLight; - - Direction direction = DIRECTION_SOUTH; - Skulls_t skull = SKULL_NONE; - - bool localMapCache[mapWalkHeight][mapWalkWidth] = {{ false }}; - bool isInternalRemoved = false; - bool isMapLoaded = false; - bool isUpdatingPath = false; - bool creatureCheck = false; - bool inCheckCreaturesVector = false; - bool skillLoss = true; - bool lootDrop = true; - bool cancelNextWalk = false; - bool hasFollowPath = false; - bool forceUpdateFollowPath = false; - bool hiddenHealth = false; - bool canUseDefense = true; - bool movementBlocked = false; - - //creature script events - bool hasEventRegistered(CreatureEventType_t event) const { - return (0 != (scriptEventsBitField & (static_cast(1) << event))); - } - CreatureEventList getCreatureEvents(CreatureEventType_t type); - - void updateMapCache(); - void updateTileCache(const Tile* tile, int32_t dx, int32_t dy); - void updateTileCache(const Tile* tile, const Position& pos); - void onCreatureDisappear(const Creature* creature, bool isLogout); - virtual void doAttacking(uint32_t) {} - virtual bool hasExtraSwing() { - return false; - } - - virtual uint64_t getLostExperience() const { - return 0; - } - virtual void dropLoot(Container*, Creature*) {} - virtual uint16_t getLookCorpse() const { - return 0; - } - virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; - virtual void death(Creature*) {} - virtual bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified); - virtual Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature); - - friend class Game; - friend class Map; - friend class LuaScriptInterface; + } else if (oldSpeed <= 0 && !listWalkDir.empty()) { + addEventWalk(); + } + } + + void setBaseSpeed(uint32_t newBaseSpeed) { baseSpeed = newBaseSpeed; } + uint32_t getBaseSpeed() const { return baseSpeed; } + + int32_t getHealth() const { return health; } + virtual int32_t getMaxHealth() const { return healthMax; } + + void setDrunkenness(uint8_t newDrunkenness) { drunkenness = newDrunkenness; } + uint8_t getDrunkenness() const { return drunkenness; } + + const Outfit_t getCurrentOutfit() const { return currentOutfit; } + void setCurrentOutfit(Outfit_t outfit) { currentOutfit = outfit; } + const Outfit_t getDefaultOutfit() const { return defaultOutfit; } + bool isInvisible() const; + ZoneType_t getZone() const { return getTile()->getZone(); } + + // walk functions + void startAutoWalk(); + void startAutoWalk(Direction direction); + void startAutoWalk(const std::vector& listDir); + void addEventWalk(bool firstStep = false); + void stopEventWalk(); + virtual void goToFollowCreature(); + + // walk events + virtual void onWalk(Direction& dir); + virtual void onWalkAborted() {} + virtual void onWalkComplete() {} + + // follow functions + Creature* getFollowCreature() const { return followCreature; } + virtual bool setFollowCreature(Creature* creature); + + // follow events + virtual void onFollowCreature(const Creature*) {} + virtual void onFollowCreatureComplete(const Creature*) {} + + // combat functions + Creature* getAttackedCreature() { return attackedCreature; } + virtual bool setAttackedCreature(Creature* creature); + virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false, + bool ignoreResistances = false); + + bool setMaster(Creature* newMaster); + + void removeMaster() + { + if (master) { + master = nullptr; + decrementReferenceCounter(); + } + } + + bool isSummon() const { return master != nullptr; } + Creature* getMaster() const { return master; } + + const std::list& getSummons() const { return summons; } + + virtual int32_t getArmor() const { return 0; } + virtual int32_t getDefense() const { return 0; } + virtual float getAttackFactor() const { return 1.0f; } + virtual float getDefenseFactor() const { return 1.0f; } + + virtual uint8_t getSpeechBubble() const { return SPEECHBUBBLE_NONE; } + + bool addCondition(Condition* condition, bool force = false); + bool addCombatCondition(Condition* condition); + void removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force = false); + void removeCondition(ConditionType_t type, bool force = false); + void removeCondition(Condition* condition, bool force = false); + void removeCombatCondition(ConditionType_t type); + Condition* getCondition(ConditionType_t type) const; + Condition* getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId = 0) const; + void executeConditions(uint32_t interval); + bool hasCondition(ConditionType_t type, uint32_t subId = 0) const; + virtual bool isImmune(ConditionType_t type) const; + virtual bool isImmune(CombatType_t type) const; + virtual bool isSuppress(ConditionType_t type) const; + virtual uint32_t getDamageImmunities() const { return 0; } + virtual uint32_t getConditionImmunities() const { return 0; } + virtual uint32_t getConditionSuppressions() const { return 0; } + virtual bool isAttackable() const { return true; } + + virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); + + void gainHealth(Creature* healer, int32_t healthGain); + virtual void drainHealth(Creature* attacker, int32_t damage); + + virtual bool challengeCreature(Creature*, bool) { return false; } + + CreatureVector getKillers(); + void onDeath(); + virtual uint64_t getGainedExperience(Creature* attacker) const; + void addDamagePoints(Creature* attacker, int32_t damagePoints); + bool hasBeenAttacked(uint32_t attackerId); + + // combat event functions + virtual void onAddCondition(ConditionType_t type); + virtual void onAddCombatCondition(ConditionType_t type); + virtual void onEndCondition(ConditionType_t type); + void onTickCondition(ConditionType_t type, bool& bRemove); + virtual void onCombatRemoveCondition(Condition* condition); + virtual void onAttackedCreature(Creature*, bool = true) {} + virtual void onAttacked(); + virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points); + virtual void onTargetCreatureGainHealth(Creature*, int32_t) {} + virtual bool onKilledCreature(Creature* target, bool lastHit = true); + virtual void onGainExperience(uint64_t gainExp, Creature* target); + virtual void onAttackedCreatureBlockHit(BlockType_t) {} + virtual void onBlockHit() {} + virtual void onChangeZone(ZoneType_t zone); + virtual void onAttackedCreatureChangeZone(ZoneType_t zone); + virtual void onIdleStatus(); + + virtual LightInfo getCreatureLight() const; + virtual void setNormalCreatureLight(); + void setCreatureLight(LightInfo lightInfo); + + virtual void onThink(uint32_t interval); + void onAttacking(uint32_t interval); + virtual void onWalk(); + virtual bool getNextStep(Direction& dir, uint32_t& flags); + + void onAddTileItem(const Tile* tile, const Position& pos); + virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, const ItemType& oldType, + const Item* newItem, const ItemType& newType); + virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, const Item* item); + + virtual void onCreatureAppear(Creature* creature, bool isLogin); + virtual void onRemoveCreature(Creature* creature, bool isLogout); + virtual void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport); + + virtual void onAttackedCreatureDisappear(bool) {} + virtual void onFollowCreatureDisappear(bool) {} + + virtual void onCreatureSay(Creature*, SpeakClasses, const std::string&) {} + + virtual void onPlacedCreature() {} + + virtual bool getCombatValues(int32_t&, int32_t&) { return false; } + + size_t getSummonCount() const { return summons.size(); } + void setDropLoot(bool lootDrop) { this->lootDrop = lootDrop; } + void setSkillLoss(bool skillLoss) { this->skillLoss = skillLoss; } + void setUseDefense(bool useDefense) { canUseDefense = useDefense; } + void setMovementBlocked(bool state) + { + movementBlocked = state; + cancelNextWalk = true; + } + bool isMovementBlocked() const { return movementBlocked; } + + // creature script events + bool registerCreatureEvent(const std::string& name); + bool unregisterCreatureEvent(const std::string& name); + + Cylinder* getParent() const override final { return tile; } + void setParent(Cylinder* cylinder) override final + { + tile = static_cast(cylinder); + position = tile->getPosition(); + } + + const Position& getPosition() const override final { return position; } + + Tile* getTile() override final { return tile; } + const Tile* getTile() const override final { return tile; } + + int32_t getWalkCache(const Position& pos) const; + + const Position& getLastPosition() const { return lastPosition; } + void setLastPosition(Position newLastPos) { lastPosition = newLastPos; } + + static bool canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY); + + double getDamageRatio(Creature* attacker) const; + + bool getPathTo(const Position& targetPos, std::vector& dirList, const FindPathParams& fpp) const; + bool getPathTo(const Position& targetPos, std::vector& dirList, int32_t minTargetDist, + int32_t maxTargetDist, bool fullPathSearch = true, bool clearSight = true, + int32_t maxSearchDist = 0) const; + + void incrementReferenceCounter() { ++referenceCounter; } + void decrementReferenceCounter() + { + if (--referenceCounter == 0) { + delete this; + } + } + +protected: + virtual bool useCacheMap() const { return false; } + + struct CountBlock_t + { + int32_t total; + int64_t ticks; + }; + + static constexpr int32_t mapWalkWidth = Map::maxViewportX * 2 + 1; + static constexpr int32_t mapWalkHeight = Map::maxViewportY * 2 + 1; + static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; + static constexpr int32_t maxWalkCacheHeight = (mapWalkHeight - 1) / 2; + + Position position; + + using CountMap = std::map; + CountMap damageMap; + + std::list summons; + CreatureEventList eventsList; + ConditionList conditions; + + std::vector listWalkDir; + + Tile* tile = nullptr; + Creature* attackedCreature = nullptr; + Creature* master = nullptr; + Creature* followCreature = nullptr; + + uint64_t lastStep = 0; + uint32_t referenceCounter = 0; + uint32_t id = 0; + uint32_t scriptEventsBitField = 0; + uint32_t eventWalk = 0; + uint32_t walkUpdateTicks = 0; + uint32_t lastHitCreatureId = 0; + uint32_t blockCount = 0; + uint32_t blockTicks = 0; + uint32_t lastStepCost = 1; + uint32_t baseSpeed = 220; + int32_t varSpeed = 0; + int32_t health = 1000; + int32_t healthMax = 1000; + uint8_t drunkenness = 0; + + Outfit_t currentOutfit; + Outfit_t defaultOutfit; + + Position lastPosition; + LightInfo internalLight; + + Direction direction = DIRECTION_SOUTH; + Skulls_t skull = SKULL_NONE; + + bool localMapCache[mapWalkHeight][mapWalkWidth] = {{false}}; + bool isInternalRemoved = false; + bool isMapLoaded = false; + bool isUpdatingPath = false; + bool creatureCheck = false; + bool inCheckCreaturesVector = false; + bool skillLoss = true; + bool lootDrop = true; + bool cancelNextWalk = false; + bool hasFollowPath = false; + bool forceUpdateFollowPath = false; + bool hiddenHealth = false; + bool canUseDefense = true; + bool movementBlocked = false; + + // creature script events + bool hasEventRegistered(CreatureEventType_t event) const + { + return (0 != (scriptEventsBitField & (static_cast(1) << event))); + } + CreatureEventList getCreatureEvents(CreatureEventType_t type); + + void updateMapCache(); + void updateTileCache(const Tile* tile, int32_t dx, int32_t dy); + void updateTileCache(const Tile* tile, const Position& pos); + void onCreatureDisappear(const Creature* creature, bool isLogout); + virtual void doAttacking(uint32_t) {} + virtual bool hasExtraSwing() { return false; } + + virtual uint64_t getLostExperience() const { return 0; } + virtual void dropLoot(Container*, Creature*) {} + virtual uint16_t getLookCorpse() const { return 0; } + virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; + virtual void death(Creature*) {} + virtual bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, + bool mostDamageUnjustified); + virtual Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature); + + friend class Game; + friend class Map; + friend class LuaScriptInterface; }; -#endif +#endif // FS_CREATURE_H diff --git a/src/creatureevent.cpp b/src/creatureevent.cpp index c242c26591..15ecc925d9 100644 --- a/src/creatureevent.cpp +++ b/src/creatureevent.cpp @@ -1,33 +1,14 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "creatureevent.h" + +#include "item.h" #include "tools.h" -#include "player.h" -CreatureEvents::CreatureEvents() : - scriptInterface("CreatureScript Interface") -{ - scriptInterface.initState(); -} +CreatureEvents::CreatureEvents() : scriptInterface("CreatureScript Interface") { scriptInterface.initState(); } void CreatureEvents::clear(bool fromLua) { @@ -49,19 +30,13 @@ void CreatureEvents::removeInvalidEvents() } } -LuaScriptInterface& CreatureEvents::getScriptInterface() -{ - return scriptInterface; -} +LuaScriptInterface& CreatureEvents::getScriptInterface() { return scriptInterface; } -std::string CreatureEvents::getScriptBaseName() const -{ - return "creaturescripts"; -} +std::string CreatureEvents::getScriptBaseName() const { return "creaturescripts"; } Event_ptr CreatureEvents::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "event") != 0) { + if (!caseInsensitiveEqual(nodeName, "event")) { return nullptr; } return Event_ptr(new CreatureEvent(&scriptInterface)); @@ -69,7 +44,8 @@ Event_ptr CreatureEvents::getEvent(const std::string& nodeName) bool CreatureEvents::registerEvent(Event_ptr event, const pugi::xml_node&) { - CreatureEvent_ptr creatureEvent{static_cast(event.release())}; //event is guaranteed to be a CreatureEvent + CreatureEvent_ptr creatureEvent{ + static_cast(event.release())}; // event is guaranteed to be a CreatureEvent if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { std::cout << "Error: [CreatureEvents::registerEvent] Trying to register event without type!" << std::endl; return false; @@ -77,23 +53,22 @@ bool CreatureEvents::registerEvent(Event_ptr event, const pugi::xml_node&) CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false); if (oldEvent) { - //if there was an event with the same that is not loaded + // if there was an event with the same that is not loaded //(happens when reloading), it is reused if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) { oldEvent->copyEvent(creatureEvent.get()); } - return false; - } else { - //if not, register it normally - creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); - return true; } + + // if not, register it normally + creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); + return true; } bool CreatureEvents::registerLuaEvent(CreatureEvent* event) { - CreatureEvent_ptr creatureEvent{ event }; + CreatureEvent_ptr creatureEvent{event}; if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { std::cout << "Error: [CreatureEvents::registerLuaEvent] Trying to register event without type!" << std::endl; return false; @@ -101,18 +76,17 @@ bool CreatureEvents::registerLuaEvent(CreatureEvent* event) CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false); if (oldEvent) { - //if there was an event with the same that is not loaded + // if there was an event with the same that is not loaded //(happens when reloading), it is reused if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) { oldEvent->copyEvent(creatureEvent.get()); } - return false; - } else { - //if not, register it normally - creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); - return true; } + + // if not, register it normally + creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); + return true; } CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forceLoaded /*= true*/) @@ -128,7 +102,7 @@ CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forc bool CreatureEvents::playerLogin(Player* player) const { - //fire global event if is registered + // fire global event if is registered for (const auto& it : creatureEvents) { if (it.second.getEventType() == CREATURE_EVENT_LOGIN) { if (!it.second.executeOnLogin(player)) { @@ -141,7 +115,7 @@ bool CreatureEvents::playerLogin(Player* player) const bool CreatureEvents::playerLogout(Player* player) const { - //fire global event if is registered + // fire global event if is registered for (const auto& it : creatureEvents) { if (it.second.getEventType() == CREATURE_EVENT_LOGOUT) { if (!it.second.executeOnLogout(player)) { @@ -152,8 +126,7 @@ bool CreatureEvents::playerLogout(Player* player) const return true; } -bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, - uint32_t newLevel) +bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, uint32_t newLevel) { for (auto& it : creatureEvents) { if (it.second.getEventType() == CREATURE_EVENT_ADVANCE) { @@ -167,13 +140,12 @@ bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldL ///////////////////////////////////// -CreatureEvent::CreatureEvent(LuaScriptInterface* interface) : - Event(interface), type(CREATURE_EVENT_NONE), loaded(false) {} +CreatureEvent::CreatureEvent(LuaScriptInterface* interface) : Event(interface), type(CREATURE_EVENT_NONE), loaded(false) +{} bool CreatureEvent::configureEvent(const pugi::xml_node& node) { - // Name that will be used in monster xml files and - // lua function to register events to reference this event + // Name that will be used in monster xml files and lua function to register events to reference this event pugi::xml_attribute nameAttribute = node.attribute("name"); if (!nameAttribute) { std::cout << "[Error - CreatureEvent::configureEvent] Missing name for creature event" << std::endl; @@ -184,11 +156,12 @@ bool CreatureEvent::configureEvent(const pugi::xml_node& node) pugi::xml_attribute typeAttribute = node.attribute("type"); if (!typeAttribute) { - std::cout << "[Error - CreatureEvent::configureEvent] Missing type for creature event: " << eventName << std::endl; + std::cout << "[Error - CreatureEvent::configureEvent] Missing type for creature event: " << eventName + << std::endl; return false; } - std::string tmpStr = asLowerCaseString(typeAttribute.as_string()); + std::string tmpStr = boost::algorithm::to_lower_copy(typeAttribute.as_string()); if (tmpStr == "login") { type = CREATURE_EVENT_LOGIN; } else if (tmpStr == "logout") { @@ -214,7 +187,8 @@ bool CreatureEvent::configureEvent(const pugi::xml_node& node) } else if (tmpStr == "extendedopcode") { type = CREATURE_EVENT_EXTENDED_OPCODE; } else { - std::cout << "[Error - CreatureEvent::configureEvent] Invalid type for creature event: " << eventName << std::endl; + std::cout << "[Error - CreatureEvent::configureEvent] Invalid type for creature event: " << eventName + << std::endl; return false; } @@ -224,7 +198,7 @@ bool CreatureEvent::configureEvent(const pugi::xml_node& node) std::string CreatureEvent::getScriptEventName() const { - //Depending on the type script event name is different + // Depending on the type script event name is different switch (type) { case CREATURE_EVENT_LOGIN: return "onLogin"; @@ -286,7 +260,7 @@ void CreatureEvent::clearEvent() bool CreatureEvent::executeOnLogin(Player* player) const { - //onLogin(player) + // onLogin(player) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnLogin] Call stack overflow" << std::endl; return false; @@ -305,7 +279,7 @@ bool CreatureEvent::executeOnLogin(Player* player) const bool CreatureEvent::executeOnLogout(Player* player) const { - //onLogout(player) + // onLogout(player) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnLogout] Call stack overflow" << std::endl; return false; @@ -324,7 +298,7 @@ bool CreatureEvent::executeOnLogout(Player* player) const bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) { - //onThink(creature, interval) + // onThink(creature, interval) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnThink] Call stack overflow" << std::endl; return false; @@ -345,7 +319,7 @@ bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) bool CreatureEvent::executeOnPrepareDeath(Creature* creature, Creature* killer) { - //onPrepareDeath(creature, killer) + // onPrepareDeath(creature, killer) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnPrepareDeath] Call stack overflow" << std::endl; return false; @@ -371,9 +345,10 @@ bool CreatureEvent::executeOnPrepareDeath(Creature* creature, Creature* killer) return scriptInterface->callFunction(2); } -bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) +bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, + bool lastHitUnjustified, bool mostDamageUnjustified) { - //onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + // onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnDeath] Call stack overflow" << std::endl; return false; @@ -410,10 +385,9 @@ bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* k return scriptInterface->callFunction(6); } -bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldLevel, - uint32_t newLevel) +bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldLevel, uint32_t newLevel) { - //onAdvance(player, skill, oldLevel, newLevel) + // onAdvance(player, skill, oldLevel, newLevel) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeAdvance] Call stack overflow" << std::endl; return false; @@ -436,7 +410,7 @@ bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldL void CreatureEvent::executeOnKill(Creature* creature, Creature* target) { - //onKill(creature, target) + // onKill(creature, target) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnKill] Call stack overflow" << std::endl; return; @@ -457,7 +431,7 @@ void CreatureEvent::executeOnKill(Creature* creature, Creature* target) void CreatureEvent::executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId) { - //onModalWindow(player, modalWindowId, buttonId, choiceId) + // onModalWindow(player, modalWindowId, buttonId, choiceId) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeModalWindow] Call stack overflow" << std::endl; return; @@ -481,7 +455,7 @@ void CreatureEvent::executeModalWindow(Player* player, uint32_t modalWindowId, u bool CreatureEvent::executeTextEdit(Player* player, Item* item, const std::string& text) { - //onTextEdit(player, item, text) + // onTextEdit(player, item, text) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeTextEdit] Call stack overflow" << std::endl; return false; @@ -504,7 +478,7 @@ bool CreatureEvent::executeTextEdit(Player* player, Item* item, const std::strin void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage) { - //onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + // onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeHealthChange] Call stack overflow" << std::endl; return; @@ -545,8 +519,9 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, scriptInterface->resetScriptEnv(); } -void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage) { - //onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) +void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage) +{ + // onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeManaChange] Call stack overflow" << std::endl; return; @@ -584,7 +559,7 @@ void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, Co void CreatureEvent::executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer) { - //onExtendedOpcode(player, opcode, buffer) + // onExtendedOpcode(player, opcode, buffer) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeExtendedOpcode] Call stack overflow" << std::endl; return; diff --git a/src/creatureevent.h b/src/creatureevent.h index 6b2c9dd9eb..668639f25e 100644 --- a/src/creatureevent.h +++ b/src/creatureevent.h @@ -1,33 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9 -#define FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CREATUREEVENT_H +#define FS_CREATUREEVENT_H -#include "luascript.h" #include "baseevents.h" #include "enums.h" +#include "luascript.h" class CreatureEvent; using CreatureEvent_ptr = std::unique_ptr; -enum CreatureEventType_t { +enum CreatureEventType_t +{ CREATURE_EVENT_NONE, CREATURE_EVENT_LOGIN, CREATURE_EVENT_LOGOUT, @@ -45,88 +30,77 @@ enum CreatureEventType_t { class CreatureEvent final : public Event { - public: - explicit CreatureEvent(LuaScriptInterface* interface); - - bool configureEvent(const pugi::xml_node& node) override; - - CreatureEventType_t getEventType() const { - return type; - } - void setEventType(CreatureEventType_t eventType) { - type = eventType; - } - const std::string& getName() const { - return eventName; - } - void setName(const std::string& name) { - eventName = name; - } - bool isLoaded() const { - return loaded; - } - void setLoaded(bool b) { - loaded = b; - } - - void clearEvent(); - void copyEvent(CreatureEvent* creatureEvent); - - //scripting - bool executeOnLogin(Player* player) const; - bool executeOnLogout(Player* player) const; - bool executeOnThink(Creature* creature, uint32_t interval); - bool executeOnPrepareDeath(Creature* creature, Creature* killer); - bool executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified); - void executeOnKill(Creature* creature, Creature* target); - bool executeAdvance(Player* player, skills_t, uint32_t, uint32_t); - void executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId); - bool executeTextEdit(Player* player, Item* item, const std::string& text); - void executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); - void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); - void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer); - // - - private: - std::string getScriptEventName() const override; - - std::string eventName; - CreatureEventType_t type; - bool loaded; +public: + explicit CreatureEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) override; + + CreatureEventType_t getEventType() const { return type; } + void setEventType(CreatureEventType_t eventType) { type = eventType; } + const std::string& getName() const { return eventName; } + void setName(const std::string& name) { eventName = name; } + bool isLoaded() const { return loaded; } + void setLoaded(bool b) { loaded = b; } + + void clearEvent(); + void copyEvent(CreatureEvent* creatureEvent); + + // scripting + bool executeOnLogin(Player* player) const; + bool executeOnLogout(Player* player) const; + bool executeOnThink(Creature* creature, uint32_t interval); + bool executeOnPrepareDeath(Creature* creature, Creature* killer); + bool executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, + bool lastHitUnjustified, bool mostDamageUnjustified); + void executeOnKill(Creature* creature, Creature* target); + bool executeAdvance(Player* player, skills_t, uint32_t, uint32_t); + void executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId); + bool executeTextEdit(Player* player, Item* item, const std::string& text); + void executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer); + // + +private: + std::string getScriptEventName() const override; + + std::string eventName; + CreatureEventType_t type; + bool loaded; }; class CreatureEvents final : public BaseEvents { - public: - CreatureEvents(); +public: + CreatureEvents(); - // non-copyable - CreatureEvents(const CreatureEvents&) = delete; - CreatureEvents& operator=(const CreatureEvents&) = delete; + // non-copyable + CreatureEvents(const CreatureEvents&) = delete; + CreatureEvents& operator=(const CreatureEvents&) = delete; - // global events - bool playerLogin(Player* player) const; - bool playerLogout(Player* player) const; - bool playerAdvance(Player* player, skills_t, uint32_t, uint32_t); + // global events + bool playerLogin(Player* player) const; + bool playerLogout(Player* player) const; + bool playerAdvance(Player* player, skills_t, uint32_t, uint32_t); - CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true); + CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true); - bool registerLuaEvent(CreatureEvent* event); - void clear(bool fromLua) override final; + bool registerLuaEvent(CreatureEvent* event); + void clear(bool fromLua) override final; - void removeInvalidEvents(); + void removeInvalidEvents(); - private: - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; +private: + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - //creature events - using CreatureEventMap = std::map; - CreatureEventMap creatureEvents; + // creature events + using CreatureEventMap = std::map; + CreatureEventMap creatureEvents; - LuaScriptInterface scriptInterface; + LuaScriptInterface scriptInterface; }; -#endif +#endif // FS_CREATUREEVENT_H diff --git a/src/cylinder.cpp b/src/cylinder.cpp index 4032343bcd..f352513a88 100644 --- a/src/cylinder.cpp +++ b/src/cylinder.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -23,35 +7,20 @@ VirtualCylinder* VirtualCylinder::virtualCylinder = new VirtualCylinder; -int32_t Cylinder::getThingIndex(const Thing*) const -{ - return -1; -} +int32_t Cylinder::getThingIndex(const Thing*) const { return -1; } -size_t Cylinder::getFirstIndex() const -{ - return 0; -} +size_t Cylinder::getFirstIndex() const { return 0; } -size_t Cylinder::getLastIndex() const -{ - return 0; -} +size_t Cylinder::getLastIndex() const { return 0; } -uint32_t Cylinder::getItemTypeCount(uint16_t, int32_t) const -{ - return 0; -} +uint32_t Cylinder::getItemTypeCount(uint16_t, int32_t) const { return 0; } std::map& Cylinder::getAllItemTypeCount(std::map& countMap) const { return countMap; } -Thing* Cylinder::getThing(size_t) const -{ - return nullptr; -} +Thing* Cylinder::getThing(size_t) const { return nullptr; } void Cylinder::internalAddThing(Thing*) { diff --git a/src/cylinder.h b/src/cylinder.h index b473cefcf7..1a14252144 100644 --- a/src/cylinder.h +++ b/src/cylinder.h @@ -1,45 +1,31 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150 -#define FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_CYLINDER_H +#define FS_CYLINDER_H #include "enums.h" #include "thing.h" -class Item; class Creature; +class Item; static constexpr int32_t INDEX_WHEREEVER = -1; -enum cylinderflags_t { - FLAG_NOLIMIT = 1 << 0, //Bypass limits like capacity/container limits, blocking items/creatures etc. - FLAG_IGNOREBLOCKITEM = 1 << 1, //Bypass movable blocking item checks - FLAG_IGNOREBLOCKCREATURE = 1 << 2, //Bypass creature checks - FLAG_CHILDISOWNER = 1 << 3, //Used by containers to query capacity of the carrier (player) - FLAG_PATHFINDING = 1 << 4, //An additional check is done for floor changing/teleport items - FLAG_IGNOREFIELDDAMAGE = 1 << 5, //Bypass field damage checks - FLAG_IGNORENOTMOVEABLE = 1 << 6, //Bypass check for mobility - FLAG_IGNOREAUTOSTACK = 1 << 7, //queryDestination will not try to stack items together +enum cylinderflags_t +{ + FLAG_NOLIMIT = 1 << 0, // Bypass limits like capacity/container limits, blocking items/creatures etc. + FLAG_IGNOREBLOCKITEM = 1 << 1, // Bypass movable blocking item checks + FLAG_IGNOREBLOCKCREATURE = 1 << 2, // Bypass creature checks + FLAG_CHILDISOWNER = 1 << 3, // Used by containers to query capacity of the carrier (player) + FLAG_PATHFINDING = 1 << 4, // An additional check is done for floor changing/teleport items + FLAG_IGNOREFIELDDAMAGE = 1 << 5, // Bypass field damage checks + FLAG_IGNORENOTMOVEABLE = 1 << 6, // Bypass check for mobility + FLAG_IGNOREAUTOSTACK = 1 << 7, // queryDestination will not try to stack items together }; -enum cylinderlink_t { +enum cylinderlink_t +{ LINK_OWNER, LINK_PARENT, LINK_TOPPARENT, @@ -48,202 +34,195 @@ enum cylinderlink_t { class Cylinder : virtual public Thing { - public: - /** - * Query if the cylinder can add an object - * \param index points to the destination index (inventory slot/container position) - * -1 is a internal value and means add to a empty position, with no destItem - * \param thing the object to move/add - * \param count is the amount that we want to move/add - * \param flags if FLAG_CHILDISOWNER if set the query is from a child-cylinder (check cap etc.) - * if FLAG_NOLIMIT is set blocking items/container limits is ignored - * \param actor the creature trying to add the thing - * \returns ReturnValue holds the return value - */ - virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const = 0; - - /** - * Query the cylinder how much it can accept - * \param index points to the destination index (inventory slot/container position) - * -1 is a internal value and means add to a empty position, with no destItem - * \param thing the object to move/add - * \param count is the amount that we want to move/add - * \param maxQueryCount is the max amount that the cylinder can accept - * \param flags optional flags to modify the default behaviour - * \returns ReturnValue holds the return value - */ - virtual ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, - uint32_t flags) const = 0; - - /** - * Query if the cylinder can remove an object - * \param thing the object to move/remove - * \param count is the amount that we want to remove - * \param flags optional flags to modify the default behaviour - * \returns ReturnValue holds the return value - */ - virtual ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* = nullptr) const = 0; - - /** - * Query the destination cylinder - * \param index points to the destination index (inventory slot/container position), - * -1 is a internal value and means add to a empty position, with no destItem - * this method can change the index to point to the new cylinder index - * \param destItem is the destination object - * \param flags optional flags to modify the default behaviour - * this method can modify the flags - * \returns Cylinder returns the destination cylinder - */ - virtual Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) = 0; - - /** - * Add the object to the cylinder - * \param thing is the object to add - */ - virtual void addThing(Thing* thing) = 0; - - /** - * Add the object to the cylinder - * \param index points to the destination index (inventory slot/container position) - * \param thing is the object to add - */ - virtual void addThing(int32_t index, Thing* thing) = 0; - - /** - * Update the item count or type for an object - * \param thing is the object to update - * \param itemId is the new item id - * \param count is the new count value - */ - virtual void updateThing(Thing* thing, uint16_t itemId, uint32_t count) = 0; - - /** - * Replace an object with a new - * \param index is the position to change (inventory slot/container position) - * \param thing is the object to update - */ - virtual void replaceThing(uint32_t index, Thing* thing) = 0; - - /** - * Remove an object - * \param thing is the object to delete - * \param count is the new count value - */ - virtual void removeThing(Thing* thing, uint32_t count) = 0; - - /** - * Is sent after an operation (move/add) to update internal values - * \param thing is the object that has been added - * \param index is the objects new index value - * \param link holds the relation the object has to the cylinder - */ - virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; - - /** - * Is sent after an operation (move/remove) to update internal values - * \param thing is the object that has been removed - * \param index is the previous index of the removed object - * \param link holds the relation the object has to the cylinder - */ - virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; - - /** - * Gets the index of an object - * \param thing the object to get the index value from - * \returns the index of the object, returns -1 if not found - */ - virtual int32_t getThingIndex(const Thing* thing) const; - - /** - * Returns the first index - * \returns the first index, if not implemented 0 is returned - */ - virtual size_t getFirstIndex() const; - - /** - * Returns the last index - * \returns the last index, if not implemented 0 is returned - */ - virtual size_t getLastIndex() const; - - /** - * Gets the object based on index - * \returns the object, returns nullptr if not found - */ - virtual Thing* getThing(size_t index) const; - - /** - * Get the amount of items of a certain type - * \param itemId is the item type to the get the count of - * \param subType is the extra type an item can have such as charges/fluidtype, -1 means not used - * \returns the amount of items of the asked item type - */ - virtual uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; - - /** - * Get the amount of items of a all types - * \param countMap a map to put the itemID:count mapping in - * \returns a map mapping item id to count (same as first argument) - */ - virtual std::map& getAllItemTypeCount(std::map& countMap) const; - - /** - * Adds an object to the cylinder without sending to the client(s) - * \param thing is the object to add - */ - virtual void internalAddThing(Thing* thing); - - /** - * Adds an object to the cylinder without sending to the client(s) - * \param thing is the object to add - * \param index points to the destination index (inventory slot/container position) - */ - virtual void internalAddThing(uint32_t index, Thing* thing); - - virtual void startDecaying(); +public: + /** + * Query if the cylinder can add an object + * \param index points to the destination index (inventory slot/container + * position) -1 is a internal value and means add to a empty position, with + * no destItem \param thing the object to move/add \param count is the + * amount that we want to move/add \param flags if FLAG_CHILDISOWNER if set + * the query is from a child-cylinder (check cap etc.) if FLAG_NOLIMIT is + * set blocking items/container limits is ignored \param actor the creature + * trying to add the thing \returns ReturnValue holds the return value + */ + virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const = 0; + + /** + * Query the cylinder how much it can accept + * \param index points to the destination index (inventory slot/container + * position) -1 is a internal value and means add to a empty position, with + * no destItem \param thing the object to move/add \param count is the + * amount that we want to move/add \param maxQueryCount is the max amount + * that the cylinder can accept \param flags optional flags to modify the + * default behaviour \returns ReturnValue holds the return value + */ + virtual ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const = 0; + + /** + * Query if the cylinder can remove an object + * \param thing the object to move/remove + * \param count is the amount that we want to remove + * \param flags optional flags to modify the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* = nullptr) const = 0; + + /** + * Query the destination cylinder + * \param index points to the destination index (inventory slot/container + * position), -1 is a internal value and means add to a empty position, with + * no destItem this method can change the index to point to the new cylinder + * index \param destItem is the destination object \param flags optional + * flags to modify the default behaviour this method can modify the flags + * \returns Cylinder returns the destination cylinder + */ + virtual Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) = 0; + + /** + * Add the object to the cylinder + * \param thing is the object to add + */ + virtual void addThing(Thing* thing) = 0; + + /** + * Add the object to the cylinder + * \param index points to the destination index (inventory slot/container + * position) \param thing is the object to add + */ + virtual void addThing(int32_t index, Thing* thing) = 0; + + /** + * Update the item count or type for an object + * \param thing is the object to update + * \param itemId is the new item id + * \param count is the new count value + */ + virtual void updateThing(Thing* thing, uint16_t itemId, uint32_t count) = 0; + + /** + * Replace an object with a new + * \param index is the position to change (inventory slot/container + * position) \param thing is the object to update + */ + virtual void replaceThing(uint32_t index, Thing* thing) = 0; + + /** + * Remove an object + * \param thing is the object to delete + * \param count is the new count value + */ + virtual void removeThing(Thing* thing, uint32_t count) = 0; + + /** + * Is sent after an operation (move/add) to update internal values + * \param thing is the object that has been added + * \param index is the objects new index value + * \param link holds the relation the object has to the cylinder + */ + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Is sent after an operation (move/remove) to update internal values + * \param thing is the object that has been removed + * \param index is the previous index of the removed object + * \param link holds the relation the object has to the cylinder + */ + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Gets the index of an object + * \param thing the object to get the index value from + * \returns the index of the object, returns -1 if not found + */ + virtual int32_t getThingIndex(const Thing* thing) const; + + /** + * Returns the first index + * \returns the first index, if not implemented 0 is returned + */ + virtual size_t getFirstIndex() const; + + /** + * Returns the last index + * \returns the last index, if not implemented 0 is returned + */ + virtual size_t getLastIndex() const; + + /** + * Gets the object based on index + * \returns the object, returns nullptr if not found + */ + virtual Thing* getThing(size_t index) const; + + /** + * Get the amount of items of a certain type + * \param itemId is the item type to the get the count of + * \param subType is the extra type an item can have such as + * charges/fluidtype, -1 means not used \returns the amount of items of the + * asked item type + */ + virtual uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + + /** + * Get the amount of items of a all types + * \param countMap a map to put the itemID:count mapping in + * \returns a map mapping item id to count (same as first argument) + */ + virtual std::map& getAllItemTypeCount(std::map& countMap) const; + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + */ + virtual void internalAddThing(Thing* thing); + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + * \param index points to the destination index (inventory slot/container + * position) + */ + virtual void internalAddThing(uint32_t index, Thing* thing); + + virtual void startDecaying(); }; class VirtualCylinder final : public Cylinder { - public: - static VirtualCylinder* virtualCylinder; - - virtual ReturnValue queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override { - return RETURNVALUE_NOTPOSSIBLE; - } - virtual ReturnValue queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const override { - return RETURNVALUE_NOTPOSSIBLE; - } - virtual ReturnValue queryRemove(const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override { - return RETURNVALUE_NOTPOSSIBLE; - } - virtual Cylinder* queryDestination(int32_t&, const Thing&, Item**, uint32_t&) override { - return nullptr; - } - - virtual void addThing(Thing*) override {} - virtual void addThing(int32_t, Thing*) override {} - virtual void updateThing(Thing*, uint16_t, uint32_t) override {} - virtual void replaceThing(uint32_t, Thing*) override {} - virtual void removeThing(Thing*, uint32_t) override {} - - virtual void postAddNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} - virtual void postRemoveNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} - - bool isPushable() const override { - return false; - } - int32_t getThrowRange() const override { - return 1; - } - std::string getDescription(int32_t) const override { - return {}; - } - bool isRemoved() const override { - return false; - } +public: + static VirtualCylinder* virtualCylinder; + + virtual ReturnValue queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override + { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual ReturnValue queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const override + { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual ReturnValue queryRemove(const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override + { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual Cylinder* queryDestination(int32_t&, const Thing&, Item**, uint32_t&) override { return nullptr; } + + virtual void addThing(Thing*) override {} + virtual void addThing(int32_t, Thing*) override {} + virtual void updateThing(Thing*, uint16_t, uint32_t) override {} + virtual void replaceThing(uint32_t, Thing*) override {} + virtual void removeThing(Thing*, uint32_t) override {} + + virtual void postAddNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} + virtual void postRemoveNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} + + bool isPushable() const override { return false; } + int32_t getThrowRange() const override { return 1; } + std::string getDescription(int32_t) const override { return {}; } + bool isRemoved() const override { return false; } }; -#endif +#endif // FS_CYLINDER_H diff --git a/src/database.cpp b/src/database.cpp index 8e74801e72..10f9b688de 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,34 +1,19 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "configmanager.h" #include "database.h" +#include "configmanager.h" + #include extern ConfigManager g_config; Database::~Database() { - if (handle != nullptr) { + if (handle) { mysql_close(handle); } } @@ -47,7 +32,11 @@ bool Database::connect() mysql_options(handle, MYSQL_OPT_RECONNECT, &reconnect); // connects to database - if (!mysql_real_connect(handle, g_config.getString(ConfigManager::MYSQL_HOST).c_str(), g_config.getString(ConfigManager::MYSQL_USER).c_str(), g_config.getString(ConfigManager::MYSQL_PASS).c_str(), g_config.getString(ConfigManager::MYSQL_DB).c_str(), g_config.getNumber(ConfigManager::SQL_PORT), g_config.getString(ConfigManager::MYSQL_SOCK).c_str(), 0)) { + if (!mysql_real_connect( + handle, g_config.getString(ConfigManager::MYSQL_HOST).c_str(), + g_config.getString(ConfigManager::MYSQL_USER).c_str(), + g_config.getString(ConfigManager::MYSQL_PASS).c_str(), g_config.getString(ConfigManager::MYSQL_DB).c_str(), + g_config.getNumber(ConfigManager::SQL_PORT), g_config.getString(ConfigManager::MYSQL_SOCK).c_str(), 0)) { std::cout << std::endl << "MySQL Error Message: " << mysql_error(handle) << std::endl; return false; } @@ -101,9 +90,11 @@ bool Database::executeQuery(const std::string& query) databaseLock.lock(); while (mysql_real_query(handle, query.c_str(), query.length()) != 0) { - std::cout << "[Error - mysql_real_query] Query: " << query.substr(0, 256) << std::endl << "Message: " << mysql_error(handle) << std::endl; + std::cout << "[Error - mysql_real_query] Query: " << query.substr(0, 256) << std::endl + << "Message: " << mysql_error(handle) << std::endl; auto error = mysql_errno(handle); - if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && + error != 1053 /*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { success = false; break; } @@ -124,11 +115,13 @@ DBResult_ptr Database::storeQuery(const std::string& query) { databaseLock.lock(); - retry: +retry: while (mysql_real_query(handle, query.c_str(), query.length()) != 0) { - std::cout << "[Error - mysql_real_query] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl; + std::cout << "[Error - mysql_real_query] Query: " << query << std::endl + << "Message: " << mysql_error(handle) << std::endl; auto error = mysql_errno(handle); - if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && + error != 1053 /*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { break; } std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -137,10 +130,12 @@ DBResult_ptr Database::storeQuery(const std::string& query) // we should call that every time as someone would call executeQuery('SELECT...') // as it is described in MySQL manual: "it doesn't hurt" :P MYSQL_RES* res = mysql_store_result(handle); - if (res == nullptr) { - std::cout << "[Error - mysql_store_result] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl; + if (!res) { + std::cout << "[Error - mysql_store_result] Query: " << query << std::endl + << "Message: " << mysql_error(handle) << std::endl; auto error = mysql_errno(handle); - if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && + error != 1053 /*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { databaseLock.unlock(); return nullptr; } @@ -156,10 +151,7 @@ DBResult_ptr Database::storeQuery(const std::string& query) return result; } -std::string Database::escapeString(const std::string& s) const -{ - return escapeBlob(s.c_str(), s.length()); -} +std::string Database::escapeString(const std::string& s) const { return escapeBlob(s.c_str(), s.length()); } std::string Database::escapeBlob(const char* s, uint32_t length) const { @@ -196,10 +188,7 @@ DBResult::DBResult(MYSQL_RES* res) row = mysql_fetch_row(handle); } -DBResult::~DBResult() -{ - mysql_free_result(handle); -} +DBResult::~DBResult() { mysql_free_result(handle); } std::string DBResult::getString(const std::string& s) const { @@ -209,7 +198,7 @@ std::string DBResult::getString(const std::string& s) const return std::string(); } - if (row[it->second] == nullptr) { + if (!row[it->second]) { return std::string(); } @@ -225,7 +214,7 @@ const char* DBResult::getStream(const std::string& s, unsigned long& size) const return nullptr; } - if (row[it->second] == nullptr) { + if (!row[it->second]) { size = 0; return nullptr; } @@ -234,21 +223,15 @@ const char* DBResult::getStream(const std::string& s, unsigned long& size) const return row[it->second]; } -bool DBResult::hasNext() const -{ - return row != nullptr; -} +bool DBResult::hasNext() const { return row; } bool DBResult::next() { row = mysql_fetch_row(handle); - return row != nullptr; + return row; } -DBInsert::DBInsert(std::string query) : query(std::move(query)) -{ - this->length = this->query.length(); -} +DBInsert::DBInsert(std::string query) : query(std::move(query)) { this->length = this->query.length(); } bool DBInsert::addRow(const std::string& row) { diff --git a/src/database.h b/src/database.h index 3733576047..4c7654a6fe 100644 --- a/src/database.h +++ b/src/database.h @@ -1,184 +1,158 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693 -#define FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#include +#ifndef FS_DATABASE_H +#define FS_DATABASE_H -#include +#include "pugicast.h" class DBResult; using DBResult_ptr = std::shared_ptr; class Database { - public: - Database() = default; - ~Database(); - - // non-copyable - Database(const Database&) = delete; - Database& operator=(const Database&) = delete; - - /** - * Singleton implementation. - * - * @return database connection handler singleton - */ - static Database& getInstance() - { - static Database instance; - return instance; - } - - /** - * Connects to the database - * - * @return true on successful connection, false on error - */ - bool connect(); - - /** - * Executes command. - * - * Executes query which doesn't generates results (eg. INSERT, UPDATE, DELETE...). - * - * @param query command - * @return true on success, false on error - */ - bool executeQuery(const std::string& query); - - /** - * Queries database. - * - * Executes query which generates results (mostly SELECT). - * - * @return results object (nullptr on error) - */ - DBResult_ptr storeQuery(const std::string& query); - - /** - * Escapes string for query. - * - * Prepares string to fit SQL queries including quoting it. - * - * @param s string to be escaped - * @return quoted string - */ - std::string escapeString(const std::string& s) const; - - /** - * Escapes binary stream for query. - * - * Prepares binary stream to fit SQL queries. - * - * @param s binary stream - * @param length stream length - * @return quoted string - */ - std::string escapeBlob(const char* s, uint32_t length) const; - - /** - * Retrieve id of last inserted row - * - * @return id on success, 0 if last query did not result on any rows with auto_increment keys - */ - uint64_t getLastInsertId() const { - return static_cast(mysql_insert_id(handle)); - } - - /** - * Get database engine version - * - * @return the database engine version - */ - static const char* getClientVersion() { - return mysql_get_client_info(); - } - - uint64_t getMaxPacketSize() const { - return maxPacketSize; - } - - private: - /** - * Transaction related methods. - * - * Methods for starting, committing and rolling back transaction. Each of the returns boolean value. - * - * @return true on success, false on error - */ - bool beginTransaction(); - bool rollback(); - bool commit(); - - MYSQL* handle = nullptr; - std::recursive_mutex databaseLock; - uint64_t maxPacketSize = 1048576; +public: + Database() = default; + ~Database(); + + // non-copyable + Database(const Database&) = delete; + Database& operator=(const Database&) = delete; + + /** + * Singleton implementation. + * + * @return database connection handler singleton + */ + static Database& getInstance() + { + static Database instance; + return instance; + } + + /** + * Connects to the database + * + * @return true on successful connection, false on error + */ + bool connect(); + + /** + * Executes command. + * + * Executes query which doesn't generates results (eg. INSERT, UPDATE, + * DELETE...). + * + * @param query command + * @return true on success, false on error + */ + bool executeQuery(const std::string& query); + + /** + * Queries database. + * + * Executes query which generates results (mostly SELECT). + * + * @return results object (nullptr on error) + */ + DBResult_ptr storeQuery(const std::string& query); + + /** + * Escapes string for query. + * + * Prepares string to fit SQL queries including quoting it. + * + * @param s string to be escaped + * @return quoted string + */ + std::string escapeString(const std::string& s) const; + + /** + * Escapes binary stream for query. + * + * Prepares binary stream to fit SQL queries. + * + * @param s binary stream + * @param length stream length + * @return quoted string + */ + std::string escapeBlob(const char* s, uint32_t length) const; + + /** + * Retrieve id of last inserted row + * + * @return id on success, 0 if last query did not result on any rows with + * auto_increment keys + */ + uint64_t getLastInsertId() const { return static_cast(mysql_insert_id(handle)); } + + /** + * Get database engine version + * + * @return the database engine version + */ + static const char* getClientVersion() { return mysql_get_client_info(); } + + uint64_t getMaxPacketSize() const { return maxPacketSize; } + +private: + /** + * Transaction related methods. + * + * Methods for starting, committing and rolling back transaction. Each of + * the returns boolean value. + * + * @return true on success, false on error + */ + bool beginTransaction(); + bool rollback(); + bool commit(); + + MYSQL* handle = nullptr; + std::recursive_mutex databaseLock; + uint64_t maxPacketSize = 1048576; friend class DBTransaction; }; class DBResult { - public: - explicit DBResult(MYSQL_RES* res); - ~DBResult(); - - // non-copyable - DBResult(const DBResult&) = delete; - DBResult& operator=(const DBResult&) = delete; - - template - T getNumber(const std::string& s) const - { - auto it = listNames.find(s); - if (it == listNames.end()) { - std::cout << "[Error - DBResult::getNumber] Column '" << s << "' doesn't exist in the result set" << std::endl; - return static_cast(0); - } - - if (row[it->second] == nullptr) { - return static_cast(0); - } - - T data; - try { - data = boost::lexical_cast(row[it->second]); - } catch (boost::bad_lexical_cast&) { - data = 0; - } - return data; +public: + explicit DBResult(MYSQL_RES* res); + ~DBResult(); + + // non-copyable + DBResult(const DBResult&) = delete; + DBResult& operator=(const DBResult&) = delete; + + template + T getNumber(const std::string& s) const + { + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getNumber] Column '" << s << "' doesn't exist in the result set" + << std::endl; + return {}; } - std::string getString(const std::string& s) const; - const char* getStream(const std::string& s, unsigned long& size) const; + if (!row[it->second]) { + return {}; + } + + return pugi::cast(row[it->second]); + } + + std::string getString(const std::string& s) const; + const char* getStream(const std::string& s, unsigned long& size) const; - bool hasNext() const; - bool next(); + bool hasNext() const; + bool next(); - private: - MYSQL_RES* handle; - MYSQL_ROW row; +private: + MYSQL_RES* handle; + MYSQL_ROW row; - std::map listNames; + std::map listNames; friend class Database; }; @@ -188,55 +162,59 @@ class DBResult */ class DBInsert { - public: - explicit DBInsert(std::string query); - bool addRow(const std::string& row); - bool addRow(std::ostringstream& row); - bool execute(); - - private: - std::string query; - std::string values; - size_t length; +public: + explicit DBInsert(std::string query); + bool addRow(const std::string& row); + bool addRow(std::ostringstream& row); + bool execute(); + +private: + std::string query; + std::string values; + size_t length; }; class DBTransaction { - public: - constexpr DBTransaction() = default; +public: + constexpr DBTransaction() = default; - ~DBTransaction() { - if (state == STATE_START) { - Database::getInstance().rollback(); - } + ~DBTransaction() + { + if (state == STATE_START) { + Database::getInstance().rollback(); } - - // non-copyable - DBTransaction(const DBTransaction&) = delete; - DBTransaction& operator=(const DBTransaction&) = delete; - - bool begin() { - state = STATE_START; - return Database::getInstance().beginTransaction(); + } + + // non-copyable + DBTransaction(const DBTransaction&) = delete; + DBTransaction& operator=(const DBTransaction&) = delete; + + bool begin() + { + state = STATE_START; + return Database::getInstance().beginTransaction(); + } + + bool commit() + { + if (state != STATE_START) { + return false; } - bool commit() { - if (state != STATE_START) { - return false; - } - - state = STATE_COMMIT; - return Database::getInstance().commit(); - } + state = STATE_COMMIT; + return Database::getInstance().commit(); + } - private: - enum TransactionStates_t { - STATE_NO_START, - STATE_START, - STATE_COMMIT, - }; +private: + enum TransactionStates_t + { + STATE_NO_START, + STATE_START, + STATE_COMMIT, + }; - TransactionStates_t state = STATE_NO_START; + TransactionStates_t state = STATE_NO_START; }; -#endif +#endif // FS_DATABASE_H diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp index 6a5765242f..2875009d94 100644 --- a/src/databasemanager.cpp +++ b/src/databasemanager.cpp @@ -1,26 +1,11 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "configmanager.h" #include "databasemanager.h" + +#include "configmanager.h" #include "luascript.h" extern ConfigManager g_config; @@ -28,10 +13,10 @@ extern ConfigManager g_config; bool DatabaseManager::optimizeTables() { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = " << db.escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `DATA_FREE` > 0"; - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = {:s} AND `DATA_FREE` > 0", + db.escapeString(g_config.getString(ConfigManager::MYSQL_DB)))); if (!result) { return false; } @@ -40,10 +25,7 @@ bool DatabaseManager::optimizeTables() std::string tableName = result->getString("TABLE_NAME"); std::cout << "> Optimizing table " << tableName << "..." << std::flush; - query.str(std::string()); - query << "OPTIMIZE TABLE `" << tableName << '`'; - - if (db.executeQuery(query.str())) { + if (db.executeQuery(fmt::format("OPTIMIZE TABLE `{:s}`", tableName))) { std::cout << " [success]" << std::endl; } else { std::cout << " [failed]" << std::endl; @@ -55,25 +37,28 @@ bool DatabaseManager::optimizeTables() bool DatabaseManager::tableExists(const std::string& tableName) { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db.escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `TABLE_NAME` = " << db.escapeString(tableName) << " LIMIT 1"; - return db.storeQuery(query.str()).get() != nullptr; + return db + .storeQuery(fmt::format( + "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = {:s} AND `TABLE_NAME` = {:s} LIMIT 1", + db.escapeString(g_config.getString(ConfigManager::MYSQL_DB)), db.escapeString(tableName))) + .get(); } bool DatabaseManager::isDatabaseSetup() { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db.escapeString(g_config.getString(ConfigManager::MYSQL_DB)); - return db.storeQuery(query.str()).get() != nullptr; + return db + .storeQuery(fmt::format("SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = {:s}", + db.escapeString(g_config.getString(ConfigManager::MYSQL_DB)))) + .get(); } int32_t DatabaseManager::getDatabaseVersion() { if (!tableExists("server_config")) { Database& db = Database::getInstance(); - db.executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB"); + db.executeQuery( + "CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB"); db.executeQuery("INSERT INTO `server_config` VALUES ('db_version', 0)"); return 0; } @@ -95,23 +80,22 @@ void DatabaseManager::updateDatabase() luaL_openlibs(L); #ifndef LUAJIT_VERSION - //bit operations for Lua, based on bitlib project release 24 - //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift + // bit operations for Lua, based on bitlib project release 24 + // bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift luaL_register(L, "bit", LuaScriptInterface::luaBitReg); #endif - //db table + // db table luaL_register(L, "db", LuaScriptInterface::luaDatabaseTable); - //result table + // result table luaL_register(L, "result", LuaScriptInterface::luaResultTable); int32_t version = getDatabaseVersion(); do { - std::ostringstream ss; - ss << "data/migrations/" << version << ".lua"; - if (luaL_dofile(L, ss.str().c_str()) != 0) { - std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " << lua_tostring(L, -1) << std::endl; + if (luaL_dofile(L, fmt::format("data/migrations/{:d}.lua", version).c_str()) != 0) { + std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " + << lua_tostring(L, -1) << std::endl; break; } @@ -122,7 +106,8 @@ void DatabaseManager::updateDatabase() lua_getglobal(L, "onUpdateDatabase"); if (lua_pcall(L, 0, 1, 0) != 0) { LuaScriptInterface::resetScriptEnv(); - std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " << lua_tostring(L, -1) << std::endl; + std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " + << lua_tostring(L, -1) << std::endl; break; } @@ -143,10 +128,9 @@ void DatabaseManager::updateDatabase() bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `value` FROM `server_config` WHERE `config` = " << db.escapeString(config); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery( + fmt::format("SELECT `value` FROM `server_config` WHERE `config` = {:s}", db.escapeString(config))); if (!result) { return false; } @@ -158,15 +142,14 @@ bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& valu void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value) { Database& db = Database::getInstance(); - std::ostringstream query; int32_t tmp; if (!getDatabaseConfig(config, tmp)) { - query << "INSERT INTO `server_config` VALUES (" << db.escapeString(config) << ", '" << value << "')"; + db.executeQuery( + fmt::format("INSERT INTO `server_config` VALUES ({:s}, '{:d}')", db.escapeString(config), value)); } else { - query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db.escapeString(config); + db.executeQuery(fmt::format("UPDATE `server_config` SET `value` = '{:d}' WHERE `config` = {:s}", value, + db.escapeString(config))); } - - db.executeQuery(query.str()); } diff --git a/src/databasemanager.h b/src/databasemanager.h index 2e60c44739..28834c2d18 100644 --- a/src/databasemanager.h +++ b/src/databasemanager.h @@ -1,38 +1,22 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C -#define FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C -#include "database.h" +#ifndef FS_DATABASEMANAGER_H +#define FS_DATABASEMANAGER_H class DatabaseManager { - public: - static bool tableExists(const std::string& tableName); +public: + static bool tableExists(const std::string& tableName); - static int32_t getDatabaseVersion(); - static bool isDatabaseSetup(); + static int32_t getDatabaseVersion(); + static bool isDatabaseSetup(); - static bool optimizeTables(); - static void updateDatabase(); + static bool optimizeTables(); + static void updateDatabase(); - static bool getDatabaseConfig(const std::string& config, int32_t& value); - static void registerDatabaseConfig(const std::string& config, int32_t value); + static bool getDatabaseConfig(const std::string& config, int32_t& value); + static void registerDatabaseConfig(const std::string& config, int32_t value); }; -#endif + +#endif // FS_DATABASEMANAGER_H diff --git a/src/databasetasks.cpp b/src/databasetasks.cpp index 8e36f19117..3a6f83a39c 100644 --- a/src/databasetasks.cpp +++ b/src/databasetasks.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "databasetasks.h" + #include "tasks.h" extern Dispatcher g_dispatcher; @@ -50,7 +35,8 @@ void DatabaseTasks::threadMain() } } -void DatabaseTasks::addTask(std::string query, std::function callback/* = nullptr*/, bool store/* = false*/) +void DatabaseTasks::addTask(std::string query, std::function callback /* = nullptr*/, + bool store /* = false*/) { bool signal = false; taskLock.lock(); @@ -78,13 +64,13 @@ void DatabaseTasks::runTask(const DatabaseTask& task) } if (task.callback) { - g_dispatcher.addTask(createTask(std::bind(task.callback, result, success))); + g_dispatcher.addTask(createTask([=, callback = task.callback]() { callback(result, success); })); } } void DatabaseTasks::flush() { - std::unique_lock guard{ taskLock }; + std::unique_lock guard{taskLock}; while (!tasks.empty()) { auto task = std::move(tasks.front()); tasks.pop_front(); diff --git a/src/databasetasks.h b/src/databasetasks.h index 2d194427de..b9ee88ecc7 100644 --- a/src/databasetasks.h +++ b/src/databasetasks.h @@ -1,33 +1,17 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576 -#define FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576 - -#include -#include "thread_holder_base.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_DATABASETASKS_H +#define FS_DATABASETASKS_H + #include "database.h" -#include "enums.h" +#include "thread_holder_base.h" -struct DatabaseTask { +struct DatabaseTask +{ DatabaseTask(std::string&& query, std::function&& callback, bool store) : - query(std::move(query)), callback(std::move(callback)), store(store) {} + query(std::move(query)), callback(std::move(callback)), store(store) + {} std::string query; std::function callback; @@ -36,25 +20,26 @@ struct DatabaseTask { class DatabaseTasks : public ThreadHolder { - public: - DatabaseTasks() = default; - void start(); - void flush(); - void shutdown(); - - void addTask(std::string query, std::function callback = nullptr, bool store = false); - - void threadMain(); - private: - void runTask(const DatabaseTask& task); - - Database db; - std::thread thread; - std::list tasks; - std::mutex taskLock; - std::condition_variable taskSignal; +public: + DatabaseTasks() = default; + void start(); + void flush(); + void shutdown(); + + void addTask(std::string query, std::function callback = nullptr, bool store = false); + + void threadMain(); + +private: + void runTask(const DatabaseTask& task); + + Database db; + std::thread thread; + std::list tasks; + std::mutex taskLock; + std::condition_variable taskSignal; }; extern DatabaseTasks g_databaseTasks; -#endif +#endif // FS_DATABASETASKS_H diff --git a/src/definitions.h b/src/definitions.h index a431a80dc3..d43c51f8b6 100644 --- a/src/definitions.h +++ b/src/definitions.h @@ -1,32 +1,16 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 -#define FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 +#ifndef FS_DEFINITIONS_H +#define FS_DEFINITIONS_H static constexpr auto STATUS_SERVER_NAME = "The Forgotten Server"; -static constexpr auto STATUS_SERVER_VERSION = "1.3"; -static constexpr auto STATUS_SERVER_DEVELOPERS = "Mark Samman"; +static constexpr auto STATUS_SERVER_VERSION = "1.5"; +static constexpr auto STATUS_SERVER_DEVELOPERS = "The Forgotten Server Team"; -static constexpr auto CLIENT_VERSION_MIN = 1097; -static constexpr auto CLIENT_VERSION_MAX = 1098; -static constexpr auto CLIENT_VERSION_STR = "10.98"; +static constexpr auto CLIENT_VERSION_MIN = 1280; +static constexpr auto CLIENT_VERSION_MAX = 1286; +static constexpr auto CLIENT_VERSION_STR = "12.86"; static constexpr auto AUTHENTICATOR_DIGITS = 6U; static constexpr auto AUTHENTICATOR_PERIOD = 30U; @@ -43,8 +27,6 @@ static constexpr auto AUTHENTICATOR_PERIOD = 30U; #define _USE_MATH_DEFINES #endif -#include - #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -58,22 +40,19 @@ static constexpr auto AUTHENTICATOR_PERIOD = 30U; #define HAS_ITERATOR_DEBUGGING 0 #endif -#pragma warning(disable:4127) // conditional expression is constant -#pragma warning(disable:4244) // 'argument' : conversion from 'type1' to 'type2', possible loss of data -#pragma warning(disable:4250) // 'class1' : inherits 'class2::member' via dominance -#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data -#pragma warning(disable:4319) // '~': zero extending 'unsigned int' to 'lua_Number' of greater size -#pragma warning(disable:4351) // new behavior: elements of array will be default initialized -#pragma warning(disable:4458) // declaration hides class member +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4244) // 'argument' : conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4250) // 'class1' : inherits 'class2::member' via dominance +#pragma warning(disable : 4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data +#pragma warning(disable : 4319) // '~': zero extending 'unsigned int' to 'lua_Number' of greater size +#pragma warning(disable : 4351) // new behavior: elements of array will be default initialized +#pragma warning(disable : 4458) // declaration hides class member #endif -#define strcasecmp _stricmp -#define strncasecmp _strnicmp - #ifndef _WIN32_WINNT // 0x0602: Windows 7 #define _WIN32_WINNT 0x0602 #endif #endif -#endif +#endif // FS_DEFINITIONS_H diff --git a/src/depotchest.cpp b/src/depotchest.cpp index 8d28e2222e..fa3db41ece 100644 --- a/src/depotchest.cpp +++ b/src/depotchest.cpp @@ -1,35 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "depotchest.h" + #include "tools.h" -DepotChest::DepotChest(uint16_t type) : - Container(type), maxDepotItems(1500) {} +DepotChest::DepotChest(uint16_t type, bool paginated /*= true*/) : + Container{type, items[type].maxItems, true, paginated} +{} -ReturnValue DepotChest::queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor/* = nullptr*/) const +ReturnValue DepotChest::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor /* = nullptr*/) const { const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -60,7 +46,7 @@ ReturnValue DepotChest::queryAdd(int32_t index, const Thing& thing, uint32_t cou void DepotChest::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) { Cylinder* parent = getParent(); - if (parent != nullptr) { + if (parent) { parent->postAddNotification(thing, oldParent, index, LINK_PARENT); } } @@ -68,7 +54,7 @@ void DepotChest::postAddNotification(Thing* thing, const Cylinder* oldParent, in void DepotChest::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) { Cylinder* parent = getParent(); - if (parent != nullptr) { + if (parent) { parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); } } diff --git a/src/depotchest.h b/src/depotchest.h index b1db4ead8b..6ab5737b3f 100644 --- a/src/depotchest.h +++ b/src/depotchest.h @@ -1,57 +1,36 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_DEPOTCHEST_H_6538526014684E3DBC92CC12815B6766 -#define FS_DEPOTCHEST_H_6538526014684E3DBC92CC12815B6766 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_DEPOTCHEST_H +#define FS_DEPOTCHEST_H #include "container.h" class DepotChest final : public Container { - public: - explicit DepotChest(uint16_t type); +public: + explicit DepotChest(uint16_t type, bool paginated = true); - //serialization - void setMaxDepotItems(uint32_t maxitems) { - maxDepotItems = maxitems; - } + // serialization + void setMaxDepotItems(uint32_t maxitems) { maxDepotItems = maxitems; } - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; - //overrides - bool canRemove() const override { - return false; - } + // overrides + bool canRemove() const override { return false; } - Cylinder* getParent() const override; - Cylinder* getRealParent() const override { - return parent; - } + Cylinder* getParent() const override; + Cylinder* getRealParent() const override { return parent; } - private: - uint32_t maxDepotItems; +private: + uint32_t maxDepotItems = 0; }; -#endif - +#endif // FS_DEPOTCHEST_H diff --git a/src/depotlocker.cpp b/src/depotlocker.cpp index 6cce8567ac..5784cc04ee 100644 --- a/src/depotlocker.cpp +++ b/src/depotlocker.cpp @@ -1,28 +1,13 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "depotlocker.h" -DepotLocker::DepotLocker(uint16_t type) : - Container(type, 3), depotId(0) {} +#include "inbox.h" + +DepotLocker::DepotLocker(uint16_t type) : Container(type), depotId(0) {} Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) { @@ -42,14 +27,14 @@ ReturnValue DepotLocker::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Cre void DepotLocker::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) { - if (parent != nullptr) { + if (parent) { parent->postAddNotification(thing, oldParent, index, LINK_PARENT); } } void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) { - if (parent != nullptr) { + if (parent) { parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); } } diff --git a/src/depotlocker.h b/src/depotlocker.h index 750ec42ec8..7bc46a64dc 100644 --- a/src/depotlocker.h +++ b/src/depotlocker.h @@ -1,66 +1,44 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 -#define FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 +#ifndef FS_DEPOTLOCKER_H +#define FS_DEPOTLOCKER_H #include "container.h" -#include "inbox.h" + +class Inbox; + +using DepotLocker_ptr = std::shared_ptr; class DepotLocker final : public Container { - public: - explicit DepotLocker(uint16_t type); +public: + explicit DepotLocker(uint16_t type); - DepotLocker* getDepotLocker() override { - return this; - } - const DepotLocker* getDepotLocker() const override { - return this; - } + DepotLocker* getDepotLocker() override { return this; } + const DepotLocker* getDepotLocker() const override { return this; } - void removeInbox(Inbox* inbox); + void removeInbox(Inbox* inbox); - //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + // serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - uint16_t getDepotId() const { - return depotId; - } - void setDepotId(uint16_t depotId) { - this->depotId = depotId; - } + uint16_t getDepotId() const { return depotId; } + void setDepotId(uint16_t depotId) { this->depotId = depotId; } - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; - bool canRemove() const override { - return false; - } + bool canRemove() const override { return false; } - private: - uint16_t depotId; +private: + uint16_t depotId; }; -#endif - +#endif // FS_DEPOTLOCKER_H diff --git a/src/enums.h b/src/enums.h index a6664f61a2..5e37715002 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,32 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE -#define FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE - -enum RuleViolationType_t : uint8_t { +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_ENUMS_H +#define FS_ENUMS_H + +enum RuleViolationType_t : uint8_t +{ REPORT_TYPE_NAME = 0, REPORT_TYPE_STATEMENT = 1, REPORT_TYPE_BOT = 2 }; -enum RuleViolationReasons_t : uint8_t { +enum RuleViolationReasons_t : uint8_t +{ REPORT_REASON_NAMEINAPPROPRIATE = 0, REPORT_REASON_NAMEPOORFORMATTED = 1, REPORT_REASON_NAMEADVERTISING = 2, @@ -50,20 +36,23 @@ enum RuleViolationReasons_t : uint8_t { REPORT_REASON_SERVICEAGREEMENT = 20 }; -enum BugReportType_t : uint8_t { +enum BugReportType_t : uint8_t +{ BUG_CATEGORY_MAP = 0, BUG_CATEGORY_TYPO = 1, BUG_CATEGORY_TECHNICAL = 2, BUG_CATEGORY_OTHER = 3 }; -enum ThreadState { +enum ThreadState +{ THREAD_STATE_RUNNING, THREAD_STATE_CLOSING, THREAD_STATE_TERMINATED, }; -enum itemAttrTypes : uint32_t { +enum itemAttrTypes : uint32_t +{ ITEM_ATTRIBUTE_NONE, ITEM_ATTRIBUTE_ACTIONID = 1 << 0, @@ -92,27 +81,35 @@ enum itemAttrTypes : uint32_t { ITEM_ATTRIBUTE_DECAYTO = 1 << 23, ITEM_ATTRIBUTE_WRAPID = 1 << 24, ITEM_ATTRIBUTE_STOREITEM = 1 << 25, + ITEM_ATTRIBUTE_ATTACK_SPEED = 1 << 26, + ITEM_ATTRIBUTE_OPENCONTAINER = 1 << 27, ITEM_ATTRIBUTE_CUSTOM = 1U << 31 }; -enum VipStatus_t : uint8_t { +enum VipStatus_t : uint8_t +{ VIPSTATUS_OFFLINE = 0, VIPSTATUS_ONLINE = 1, - VIPSTATUS_PENDING = 2 + VIPSTATUS_PENDING = 2, + VIPSTATUS_TRAINING = 3 }; -enum MarketAction_t { +enum MarketAction_t +{ MARKETACTION_BUY = 0, MARKETACTION_SELL = 1, }; -enum MarketRequest_t { - MARKETREQUEST_OWN_OFFERS = 0xFFFE, - MARKETREQUEST_OWN_HISTORY = 0xFFFF, +enum MarketRequest_t +{ + MARKETREQUEST_OWN_HISTORY = 1, + MARKETREQUEST_OWN_OFFERS = 2, + MARKETREQUEST_ITEM = 3, }; -enum MarketOfferState_t { +enum MarketOfferState_t +{ OFFERSTATE_ACTIVE = 0, OFFERSTATE_CANCELLED = 1, OFFERSTATE_EXPIRED = 2, @@ -121,65 +118,84 @@ enum MarketOfferState_t { OFFERSTATE_ACCEPTEDEX = 255, }; -enum ChannelEvent_t : uint8_t { +enum ChannelEvent_t : uint8_t +{ CHANNELEVENT_JOIN = 0, CHANNELEVENT_LEAVE = 1, CHANNELEVENT_INVITE = 2, CHANNELEVENT_EXCLUDE = 3, }; -enum CreatureType_t : uint8_t { +enum CreatureType_t : uint8_t +{ CREATURETYPE_PLAYER = 0, CREATURETYPE_MONSTER = 1, CREATURETYPE_NPC = 2, CREATURETYPE_SUMMON_OWN = 3, CREATURETYPE_SUMMON_OTHERS = 4, + CREATURETYPE_HIDDEN = 5, }; -enum OperatingSystem_t : uint8_t { +enum OperatingSystem_t : uint8_t +{ CLIENTOS_NONE = 0, CLIENTOS_LINUX = 1, CLIENTOS_WINDOWS = 2, CLIENTOS_FLASH = 3, + CLIENTOS_QT_LINUX = 4, + CLIENTOS_QT_WINDOWS = 5, + CLIENTOS_QT_MAC = 6, + CLIENTOS_QT_LINUX2 = 7, CLIENTOS_OTCLIENT_LINUX = 10, CLIENTOS_OTCLIENT_WINDOWS = 11, CLIENTOS_OTCLIENT_MAC = 12, }; -enum SpellGroup_t : uint8_t { +enum SpellGroup_t : uint8_t +{ SPELLGROUP_NONE = 0, SPELLGROUP_ATTACK = 1, SPELLGROUP_HEALING = 2, SPELLGROUP_SUPPORT = 3, SPELLGROUP_SPECIAL = 4, + // SPELLGROUP_CONJURE = 5, + SPELLGROUP_CRIPPLING = 6, + SPELLGROUP_FOCUS = 7, + SPELLGROUP_ULTIMATESTRIKES = 8, }; -enum SpellType_t : uint8_t { +enum SpellType_t : uint8_t +{ SPELL_UNDEFINED = 0, SPELL_INSTANT = 1, SPELL_RUNE = 2, }; -enum AccountType_t : uint8_t { +enum AccountType_t : uint8_t +{ ACCOUNT_TYPE_NORMAL = 1, ACCOUNT_TYPE_TUTOR = 2, ACCOUNT_TYPE_SENIORTUTOR = 3, ACCOUNT_TYPE_GAMEMASTER = 4, - ACCOUNT_TYPE_GOD = 5 + ACCOUNT_TYPE_COMMUNITYMANAGER = 5, + ACCOUNT_TYPE_GOD = 6 }; -enum RaceType_t : uint8_t { +enum RaceType_t : uint8_t +{ RACE_NONE, RACE_VENOM, RACE_BLOOD, RACE_UNDEAD, RACE_FIRE, RACE_ENERGY, + RACE_INK, }; -enum CombatType_t : uint16_t { +enum CombatType_t : uint16_t +{ COMBAT_NONE = 0, COMBAT_PHYSICALDAMAGE = 1 << 0, @@ -198,7 +214,8 @@ enum CombatType_t : uint16_t { COMBAT_COUNT = 12 }; -enum CombatParam_t { +enum CombatParam_t +{ COMBAT_PARAM_TYPE, COMBAT_PARAM_EFFECT, COMBAT_PARAM_DISTANCEEFFECT, @@ -211,17 +228,19 @@ enum CombatParam_t { COMBAT_PARAM_USECHARGES, }; -enum CallBackParam_t { +enum CallBackParam_t +{ CALLBACK_PARAM_LEVELMAGICVALUE, CALLBACK_PARAM_SKILLVALUE, CALLBACK_PARAM_TARGETTILE, CALLBACK_PARAM_TARGETCREATURE, }; -enum ConditionParam_t { +enum ConditionParam_t +{ CONDITION_PARAM_OWNER = 1, CONDITION_PARAM_TICKS = 2, - //CONDITION_PARAM_OUTFIT = 3, + // CONDITION_PARAM_OUTFIT = 3, CONDITION_PARAM_HEALTHGAIN = 4, CONDITION_PARAM_HEALTHTICKS = 5, CONDITION_PARAM_MANAGAIN = 6, @@ -273,16 +292,19 @@ enum ConditionParam_t { CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE = 52, CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT = 53, CONDITION_PARAM_AGGRESSIVE = 54, + CONDITION_PARAM_DRUNKENNESS = 55, }; -enum BlockType_t : uint8_t { +enum BlockType_t : uint8_t +{ BLOCK_NONE, BLOCK_DEFENSE, BLOCK_ARMOR, BLOCK_IMMUNITY }; -enum skills_t : uint8_t { +enum skills_t : uint8_t +{ SKILL_FIST = 0, SKILL_CLUB = 1, SKILL_SWORD = 2, @@ -298,7 +320,8 @@ enum skills_t : uint8_t { SKILL_LAST = SKILL_FISHING }; -enum stats_t { +enum stats_t +{ STAT_MAXHITPOINTS, STAT_MAXMANAPOINTS, STAT_SOULPOINTS, // unused @@ -308,7 +331,8 @@ enum stats_t { STAT_LAST = STAT_MAGICPOINTS }; -enum SpecialSkills_t { +enum SpecialSkills_t +{ SPECIALSKILL_CRITICALHITCHANCE, SPECIALSKILL_CRITICALHITAMOUNT, SPECIALSKILL_LIFELEECHCHANCE, @@ -320,14 +344,16 @@ enum SpecialSkills_t { SPECIALSKILL_LAST = SPECIALSKILL_MANALEECHAMOUNT }; -enum formulaType_t { +enum formulaType_t +{ COMBAT_FORMULA_UNDEFINED, COMBAT_FORMULA_LEVELMAGIC, COMBAT_FORMULA_SKILL, COMBAT_FORMULA_DAMAGE, }; -enum ConditionType_t { +enum ConditionType_t +{ CONDITION_NONE, CONDITION_POISON = 1 << 0, @@ -354,13 +380,15 @@ enum ConditionType_t { CONDITION_DAZZLED = 1 << 21, CONDITION_CURSED = 1 << 22, CONDITION_EXHAUST_COMBAT = 1 << 23, // unused - CONDITION_EXHAUST_HEAL = 1 << 24, // unused + CONDITION_EXHAUST_HEAL = 1 << 24, // unused CONDITION_PACIFIED = 1 << 25, CONDITION_SPELLCOOLDOWN = 1 << 26, CONDITION_SPELLGROUPCOOLDOWN = 1 << 27, + CONDITION_ROOT = 1 << 28, }; -enum ConditionId_t : int8_t { +enum ConditionId_t : int8_t +{ CONDITIONID_DEFAULT = -1, CONDITIONID_COMBAT, CONDITIONID_HEAD, @@ -375,18 +403,21 @@ enum ConditionId_t : int8_t { CONDITIONID_AMMO, }; -enum PlayerSex_t : uint8_t { +enum PlayerSex_t : uint8_t +{ PLAYERSEX_FEMALE = 0, PLAYERSEX_MALE = 1, PLAYERSEX_LAST = PLAYERSEX_MALE }; -enum Vocation_t : uint16_t { +enum Vocation_t : uint16_t +{ VOCATION_NONE = 0 }; -enum ReturnValue { +enum ReturnValue +{ RETURNVALUE_NOERROR, RETURNVALUE_NOTPOSSIBLE, RETURNVALUE_NOTENOUGHROOM, @@ -461,6 +492,7 @@ enum ReturnValue { RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION, RETURNVALUE_CANNOTMOVEITEMISNOTSTOREITEM, RETURNVALUE_ITEMCANNOTBEMOVEDTHERE, + RETURNVALUE_YOUCANNOTUSETHISBED, }; enum SpeechBubble_t @@ -469,7 +501,12 @@ enum SpeechBubble_t SPEECHBUBBLE_NORMAL = 1, SPEECHBUBBLE_TRADE = 2, SPEECHBUBBLE_QUEST = 3, - SPEECHBUBBLE_QUESTTRADER = 4, + SPEECHBUBBLE_COMPASS = 4, + SPEECHBUBBLE_NORMAL2 = 5, + SPEECHBUBBLE_NORMAL3 = 6, + SPEECHBUBBLE_HIRELING = 7, + + SPEECHBUBBLE_LAST = SPEECHBUBBLE_HIRELING }; enum MapMark_t @@ -496,44 +533,48 @@ enum MapMark_t MAPMARK_GREENSOUTH = 19, }; -struct Outfit_t { +struct Outfit_t +{ uint16_t lookType = 0; uint16_t lookTypeEx = 0; - uint16_t lookMount = 0; uint8_t lookHead = 0; uint8_t lookBody = 0; uint8_t lookLegs = 0; uint8_t lookFeet = 0; uint8_t lookAddons = 0; + uint16_t lookMount = 0; + uint8_t lookMountHead = 0; + uint8_t lookMountBody = 0; + uint8_t lookMountLegs = 0; + uint8_t lookMountFeet = 0; }; -struct LightInfo { +struct LightInfo +{ uint8_t level = 0; uint8_t color = 0; constexpr LightInfo() = default; constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {} }; -struct ShopInfo { - uint16_t itemId; - int32_t subType; - uint32_t buyPrice; - uint32_t sellPrice; - std::string realName; - - ShopInfo() { - itemId = 0; - subType = 1; - buyPrice = 0; - sellPrice = 0; - } +struct ShopInfo +{ + uint16_t itemId = 0; + int32_t subType = 1; + int64_t buyPrice = 0; + int64_t sellPrice = 0; + std::string realName = ""; - ShopInfo(uint16_t itemId, int32_t subType = 0, uint32_t buyPrice = 0, uint32_t sellPrice = 0, std::string realName = "") - : itemId(itemId), subType(subType), buyPrice(buyPrice), sellPrice(sellPrice), realName(std::move(realName)) {} + ShopInfo() = default; + ShopInfo(uint16_t itemId, int32_t subType = 0, int64_t buyPrice = 0, int64_t sellPrice = 0, + std::string realName = "") : + itemId(itemId), subType(subType), buyPrice(buyPrice), sellPrice(sellPrice), realName(std::move(realName)) + {} }; -struct MarketOffer { - uint32_t price; +struct MarketOffer +{ + uint64_t price; uint32_t timestamp; uint16_t amount; uint16_t counter; @@ -541,17 +582,25 @@ struct MarketOffer { std::string playerName; }; -struct MarketOfferEx { +struct MarketOfferEx +{ MarketOfferEx() = default; MarketOfferEx(MarketOfferEx&& other) : - id(other.id), playerId(other.playerId), timestamp(other.timestamp), price(other.price), - amount(other.amount), counter(other.counter), itemId(other.itemId), type(other.type), - playerName(std::move(other.playerName)) {} + id(other.id), + playerId(other.playerId), + timestamp(other.timestamp), + price(other.price), + amount(other.amount), + counter(other.counter), + itemId(other.itemId), + type(other.type), + playerName(std::move(other.playerName)) + {} uint32_t id; uint32_t playerId; uint32_t timestamp; - uint32_t price; + uint64_t price; uint16_t amount; uint16_t counter; uint16_t itemId; @@ -559,26 +608,21 @@ struct MarketOfferEx { std::string playerName; }; -struct HistoryMarketOffer { +struct HistoryMarketOffer +{ uint32_t timestamp; - uint32_t price; + uint64_t price; uint16_t itemId; uint16_t amount; MarketOfferState_t state; }; -struct MarketStatistics { - MarketStatistics() { - numTransactions = 0; - highestPrice = 0; - totalPrice = 0; - lowestPrice = 0; - } - - uint32_t numTransactions; - uint32_t highestPrice; - uint64_t totalPrice; - uint32_t lowestPrice; +struct MarketStatistics +{ + uint32_t numTransactions = 0; + uint32_t highestPrice = 0; + uint64_t totalPrice = 0; + uint32_t lowestPrice = 0; }; struct ModalWindow @@ -586,11 +630,12 @@ struct ModalWindow std::list> buttons, choices; std::string title, message; uint32_t id; - uint8_t defaultEnterButton, defaultEscapeButton; - bool priority; + uint8_t defaultEnterButton = 0xFF, defaultEscapeButton = 0xFF; + bool priority = false; - ModalWindow(uint32_t id, std::string title, std::string message) - : title(std::move(title)), message(std::move(message)), id(id), defaultEnterButton(0xFF), defaultEscapeButton(0xFF), priority(false) {} + ModalWindow(uint32_t id, std::string title, std::string message) : + title(std::move(title)), message(std::move(message)), id(id) + {} }; enum CombatOrigin @@ -600,35 +645,30 @@ enum CombatOrigin ORIGIN_SPELL, ORIGIN_MELEE, ORIGIN_RANGED, + ORIGIN_WAND, + ORIGIN_REFLECT, }; struct CombatDamage { - struct { - CombatType_t type; - int32_t value; - } primary, secondary; - - CombatOrigin origin; - BlockType_t blockType; - bool critical; - bool leeched; - CombatDamage() + struct { - origin = ORIGIN_NONE; - blockType = BLOCK_NONE; - primary.type = secondary.type = COMBAT_NONE; - primary.value = secondary.value = 0; - critical = false; - leeched = false; - } + CombatType_t type = COMBAT_NONE; + int32_t value = 0; + } primary = {}, secondary = {}; + + CombatOrigin origin = ORIGIN_NONE; + BlockType_t blockType = BLOCK_NONE; + bool critical = false; + bool leeched = false; }; using MarketOfferList = std::list; using HistoryMarketOfferList = std::list; using ShopInfoList = std::list; -enum MonstersEvent_t : uint8_t { +enum MonstersEvent_t : uint8_t +{ MONSTERS_EVENT_NONE = 0, MONSTERS_EVENT_THINK = 1, MONSTERS_EVENT_APPEAR = 2, @@ -637,4 +677,20 @@ enum MonstersEvent_t : uint8_t { MONSTERS_EVENT_SAY = 5, }; -#endif +struct Reflect +{ + Reflect() = default; + Reflect(uint16_t percent, uint16_t chance) : percent(percent), chance(chance){}; + + Reflect& operator+=(const Reflect& other) + { + percent += other.percent; + chance = std::min(100, chance + other.chance); + return *this; + } + + uint16_t percent = 0; + uint16_t chance = 0; +}; + +#endif // FS_ENUMS_H diff --git a/src/events.cpp b/src/events.cpp index e5490d1ad8..73eed12880 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -1,36 +1,14 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "events.h" -#include "tools.h" + #include "item.h" #include "player.h" -#include - -Events::Events() : - scriptInterface("Event Interface") -{ - scriptInterface.initState(); -} +Events::Events() : scriptInterface("Event Interface") { scriptInterface.initState(); } bool Events::load() { @@ -52,7 +30,7 @@ bool Events::load() const std::string& className = eventNode.attribute("class").as_string(); auto res = classes.insert(className); if (res.second) { - const std::string& lowercase = asLowerCaseString(className); + const std::string& lowercase = boost::algorithm::to_lower_copy(className); if (scriptInterface.loadFile("data/events/scripts/" + lowercase + ".lua") != 0) { std::cout << "[Warning - Events::load] Can not load script: " << lowercase << ".lua" << std::endl; std::cout << scriptInterface.getLastLuaError() << std::endl; @@ -96,12 +74,18 @@ bool Events::load() info.playerOnLookInTrade = event; } else if (methodName == "onLookInShop") { info.playerOnLookInShop = event; + } else if (methodName == "onLookInMarket") { + info.playerOnLookInMarket = event; } else if (methodName == "onTradeRequest") { info.playerOnTradeRequest = event; } else if (methodName == "onTradeAccept") { info.playerOnTradeAccept = event; } else if (methodName == "onTradeCompleted") { info.playerOnTradeCompleted = event; + } else if (methodName == "onPodiumRequest") { + info.playerOnPodiumRequest = event; + } else if (methodName == "onPodiumEdit") { + info.playerOnPodiumEdit = event; } else if (methodName == "onMoveItem") { info.playerOnMoveItem = event; } else if (methodName == "onItemMoved") { @@ -122,6 +106,8 @@ bool Events::load() info.playerOnGainSkillTries = event; } else if (methodName == "onWrapItem") { info.playerOnWrapItem = event; + } else if (methodName == "onInventoryUpdate") { + info.playerOnInventoryUpdate = event; } else { std::cout << "[Warning - Events::load] Unknown player method: " << methodName << std::endl; } @@ -448,7 +434,8 @@ bool Events::eventPlayerOnBrowseField(Player* player, const Position& position) return scriptInterface.callFunction(2); } -void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance) +void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, + int32_t lookDistance) { // Player:onLook(thing, position, distance) or Player.onLook(self, thing, position, distance) if (info.playerOnLook == -1) { @@ -487,7 +474,8 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance) { - // Player:onLookInBattleList(creature, position, distance) or Player.onLookInBattleList(self, creature, position, distance) + // Player:onLookInBattleList(creature, position, distance) or Player.onLookInBattleList(self, creature, position, + // distance) if (info.playerOnLookInBattleList == -1) { return; } @@ -546,9 +534,9 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite scriptInterface.callVoidFunction(4); } -bool Events::eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count, const std::string& description) +bool Events::eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count) { - // Player:onLookInShop(itemType, count, description) or Player.onLookInShop(self, itemType, count, description) + // Player:onLookInShop(itemType, count) or Player.onLookInShop(self, itemType, count) if (info.playerOnLookInShop == -1) { return true; } @@ -571,23 +559,51 @@ bool Events::eventPlayerOnLookInShop(Player* player, const ItemType* itemType, u LuaScriptInterface::setMetatable(L, -1, "ItemType"); lua_pushnumber(L, count); - lua_pushstring(L, description.c_str()); - return scriptInterface.callFunction(4); + return scriptInterface.callFunction(3); } -bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) +bool Events::eventPlayerOnLookInMarket(Player* player, const ItemType* itemType) { - // Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if (info.playerOnMoveItem == -1) { + // Player:onLookInMarket(itemType) or Player.onLookInMarket(self, itemType) + if (info.playerOnLookInMarket == -1) { return true; } if (!scriptInterface.reserveScriptEnv()) { - std::cout << "[Error - Events::eventPlayerOnMoveItem] Call stack overflow" << std::endl; + std::cout << "[Error - Events::eventPlayerOnLookInMarket] Call stack overflow" << std::endl; return false; } + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnLookInMarket, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnLookInMarket); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, itemType); + LuaScriptInterface::setMetatable(L, -1, "ItemType"); + + return scriptInterface.callFunction(2); +} + +ReturnValue Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, + const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) +{ + // Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, + // toPosition, fromCylinder, toCylinder) + if (info.playerOnMoveItem == -1) { + return RETURNVALUE_NOERROR; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnMoveItem] Call stack overflow" << std::endl; + return RETURNVALUE_NOTPOSSIBLE; + } + ScriptEnvironment* env = scriptInterface.getScriptEnv(); env->setScriptId(info.playerOnMoveItem, &scriptInterface); @@ -607,12 +623,24 @@ bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, c LuaScriptInterface::pushCylinder(L, fromCylinder); LuaScriptInterface::pushCylinder(L, toCylinder); - return scriptInterface.callFunction(7); + ReturnValue returnValue; + if (scriptInterface.protectedCall(L, 7, 1) != 0) { + returnValue = RETURNVALUE_NOTPOSSIBLE; + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else { + returnValue = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 1); + } + + scriptInterface.resetScriptEnv(); + return returnValue; } -void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) +void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, + const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) { - // Player:onItemMoved(item, count, fromPosition, toPosition) or Player.onItemMoved(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) + // Player:onItemMoved(item, count, fromPosition, toPosition) or Player.onItemMoved(self, item, count, fromPosition, + // toPosition, fromCylinder, toCylinder) if (info.playerOnItemMoved == -1) { return; } @@ -644,9 +672,11 @@ void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, scriptInterface.callVoidFunction(7); } -bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition) +bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, + const Position& toPosition) { - // Player:onMoveCreature(creature, fromPosition, toPosition) or Player.onMoveCreature(self, creature, fromPosition, toPosition) + // Player:onMoveCreature(creature, fromPosition, toPosition) or Player.onMoveCreature(self, creature, fromPosition, + // toPosition) if (info.playerOnMoveCreature == -1) { return true; } @@ -674,7 +704,9 @@ bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const return scriptInterface.callFunction(4); } -void Events::eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation) +void Events::eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, + uint8_t reportReason, const std::string& comment, + const std::string& translation) { // Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) if (info.playerOnReportRuleViolation == -1) { @@ -706,7 +738,8 @@ void Events::eventPlayerOnReportRuleViolation(Player* player, const std::string& scriptInterface.callVoidFunction(6); } -bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category) +bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, + uint8_t category) { // Player:onReportBug(message, position, category) if (info.playerOnReportBug == -1) { @@ -858,10 +891,70 @@ void Events::eventPlayerOnTradeCompleted(Player* player, Player* target, Item* i return scriptInterface.callVoidFunction(5); } +void Events::eventPlayerOnPodiumRequest(Player* player, Item* item) +{ + // Player:onPodiumRequest(item) or Player.onPodiumRequest(self, item) + if (info.playerOnPodiumRequest == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnPodiumRequest] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnPodiumRequest, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnPodiumRequest); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + scriptInterface.callFunction(2); +} + +void Events::eventPlayerOnPodiumEdit(Player* player, Item* item, const Outfit_t& outfit, bool podiumVisible, + Direction direction) +{ + // Player:onPodiumEdit(item, outfit, direction, isVisible) or Player.onPodiumEdit(self, item, outfit, direction, + // isVisible) + if (info.playerOnPodiumEdit == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnPodiumEdit] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnPodiumEdit, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnPodiumEdit); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + LuaScriptInterface::pushOutfit(L, outfit); + + lua_pushnumber(L, direction); + lua_pushboolean(L, podiumVisible); + + scriptInterface.callFunction(5); +} + void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp) { - // Player:onGainExperience(source, exp, rawExp) - // rawExp gives the original exp which is not multiplied + // Player:onGainExperience(source, exp, rawExp) rawExp gives the original exp which is not multiplied if (info.playerOnGainExperience == -1) { return; } @@ -994,6 +1087,36 @@ void Events::eventPlayerOnWrapItem(Player* player, Item* item) scriptInterface.callVoidFunction(2); } +void Events::eventPlayerOnInventoryUpdate(Player* player, Item* item, slots_t slot, bool equip) +{ + // Player:onInventoryUpdate(item, slot, equip) + if (info.playerOnInventoryUpdate == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnInventoryUpdate] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnInventoryUpdate, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnInventoryUpdate); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setItemMetatable(L, -1, item); + + lua_pushnumber(L, slot); + LuaScriptInterface::pushBoolean(L, equip); + + scriptInterface.callVoidFunction(4); +} + void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse) { // Monster:onDropLoot(corpse) @@ -1020,4 +1143,3 @@ void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse) return scriptInterface.callVoidFunction(2); } - diff --git a/src/events.h b/src/events.h index 8d182b571a..9d7696d273 100644 --- a/src/events.h +++ b/src/events.h @@ -1,35 +1,26 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_EVENTS_H_BD444CC0EE167E5777E4C90C766B36DC -#define FS_EVENTS_H_BD444CC0EE167E5777E4C90C766B36DC +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_EVENTS_H +#define FS_EVENTS_H -#include "luascript.h" #include "const.h" +#include "creature.h" +#include "luascript.h" -class Party; class ItemType; +class Party; class Tile; +enum class EventInfoId +{ + CREATURE_ONHEAR +}; + class Events { - struct EventsInfo { + struct EventsInfo + { // Creature int32_t creatureOnChangeOutfit = -1; int32_t creatureOnAreaCombat = -1; @@ -48,6 +39,7 @@ class Events int32_t playerOnLookInBattleList = -1; int32_t playerOnLookInTrade = -1; int32_t playerOnLookInShop = -1; + int32_t playerOnLookInMarket = -1; int32_t playerOnMoveItem = -1; int32_t playerOnItemMoved = -1; int32_t playerOnMoveCreature = -1; @@ -57,60 +49,84 @@ class Events int32_t playerOnTradeRequest = -1; int32_t playerOnTradeAccept = -1; int32_t playerOnTradeCompleted = -1; + int32_t playerOnPodiumRequest = -1; + int32_t playerOnPodiumEdit = -1; int32_t playerOnGainExperience = -1; int32_t playerOnLoseExperience = -1; int32_t playerOnGainSkillTries = -1; int32_t playerOnWrapItem = -1; + int32_t playerOnInventoryUpdate = -1; // Monster int32_t monsterOnDropLoot = -1; int32_t monsterOnSpawn = -1; }; - public: - Events(); +public: + Events(); - bool load(); + bool load(); - // Creature - bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); - ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); - ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); - void eventCreatureOnHear(Creature* creature, Creature* speaker, const std::string& words, SpeakClasses type); + // Creature + bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); + ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); + ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); + void eventCreatureOnHear(Creature* creature, Creature* speaker, const std::string& words, SpeakClasses type); - // Party - bool eventPartyOnJoin(Party* party, Player* player); - bool eventPartyOnLeave(Party* party, Player* player); - bool eventPartyOnDisband(Party* party); - void eventPartyOnShareExperience(Party* party, uint64_t& exp); + // Party + bool eventPartyOnJoin(Party* party, Player* player); + bool eventPartyOnLeave(Party* party, Player* player); + bool eventPartyOnDisband(Party* party); + void eventPartyOnShareExperience(Party* party, uint64_t& exp); - // Player - bool eventPlayerOnBrowseField(Player* player, const Position& position); - void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance); - void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); - void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); - bool eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count, const std::string& description); - bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); - void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); - bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition); - void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); - bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category); - bool eventPlayerOnTurn(Player* player, Direction direction); - bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); - bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); - void eventPlayerOnTradeCompleted(Player* player, Player* target, Item* item, Item* targetItem, bool isSuccess); - void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); - void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); - void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); - void eventPlayerOnWrapItem(Player* player, Item* item); + // Player + bool eventPlayerOnBrowseField(Player* player, const Position& position); + void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, + int32_t lookDistance); + void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); + void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); + bool eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count); + bool eventPlayerOnLookInMarket(Player* player, const ItemType* itemType); + ReturnValue eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, + const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, + const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, + const Position& toPosition); + void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, + uint8_t reportReason, const std::string& comment, + const std::string& translation); + bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category); + bool eventPlayerOnTurn(Player* player, Direction direction); + bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); + bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); + void eventPlayerOnTradeCompleted(Player* player, Player* target, Item* item, Item* targetItem, bool isSuccess); + void eventPlayerOnPodiumRequest(Player* player, Item* item); + void eventPlayerOnPodiumEdit(Player* player, Item* item, const Outfit_t& outfit, bool podiumVisible, + Direction direction); + void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); + void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); + void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); + void eventPlayerOnWrapItem(Player* player, Item* item); + void eventPlayerOnInventoryUpdate(Player* player, Item* item, slots_t slot, bool equip); - // Monster - void eventMonsterOnDropLoot(Monster* monster, Container* corpse); - bool eventMonsterOnSpawn(Monster* monster, const Position& position, bool startup, bool artificial); + // Monster + void eventMonsterOnDropLoot(Monster* monster, Container* corpse); + bool eventMonsterOnSpawn(Monster* monster, const Position& position, bool startup, bool artificial); + + int32_t getScriptId(EventInfoId eventInfoId) + { + switch (eventInfoId) { + case EventInfoId::CREATURE_ONHEAR: + return info.creatureOnHear; + default: + return -1; + } + }; - private: - LuaScriptInterface scriptInterface; - EventsInfo info; +private: + LuaScriptInterface scriptInterface; + EventsInfo info; }; -#endif +#endif // FS_EVENTS_H diff --git a/src/fileloader.cpp b/src/fileloader.cpp index 4a1a3a613b..f9169dd9a1 100644 --- a/src/fileloader.cpp +++ b/src/fileloader.cpp @@ -1,33 +1,17 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include #include "fileloader.h" +#include + namespace OTB { constexpr Identifier wildcard = {{'\0', '\0', '\0', '\0'}}; -Loader::Loader(const std::string& fileName, const Identifier& acceptedIdentifier): - fileContents(fileName) +Loader::Loader(const std::string& fileName, const Identifier& acceptedIdentifier) : fileContents(fileName) { constexpr auto minimalSize = sizeof(Identifier) + sizeof(Node::START) + sizeof(Node::type) + sizeof(Node::END); if (fileContents.size() <= minimalSize) { @@ -42,7 +26,8 @@ Loader::Loader(const std::string& fileName, const Identifier& acceptedIdentifier } using NodeStack = std::stack>; -static Node& getCurrentNode(const NodeStack& nodeStack) { +static Node& getCurrentNode(const NodeStack& nodeStack) +{ if (nodeStack.empty()) { throw InvalidOTBFormat{}; } @@ -61,7 +46,7 @@ const Node& Loader::parseTree() parseStack.push(&root); for (; it != fileContents.end(); ++it) { - switch(static_cast(*it)) { + switch (static_cast(*it)) { case Node::START: { auto& currentNode = getCurrentNode(parseStack); if (currentNode.children.empty()) { @@ -112,12 +97,13 @@ bool Loader::getProps(const Node& node, PropStream& props) propBuffer.resize(size); bool lastEscaped = false; - auto escapedPropEnd = std::copy_if(node.propsBegin, node.propsEnd, propBuffer.begin(), [&lastEscaped](const char& byte) { - lastEscaped = byte == static_cast(Node::ESCAPE) && !lastEscaped; - return !lastEscaped; - }); + auto escapedPropEnd = + std::copy_if(node.propsBegin, node.propsEnd, propBuffer.begin(), [&lastEscaped](const char& byte) { + lastEscaped = byte == static_cast(Node::ESCAPE) && !lastEscaped; + return !lastEscaped; + }); props.init(&propBuffer[0], std::distance(propBuffer.begin(), escapedPropEnd)); return true; } -} //namespace OTB +} // namespace OTB diff --git a/src/fileloader.h b/src/fileloader.h index c55f6249a6..8a6e1eacba 100644 --- a/src/fileloader.h +++ b/src/fileloader.h @@ -1,34 +1,14 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E -#define FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E - -#include -#include -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_FILELOADER_H +#define FS_FILELOADER_H class PropStream; namespace OTB { using MappedFile = boost::iostreams::mapped_file_source; -using ContentIt = MappedFile::iterator; +using ContentIt = MappedFile::iterator; using Identifier = std::array; struct Node @@ -36,132 +16,137 @@ struct Node using ChildrenVector = std::vector; ChildrenVector children; - ContentIt propsBegin; - ContentIt propsEnd; - uint8_t type; - enum NodeChar: uint8_t + ContentIt propsBegin; + ContentIt propsEnd; + uint8_t type; + enum NodeChar : uint8_t { ESCAPE = 0xFD, - START = 0xFE, - END = 0xFF, + START = 0xFE, + END = 0xFF, }; }; -struct LoadError : std::exception { +struct LoadError : std::exception +{ const char* what() const noexcept override = 0; }; -struct InvalidOTBFormat final : LoadError { - const char* what() const noexcept override { - return "Invalid OTBM file format"; - } +struct InvalidOTBFormat final : LoadError +{ + const char* what() const noexcept override { return "Invalid OTBM file format"; } }; -class Loader { - MappedFile fileContents; - Node root; +class Loader +{ + MappedFile fileContents; + Node root; std::vector propBuffer; + public: Loader(const std::string& fileName, const Identifier& acceptedIdentifier); bool getProps(const Node& node, PropStream& props); const Node& parseTree(); }; -} //namespace OTB +} // namespace OTB class PropStream { - public: - void init(const char* a, size_t size) { - p = a; - end = a + size; - } +public: + void init(const char* a, size_t size) + { + p = a; + end = a + size; + } - size_t size() const { - return end - p; + size_t size() const { return end - p; } + + template + bool read(T& ret) + { + if (size() < sizeof(T)) { + return false; } - template - bool read(T& ret) { - if (size() < sizeof(T)) { - return false; - } + memcpy(&ret, p, sizeof(T)); + p += sizeof(T); + return true; + } - memcpy(&ret, p, sizeof(T)); - p += sizeof(T); - return true; + bool readString(std::string& ret) + { + uint16_t strLen; + if (!read(strLen)) { + return false; } - bool readString(std::string& ret) { - uint16_t strLen; - if (!read(strLen)) { - return false; - } - - if (size() < strLen) { - return false; - } - - char* str = new char[strLen + 1]; - memcpy(str, p, strLen); - str[strLen] = 0; - ret.assign(str, strLen); - delete[] str; - p += strLen; - return true; + if (size() < strLen) { + return false; } - bool skip(size_t n) { - if (size() < n) { - return false; - } + char* str = new char[strLen + 1]; + memcpy(str, p, strLen); + str[strLen] = 0; + ret.assign(str, strLen); + delete[] str; + p += strLen; + return true; + } - p += n; - return true; + bool skip(size_t n) + { + if (size() < n) { + return false; } - private: - const char* p = nullptr; - const char* end = nullptr; + p += n; + return true; + } + +private: + const char* p = nullptr; + const char* end = nullptr; }; class PropWriteStream { - public: - PropWriteStream() = default; - - // non-copyable - PropWriteStream(const PropWriteStream&) = delete; - PropWriteStream& operator=(const PropWriteStream&) = delete; +public: + PropWriteStream() = default; - const char* getStream(size_t& size) const { - size = buffer.size(); - return buffer.data(); - } + // non-copyable + PropWriteStream(const PropWriteStream&) = delete; + PropWriteStream& operator=(const PropWriteStream&) = delete; - void clear() { - buffer.clear(); - } + const char* getStream(size_t& size) const + { + size = buffer.size(); + return buffer.data(); + } - template - void write(T add) { - char* addr = reinterpret_cast(&add); - std::copy(addr, addr + sizeof(T), std::back_inserter(buffer)); - } + void clear() { buffer.clear(); } - void writeString(const std::string& str) { - size_t strLength = str.size(); - if (strLength > std::numeric_limits::max()) { - write(0); - return; - } + template + void write(T add) + { + char* addr = reinterpret_cast(&add); + std::copy(addr, addr + sizeof(T), std::back_inserter(buffer)); + } - write(static_cast(strLength)); - std::copy(str.begin(), str.end(), std::back_inserter(buffer)); + void writeString(const std::string& str) + { + size_t strLength = str.size(); + if (strLength > std::numeric_limits::max()) { + write(0); + return; } - private: - std::vector buffer; + write(static_cast(strLength)); + std::copy(str.begin(), str.end(), std::back_inserter(buffer)); + } + +private: + std::vector buffer; }; -#endif +#endif // FS_FILELOADER_H diff --git a/src/game.cpp b/src/game.cpp index 4a2f8d6151..53bd3998a2 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,25 +1,9 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "pugicast.h" +#include "game.h" #include "actions.h" #include "bed.h" @@ -27,22 +11,28 @@ #include "creature.h" #include "creatureevent.h" #include "databasetasks.h" +#include "depotchest.h" #include "events.h" -#include "game.h" #include "globalevent.h" +#include "housetile.h" +#include "inbox.h" #include "iologindata.h" #include "iomarket.h" #include "items.h" #include "monster.h" #include "movement.h" +#include "npc.h" +#include "outfit.h" +#include "party.h" +#include "podium.h" #include "scheduler.h" +#include "script.h" #include "server.h" +#include "spectators.h" #include "spells.h" +#include "storeinbox.h" #include "talkaction.h" #include "weapons.h" -#include "script.h" - -#include extern ConfigManager g_config; extern Actions* g_actions; @@ -85,26 +75,20 @@ void Game::start(ServiceManager* manager) updateWorldTime(); if (g_config.getBoolean(ConfigManager::DEFAULT_WORLD_LIGHT)) { - g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); + g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, [this]() { checkLight(); })); } - g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, std::bind(&Game::checkCreatures, this, 0))); - g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); + g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, [this]() { checkCreatures(0); })); + g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, [this]() { checkDecay(); })); } -GameState_t Game::getGameState() const -{ - return gameState; -} +GameState_t Game::getGameState() const { return gameState; } -void Game::setWorldType(WorldType_t type) -{ - worldType = type; -} +void Game::setWorldType(WorldType_t type) { worldType = type; } void Game::setGameState(GameState_t newState) { if (gameState == GAME_STATE_SHUTDOWN) { - return; //this cannot be stopped + return; // this cannot be stopped } if (gameState == newState) { @@ -125,8 +109,8 @@ void Game::setGameState(GameState_t newState) quests.loadFromXml(); mounts.loadFromXml(); - loadMotdNum(); loadPlayersRecord(); + loadAccountStorageValues(); g_globalEvents->startup(); break; @@ -135,18 +119,16 @@ void Game::setGameState(GameState_t newState) case GAME_STATE_SHUTDOWN: { g_globalEvents->execute(GLOBALEVENT_SHUTDOWN); - //kick all players that are still online + // kick all players that are still online auto it = players.begin(); while (it != players.end()) { it->second->kickPlayer(true); it = players.begin(); } - saveMotdNum(); saveGameState(); - g_dispatcher.addTask( - createTask(std::bind(&Game::shutdown, this))); + g_dispatcher.addTask(createTask([this]() { shutdown(); })); g_scheduler.stop(); g_databaseTasks.stop(); @@ -183,6 +165,10 @@ void Game::saveGameState() std::cout << "Saving server..." << std::endl; + if (!saveAccountStorageValues()) { + std::cout << "[Error - Game::saveGameState] Failed to save account-level storage values." << std::endl; + } + for (const auto& it : players) { it.second->loginPosition = it.second->getPosition(); IOLoginData::savePlayer(it.second); @@ -197,17 +183,9 @@ void Game::saveGameState() } } -bool Game::loadMainMap(const std::string& filename) -{ - Monster::despawnRange = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE); - Monster::despawnRadius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS); - return map.loadMap("data/world/" + filename + ".otbm", true); -} +bool Game::loadMainMap(const std::string& filename) { return map.loadMap("data/world/" + filename + ".otbm", true); } -void Game::loadMap(const std::string& path) -{ - map.loadMap(path, false); -} +void Game::loadMap(const std::string& path) { map.loadMap(path, false); } Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) const { @@ -215,17 +193,18 @@ Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) const return map.getTile(pos); } - //container + // container if (pos.y & 0x40) { uint8_t from_cid = pos.y & 0x0F; return player->getContainerByID(from_cid); } - //inventory + // inventory return player; } -Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, stackPosType_t type) const +Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, + stackPosType_t type) const { if (pos.x != 0xFFFF) { Tile* tile = map.getTile(pos); @@ -274,7 +253,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index } if (player && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { - //do extra checks here if the thing is accessible + // do extra checks here if the thing is accessible if (thing && thing->getItem()) { if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { if (player->getPosition().x + 1 == tile->getPosition().x) { @@ -290,7 +269,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index return thing; } - //container + // container if (pos.y & 0x40) { uint8_t fromCid = pos.y & 0x0F; @@ -332,7 +311,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index return findItemOfType(player, it.id, true, subType); } - //inventory + // inventory slots_t slot = static_cast(pos.y); if (slot == CONST_SLOT_STORE_INBOX) { return player->getStoreInbox(); @@ -371,12 +350,12 @@ void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos) Creature* Game::getCreatureByID(uint32_t id) { - if (id <= Player::playerAutoID) { + if (id <= Player::playerIDLimit) { return getPlayerByID(id); - } else if (id <= Monster::monsterAutoID) { - return getMonsterByID(id); } else if (id <= Npc::npcAutoID) { return getNpcByID(id); + } else if (id <= Monster::monsterAutoID) { + return getMonsterByID(id); } return nullptr; } @@ -426,34 +405,25 @@ Creature* Game::getCreatureByName(const std::string& s) return nullptr; } - const std::string& lowerCaseName = asLowerCaseString(s); + const std::string& lowerCaseName = boost::algorithm::to_lower_copy(s); - { - auto it = mappedPlayerNames.find(lowerCaseName); - if (it != mappedPlayerNames.end()) { - return it->second; - } + if (auto it = mappedPlayerNames.find(lowerCaseName); it != mappedPlayerNames.end()) { + return it->second; } auto equalCreatureName = [&](const std::pair& it) { auto name = it.second->getName(); - return lowerCaseName.size() == name.size() && std::equal(lowerCaseName.begin(), lowerCaseName.end(), name.begin(), [](char a, char b) { - return a == std::tolower(b); - }); + return lowerCaseName.size() == name.size() && + std::equal(lowerCaseName.begin(), lowerCaseName.end(), name.begin(), + [](char a, char b) { return a == std::tolower(b); }); }; - { - auto it = std::find_if(npcs.begin(), npcs.end(), equalCreatureName); - if (it != npcs.end()) { - return it->second; - } + if (auto it = std::find_if(npcs.begin(), npcs.end(), equalCreatureName); it != npcs.end()) { + return it->second; } - { - auto it = std::find_if(monsters.begin(), monsters.end(), equalCreatureName); - if (it != monsters.end()) { - return it->second; - } + if (auto it = std::find_if(monsters.begin(), monsters.end(), equalCreatureName); it != monsters.end()) { + return it->second; } return nullptr; @@ -467,7 +437,7 @@ Npc* Game::getNpcByName(const std::string& s) const char* npcName = s.c_str(); for (const auto& it : npcs) { - if (strcasecmp(npcName, it.second->getName().c_str()) == 0) { + if (caseInsensitiveEqual(npcName, it.second->getName())) { return it.second; } } @@ -480,7 +450,7 @@ Player* Game::getPlayerByName(const std::string& s) return nullptr; } - auto it = mappedPlayerNames.find(asLowerCaseString(s)); + auto it = mappedPlayerNames.find(boost::algorithm::to_lower_copy(s)); if (it == mappedPlayerNames.end()) { return nullptr; } @@ -503,12 +473,12 @@ Player* Game::getPlayerByGUID(const uint32_t& guid) ReturnValue Game::getPlayerByNameWildcard(const std::string& s, Player*& player) { size_t strlen = s.length(); - if (strlen == 0 || strlen > 20) { + if (strlen == 0 || strlen > PLAYER_NAME_LENGTH) { return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; } if (s.back() == '~') { - const std::string& query = asLowerCaseString(s.substr(0, strlen - 1)); + const std::string& query = boost::algorithm::to_lower_copy(s.substr(0, strlen - 1)); std::string result; ReturnValue ret = wildcardTree.findOne(query, result); if (ret != RETURNVALUE_NOERROR) { @@ -537,9 +507,10 @@ Player* Game::getPlayerByAccount(uint32_t acc) return nullptr; } -bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, + bool forced /*= false*/) { - if (creature->getParent() != nullptr) { + if (creature->getParent()) { return false; } @@ -553,7 +524,8 @@ bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool e return true; } -bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/, + MagicEffectClasses magicEffect /*= CONST_ME_TELEPORT*/) { if (!internalPlaceCreature(creature, pos, extendedPos, forced)) { return false; @@ -563,7 +535,7 @@ bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedP map.getSpectators(spectators, creature->getPosition(), true); for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); + tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), magicEffect); } } @@ -578,7 +550,7 @@ bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedP return true; } -bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) +bool Game::removeCreature(Creature* creature, bool isLogout /* = true*/) { if (creature->isRemoved()) { return false; @@ -592,7 +564,8 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) map.getSpectators(spectators, tile->getPosition(), true); for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { - oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getClientIndexOfCreature(player, creature) : -1); + oldStackPosVector.push_back( + player->canSeeCreature(creature) ? tile->getClientIndexOfCreature(player, creature) : -1); } } @@ -600,7 +573,7 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) const Position& tilePosition = tile->getPosition(); - //send to client + // send to client size_t i = 0; for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { @@ -608,11 +581,16 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) } } - //event method + // event method for (Creature* spectator : spectators) { spectator->onRemoveCreature(creature, isLogout); } + Creature* master = creature->getMaster(); + if (master && !master->isRemoved()) { + creature->setMaster(nullptr); + } + creature->getParent()->postRemoveNotification(creature, nullptr, 0); creature->removeList(); @@ -636,8 +614,8 @@ void Game::executeDeath(uint32_t creatureId) } } -void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, - uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count) { Player* player = getPlayerByID(playerId); if (!player) { @@ -669,9 +647,10 @@ void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, } if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(), player->getPosition())) { - SchedulerTask* task = createSchedulerTask(1000, - std::bind(&Game::playerMoveCreatureByID, this, player->getID(), - movingCreature->getID(), movingCreature->getPosition(), tile->getPosition())); + SchedulerTask* task = createSchedulerTask( + MOVE_CREATURE_INTERVAL, [=, playerID = player->getID(), creatureID = movingCreature->getID()]() { + playerMoveCreatureByID(playerID, creatureID, fromPos, toPos); + }); player->setNextActionTask(task); } else { playerMoveCreature(player, movingCreature, movingCreature->getPosition(), tile); @@ -687,7 +666,8 @@ void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, } } -void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos) +void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, + const Position& toPos) { Player* player = getPlayerByID(playerId); if (!player) { @@ -708,12 +688,16 @@ void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, playerMoveCreature(player, movingCreature, movingCreatureOrigPos, toTile); } -void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile) +void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, + Tile* toTile) { if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); - SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveCreatureByID, - this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); + SchedulerTask* task = + createSchedulerTask(delay, [=, playerID = player->getID(), movingCreatureID = movingCreature->getID(), + toPos = toTile->getPosition()]() { + playerMoveCreatureByID(playerID, movingCreatureID, movingCreatureOrigPos, toPos); + }); player->setNextActionTask(task); return; } @@ -726,13 +710,18 @@ void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Po player->setNextActionTask(nullptr); if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { - //need to walk to the creature first before moving it + // need to walk to the creature first before moving it std::vector listDir; if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - SchedulerTask* task = createSchedulerTask(1500, std::bind(&Game::playerMoveCreatureByID, this, - player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = + createSchedulerTask(RANGE_MOVE_CREATURE_INTERVAL, [=, playerID = player->getID(), + movingCreatureID = movingCreature->getID(), + toPos = toTile->getPosition()] { + playerMoveCreatureByID(playerID, movingCreatureID, movingCreatureOrigPos, toPos); + }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -741,24 +730,32 @@ void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Po } if ((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) || - (movingCreature->isInGhostMode() && !player->isAccessPlayer())) { + (movingCreature->isInGhostMode() && !player->canSeeGhostMode(movingCreature))) { player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); return; } - //check throw distance + // check throw distance const Position& movingCreaturePos = movingCreature->getPosition(); const Position& toPos = toTile->getPosition(); - if ((Position::getDistanceX(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceY(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceZ(movingCreaturePos, toPos) * 4 > movingCreature->getThrowRange())) { + if ((Position::getDistanceX(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || + (Position::getDistanceY(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || + (Position::getDistanceZ(movingCreaturePos, toPos) * 4 > movingCreature->getThrowRange())) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); return; } + if (!Position::areInRange<1, 1, 0>(movingCreaturePos, player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + if (player != movingCreature) { if (toTile->hasFlag(TILESTATE_BLOCKPATH)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); return; - } else if ((movingCreature->getZone() == ZONE_PROTECTION && !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) || (movingCreature->getZone() == ZONE_NOPVP && !toTile->hasFlag(TILESTATE_NOPVPZONE))) { + } else if ((movingCreature->getZone() == ZONE_PROTECTION && !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) || + (movingCreature->getZone() == ZONE_NOPVP && !toTile->hasFlag(TILESTATE_NOPVPZONE))) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } else { @@ -798,12 +795,12 @@ ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; if (player && !diagonalMovement) { - //try to go up + // try to go up if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); - if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + if (!tmpTile || (!tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); - if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) { + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; if (!tmpTile->hasFlag(TILESTATE_FLOORCHANGE)) { @@ -814,12 +811,12 @@ ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, } } - //try to go down + // try to go down if (currentPos.z != 7 && currentPos.z == destPos.z) { Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); - if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + if (!tmpTile || (!tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); - if (tmpTile && tmpTile->hasHeight(3)) { + if (tmpTile && tmpTile->hasHeight(3) && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; player->setDirection(direction); destPos.z++; @@ -837,7 +834,11 @@ ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/) { - //check if we can move the creature to the destination + if (creature.hasCondition(CONDITION_ROOT)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + // check if we can move the creature to the destination ReturnValue ret = toTile.queryAdd(0, creature, 1, flags); if (ret != RETURNVALUE_NOERROR) { return ret; @@ -859,7 +860,7 @@ ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_ map.moveCreature(creature, *subCylinder); if (creature.getParent() != subCylinder) { - //could happen if a script move the creature + // could happen if a script move the creature fromCylinder = nullptr; break; } @@ -868,7 +869,7 @@ ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_ toCylinder = subCylinder; flags = 0; - //to prevent infinite loop + // to prevent infinite loop if (++n >= MAP_MAX_LAYERS) { break; } @@ -888,7 +889,8 @@ ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_ return RETURNVALUE_NOERROR; } -void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count) { Player* player = getPlayerByID(playerId); if (!player) { @@ -897,20 +899,21 @@ void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, nullptr, nullptr); } -void Game::playerMoveItem(Player* player, const Position& fromPos, - uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder) +void Game::playerMoveItem(Player* player, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder) { if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); - SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveItemByPlayerID, this, - player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); + SchedulerTask* task = createSchedulerTask(delay, [=, playerID = player->getID()]() { + playerMoveItemByPlayerID(playerID, fromPos, spriteId, fromStackPos, toPos, count); + }); player->setNextActionTask(task); return; } player->setNextActionTask(nullptr); - if (item == nullptr) { + if (!item) { uint8_t fromIndex = 0; if (fromPos.x == 0xFFFF) { if (fromPos.y & 0x40) { @@ -937,14 +940,14 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } Cylinder* fromCylinder = internalGetCylinder(player, fromPos); - if (fromCylinder == nullptr) { + if (!fromCylinder) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } - if (toCylinder == nullptr) { + if (!toCylinder) { toCylinder = internalGetCylinder(player, toPos); - if (toCylinder == nullptr) { + if (!toCylinder) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -958,19 +961,21 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, const Position& playerPos = player->getPosition(); const Position& mapFromPos = fromCylinder->getTile()->getPosition(); if (playerPos.z != mapFromPos.z) { - player->sendCancelMessage(playerPos.z > mapFromPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + player->sendCancelMessage(playerPos.z > mapFromPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS + : RETURNVALUE_FIRSTGODOWNSTAIRS); return; } if (!Position::areInRange<1, 1>(playerPos, mapFromPos)) { - //need to walk to the item first before using it + // need to walk to the item first before using it std::vector listDir; if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, - player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask(RANGE_MOVE_ITEM_INTERVAL, [=, playerID = player->getID()]() { + playerMoveItemByPlayerID(playerID, fromPos, spriteId, fromStackPos, toPos, count); + }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -981,9 +986,9 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, const Tile* toCylinderTile = toCylinder->getTile(); const Position& mapToPos = toCylinderTile->getPosition(); - //hangable item specific code + // hangable item specific code if (item->isHangable() && toCylinderTile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { - //destination supports hangable objects so need to move there first + // destination supports hangable objects so need to move there first bool vertical = toCylinderTile->hasProperty(CONST_PROP_ISVERTICAL); if (vertical) { if (playerPos.x + 1 == mapToPos.x) { @@ -1008,28 +1013,32 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, Position itemPos = fromPos; uint8_t itemStackPos = fromStackPos; - if (fromPos.x != 0xFFFF && Position::areInRange<1, 1>(mapFromPos, playerPos) - && !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) { - //need to pickup the item first + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1>(mapFromPos, playerPos) && + !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) { + // need to pickup the item first Item* moveItem = nullptr; - ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem, 0, player, nullptr, &fromPos, &toPos); + ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem, 0, + player, nullptr, &fromPos, &toPos); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return; } - //changing the position since its now in the inventory of the player + // changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackPos); } std::vector listDir; if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, - player->getID(), itemPos, spriteId, itemStackPos, toPos, count)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask( + RANGE_MOVE_ITEM_INTERVAL, + [this, playerID = player->getID(), itemPos, spriteId, itemStackPos, toPos, count]() { + playerMoveItemByPlayerID(playerID, itemPos, spriteId, itemStackPos, toPos, count); + }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -1038,14 +1047,19 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) || - (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) || - (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > item->getThrowRange())) { + if (!item->isPickupable() && playerPos.z != mapToPos.z) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + return; + } + + int32_t throwRange = item->getThrowRange(); + if ((Position::getDistanceX(playerPos, mapToPos) > throwRange) || + (Position::getDistanceY(playerPos, mapToPos) > throwRange)) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); return; } - if (!canThrowObjectTo(mapFromPos, mapToPos)) { + if (!canThrowObjectTo(mapFromPos, mapToPos, true, false, throwRange, throwRange)) { player->sendCancelMessage(RETURNVALUE_CANNOTTHROW); return; } @@ -1059,19 +1073,24 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player, nullptr, &fromPos, &toPos); + ReturnValue ret = + internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player, nullptr, &fromPos, &toPos); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } } -ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, - Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/, const Position* fromPos /*= nullptr*/, const Position* toPos/*= nullptr*/) +ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, Item* item, + uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, + Creature* actor /* = nullptr*/, Item* tradeItem /* = nullptr*/, + const Position* fromPos /*= nullptr*/, const Position* toPos /*= nullptr*/) { Player* actorPlayer = actor ? actor->getPlayer() : nullptr; if (actorPlayer && fromPos && toPos) { - if (!g_events->eventPlayerOnMoveItem(actorPlayer, item, count, *fromPos, *toPos, fromCylinder, toCylinder)) { - return RETURNVALUE_NOTPOSSIBLE; + const ReturnValue ret = + g_events->eventPlayerOnMoveItem(actorPlayer, item, count, *fromPos, *toPos, fromCylinder, toCylinder); + if (ret != RETURNVALUE_NOERROR) { + return ret; } } @@ -1090,28 +1109,36 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, while ((subCylinder = toCylinder->queryDestination(index, *item, &toItem, flags)) != toCylinder) { toCylinder = subCylinder; - flags = 0; - //to prevent infinite loop + // to prevent infinite loop if (++floorN >= MAP_MAX_LAYERS) { break; } } - //destination is the same as the source? + // destination is the same as the source? if (item == toItem) { - return RETURNVALUE_NOERROR; //silently ignore move + return RETURNVALUE_NOERROR; // silently ignore move } - //check if we can add this item + // check if we can add this item ReturnValue ret = toCylinder->queryAdd(index, *item, count, flags, actor); if (ret == RETURNVALUE_NEEDEXCHANGE) { - //check if we can add it to source cylinder + // check if we can add it to source cylinder ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), *toItem, toItem->getItemCount(), 0); if (ret == RETURNVALUE_NOERROR) { - //check how much we can move + if (actorPlayer && fromPos && toPos) { + const ReturnValue eventRet = g_events->eventPlayerOnMoveItem( + actorPlayer, toItem, toItem->getItemCount(), *toPos, *fromPos, toCylinder, fromCylinder); + if (eventRet != RETURNVALUE_NOERROR) { + return eventRet; + } + } + + // check how much we can move uint32_t maxExchangeQueryCount = 0; - ReturnValue retExchangeMaxCount = fromCylinder->queryMaxCount(INDEX_WHEREEVER, *toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); + ReturnValue retExchangeMaxCount = + fromCylinder->queryMaxCount(INDEX_WHEREEVER, *toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); if (retExchangeMaxCount != RETURNVALUE_NOERROR && maxExchangeQueryCount == 0) { return retExchangeMaxCount; @@ -1132,6 +1159,12 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, } ret = toCylinder->queryAdd(index, *item, count, flags); + + if (actorPlayer && fromPos && toPos && !toItem->isRemoved()) { + g_events->eventPlayerOnItemMoved(actorPlayer, toItem, toItem->getItemCount(), *toPos, *fromPos, + toCylinder, fromCylinder); + } + toItem = nullptr; } } @@ -1141,7 +1174,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, return ret; } - //check how much we can move + // check how much we can move uint32_t maxQueryCount = 0; ReturnValue retMaxCount = toCylinder->queryMaxCount(index, *item, count, maxQueryCount, flags); if (retMaxCount != RETURNVALUE_NOERROR && maxQueryCount == 0) { @@ -1157,7 +1190,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, Item* moveItem = item; - //check if we can remove this item + // check if we can remove this item ret = fromCylinder->queryRemove(*item, m, flags, actor); if (ret != RETURNVALUE_NOERROR) { return ret; @@ -1178,12 +1211,12 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, } } - //remove the item + // remove the item int32_t itemIndex = fromCylinder->getThingIndex(item); Item* updateItem = nullptr; fromCylinder->removeThing(item, m); - //update item(s) + // update item(s) if (item->isStackable()) { uint32_t n; @@ -1208,7 +1241,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, } } - //add item + // add item if (moveItem /*m - n > 0*/) { toCylinder->addThing(index, moveItem); } @@ -1239,7 +1272,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, } } - //we could not move all, inform the player + // we could not move all, inform the player if (item->isStackable() && maxQueryCount < count) { return retMaxCount; } @@ -1253,23 +1286,30 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, } if (actorPlayer && fromPos && toPos) { - g_events->eventPlayerOnItemMoved(actorPlayer, item, count, *fromPos, *toPos, fromCylinder, toCylinder); + if (updateItem && !updateItem->isRemoved()) { + g_events->eventPlayerOnItemMoved(actorPlayer, updateItem, count, *fromPos, *toPos, fromCylinder, + toCylinder); + } else if (moveItem && !moveItem->isRemoved()) { + g_events->eventPlayerOnItemMoved(actorPlayer, moveItem, count, *fromPos, *toPos, fromCylinder, toCylinder); + } else if (item && !item->isRemoved()) { + g_events->eventPlayerOnItemMoved(actorPlayer, item, count, *fromPos, *toPos, fromCylinder, toCylinder); + } } return ret; } ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/, - uint32_t flags/* = 0*/, bool test/* = false*/) + uint32_t flags /* = 0*/, bool test /* = false*/) { uint32_t remainderCount = 0; return internalAddItem(toCylinder, item, index, flags, test, remainderCount); } -ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, - uint32_t flags, bool test, uint32_t& remainderCount) +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, uint32_t flags, bool test, + uint32_t& remainderCount) { - if (toCylinder == nullptr || item == nullptr) { + if (!toCylinder || !item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -1277,7 +1317,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde Item* toItem = nullptr; toCylinder = toCylinder->queryDestination(index, *item, &toItem, flags); - //check if we can add this item + // check if we can add this item ReturnValue ret = toCylinder->queryAdd(index, *item, item->getItemCount(), flags); if (ret != RETURNVALUE_NOERROR) { return ret; @@ -1309,7 +1349,8 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde if (item->getItemCount() != count) { Item* remainderItem = item->clone(); remainderItem->setItemCount(count); - if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != RETURNVALUE_NOERROR) { + if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != + RETURNVALUE_NOERROR) { ReleaseItem(remainderItem); remainderCount = count; } @@ -1322,7 +1363,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde } } } else { - //fully merged with toItem, item will be destroyed + // fully merged with toItem, item will be destroyed item->onRemoved(); ReleaseItem(item); @@ -1352,7 +1393,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/) { Cylinder* cylinder = item->getParent(); - if (cylinder == nullptr) { + if (!cylinder) { return RETURNVALUE_NOTPOSSIBLE; } @@ -1368,7 +1409,7 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te count = item->getItemCount(); } - //check if we can remove this item + // check if we can remove this item ReturnValue ret = cylinder->queryRemove(*item, count, flags | FLAG_IGNORENOTMOVEABLE); if (ret != RETURNVALUE_NOERROR) { return ret; @@ -1381,7 +1422,7 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te if (!test) { int32_t index = cylinder->getThingIndex(item); - //remove the item + // remove the item cylinder->removeThing(item, count); if (item->isRemoved()) { @@ -1398,7 +1439,8 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te return RETURNVALUE_NOERROR; } -ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, slots_t slot /*= CONST_SLOT_WHEREEVER*/) +ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, + slots_t slot /*= CONST_SLOT_WHEREEVER*/) { uint32_t remainderCount = 0; ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); @@ -1417,10 +1459,10 @@ ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnM return ret; } -Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, - bool depthSearch /*= true*/, int32_t subType /*= -1*/) const +Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, bool depthSearch /*= true*/, + int32_t subType /*= -1*/) const { - if (cylinder == nullptr) { + if (!cylinder) { return nullptr; } @@ -1467,7 +1509,7 @@ Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) { - if (cylinder == nullptr) { + if (!cylinder) { return false; } @@ -1550,51 +1592,39 @@ void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) return; } - uint32_t crystalCoins = money / 10000; - money -= crystalCoins * 10000; - while (crystalCoins > 0) { - const uint16_t count = std::min(100, crystalCoins); - - Item* remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count); + for (const auto& it : Item::items.currencyItems) { + const uint64_t worth = it.first; - ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); - if (ret != RETURNVALUE_NOERROR) { - internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + uint32_t currencyCoins = money / worth; + if (currencyCoins <= 0) { + continue; } - crystalCoins -= count; - } - - uint16_t platinumCoins = money / 100; - if (platinumCoins != 0) { - Item* remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins); - - ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); - if (ret != RETURNVALUE_NOERROR) { - internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); - } + money -= currencyCoins * worth; + while (currencyCoins > 0) { + const uint16_t count = std::min(100, currencyCoins); - money -= platinumCoins * 100; - } + Item* remaindItem = Item::CreateItem(it.second, count); - if (money != 0) { - Item* remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money); + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } - ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); - if (ret != RETURNVALUE_NOERROR) { - internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + currencyCoins -= count; } } } Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) { - if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { //chargeless item placed on map = infinite + if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && + newCount != 0))) { // chargeless item placed on map = infinite return item; } Cylinder* cylinder = item->getParent(); - if (cylinder == nullptr) { + if (!cylinder) { return nullptr; } @@ -1622,8 +1652,8 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) const ItemType& curType = Item::items[item->getID()]; if (curType.alwaysOnTop != newType.alwaysOnTop) { - //This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa) - //Remove the old, and add the new + // This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa) + // Remove the old, and add the new cylinder->removeThing(item, item->getItemCount()); cylinder->postRemoveNotification(item, cylinder, itemIndex); @@ -1634,7 +1664,7 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) cylinder->addThing(item); Cylinder* newParent = item->getParent(); - if (newParent == nullptr) { + if (!newParent) { ReleaseItem(item); return nullptr; } @@ -1644,7 +1674,7 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) } if (curType.type == newType.type) { - //Both items has the same type so we can safely change id/subtype + // Both items has the same type so we can safely change id/subtype if (newCount == 0 && (item->isStackable() || item->hasAttribute(ITEM_ATTRIBUTE_CHARGES))) { if (item->isStackable()) { internalRemoveItem(item); @@ -1659,9 +1689,9 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) internalRemoveItem(item); return nullptr; } else if (newItemId != newId) { - //Replacing the the old item with the new while maintaining the old position + // Replacing the the old item with the new while maintaining the old position Item* newItem = Item::CreateItem(newItemId, 1); - if (newItem == nullptr) { + if (!newItem) { return nullptr; } @@ -1672,9 +1702,8 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) cylinder->postRemoveNotification(item, cylinder, itemIndex); ReleaseItem(item); return newItem; - } else { - return transformItem(item, newItemId); } + return transformItem(item, newItemId); } } else { cylinder->postRemoveNotification(item, cylinder, itemIndex); @@ -1699,7 +1728,7 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) } } - //Replacing the old item with the new while maintaining the old position + // Replacing the old item with the new while maintaining the old position Item* newItem; if (newCount == -1) { newItem = Item::CreateItem(newId); @@ -1707,7 +1736,7 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) newItem = Item::CreateItem(newId, newCount); } - if (newItem == nullptr) { + if (!newItem) { return nullptr; } @@ -1729,7 +1758,8 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) return newItem; } -ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove/* = true*/, uint32_t flags /*= 0*/) +ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove /* = true*/, + uint32_t flags /*= 0*/) { if (newPos == thing->getPosition()) { return RETURNVALUE_NOERROR; @@ -1795,7 +1825,7 @@ slots_t getSlotType(const ItemType& it) return slot; } -//Implementation of player invoked events +// Implementation of player invoked events void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId) { Player* player = getPlayerByID(playerId); @@ -1819,7 +1849,8 @@ void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId) Item* slotItem = player->getInventoryItem(slot); Item* equipItem = searchForItem(backpack, it.id); if (slotItem && slotItem->getID() == it.id && (!it.stackable || slotItem->getItemCount() == 100 || !equipItem)) { - internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr); + internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), + nullptr); } else if (equipItem) { internalMoveItem(equipItem->getParent(), player, slot, equipItem, equipItem->getItemCount(), nullptr); } @@ -1964,7 +1995,7 @@ void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) g_chat->removeUserFromChannel(*player, channelId); } -void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) +void Game::playerOpenPrivateChannel(uint32_t playerId, std::string receiver) { Player* player = getPlayerByID(playerId); if (!player) { @@ -2081,26 +2112,30 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f Position itemPos = fromPos; uint8_t itemStackPos = fromStackPos; - if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && - !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && + Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && + !Position::areInRange<1, 1, 0>(fromPos, toPos)) { Item* moveItem = nullptr; - ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, 0, player, nullptr, &fromPos, &toPos); + ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), + &moveItem, 0, player, nullptr, &fromPos, &toPos); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return; } - //changing the position since its now in the inventory of the player + // changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackPos); } std::vector listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItemEx, this, - playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask(RANGE_USE_ITEM_EX_INTERVAL, [=]() { + playerUseItemEx(playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId); + }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -2114,8 +2149,9 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); - SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItemEx, this, - playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); + SchedulerTask* task = createSchedulerTask(delay, [=]() { + playerUseItemEx(playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId); + }); player->setNextActionTask(task); return; } @@ -2126,8 +2162,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f g_actions->useItemEx(player, fromPos, toPos, toStackPos, item, isHotkey); } -void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, - uint8_t index, uint16_t spriteId) +void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { @@ -2156,11 +2191,11 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo if (ret == RETURNVALUE_TOOFARAWAY) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItem, this, - playerId, pos, stackPos, index, spriteId)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask( + RANGE_USE_ITEM_INTERVAL, [=]() { playerUseItem(playerId, pos, stackPos, index, spriteId); }); player->setNextWalkActionTask(task); return; } @@ -2174,8 +2209,8 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); - SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItem, this, - playerId, pos, stackPos, index, spriteId)); + SchedulerTask* task = + createSchedulerTask(delay, [=]() { playerUseItem(playerId, pos, stackPos, index, spriteId); }); player->setNextActionTask(task); return; } @@ -2186,7 +2221,8 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo g_actions->useItem(player, pos, index, item, isHotkey); } -void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId) +void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, + uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { @@ -2198,7 +2234,8 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin return; } - if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) { + if (!Position::areInRange(creature->getPosition(), + player->getPosition())) { return; } @@ -2237,25 +2274,28 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin Position itemPos = fromPos; uint8_t itemStackPos = fromStackPos; - if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && + !Position::areInRange<1, 1, 0>(fromPos, toPos)) { Item* moveItem = nullptr; - ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, 0, player, nullptr, &fromPos, &toPos); + ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), + &moveItem, 0, player, nullptr, &fromPos, &toPos); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); return; } - //changing the position since its now in the inventory of the player + // changing the position since its now in the inventory of the player internalGetPosition(moveItem, itemPos, itemStackPos); } std::vector listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseWithCreature, this, - playerId, itemPos, itemStackPos, creatureId, spriteId)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask(RANGE_USE_WITH_CREATURE_INTERVAL, [=]() { + playerUseWithCreature(playerId, itemPos, itemStackPos, creatureId, spriteId); + }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -2269,8 +2309,8 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); - SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseWithCreature, this, - playerId, fromPos, fromStackPos, creatureId, spriteId)); + SchedulerTask* task = createSchedulerTask( + delay, [=]() { playerUseWithCreature(playerId, fromPos, fromStackPos, creatureId, spriteId); }); player->setNextActionTask(task); return; } @@ -2278,7 +2318,8 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin player->resetIdleTime(); player->setNextActionTask(nullptr); - g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, isHotkey, creature); + g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, + isHotkey, creature); } void Game::playerCloseContainer(uint32_t playerId, uint8_t cid) @@ -2320,7 +2361,8 @@ void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) parentContainer = new Container(tile); parentContainer->incrementReferenceCounter(); browseFields[tile] = parentContainer; - g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); + g_scheduler.addEvent(createSchedulerTask( + 30000, [this, position = tile->getPosition()]() { decreaseBrowseFieldRef(position); })); } else { parentContainer = it->second; } @@ -2358,7 +2400,8 @@ void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stac } Item* item = thing->getItem(); - if (!item || item->getClientID() != spriteId || !item->isRotatable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (!item || item->getClientID() != spriteId || (!item->isRotatable() && !item->isPodium()) || + item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -2366,11 +2409,11 @@ void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stac if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRotateItem, this, - playerId, pos, stackPos, spriteId)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask(RANGE_ROTATE_ITEM_INTERVAL, + [=]() { playerRotateItem(playerId, pos, stackPos, spriteId); }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -2378,9 +2421,14 @@ void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stac return; } - uint16_t newId = Item::items[item->getID()].rotateTo; - if (newId != 0) { - transformItem(item, newId); + if (Podium* podium = item->getPodium()) { + podium->setDirection(static_cast((podium->getDirection() + 1) % 4)); + updatePodium(podium); + } else { + uint16_t newId = Item::items[item->getID()].rotateTo; + if (newId != 0) { + transformItem(item, newId); + } } } @@ -2460,11 +2508,11 @@ void Game::playerBrowseField(uint32_t playerId, const Position& pos) if (!Position::areInRange<1, 1>(playerPos, pos)) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - SchedulerTask* task = createSchedulerTask(400, std::bind( - &Game::playerBrowseField, this, playerId, pos - )); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = + createSchedulerTask(RANGE_BROWSE_FIELD_INTERVAL, [=]() { playerBrowseField(playerId, pos); }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -2488,7 +2536,8 @@ void Game::playerBrowseField(uint32_t playerId, const Position& pos) container = new Container(tile); container->incrementReferenceCounter(); browseFields[tile] = container; - g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); + g_scheduler.addEvent( + createSchedulerTask(30000, [this, position = tile->getPosition()]() { decreaseBrowseFieldRef(position); })); } else { container = it->second; } @@ -2535,7 +2584,8 @@ void Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t w uint32_t internalListId; House* house = player->getEditHouse(internalWindowTextId, internalListId); - if (house && house->canEditAccessList(internalListId, player) && internalWindowTextId == windowTextId && listId == 0) { + if (house && house->canEditAccessList(internalListId, player) && internalWindowTextId == windowTextId && + listId == 0) { house->setAccessList(internalListId, text); } @@ -2555,7 +2605,8 @@ void Game::playerWrapItem(uint32_t playerId, const Position& position, uint8_t s } Item* item = thing->getItem(); - if (!item || item->getClientID() != spriteId || !item->hasAttribute(ITEM_ATTRIBUTE_WRAPID) || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (!item || item->getClientID() != spriteId || !item->hasAttribute(ITEM_ATTRIBUTE_WRAPID) || + item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -2563,11 +2614,11 @@ void Game::playerWrapItem(uint32_t playerId, const Position& position, uint8_t s if (position.x != 0xFFFF && !Position::areInRange<1, 1, 0>(position, player->getPosition())) { std::vector listDir; if (player->getPathTo(position, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerWrapItem, this, - playerId, position, stackPos, spriteId)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask( + RANGE_WRAP_ITEM_INTERVAL, [=]() { playerWrapItem(playerId, position, stackPos, spriteId); }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -2578,8 +2629,8 @@ void Game::playerWrapItem(uint32_t playerId, const Position& position, uint8_t s g_events->eventPlayerOnWrapItem(player, item); } -void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, - uint32_t tradePlayerId, uint16_t spriteId) +void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, uint32_t tradePlayerId, + uint16_t spriteId) { Player* player = getPlayerByID(playerId); if (!player) { @@ -2588,19 +2639,17 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st Player* tradePartner = getPlayerByID(tradePlayerId); if (!tradePartner || tradePartner == player) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "Sorry, not possible."); + player->sendCancelMessage("Select a player to trade with."); return; } if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { - std::ostringstream ss; - ss << tradePartner->getName() << " tells you to move closer."; - player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); return; } - if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { - player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition(), true, true)) { + player->sendCancelMessage(RETURNVALUE_CANNOTTHROW); return; } @@ -2611,16 +2660,16 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st } Item* tradeItem = tradeThing->getItem(); - if (tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || + tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } if (g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { - if (HouseTile* houseTile = dynamic_cast(tradeItem->getTile())) { - House* house = houseTile->getHouse(); - if (house && !house->isInvited(player)) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + if (const HouseTile* const houseTile = dynamic_cast(tradeItem->getTile())) { + if (!tradeItem->getTopParent()->getCreature() && !houseTile->getHouse()->isInvited(player)) { + player->sendCancelMessage(RETURNVALUE_PLAYERISNOTINVITED); return; } } @@ -2629,18 +2678,20 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st const Position& playerPosition = player->getPosition(); const Position& tradeItemPosition = tradeItem->getPosition(); if (playerPosition.z != tradeItemPosition.z) { - player->sendCancelMessage(playerPosition.z > tradeItemPosition.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + player->sendCancelMessage(playerPosition.z > tradeItemPosition.z ? RETURNVALUE_FIRSTGOUPSTAIRS + : RETURNVALUE_FIRSTGODOWNSTAIRS); return; } if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), std::move(listDir)))); - - SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRequestTrade, this, - playerId, pos, stackPos, tradePlayerId, spriteId)); + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask(RANGE_REQUEST_TRADE_INTERVAL, [=]() { + playerRequestTrade(playerId, pos, stackPos, tradePlayerId, spriteId); + }); player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); @@ -2653,18 +2704,18 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st for (const auto& it : tradeItems) { Item* item = it.first; if (tradeItem == item) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + player->sendCancelMessage("This item is already being traded."); return; } if (tradeItemContainer->isHoldingItem(item)) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + player->sendCancelMessage("This item is already being traded."); return; } Container* container = item->getContainer(); if (container && container->isHoldingItem(tradeItem)) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + player->sendCancelMessage("This item is already being traded."); return; } } @@ -2672,13 +2723,13 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st for (const auto& it : tradeItems) { Item* item = it.first; if (tradeItem == item) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + player->sendCancelMessage("This item is already being traded."); return; } Container* container = item->getContainer(); if (container && container->isHoldingItem(tradeItem)) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + player->sendCancelMessage("This item is already being traded."); return; } } @@ -2686,7 +2737,7 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st Container* tradeContainer = tradeItem->getContainer(); if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) { - player->sendTextMessage(MESSAGE_INFO_DESCR, "You can not trade more than 100 items."); + player->sendCancelMessage("You can only trade up to 100 objects at once."); return; } @@ -2699,7 +2750,8 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem) { - if (player->tradeState != TRADE_NONE && !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) { + if (player->tradeState != TRADE_NONE && + !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) { player->sendCancelMessage(RETURNVALUE_YOUAREALREADYTRADING); return false; } else if (tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) { @@ -2716,9 +2768,7 @@ bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeI player->sendTradeItemRequest(player->getName(), tradeItem, true); if (tradePartner->tradeState == TRADE_NONE) { - std::ostringstream ss; - ss << player->getName() << " wants to trade with you."; - tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + tradePartner->sendTextMessage(MESSAGE_TRADE, fmt::format("{:s} wants to trade with you.", player->getName())); tradePartner->tradeState = TRADE_ACKNOWLEDGE; tradePartner->tradePartner = player; } else { @@ -2746,19 +2796,21 @@ void Game::playerAcceptTrade(uint32_t playerId) return; } - if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { - player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); - return; - } - player->setTradeState(TRADE_ACCEPT); if (tradePartner->getTradeState() == TRADE_ACCEPT) { + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition(), true, true)) { + internalCloseTrade(player, false); + player->sendCancelMessage(RETURNVALUE_CANNOTTHROW); + tradePartner->sendCancelMessage(RETURNVALUE_CANNOTTHROW); + return; + } + Item* playerTradeItem = player->tradeItem; Item* partnerTradeItem = tradePartner->tradeItem; if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, playerTradeItem, partnerTradeItem)) { - internalCloseTrade(player); + internalCloseTrade(player, false); return; } @@ -2784,11 +2836,21 @@ void Game::playerAcceptTrade(uint32_t playerId) // if player is trying to trade its own backpack if (tradePartner->getInventoryItem(CONST_SLOT_BACKPACK) == partnerTradeItem) { - tradePartnerRet = (tradePartner->getInventoryItem(getSlotType(Item::items[playerTradeItem->getID()])) ? RETURNVALUE_NOTENOUGHROOM : RETURNVALUE_NOERROR); + tradePartnerRet = (tradePartner->getInventoryItem(getSlotType(Item::items[playerTradeItem->getID()])) + ? RETURNVALUE_NOTENOUGHROOM + : RETURNVALUE_NOERROR); } if (player->getInventoryItem(CONST_SLOT_BACKPACK) == playerTradeItem) { - playerRet = (player->getInventoryItem(getSlotType(Item::items[partnerTradeItem->getID()])) ? RETURNVALUE_NOTENOUGHROOM : RETURNVALUE_NOERROR); + playerRet = (player->getInventoryItem(getSlotType(Item::items[partnerTradeItem->getID()])) + ? RETURNVALUE_NOTENOUGHROOM + : RETURNVALUE_NOERROR); + } + + // both players try to trade equipped backpacks + if (player->getInventoryItem(CONST_SLOT_BACKPACK) == playerTradeItem && + tradePartner->getInventoryItem(CONST_SLOT_BACKPACK) == partnerTradeItem) { + playerRet = RETURNVALUE_NOTENOUGHROOM; } if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { @@ -2798,9 +2860,12 @@ void Game::playerAcceptTrade(uint32_t playerId) playerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); tradePartnerRet = internalRemoveItem(partnerTradeItem, partnerTradeItem->getItemCount(), true); if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { - tradePartnerRet = internalMoveItem(playerTradeItem->getParent(), tradePartner, INDEX_WHEREEVER, playerTradeItem, playerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); + tradePartnerRet = internalMoveItem(playerTradeItem->getParent(), tradePartner, INDEX_WHEREEVER, + playerTradeItem, playerTradeItem->getItemCount(), nullptr, + FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); if (tradePartnerRet == RETURNVALUE_NOERROR) { - internalMoveItem(partnerTradeItem->getParent(), player, INDEX_WHEREEVER, partnerTradeItem, partnerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK); + internalMoveItem(partnerTradeItem->getParent(), player, INDEX_WHEREEVER, partnerTradeItem, + partnerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK); playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player); isSuccess = true; @@ -2843,28 +2908,12 @@ std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) { if (item) { if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { - std::ostringstream ss; - ss << "You do not have enough capacity to carry"; - - if (item->isStackable() && item->getItemCount() > 1) { - ss << " these objects."; - } else { - ss << " this object."; - } - - ss << "\n " << item->getWeightDescription(); - return ss.str(); + return fmt::format("You do not have enough capacity to carry {:s}.\n {:s}", + item->isStackable() && item->getItemCount() > 1 ? "these objects" : "this object", + item->getWeightDescription()); } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { - std::ostringstream ss; - ss << "You do not have enough room to carry"; - - if (item->isStackable() && item->getItemCount() > 1) { - ss << " these objects."; - } else { - ss << " this object."; - } - - return ss.str(); + return fmt::format("You do not have enough room to carry {:s}.", + item->isStackable() && item->getItemCount() > 1 ? "these objects" : "this object"); } } return "Trade could not be completed."; @@ -2908,7 +2957,7 @@ void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t return; } - std::vector containers {tradeContainer}; + std::vector containers{tradeContainer}; size_t i = 0; while (i < containers.size()) { const Container* container = containers[i++]; @@ -2936,10 +2985,11 @@ void Game::playerCloseTrade(uint32_t playerId) internalCloseTrade(player); } -void Game::internalCloseTrade(Player* player) +void Game::internalCloseTrade(Player* player, bool sendCancel /* = true*/) { Player* tradePartner = player->tradePartner; - if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) { + if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || + player->getTradeState() == TRADE_TRANSFER) { return; } @@ -2957,7 +3007,9 @@ void Game::internalCloseTrade(Player* player) player->setTradeState(TRADE_NONE); player->tradePartner = nullptr; - player->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + if (sendCancel) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + } player->sendTradeClose(); if (tradePartner) { @@ -2975,13 +3027,15 @@ void Game::internalCloseTrade(Player* player) tradePartner->setTradeState(TRADE_NONE); tradePartner->tradePartner = nullptr; - tradePartner->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + if (sendCancel) { + tradePartner->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + } tradePartner->sendTradeClose(); } } void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, - bool ignoreCap/* = false*/, bool inBackpacks/* = false*/) + bool ignoreCap /* = false*/, bool inBackpacks /* = false*/) { if (amount == 0 || amount > 100) { return; @@ -3091,8 +3145,7 @@ void Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count) return; } - const std::string& description = Item::getDescription(it, 1, nullptr, subType); - g_events->eventPlayerOnLookInShop(player, &it, subType, description); + g_events->eventPlayerOnLookInShop(player, &it, subType); } void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos) @@ -3118,7 +3171,8 @@ void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos int32_t lookDistance; if (thing != player) { - lookDistance = std::max(Position::getDistanceX(playerPos, thingPos), Position::getDistanceY(playerPos, thingPos)); + lookDistance = + std::max(Position::getDistanceX(playerPos, thingPos), Position::getDistanceY(playerPos, thingPos)); if (playerPos.z != thingPos.z) { lookDistance += 15; } @@ -3153,7 +3207,8 @@ void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) int32_t lookDistance; if (creature != player) { const Position& playerPos = player->getPosition(); - lookDistance = std::max(Position::getDistanceX(playerPos, creaturePos), Position::getDistanceY(playerPos, creaturePos)); + lookDistance = std::max(Position::getDistanceX(playerPos, creaturePos), + Position::getDistanceY(playerPos, creaturePos)); if (playerPos.z != creaturePos.z) { lookDistance += 15; } @@ -3205,7 +3260,7 @@ void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) } player->setAttackedCreature(attackCreature); - g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); + g_dispatcher.addTask(createTask([this, id = player->getID()]() { updateCreatureWalk(id); })); } void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) @@ -3216,7 +3271,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) } player->setAttackedCreature(nullptr); - g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); + g_dispatcher.addTask(createTask([this, id = player->getID()]() { updateCreatureWalk(id); })); player->setFollowCreature(getCreatureByID(creatureId)); } @@ -3234,7 +3289,7 @@ void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool ch void Game::playerRequestAddVip(uint32_t playerId, const std::string& name) { - if (name.length() > 20) { + if (name.length() > PLAYER_NAME_LENGTH) { return; } @@ -3265,7 +3320,7 @@ void Game::playerRequestAddVip(uint32_t playerId, const std::string& name) return; } - if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) { + if (!vipPlayer->isInGhostMode() || player->canSeeGhostMode(vipPlayer)) { player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_ONLINE); } else { player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_OFFLINE); @@ -3283,7 +3338,8 @@ void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) player->removeVIP(guid); } -void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, + bool notify) { Player* player = getPlayerByID(playerId); if (!player) { @@ -3322,6 +3378,78 @@ void Game::playerRequestOutfit(uint32_t playerId) player->sendOutfitWindow(); } +void Game::playerRequestEditPodium(uint32_t playerId, const Position& position, uint8_t stackPos, + const uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, position, stackPos, 0, STACKPOS_TOPDOWN_ITEM); + if (!thing) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0) { + return; + } + + Item* item = thing->getItem(); + if (!item || item->getClientID() != spriteId || it.type != ITEM_TYPE_PODIUM) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + // player has to walk to podium + // gm/god can edit instantly + if (!player->isAccessPlayer()) { + if (position.x != 0xFFFF && !Position::areInRange<1, 1, 0>(position, player->getPosition())) { + std::vector listDir; + if (player->getPathTo(position, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask([=, playerID = player->getID(), listDir = std::move(listDir)]() { + playerAutoWalk(playerID, listDir); + })); + SchedulerTask* task = createSchedulerTask( + 400, [=]() { playerRequestEditPodium(playerId, position, stackPos, spriteId); }); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + } + + g_events->eventPlayerOnPodiumRequest(player, item); +} + +void Game::playerEditPodium(uint32_t playerId, Outfit_t outfit, const Position& position, uint8_t stackPos, + const uint16_t spriteId, bool podiumVisible, Direction direction) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, position, stackPos, 0, STACKPOS_TOPDOWN_ITEM); + if (!thing) { + return; + } + + Item* item = thing->getItem(); + if (!item) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0) { + return; + } + + g_events->eventPlayerOnPodiumEdit(player, item, outfit, podiumVisible, direction); +} + void Game::playerToggleMount(uint32_t playerId, bool mount) { Player* player = getPlayerByID(playerId); @@ -3358,17 +3486,16 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) return; } + int32_t speedChange = mount->speed; if (player->isMounted()) { Mount* prevMount = mounts.getMountByID(player->getCurrentMount()); if (prevMount) { - changeSpeed(player, mount->speed - prevMount->speed); + speedChange -= prevMount->speed; } - - player->setCurrentMount(mount->id); - } else { - player->setCurrentMount(mount->id); - outfit.lookMount = 0; } + + changeSpeed(player, speedChange); + player->setCurrentMount(mount->id); } else if (player->isMounted()) { player->dismount(); } @@ -3409,8 +3536,18 @@ void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) player->sendQuestLine(quest); } -void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, - const std::string& receiver, const std::string& text) +void Game::playerResetQuestTracker(uint32_t playerId, const std::vector& missionIds) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetQuestTracker(missionIds); +} + +void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, + const std::string& text) { Player* player = getPlayerByID(playerId); if (!player) { @@ -3423,11 +3560,14 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, return; } + if (type == TALKTYPE_PRIVATE_PN) { + playerSpeakToNpc(player, text); + return; + } + uint32_t muteTime = player->isMuted(); if (muteTime > 0) { - std::ostringstream ss; - ss << "You are still muted for " << muteTime << " seconds."; - player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + player->sendTextMessage(MESSAGE_STATUS_SMALL, fmt::format("You are still muted for {:d} seconds.", muteTime)); return; } @@ -3435,9 +3575,7 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, return; } - if (type != TALKTYPE_PRIVATE_PN) { - player->removeMessageBuffer(); - } + player->removeMessageBuffer(); switch (type) { case TALKTYPE_SAY: @@ -3463,10 +3601,6 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, g_chat->talkToChannel(*player, type, text, channelId); break; - case TALKTYPE_PRIVATE_PN: - playerSpeakToNpc(player, text); - break; - case TALKTYPE_BROADCAST: playerBroadcastMessage(player, text); break; @@ -3488,11 +3622,9 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& result = g_spells->playerSaySpell(player, words); if (result == TALKACTION_BREAK) { if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) { - return internalCreatureSay(player, TALKTYPE_SAY, words, false); - } else { - return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false); + return internalCreatureSay(player, TALKTYPE_SPELL, words, false); } - + return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false); } else if (result == TALKACTION_FAILED) { return true; } @@ -3503,11 +3635,10 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& void Game::playerWhisper(Player* player, const std::string& text) { SpectatorVec spectators; - map.getSpectators(spectators, player->getPosition(), false, false, - Map::maxClientViewportX, Map::maxClientViewportX, - Map::maxClientViewportY, Map::maxClientViewportY); + map.getSpectators(spectators, player->getPosition(), false, false, Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); - //send to client + // send to client for (Creature* spectator : spectators) { if (Player* spectatorPlayer = spectator->getPlayer()) { if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { @@ -3518,7 +3649,7 @@ void Game::playerWhisper(Player* player, const std::string& text) } } - //event method + // event method for (Creature* spectator : spectators) { spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); } @@ -3531,34 +3662,34 @@ bool Game::playerYell(Player* player, const std::string& text) return false; } - uint32_t minimumLevel = g_config.getNumber(ConfigManager::YELL_MINIMUM_LEVEL); - if (player->getLevel() < minimumLevel) { - std::ostringstream ss; - ss << "You may not yell unless you have reached level " << minimumLevel; - if (g_config.getBoolean(ConfigManager::YELL_ALLOW_PREMIUM)) { - if (player->isPremium()) { - internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false); - return true; + if (!player->isAccessPlayer() && !player->hasFlag(PlayerFlag_IgnoreYellCheck)) { + uint32_t minimumLevel = g_config.getNumber(ConfigManager::YELL_MINIMUM_LEVEL); + if (player->getLevel() < minimumLevel) { + if (g_config.getBoolean(ConfigManager::YELL_ALLOW_PREMIUM)) { + if (!player->isPremium()) { + player->sendTextMessage( + MESSAGE_STATUS_SMALL, + fmt::format("You may not yell unless you have reached level {:d} or have a premium account.", + minimumLevel)); + return false; + } } else { - ss << " or have a premium account"; + player->sendTextMessage( + MESSAGE_STATUS_SMALL, + fmt::format("You may not yell unless you have reached level {:d}.", minimumLevel)); + return false; } } - ss << "."; - player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); - return false; - } - if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0); player->addCondition(condition); } - internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false); + internalCreatureSay(player, TALKTYPE_YELL, boost::algorithm::to_upper_copy(text), false); return true; } -bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, - const std::string& text) +bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text) { Player* toPlayer = getPlayerByName(receiver); if (!toPlayer) { @@ -3566,21 +3697,41 @@ bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& r return false; } - if (type == TALKTYPE_PRIVATE_RED_TO && (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) { + if (type == TALKTYPE_PRIVATE_RED_TO && + (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) { type = TALKTYPE_PRIVATE_RED_FROM; } else { type = TALKTYPE_PRIVATE_FROM; } + if (!player->isAccessPlayer() && !player->hasFlag(PlayerFlag_IgnoreSendPrivateCheck)) { + uint32_t minimumLevel = g_config.getNumber(ConfigManager::MINIMUM_LEVEL_TO_SEND_PRIVATE); + if (player->getLevel() < minimumLevel) { + if (g_config.getBoolean(ConfigManager::PREMIUM_TO_SEND_PRIVATE)) { + if (!player->isPremium()) { + player->sendTextMessage( + MESSAGE_STATUS_SMALL, + fmt::format( + "You may not send private messages unless you have reached level {:d} or have a premium account.", + minimumLevel)); + return false; + } + } else { + player->sendTextMessage( + MESSAGE_STATUS_SMALL, + fmt::format("You may not send private messages unless you have reached level {:d}.", minimumLevel)); + return false; + } + } + } + toPlayer->sendPrivateMessage(player, type, text); toPlayer->onCreatureSay(player, type, text); - if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) { + if (toPlayer->isInGhostMode() && !player->canSeeGhostMode(toPlayer)) { player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); } else { - std::ostringstream ss; - ss << "Message sent to " << toPlayer->getName() << '.'; - player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + player->sendTextMessage(MESSAGE_STATUS_SMALL, fmt::format("Message sent to {:s}.", toPlayer->getName())); } return true; } @@ -3598,14 +3749,15 @@ void Game::playerSpeakToNpc(Player* player, const std::string& text) //-- bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, - int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const + bool sameFloor /*= false*/, int32_t rangex /*= Map::maxClientViewportX*/, + int32_t rangey /*= Map::maxClientViewportY*/) const { - return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey); + return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, sameFloor, rangex, rangey); } -bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const +bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor /*= false*/) const { - return map.isSightClear(fromPos, toPos, floorCheck); + return map.isSightClear(fromPos, toPos, sameFloor); } bool Game::internalCreatureTurn(Creature* creature, Direction dir) @@ -3616,7 +3768,7 @@ bool Game::internalCreatureTurn(Creature* creature, Direction dir) creature->setDirection(dir); - //send to client + // send to client SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { @@ -3625,8 +3777,9 @@ bool Game::internalCreatureTurn(Creature* creature, Direction dir) return true; } -bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, - bool ghostMode, SpectatorVec* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/) +bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, bool ghostMode, + SpectatorVec* spectatorsPtr /* = nullptr*/, const Position* pos /* = nullptr*/, + bool echo /* = false*/) { if (text.empty()) { return false; @@ -3644,17 +3797,18 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { - map.getSpectators(spectators, *pos, false, false, - Map::maxClientViewportX, Map::maxClientViewportX, - Map::maxClientViewportY, Map::maxClientViewportY); + map.getSpectators(spectators, *pos, false, false, Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); } else { - map.getSpectators(spectators, *pos, true, false, 18, 18, 14, 14); + map.getSpectators(spectators, *pos, true, false, (Map::maxClientViewportX * 2) + 2, + (Map::maxClientViewportX * 2) + 2, (Map::maxClientViewportY * 2) + 2, + (Map::maxClientViewportY * 2) + 2); } } else { spectators = (*spectatorsPtr); } - //send to client + // send to client for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { @@ -3663,11 +3817,13 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: } } - //event method - for (Creature* spectator : spectators) { - spectator->onCreatureSay(creature, type, text); - if (creature != spectator) { - g_events->eventCreatureOnHear(spectator, creature, text, type); + // event method + if (!echo) { + for (Creature* spectator : spectators) { + spectator->onCreatureSay(creature, type, text); + if (creature != spectator) { + g_events->eventCreatureOnHear(spectator, creature, text, type); + } } } return true; @@ -3721,7 +3877,8 @@ void Game::removeCreatureCheck(Creature* creature) void Game::checkCreatures(size_t index) { - g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT))); + g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, + [=]() { checkCreatures((index + 1) % EVENT_CREATURECOUNT); })); auto& checkCreatureList = checkCreatureLists[index]; auto it = checkCreatureList.begin(), end = checkCreatureList.end(); @@ -3751,7 +3908,7 @@ void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) creature->setSpeed(varSpeed); - //send to clients + // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), false, true); for (Creature* spectator : spectators) { @@ -3771,7 +3928,7 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf return; } - //send to clients + // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { @@ -3781,7 +3938,7 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf void Game::internalCreatureChangeVisible(Creature* creature, bool visible) { - //send to clients + // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { @@ -3791,7 +3948,7 @@ void Game::internalCreatureChangeVisible(Creature* creature, bool visible) void Game::changeLight(const Creature* creature) { - //send to clients + // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { @@ -3799,7 +3956,8 @@ void Game::changeLight(const Creature* creature) } } -bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field, bool ignoreResistances /*= false */) +bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, + bool checkArmor, bool field, bool ignoreResistances /*= false */) { if (damage.primary.type == COMBAT_NONE && damage.secondary.type == COMBAT_NONE) { return true; @@ -3813,7 +3971,8 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta return false; } - static const auto sendBlockEffect = [this](BlockType_t blockType, CombatType_t combatType, const Position& targetPos) { + static const auto sendBlockEffect = [this](BlockType_t blockType, CombatType_t combatType, + const Position& targetPos) { if (blockType == BLOCK_DEFENSE) { addMagicEffect(targetPos, CONST_ME_POFF); } else if (blockType == BLOCK_ARMOR) { @@ -3852,7 +4011,8 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta BlockType_t primaryBlockType, secondaryBlockType; if (damage.primary.type != COMBAT_NONE) { damage.primary.value = -damage.primary.value; - primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field, ignoreResistances); + primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, + checkArmor, field, ignoreResistances); damage.primary.value = -damage.primary.value; sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition()); @@ -3862,7 +4022,8 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta if (damage.secondary.type != COMBAT_NONE) { damage.secondary.value = -damage.secondary.value; - secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field, ignoreResistances); + secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, + field, ignoreResistances); damage.secondary.value = -damage.secondary.value; sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition()); } else { @@ -3906,6 +4067,15 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo color = TEXTCOLOR_ELECTRICPURPLE; effect = CONST_ME_ENERGYHIT; break; + case RACE_INK: + color = TEXTCOLOR_DARKGREY; + effect = CONST_ME_DRAWINK; + if (const Tile* tile = target->getTile()) { + if (tile && !tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_INK); + } + } + break; default: color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; @@ -3986,7 +4156,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } Player* targetPlayer = target->getPlayer(); - if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && + attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } @@ -4029,17 +4200,23 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } else if (targetPlayer == attackerPlayer) { message.text = fmt::format("You healed yourself for {:s}.", damageString); } else { - message.text = fmt::format("You were healed by {:s} for {:s}.", attacker->getNameDescription(), damageString); + message.text = fmt::format("You were healed by {:s} for {:s}.", attacker->getNameDescription(), + damageString); } } else { message.type = MESSAGE_HEALED_OTHERS; if (spectatorMessage.empty()) { if (!attacker) { - spectatorMessage = fmt::format("{:s} was healed for {:s}.", target->getNameDescription(), damageString); + spectatorMessage = + fmt::format("{:s} was healed for {:s}.", target->getNameDescription(), damageString); } else if (attacker == target) { - spectatorMessage = fmt::format("{:s} healed {:s}self for {:s}.", attacker->getNameDescription(), targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "him") : "it", damageString); + spectatorMessage = fmt::format( + "{:s} healed {:s}self for {:s}.", attacker->getNameDescription(), + targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "him") : "it", + damageString); } else { - spectatorMessage = fmt::format("{:s} healed {:s} for {:s}.", attacker->getNameDescription(), target->getNameDescription(), damageString); + spectatorMessage = fmt::format("{:s} healed {:s} for {:s}.", attacker->getNameDescription(), + target->getNameDescription(), damageString); } spectatorMessage[0] = std::toupper(spectatorMessage[0]); } @@ -4064,7 +4241,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } Player* targetPlayer = target->getPlayer(); - if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && + attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } @@ -4080,7 +4258,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage message.position = targetPos; SpectatorVec spectators; - if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) { + if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && + damage.primary.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { if (damage.origin != ORIGIN_NONE) { @@ -4114,7 +4293,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { message.type = MESSAGE_DAMAGE_DEALT; - message.text = fmt::format("{:s} loses {:d} mana due to your attack.", target->getNameDescription(), manaDamage); + message.text = fmt::format("{:s} loses {:d} mana due to your attack.", + target->getNameDescription(), manaDamage); message.text[0] = std::toupper(message.text[0]); } else if (tmpPlayer == targetPlayer) { message.type = MESSAGE_DAMAGE_RECEIVED; @@ -4123,17 +4303,23 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } else if (targetPlayer == attackerPlayer) { message.text = fmt::format("You lose {:d} mana due to your own attack.", manaDamage); } else { - message.text = fmt::format("You lose {:d} mana due to an attack by {:s}.", manaDamage, attacker->getNameDescription()); + message.text = fmt::format("You lose {:d} mana due to an attack by {:s}.", manaDamage, + attacker->getNameDescription()); } } else { message.type = MESSAGE_DAMAGE_OTHERS; if (spectatorMessage.empty()) { if (!attacker) { - spectatorMessage = fmt::format("{:s} loses {:d} mana.", target->getNameDescription(), manaDamage); + spectatorMessage = + fmt::format("{:s} loses {:d} mana.", target->getNameDescription(), manaDamage); } else if (attacker == target) { - spectatorMessage = fmt::format("{:s} loses {:d} mana due to {:s} own attack.", target->getNameDescription(), manaDamage, targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his"); + spectatorMessage = fmt::format( + "{:s} loses {:d} mana due to {:s} own attack.", target->getNameDescription(), + manaDamage, targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his"); } else { - spectatorMessage = fmt::format("{:s} loses {:d} mana due to an attack by {:s}.", target->getNameDescription(), manaDamage, attacker->getNameDescription()); + spectatorMessage = fmt::format("{:s} loses {:d} mana due to an attack by {:s}.", + target->getNameDescription(), manaDamage, + attacker->getNameDescription()); } spectatorMessage[0] = std::toupper(spectatorMessage[0]); } @@ -4214,7 +4400,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { message.type = MESSAGE_DAMAGE_DEALT; - message.text = fmt::format("{:s} loses {:s} due to your attack.", target->getNameDescription(), damageString); + message.text = + fmt::format("{:s} loses {:s} due to your attack.", target->getNameDescription(), damageString); message.text[0] = std::toupper(message.text[0]); } else if (tmpPlayer == targetPlayer) { message.type = MESSAGE_DAMAGE_RECEIVED; @@ -4223,17 +4410,23 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } else if (targetPlayer == attackerPlayer) { message.text = fmt::format("You lose {:s} due to your own attack.", damageString); } else { - message.text = fmt::format("You lose {:s} due to an attack by {:s}.", damageString, attacker->getNameDescription()); + message.text = fmt::format("You lose {:s} due to an attack by {:s}.", damageString, + attacker->getNameDescription()); } } else { message.type = MESSAGE_DAMAGE_OTHERS; if (spectatorMessage.empty()) { if (!attacker) { - spectatorMessage = fmt::format("{:s} loses {:s}.", target->getNameDescription(), damageString); + spectatorMessage = + fmt::format("{:s} loses {:s}.", target->getNameDescription(), damageString); } else if (attacker == target) { - spectatorMessage = fmt::format("{:s} loses {:s} due to {:s} own attack.", target->getNameDescription(), damageString, targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his") : "its"); + spectatorMessage = fmt::format( + "{:s} loses {:s} due to {:s} own attack.", target->getNameDescription(), damageString, + targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his") : "its"); } else { - spectatorMessage = fmt::format("{:s} loses {:s} due to an attack by {:s}.", target->getNameDescription(), damageString, attacker->getNameDescription()); + spectatorMessage = + fmt::format("{:s} loses {:s} due to an attack by {:s}.", target->getNameDescription(), + damageString, attacker->getNameDescription()); } spectatorMessage[0] = std::toupper(spectatorMessage[0]); } @@ -4269,7 +4462,8 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& if (manaChange > 0) { if (attacker) { const Player* attackerPlayer = attacker->getPlayer(); - if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(target) == SKULL_NONE) { + if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && + attackerPlayer->getSkullClient(target) == SKULL_NONE) { return false; } } @@ -4312,7 +4506,8 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& attackerPlayer = nullptr; } - if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && + attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } @@ -4353,7 +4548,8 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& Player* tmpPlayer = spectator->getPlayer(); if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { message.type = MESSAGE_DAMAGE_DEALT; - message.text = fmt::format("{:s} loses {:d} mana due to your attack.", target->getNameDescription(), manaLoss); + message.text = + fmt::format("{:s} loses {:d} mana due to your attack.", target->getNameDescription(), manaLoss); message.text[0] = std::toupper(message.text[0]); } else if (tmpPlayer == targetPlayer) { message.type = MESSAGE_DAMAGE_RECEIVED; @@ -4362,7 +4558,8 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& } else if (targetPlayer == attackerPlayer) { message.text = fmt::format("You lose {:d} mana due to your own attack.", manaLoss); } else { - message.text = fmt::format("You lose {:d} mana due to an attack by {:s}.", manaLoss, attacker->getNameDescription()); + message.text = fmt::format("You lose {:d} mana due to an attack by {:s}.", manaLoss, + attacker->getNameDescription()); } } else { message.type = MESSAGE_DAMAGE_OTHERS; @@ -4370,9 +4567,13 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& if (!attacker) { spectatorMessage = fmt::format("{:s} loses {:d} mana.", target->getNameDescription(), manaLoss); } else if (attacker == target) { - spectatorMessage = fmt::format("{:s} loses {:d} mana due to {:s} own attack.", target->getNameDescription(), manaLoss, targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his"); + spectatorMessage = + fmt::format("{:s} loses {:d} mana due to {:s} own attack.", target->getNameDescription(), + manaLoss, targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her" : "his"); } else { - spectatorMessage = fmt::format("{:s} loses {:d} mana due to an attack by {:s}.", target->getNameDescription(), manaLoss, attacker->getNameDescription()); + spectatorMessage = + fmt::format("{:s} loses {:d} mana due to an attack by {:s}.", target->getNameDescription(), + manaLoss, attacker->getNameDescription()); } spectatorMessage[0] = std::toupper(spectatorMessage[0]); } @@ -4420,14 +4621,15 @@ void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, u void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { SpectatorVec spectators, toPosSpectators; - map.getSpectators(spectators, fromPos, false, true); - map.getSpectators(toPosSpectators, toPos, false, true); + map.getSpectators(spectators, fromPos, true, true); + map.getSpectators(toPosSpectators, toPos, true, true); spectators.addSpectators(toPosSpectators); addDistanceEffect(spectators, fromPos, toPos, effect); } -void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect) +void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, + uint8_t effect) { for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { @@ -4436,6 +4638,75 @@ void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fro } } +void Game::setAccountStorageValue(const uint32_t accountId, const uint32_t key, const int32_t value) +{ + if (value == -1) { + accountStorageMap[accountId].erase(key); + return; + } + + accountStorageMap[accountId][key] = value; +} + +int32_t Game::getAccountStorageValue(const uint32_t accountId, const uint32_t key) const +{ + const auto& accountMapIt = accountStorageMap.find(accountId); + if (accountMapIt != accountStorageMap.end()) { + const auto& storageMapIt = accountMapIt->second.find(key); + if (storageMapIt != accountMapIt->second.end()) { + return storageMapIt->second; + } + } + return -1; +} + +void Game::loadAccountStorageValues() +{ + Database& db = Database::getInstance(); + + DBResult_ptr result; + if ((result = db.storeQuery("SELECT `account_id`, `key`, `value` FROM `account_storage`"))) { + do { + g_game.setAccountStorageValue(result->getNumber("account_id"), result->getNumber("key"), + result->getNumber("value")); + } while (result->next()); + } +} + +bool Game::saveAccountStorageValues() const +{ + DBTransaction transaction; + Database& db = Database::getInstance(); + + if (!transaction.begin()) { + return false; + } + + if (!db.executeQuery("DELETE FROM `account_storage`")) { + return false; + } + + for (const auto& accountIt : g_game.accountStorageMap) { + if (accountIt.second.empty()) { + continue; + } + + DBInsert accountStorageQuery("INSERT INTO `account_storage` (`account_id`, `key`, `value`) VALUES"); + for (const auto& storageIt : accountIt.second) { + if (!accountStorageQuery.addRow( + fmt::format("{:d}, {:d}, {:d}", accountIt.first, storageIt.first, storageIt.second))) { + return false; + } + } + + if (!accountStorageQuery.execute()) { + return false; + } + } + + return transaction.commit(); +} + void Game::startDecay(Item* item) { if (!item || !item->canDecay()) { @@ -4458,22 +4729,26 @@ void Game::startDecay(Item* item) void Game::internalDecayItem(Item* item) { - const ItemType& it = Item::items[item->getID()]; - if (it.decayTo != 0) { - Item* newItem = transformItem(item, item->getDecayTo()); - startDecay(newItem); + const int32_t decayTo = item->getDecayTo(); + if (decayTo > 0) { + startDecay(transformItem(item, decayTo)); } else { + if (const Player* player = item->getHoldingPlayer()) { + const ItemType& it = Item::items[item->getID()]; + player->sendSupplyUsed(it.transformDeEquipTo != 0 ? Item::items[it.transformDeEquipTo].clientId + : item->getClientID()); + } ReturnValue ret = internalRemoveItem(item); if (ret != RETURNVALUE_NOERROR) { - std::cout << "[Debug - Game::internalDecayItem] internalDecayItem failed, error code: " << static_cast(ret) << ", item id: " << item->getID() << std::endl; + std::cout << "[Debug - Game::internalDecayItem] internalDecayItem failed, error code: " + << static_cast(ret) << ", item id: " << item->getID() << std::endl; } } } void Game::checkDecay() { - g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); - + g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, [this]() { checkDecay(); })); size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS; auto it = decayItems[bucket].begin(), end = decayItems[bucket].end(); @@ -4516,19 +4791,24 @@ void Game::checkDecay() void Game::checkLight() { - g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); + g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, [this]() { checkLight(); })); + uint8_t previousLightLevel = lightLevel; updateWorldLightLevel(); - LightInfo lightInfo = getWorldLightInfo(); - for (const auto& it : players) { - it.second->sendWorldLight(lightInfo); + if (previousLightLevel != lightLevel) { + LightInfo lightInfo = getWorldLightInfo(); + + for (const auto& it : players) { + it.second->sendWorldLight(lightInfo); + } } } void Game::updateWorldLightLevel() { if (getWorldTime() >= GAME_SUNRISE && getWorldTime() <= GAME_DAYTIME) { - lightLevel = ((GAME_DAYTIME - GAME_SUNRISE) - (GAME_DAYTIME - getWorldTime())) * float(LIGHT_CHANGE_SUNRISE) + LIGHT_NIGHT; + lightLevel = ((GAME_DAYTIME - GAME_SUNRISE) - (GAME_DAYTIME - getWorldTime())) * float(LIGHT_CHANGE_SUNRISE) + + LIGHT_NIGHT; } else if (getWorldTime() >= GAME_SUNSET && getWorldTime() <= GAME_NIGHTTIME) { lightLevel = LIGHT_DAY - ((getWorldTime() - GAME_SUNSET) * float(LIGHT_CHANGE_SUNSET)); } else if (getWorldTime() >= GAME_NIGHTTIME || getWorldTime() < GAME_SUNRISE) { @@ -4540,10 +4820,17 @@ void Game::updateWorldLightLevel() void Game::updateWorldTime() { - g_scheduler.addEvent(createSchedulerTask(EVENT_WORLDTIMEINTERVAL, std::bind(&Game::updateWorldTime, this))); + g_scheduler.addEvent(createSchedulerTask(EVENT_WORLDTIMEINTERVAL, [this]() { updateWorldTime(); })); time_t osTime = time(nullptr); tm* timeInfo = localtime(&osTime); worldTime = (timeInfo->tm_sec + (timeInfo->tm_min * 60)) / 2.5f; + + // quarter-hourly update to client clock near the minimap + if (worldTime % 15 == 0) { + for (const auto& it : players) { + it.second->sendWorldTime(); + } + } } void Game::shutdown() @@ -4569,7 +4856,7 @@ void Game::shutdown() void Game::cleanup() { - //free memory + // free memory for (auto creature : ToReleaseCreatures) { creature->decrementReferenceCounter(); } @@ -4591,15 +4878,9 @@ void Game::cleanup() toDecayItems.clear(); } -void Game::ReleaseCreature(Creature* creature) -{ - ToReleaseCreatures.push_back(creature); -} +void Game::ReleaseCreature(Creature* creature) { ToReleaseCreatures.push_back(creature); } -void Game::ReleaseItem(Item* item) -{ - ToReleaseItems.push_back(item); -} +void Game::ReleaseItem(Item* item) { ToReleaseItems.push_back(item); } void Game::broadcastMessage(const std::string& text, MessageClasses type) const { @@ -4611,7 +4892,7 @@ void Game::broadcastMessage(const std::string& text, MessageClasses type) const void Game::updateCreatureWalkthrough(const Creature* creature) { - //send to clients + // send to clients SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); for (Creature* spectator : spectators) { @@ -4642,89 +4923,6 @@ void Game::updatePlayerShield(Player* player) } } -void Game::updatePlayerHelpers(const Player& player) -{ - uint32_t creatureId = player.getID(); - uint16_t helpers = player.getHelpers(); - - SpectatorVec spectators; - map.getSpectators(spectators, player.getPosition(), true, true); - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendCreatureHelpers(creatureId, helpers); - } -} - -void Game::updateCreatureType(Creature* creature) -{ - const Player* masterPlayer = nullptr; - - uint32_t creatureId = creature->getID(); - CreatureType_t creatureType = creature->getType(); - if (creatureType == CREATURETYPE_MONSTER) { - const Creature* master = creature->getMaster(); - if (master) { - masterPlayer = master->getPlayer(); - if (masterPlayer) { - creatureType = CREATURETYPE_SUMMON_OTHERS; - } - } - } - - //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - - if (creatureType == CREATURETYPE_SUMMON_OTHERS) { - for (Creature* spectator : spectators) { - Player* player = spectator->getPlayer(); - if (masterPlayer == player) { - player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN); - } else { - player->sendCreatureType(creatureId, creatureType); - } - } - } else { - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendCreatureType(creatureId, creatureType); - } - } -} - -void Game::loadMotdNum() -{ - Database& db = Database::getInstance(); - - DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); - if (result) { - motdNum = result->getNumber("value"); - } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); - } - - result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); - if (result) { - motdHash = result->getString("value"); - if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) { - ++motdNum; - } - } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); - } -} - -void Game::saveMotdNum() const -{ - Database& db = Database::getInstance(); - - std::ostringstream query; - query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; - db.executeQuery(query.str()); - - query.str(std::string()); - query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'"; - db.executeQuery(query.str()); -} - void Game::checkPlayersRecord() { const size_t playersOnline = getPlayersOnline(); @@ -4742,10 +4940,8 @@ void Game::checkPlayersRecord() void Game::updatePlayersRecord() const { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; - db.executeQuery(query.str()); + db.executeQuery( + fmt::format("UPDATE `server_config` SET `value` = '{:d}' WHERE `config` = 'players_record'", playersRecord)); } void Game::loadPlayersRecord() @@ -4777,9 +4973,8 @@ void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) } if (invitedPlayer->getParty()) { - std::ostringstream ss; - ss << invitedPlayer->getName() << " is already in a party."; - player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + player->sendTextMessage(MESSAGE_INFO_DESCR, + fmt::format("{:s} is already in a party.", invitedPlayer->getName())); return; } @@ -4911,7 +5106,8 @@ void Game::kickPlayer(uint32_t playerId, bool displayEffect) player->kickPlayer(displayEffect); } -void Game::playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation) +void Game::playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, + uint8_t reportReason, const std::string& comment, const std::string& translation) { Player* player = getPlayerByID(playerId); if (!player) { @@ -4931,7 +5127,8 @@ void Game::playerReportBug(uint32_t playerId, const std::string& message, const g_events->eventPlayerOnReportBug(player, message, position, category); } -void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) +void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, + const std::string& description, const std::string& comment) { Player* player = getPlayerByID(playerId); if (!player) { @@ -4941,7 +5138,8 @@ void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, c // TODO: move debug assertions to database FILE* file = fopen("client_assertions.txt", "a"); if (file) { - fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(nullptr)).c_str(), player->getName().c_str(), convertIPToString(player->getIP()).c_str()); + fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(nullptr)).c_str(), player->getName().c_str(), + convertIPToString(player->getIP()).c_str()); fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(), description.c_str(), comment.c_str()); fclose(file); } @@ -4980,7 +5178,7 @@ void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId) const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); - player->sendMarketDetail(it.id); + g_events->eventPlayerOnLookInMarket(player, &it); } void Game::playerBrowseMarketOwnOffers(uint32_t playerId) @@ -5015,13 +5213,14 @@ void Game::playerBrowseMarketOwnHistory(uint32_t playerId) player->sendMarketBrowseOwnHistory(buyOffers, sellOffers); } -void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous) +void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint64_t price, + bool anonymous) { if (amount == 0 || amount > 64000) { return; } - if (price == 0 || price > 999999999) { + if (price == 0 || price > 999999999999) { return; } @@ -5039,7 +5238,7 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spr } if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) { - player->sendMarketLeave(); + player->sendTextMessage(MESSAGE_MARKET, "Only premium accounts may create offers for that object."); return; } @@ -5063,23 +5262,18 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spr } uint64_t fee = (price / 100.) * amount; - if (fee < 20) { - fee = 20; - } else if (fee > 1000) { - fee = 1000; + if (fee < MIN_MARKET_FEE) { + fee = MIN_MARKET_FEE; + } else if (fee > MAX_MARKET_FEE) { + fee = MAX_MARKET_FEE; } if (type == MARKETACTION_SELL) { - if (fee > player->bankBalance) { - return; - } - - DepotChest* depotChest = player->getDepotChest(player->getLastDepotId(), false); - if (!depotChest) { + if (fee > (player->getMoney() + player->bankBalance)) { return; } - std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); + const auto& itemList = getMarketItemList(it.wareId, amount, *player); if (itemList.empty()) { return; } @@ -5101,20 +5295,26 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spr } } - player->bankBalance -= fee; + const auto debitCash = std::min(player->getMoney(), fee); + const auto debitBank = fee - debitCash; + removeMoney(player, debitCash); + player->bankBalance -= debitBank; } else { uint64_t totalPrice = static_cast(price) * amount; totalPrice += fee; - if (totalPrice > player->bankBalance) { + if (totalPrice > (player->getMoney() + player->bankBalance)) { return; } - player->bankBalance -= totalPrice; + const auto debitCash = std::min(player->getMoney(), totalPrice); + const auto debitBank = totalPrice - debitCash; + removeMoney(player, debitCash); + player->bankBalance -= debitBank; } IOMarket::createOffer(player->getGUID(), static_cast(type), it.id, amount, price, anonymous); - player->sendMarketEnter(player->getLastDepotId()); + player->sendMarketEnter(); const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); @@ -5137,8 +5337,8 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } if (offer.type == MARKETACTION_BUY) { - player->bankBalance += static_cast(offer.price) * offer.amount; - player->sendMarketEnter(player->getLastDepotId()); + player->bankBalance += offer.price * offer.amount; + player->sendMarketEnter(); } else { const ItemType& it = Item::items[offer.itemId]; if (it.id == 0) { @@ -5179,7 +5379,7 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 offer.amount = 0; offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); player->sendMarketCancelOffer(offer); - player->sendMarketEnter(player->getLastDepotId()); + player->sendMarketEnter(); } void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) @@ -5202,6 +5402,12 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 return; } + uint32_t offerAccountId = IOLoginData::getAccountIdByPlayerId(offer.playerId); + if (offerAccountId == player->getAccount()) { + player->sendTextMessage(MESSAGE_MARKET, "You cannot accept your own offer."); + return; + } + if (amount > offer.amount) { return; } @@ -5211,15 +5417,10 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 return; } - uint64_t totalPrice = static_cast(offer.price) * amount; + uint64_t totalPrice = offer.price * amount; if (offer.type == MARKETACTION_BUY) { - DepotChest* depotChest = player->getDepotChest(player->getLastDepotId(), false); - if (!depotChest) { - return; - } - - std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); + const auto& itemList = getMarketItemList(it.wareId, amount, *player); if (itemList.empty()) { return; } @@ -5257,7 +5458,8 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 while (tmpAmount > 0) { uint16_t stackCount = std::min(100, tmpAmount); Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { delete item; break; } @@ -5274,7 +5476,8 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 for (uint16_t i = 0; i < amount; ++i) { Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { delete item; break; } @@ -5288,11 +5491,14 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 buyerPlayer->onReceiveMail(); } } else { - if (totalPrice > player->bankBalance) { + if (totalPrice > (player->getMoney() + player->bankBalance)) { return; } - player->bankBalance -= totalPrice; + const auto debitCash = std::min(player->getMoney(), totalPrice); + const auto debitBank = totalPrice - debitCash; + removeMoney(player, debitCash); + player->bankBalance -= debitBank; if (it.stackable) { uint16_t tmpAmount = amount; @@ -5335,9 +5541,12 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - IOMarket::appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX); + IOMarket::appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), + offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, + OFFERSTATE_ACCEPTEDEX); - IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED); + IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, + offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED); offer.amount -= amount; @@ -5347,7 +5556,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 IOMarket::acceptOffer(offer.id, amount); } - player->sendMarketEnter(player->getLastDepotId()); + player->sendMarketEnter(); offer.timestamp += marketOfferDuration; player->sendMarketAcceptOffer(offer); } @@ -5364,20 +5573,27 @@ void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const st } } -std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest* depotChest, Inbox* inbox) +std::vector Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, const Player& player) { - std::forward_list itemList; uint16_t count = 0; + std::list containers{player.getInbox()}; + + for (const auto& chest : player.depotChests) { + if (!chest.second->empty()) { + containers.push_front(chest.second); + } + } + + std::vector itemList; - std::list containers { depotChest, inbox }; do { Container* container = containers.front(); containers.pop_front(); for (Item* item : container->getItemList()) { - Container* c = item->getContainer(); - if (c && !c->empty()) { - containers.push_back(c); + Container* containerItem = item->getContainer(); + if (containerItem && !containerItem->empty()) { + containers.push_back(containerItem); continue; } @@ -5386,7 +5602,7 @@ std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t suffi continue; } - if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) { + if (containerItem && (!itemType.isContainer() || containerItem->capacity() != itemType.maxItems)) { continue; } @@ -5394,7 +5610,7 @@ std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t suffi continue; } - itemList.push_front(item); + itemList.push_back(item); count += Item::countByType(item, -1); if (count >= sufficientCount) { @@ -5402,7 +5618,8 @@ std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t suffi } } } while (!containers.empty()); - return std::forward_list(); + + return {}; } void Game::forceAddCondition(uint32_t creatureId, Condition* condition) @@ -5453,7 +5670,8 @@ void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, ui // offline training, hard-coded if (modalWindowId == std::numeric_limits::max()) { if (button == offlineTrainingWindow.defaultEnterButton) { - if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DISTANCE || choice == SKILL_MAGLEVEL) { + if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DISTANCE || + choice == SKILL_MAGLEVEL) { BedItem* bedItem = player->getBedItem(); if (bedItem && bedItem->sleep(player)) { player->setOfflineTrainingSkill(choice); @@ -5474,7 +5692,7 @@ void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, ui void Game::addPlayer(Player* player) { - const std::string& lowercase_name = asLowerCaseString(player->getName()); + const std::string& lowercase_name = boost::algorithm::to_lower_copy(player->getName()); mappedPlayerNames[lowercase_name] = player; mappedPlayerGuids[player->getGUID()] = player; wildcardTree.insert(lowercase_name); @@ -5483,32 +5701,20 @@ void Game::addPlayer(Player* player) void Game::removePlayer(Player* player) { - const std::string& lowercase_name = asLowerCaseString(player->getName()); + const std::string& lowercase_name = boost::algorithm::to_lower_copy(player->getName()); mappedPlayerNames.erase(lowercase_name); mappedPlayerGuids.erase(player->getGUID()); wildcardTree.remove(lowercase_name); players.erase(player->getID()); } -void Game::addNpc(Npc* npc) -{ - npcs[npc->getID()] = npc; -} +void Game::addNpc(Npc* npc) { npcs[npc->getID()] = npc; } -void Game::removeNpc(Npc* npc) -{ - npcs.erase(npc->getID()); -} +void Game::removeNpc(Npc* npc) { npcs.erase(npc->getID()); } -void Game::addMonster(Monster* monster) -{ - monsters[monster->getID()] = monster; -} +void Game::addMonster(Monster* monster) { monsters[monster->getID()] = monster; } -void Game::removeMonster(Monster* monster) -{ - monsters.erase(monster->getID()); -} +void Game::removeMonster(Monster* monster) { monsters.erase(monster->getID()); } Guild* Game::getGuild(uint32_t id) const { @@ -5519,15 +5725,9 @@ Guild* Game::getGuild(uint32_t id) const return it->second; } -void Game::addGuild(Guild* guild) -{ - guilds[guild->getId()] = guild; -} +void Game::addGuild(Guild* guild) { guilds[guild->getId()] = guild; } -void Game::removeGuild(uint32_t guildId) -{ - guilds.erase(guildId); -} +void Game::removeGuild(uint32_t guildId) { guilds.erase(guildId); } void Game::decreaseBrowseFieldRef(const Position& pos) { @@ -5570,10 +5770,7 @@ BedItem* Game::getBedBySleeper(uint32_t guid) const return it->second; } -void Game::setBedSleeper(BedItem* bed, uint32_t guid) -{ - bedSleepersMap[guid] = bed; -} +void Game::setBedSleeper(BedItem* bed, uint32_t guid) { bedSleepersMap[guid] = bed; } void Game::removeBedSleeper(uint32_t guid) { @@ -5583,6 +5780,25 @@ void Game::removeBedSleeper(uint32_t guid) } } +void Game::updatePodium(Item* item) +{ + if (!item->getPodium()) { + return; + } + + Tile* tile = item->getTile(); + if (!tile) { + return; + } + + // send to clients + SpectatorVec spectators; + map.getSpectators(spectators, item->getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendUpdateTileItem(tile, item->getPosition(), item); + } +} + Item* Game::getUniqueItem(uint16_t uniqueId) { auto it = uniqueItems.find(uniqueId); @@ -5612,27 +5828,38 @@ void Game::removeUniqueItem(uint16_t uniqueId) bool Game::reload(ReloadTypes_t reloadType) { switch (reloadType) { - case RELOAD_TYPE_ACTIONS: return g_actions->reload(); - case RELOAD_TYPE_CHAT: return g_chat->load(); - case RELOAD_TYPE_CONFIG: return g_config.reload(); + case RELOAD_TYPE_ACTIONS: + return g_actions->reload(); + case RELOAD_TYPE_CHAT: + return g_chat->load(); + case RELOAD_TYPE_CONFIG: + return g_config.load(); case RELOAD_TYPE_CREATURESCRIPTS: { g_creatureEvents->reload(); g_creatureEvents->removeInvalidEvents(); return true; } - case RELOAD_TYPE_EVENTS: return g_events->load(); - case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); - case RELOAD_TYPE_ITEMS: return Item::items.reload(); - case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); - case RELOAD_TYPE_MOUNTS: return mounts.reload(); - case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); + case RELOAD_TYPE_EVENTS: + return g_events->load(); + case RELOAD_TYPE_GLOBALEVENTS: + return g_globalEvents->reload(); + case RELOAD_TYPE_ITEMS: + return Item::items.reload(); + case RELOAD_TYPE_MONSTERS: + return g_monsters.reload(); + case RELOAD_TYPE_MOUNTS: + return mounts.reload(); + case RELOAD_TYPE_MOVEMENTS: + return g_moveEvents->reload(); case RELOAD_TYPE_NPCS: { Npcs::reload(); return true; } - case RELOAD_TYPE_QUESTS: return quests.reload(); - case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); + case RELOAD_TYPE_QUESTS: + return quests.reload(); + case RELOAD_TYPE_RAIDS: + return raids.reload() && raids.startup(); case RELOAD_TYPE_SPELLS: { if (!g_spells->reload()) { @@ -5645,7 +5872,8 @@ bool Game::reload(ReloadTypes_t reloadType) return true; } - case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); + case RELOAD_TYPE_TALKACTIONS: + return g_talkActions->reload(); case RELOAD_TYPE_WEAPONS: { bool results = g_weapons->reload(); @@ -5688,7 +5916,7 @@ bool Game::reload(ReloadTypes_t reloadType) } g_actions->reload(); - g_config.reload(); + g_config.load(); g_creatureEvents->reload(); g_monsters.reload(); g_moveEvents->reload(); @@ -5717,4 +5945,3 @@ bool Game::reload(ReloadTypes_t reloadType) } return true; } - diff --git a/src/game.h b/src/game.h index 24a1e8b517..4d7fc22bd0 100644 --- a/src/game.h +++ b/src/game.h @@ -1,45 +1,24 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F -#define FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F +#ifndef FS_GAME_H +#define FS_GAME_H -#include "account.h" -#include "combat.h" #include "groups.h" #include "map.h" -#include "position.h" -#include "item.h" -#include "container.h" +#include "mounts.h" #include "player.h" +#include "position.h" +#include "quests.h" #include "raids.h" -#include "npc.h" #include "wildcardtree.h" -#include "quests.h" -class ServiceManager; -class Creature; class Monster; class Npc; -class CombatInfo; +class ServiceManager; -enum stackPosType_t { +enum stackPosType_t +{ STACKPOS_MOVE, STACKPOS_LOOK, STACKPOS_TOPDOWN_ITEM, @@ -47,13 +26,15 @@ enum stackPosType_t { STACKPOS_USETARGET, }; -enum WorldType_t { +enum WorldType_t +{ WORLD_TYPE_NO_PVP = 1, WORLD_TYPE_PVP = 2, WORLD_TYPE_PVP_ENFORCED = 3, }; -enum GameState_t { +enum GameState_t +{ GAME_STATE_STARTUP, GAME_STATE_INIT, GAME_STATE_NORMAL, @@ -63,533 +44,546 @@ enum GameState_t { GAME_STATE_MAINTAIN, }; +static constexpr int32_t PLAYER_NAME_LENGTH = 25; + static constexpr int32_t EVENT_LIGHTINTERVAL = 10000; static constexpr int32_t EVENT_WORLDTIMEINTERVAL = 2500; static constexpr int32_t EVENT_DECAYINTERVAL = 250; static constexpr int32_t EVENT_DECAY_BUCKETS = 4; +static constexpr int32_t MOVE_CREATURE_INTERVAL = 1000; +static constexpr int32_t RANGE_MOVE_CREATURE_INTERVAL = 1500; +static constexpr int32_t RANGE_MOVE_ITEM_INTERVAL = 400; +static constexpr int32_t RANGE_USE_ITEM_INTERVAL = 400; +static constexpr int32_t RANGE_USE_ITEM_EX_INTERVAL = 400; +static constexpr int32_t RANGE_USE_WITH_CREATURE_INTERVAL = 400; +static constexpr int32_t RANGE_ROTATE_ITEM_INTERVAL = 400; +static constexpr int32_t RANGE_BROWSE_FIELD_INTERVAL = 400; +static constexpr int32_t RANGE_WRAP_ITEM_INTERVAL = 400; +static constexpr int32_t RANGE_REQUEST_TRADE_INTERVAL = 400; + /** - * Main Game class. - * This class is responsible to control everything that happens - */ + * Main Game class. + * This class is responsible to control everything that happens + */ class Game { - public: - Game(); - ~Game(); - - // non-copyable - Game(const Game&) = delete; - Game& operator=(const Game&) = delete; - - void start(ServiceManager* manager); - - void forceAddCondition(uint32_t creatureId, Condition* condition); - void forceRemoveCondition(uint32_t creatureId, ConditionType_t type); - - bool loadMainMap(const std::string& filename); - void loadMap(const std::string& path); - - /** - * Get the map size - info purpose only - * \param width width of the map - * \param height height of the map - */ - void getMapDimensions(uint32_t& width, uint32_t& height) const { - width = map.width; - height = map.height; +public: + Game(); + ~Game(); + + // non-copyable + Game(const Game&) = delete; + Game& operator=(const Game&) = delete; + + void start(ServiceManager* manager); + + void forceAddCondition(uint32_t creatureId, Condition* condition); + void forceRemoveCondition(uint32_t creatureId, ConditionType_t type); + + bool loadMainMap(const std::string& filename); + void loadMap(const std::string& path); + + /** + * Get the map size - info purpose only + * \param width width of the map + * \param height height of the map + */ + void getMapDimensions(uint32_t& width, uint32_t& height) const + { + width = map.width; + height = map.height; + } + + void setWorldType(WorldType_t type); + WorldType_t getWorldType() const { return worldType; } + + Cylinder* internalGetCylinder(Player* player, const Position& pos) const; + Thing* internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, + stackPosType_t type) const; + static void internalGetPosition(Item* item, Position& pos, uint8_t& stackpos); + + static std::string getTradeErrorDescription(ReturnValue ret, Item* item); + + /** + * Returns a creature based on the unique creature identifier + * \param id is the unique creature id to get a creature pointer to + * \returns A Creature pointer to the creature + */ + Creature* getCreatureByID(uint32_t id); + + /** + * Returns a monster based on the unique creature identifier + * \param id is the unique monster id to get a monster pointer to + * \returns A Monster pointer to the monster + */ + Monster* getMonsterByID(uint32_t id); + + /** + * Returns an npc based on the unique creature identifier + * \param id is the unique npc id to get a npc pointer to + * \returns A NPC pointer to the npc + */ + Npc* getNpcByID(uint32_t id); + + /** + * Returns a player based on the unique creature identifier + * \param id is the unique player id to get a player pointer to + * \returns A Pointer to the player + */ + Player* getPlayerByID(uint32_t id); + + /** + * Returns a creature based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the creature + */ + Creature* getCreatureByName(const std::string& s); + + /** + * Returns a npc based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the npc + */ + Npc* getNpcByName(const std::string& s); + + /** + * Returns a player based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the player + */ + Player* getPlayerByName(const std::string& s); + + /** + * Returns a player based on guid + * \returns A Pointer to the player + */ + Player* getPlayerByGUID(const uint32_t& guid); + + /** + * Returns a player based on a string name identifier, with support for the "~" wildcard. + * \param s is the name identifier, with or without wildcard + * \param player will point to the found player (if any) + * \return "RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE" or "RETURNVALUE_NAMEISTOOAMBIGIOUS" + */ + ReturnValue getPlayerByNameWildcard(const std::string& s, Player*& player); + + /** + * Returns a player based on an account number identifier + * \param acc is the account identifier + * \returns A Pointer to the player + */ + Player* getPlayerByAccount(uint32_t acc); + + /** + * Place Creature on the map without sending out events to the surrounding. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forced If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); + + /** + * Place Creature on the map. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param force If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false, + MagicEffectClasses magicEffect = CONST_ME_TELEPORT); + + /** + * Remove Creature from the map. + * Removes the Creature from the map + * \param c Creature to remove + */ + bool removeCreature(Creature* creature, bool isLogout = true); + void executeDeath(uint32_t creatureId); + + void addCreatureCheck(Creature* creature); + static void removeCreatureCheck(Creature* creature); + + size_t getPlayersOnline() const { return players.size(); } + size_t getMonstersOnline() const { return monsters.size(); } + size_t getNpcsOnline() const { return npcs.size(); } + uint32_t getPlayersRecord() const { return playersRecord; } + + LightInfo getWorldLightInfo() const { return {lightLevel, lightColor}; } + void setWorldLightInfo(LightInfo lightInfo) + { + lightLevel = lightInfo.level; + lightColor = lightInfo.color; + for (const auto& it : players) { + it.second->sendWorldLight(lightInfo); } + } + void updateWorldLightLevel(); + + ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); + ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0); + + ReturnValue internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, Item* item, + uint32_t count, Item** _moveItem, uint32_t flags = 0, Creature* actor = nullptr, + Item* tradeItem = nullptr, const Position* fromPos = nullptr, + const Position* toPos = nullptr); + + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index = INDEX_WHEREEVER, uint32_t flags = 0, + bool test = false); + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, uint32_t flags, bool test, + uint32_t& remainderCount); + ReturnValue internalRemoveItem(Item* item, int32_t count = -1, bool test = false, uint32_t flags = 0); + + ReturnValue internalPlayerAddItem(Player* player, Item* item, bool dropOnMap = true, + slots_t slot = CONST_SLOT_WHEREEVER); + + /** + * Find an item of a certain type + * \param cylinder to search the item + * \param itemId is the item to remove + * \param subType is the extra type an item can have such as charges/fluidtype, default is -1 + * meaning it's not used + * \param depthSearch if true it will check child containers aswell + * \returns A pointer to the item to an item and nullptr if not found + */ + Item* findItemOfType(Cylinder* cylinder, uint16_t itemId, bool depthSearch = true, int32_t subType = -1) const; + + /** + * Remove/Add item(s) with a monetary value + * \param cylinder to remove the money from + * \param money is the amount to remove + * \param flags optional flags to modify the default behavior + * \returns true if the removal was successful + */ + bool removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Add item(s) with monetary value + * \param cylinder which will receive money + * \param money the amount to give + * \param flags optional flags to modify default behavior + */ + void addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Transform one item to another type/count + * \param item is the item to transform + * \param newId is the new itemid + * \param newCount is the new count value, use default value (-1) to not change it + * \returns true if the transformation was successful + */ + Item* transformItem(Item* item, uint16_t newId, int32_t newCount = -1); + + /** + * Teleports an object to another position + * \param thing is the object to teleport + * \param newPos is the new position + * \param pushMove force teleport if false + * \param flags optional flags to modify default behavior + * \returns true if the teleportation was successful + */ + ReturnValue internalTeleport(Thing* thing, const Position& newPos, bool pushMove = true, uint32_t flags = 0); + + /** + * Turn a creature to a different direction. + * \param creature Creature to change the direction + * \param dir Direction to turn to + */ + bool internalCreatureTurn(Creature* creature, Direction dir); + + /** + * Creature wants to say something. + * \param creature Creature pointer + * \param type Type of message + * \param text The text to say + */ + bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, bool ghostMode, + SpectatorVec* spectatorsPtr = nullptr, const Position* pos = nullptr, bool echo = false); + + void loadPlayersRecord(); + void checkPlayersRecord(); + + void sendGuildMotd(uint32_t playerId); + void kickPlayer(uint32_t playerId, bool displayEffect); + void playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category); + void playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, + const std::string& description, const std::string& comment); + void playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice); + void playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, + uint8_t reportReason, const std::string& comment, const std::string& translation); + + bool internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem); + void internalCloseTrade(Player* player, bool sendCancel = true); + bool playerBroadcastMessage(Player* player, const std::string& text) const; + void broadcastMessage(const std::string& text, MessageClasses type) const; + + // Implementation of player invoked events + void playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count); + void playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, + const Position& toPos); + void playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, + Tile* toTile); + void playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count); + void playerMoveItem(Player* player, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder); + void playerEquipItem(uint32_t playerId, uint16_t spriteId); + void playerMove(uint32_t playerId, Direction direction); + void playerCreatePrivateChannel(uint32_t playerId); + void playerChannelInvite(uint32_t playerId, const std::string& name); + void playerChannelExclude(uint32_t playerId, const std::string& name); + void playerRequestChannels(uint32_t playerId); + void playerOpenChannel(uint32_t playerId, uint16_t channelId); + void playerCloseChannel(uint32_t playerId, uint16_t channelId); + void playerOpenPrivateChannel(uint32_t playerId, std::string receiver); + void playerCloseNpcChannel(uint32_t playerId); + void playerReceivePing(uint32_t playerId); + void playerReceivePingBack(uint32_t playerId); + void playerAutoWalk(uint32_t playerId, const std::vector& listDir); + void playerStopAutoWalk(uint32_t playerId); + void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint16_t fromSpriteId, + const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId); + void playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId); + void playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, + uint16_t spriteId); + void playerCloseContainer(uint32_t playerId, uint8_t cid); + void playerMoveUpContainer(uint32_t playerId, uint8_t cid); + void playerUpdateContainer(uint32_t playerId, uint8_t cid); + void playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId); + void playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text); + void playerBrowseField(uint32_t playerId, const Position& pos); + void playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index); + void playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text); + void playerWrapItem(uint32_t playerId, const Position& position, uint8_t stackPos, const uint16_t spriteId); + void playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, uint32_t tradePlayerId, + uint16_t spriteId); + void playerAcceptTrade(uint32_t playerId); + void playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index); + void playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreCap = false, + bool inBackpacks = false); + void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, + bool ignoreEquipped = false); + void playerCloseShop(uint32_t playerId); + void playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count); + void playerCloseTrade(uint32_t playerId); + void playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId); + void playerFollowCreature(uint32_t playerId, uint32_t creatureId); + void playerCancelAttackAndFollow(uint32_t playerId); + void playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool chaseMode, bool secureMode); + void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos); + void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); + void playerRequestAddVip(uint32_t playerId, const std::string& name); + void playerRequestRemoveVip(uint32_t playerId, uint32_t guid); + void playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, + bool notify); + void playerTurn(uint32_t playerId, Direction dir); + void playerRequestOutfit(uint32_t playerId); + void playerRequestEditPodium(uint32_t playerId, const Position& position, uint8_t stackPos, + const uint16_t spriteId); + void playerEditPodium(uint32_t playerId, Outfit_t outfit, const Position& position, uint8_t stackPos, + const uint16_t spriteId, bool podiumVisible, Direction direction); + void playerShowQuestLog(uint32_t playerId); + void playerShowQuestLine(uint32_t playerId, uint16_t questId); + void playerResetQuestTracker(uint32_t playerId, const std::vector& missionIds); + void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, + const std::string& text); + void playerChangeOutfit(uint32_t playerId, Outfit_t outfit); + void playerInviteToParty(uint32_t playerId, uint32_t invitedId); + void playerJoinParty(uint32_t playerId, uint32_t leaderId); + void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId); + void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); + void playerLeaveParty(uint32_t playerId); + void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); + void playerToggleMount(uint32_t playerId, bool mount); + void playerLeaveMarket(uint32_t playerId); + void playerBrowseMarket(uint32_t playerId, uint16_t spriteId); + void playerBrowseMarketOwnOffers(uint32_t playerId); + void playerBrowseMarketOwnHistory(uint32_t playerId); + void playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint64_t price, + bool anonymous); + void playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter); + void playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount); + + void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer); + + std::vector getMarketItemList(uint16_t wareId, uint16_t sufficientCount, const Player& player); + + void cleanup(); + void shutdown(); + void ReleaseCreature(Creature* creature); + void ReleaseItem(Item* item); + + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + bool sameFloor = false, int32_t rangex = Map::maxClientViewportX, + int32_t rangey = Map::maxClientViewportY) const; + bool isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor = false) const; + + void changeSpeed(Creature* creature, int32_t varSpeedDelta); + void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit); + void internalCreatureChangeVisible(Creature* creature, bool visible); + void changeLight(const Creature* creature); + void updateCreatureSkull(const Creature* creature); + void updatePlayerShield(Player* player); + void updateCreatureWalkthrough(const Creature* creature); + + GameState_t getGameState() const; + void setGameState(GameState_t newState); + void saveGameState(); + + // Events + void checkCreatureWalk(uint32_t creatureId); + void updateCreatureWalk(uint32_t creatureId); + void checkCreatureAttack(uint32_t creatureId); + void checkCreatures(size_t index); + void checkLight(); + + bool combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, + bool field, bool ignoreResistances = false); + + void combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect); + + bool combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage); + bool combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage); + + // animation help functions + void addCreatureHealth(const Creature* target); + static void addCreatureHealth(const SpectatorVec& spectators, const Creature* target); + void addMagicEffect(const Position& pos, uint8_t effect); + static void addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect); + void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect); + static void addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, + uint8_t effect); + + void setAccountStorageValue(const uint32_t accountId, const uint32_t key, const int32_t value); + int32_t getAccountStorageValue(const uint32_t accountId, const uint32_t key) const; + void loadAccountStorageValues(); + bool saveAccountStorageValues() const; + + void startDecay(Item* item); + + int16_t getWorldTime() { return worldTime; } + void updateWorldTime(); + + void sendOfflineTrainingDialog(Player* player); + + const std::unordered_map& getPlayers() const { return players; } + const std::map& getNpcs() const { return npcs; } + + void addPlayer(Player* player); + void removePlayer(Player* player); + + void addNpc(Npc* npc); + void removeNpc(Npc* npc); + + void addMonster(Monster* monster); + void removeMonster(Monster* monster); + + Guild* getGuild(uint32_t id) const; + void addGuild(Guild* guild); + void removeGuild(uint32_t guildId); + void decreaseBrowseFieldRef(const Position& pos); + + std::unordered_map browseFields; + + void internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable); + + BedItem* getBedBySleeper(uint32_t guid) const; + void setBedSleeper(BedItem* bed, uint32_t guid); + void removeBedSleeper(uint32_t guid); - void setWorldType(WorldType_t type); - WorldType_t getWorldType() const { - return worldType; - } + void updatePodium(Item* item); - Cylinder* internalGetCylinder(Player* player, const Position& pos) const; - Thing* internalGetThing(Player* player, const Position& pos, int32_t index, - uint32_t spriteId, stackPosType_t type) const; - static void internalGetPosition(Item* item, Position& pos, uint8_t& stackpos); - - static std::string getTradeErrorDescription(ReturnValue ret, Item* item); - - /** - * Returns a creature based on the unique creature identifier - * \param id is the unique creature id to get a creature pointer to - * \returns A Creature pointer to the creature - */ - Creature* getCreatureByID(uint32_t id); - - /** - * Returns a monster based on the unique creature identifier - * \param id is the unique monster id to get a monster pointer to - * \returns A Monster pointer to the monster - */ - Monster* getMonsterByID(uint32_t id); - - /** - * Returns an npc based on the unique creature identifier - * \param id is the unique npc id to get a npc pointer to - * \returns A NPC pointer to the npc - */ - Npc* getNpcByID(uint32_t id); - - /** - * Returns a player based on the unique creature identifier - * \param id is the unique player id to get a player pointer to - * \returns A Pointer to the player - */ - Player* getPlayerByID(uint32_t id); - - /** - * Returns a creature based on a string name identifier - * \param s is the name identifier - * \returns A Pointer to the creature - */ - Creature* getCreatureByName(const std::string& s); - - /** - * Returns a npc based on a string name identifier - * \param s is the name identifier - * \returns A Pointer to the npc - */ - Npc* getNpcByName(const std::string& s); - - /** - * Returns a player based on a string name identifier - * \param s is the name identifier - * \returns A Pointer to the player - */ - Player* getPlayerByName(const std::string& s); - - /** - * Returns a player based on guid - * \returns A Pointer to the player - */ - Player* getPlayerByGUID(const uint32_t& guid); - - /** - * Returns a player based on a string name identifier, with support for the "~" wildcard. - * \param s is the name identifier, with or without wildcard - * \param player will point to the found player (if any) - * \return "RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE" or "RETURNVALUE_NAMEISTOOAMBIGIOUS" - */ - ReturnValue getPlayerByNameWildcard(const std::string& s, Player*& player); - - /** - * Returns a player based on an account number identifier - * \param acc is the account identifier - * \returns A Pointer to the player - */ - Player* getPlayerByAccount(uint32_t acc); - - /** - * Place Creature on the map without sending out events to the surrounding. - * \param creature Creature to place on the map - * \param pos The position to place the creature - * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away - * \param forced If true, placing the creature will not fail because of obstacles (creatures/items) - */ - bool internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); - - /** - * Place Creature on the map. - * \param creature Creature to place on the map - * \param pos The position to place the creature - * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away - * \param force If true, placing the creature will not fail because of obstacles (creatures/items) - */ - bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); - - /** - * Remove Creature from the map. - * Removes the Creature from the map - * \param c Creature to remove - */ - bool removeCreature(Creature* creature, bool isLogout = true); - void executeDeath(uint32_t creatureId); - - void addCreatureCheck(Creature* creature); - static void removeCreatureCheck(Creature* creature); - - size_t getPlayersOnline() const { - return players.size(); - } - size_t getMonstersOnline() const { - return monsters.size(); - } - size_t getNpcsOnline() const { - return npcs.size(); - } - uint32_t getPlayersRecord() const { - return playersRecord; - } + Item* getUniqueItem(uint16_t uniqueId); + bool addUniqueItem(uint16_t uniqueId, Item* item); + void removeUniqueItem(uint16_t uniqueId); - LightInfo getWorldLightInfo() const { - return {lightLevel, lightColor}; - } - void setWorldLightInfo(LightInfo lightInfo) { - lightLevel = lightInfo.level; - lightColor = lightInfo.color; - for (const auto& it : players) { - it.second->sendWorldLight(lightInfo); - } - } - void updateWorldLightLevel(); - - ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); - ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0); - - ReturnValue internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, - Item* item, uint32_t count, Item** _moveItem, uint32_t flags = 0, Creature* actor = nullptr, Item* tradeItem = nullptr, const Position* fromPos = nullptr, const Position* toPos = nullptr); - - ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index = INDEX_WHEREEVER, - uint32_t flags = 0, bool test = false); - ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, - uint32_t flags, bool test, uint32_t& remainderCount); - ReturnValue internalRemoveItem(Item* item, int32_t count = -1, bool test = false, uint32_t flags = 0); - - ReturnValue internalPlayerAddItem(Player* player, Item* item, bool dropOnMap = true, slots_t slot = CONST_SLOT_WHEREEVER); - - /** - * Find an item of a certain type - * \param cylinder to search the item - * \param itemId is the item to remove - * \param subType is the extra type an item can have such as charges/fluidtype, default is -1 - * meaning it's not used - * \param depthSearch if true it will check child containers aswell - * \returns A pointer to the item to an item and nullptr if not found - */ - Item* findItemOfType(Cylinder* cylinder, uint16_t itemId, - bool depthSearch = true, int32_t subType = -1) const; - - /** - * Remove/Add item(s) with a monetary value - * \param cylinder to remove the money from - * \param money is the amount to remove - * \param flags optional flags to modify the default behavior - * \returns true if the removal was successful - */ - bool removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); - - /** - * Add item(s) with monetary value - * \param cylinder which will receive money - * \param money the amount to give - * \param flags optional flags to modify default behavior - */ - void addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); - - /** - * Transform one item to another type/count - * \param item is the item to transform - * \param newId is the new itemid - * \param newCount is the new count value, use default value (-1) to not change it - * \returns true if the transformation was successful - */ - Item* transformItem(Item* item, uint16_t newId, int32_t newCount = -1); - - /** - * Teleports an object to another position - * \param thing is the object to teleport - * \param newPos is the new position - * \param pushMove force teleport if false - * \param flags optional flags to modify default behavior - * \returns true if the teleportation was successful - */ - ReturnValue internalTeleport(Thing* thing, const Position& newPos, bool pushMove = true, uint32_t flags = 0); - - /** - * Turn a creature to a different direction. - * \param creature Creature to change the direction - * \param dir Direction to turn to - */ - bool internalCreatureTurn(Creature* creature, Direction dir); - - /** - * Creature wants to say something. - * \param creature Creature pointer - * \param type Type of message - * \param text The text to say - */ - bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, - bool ghostMode, SpectatorVec* spectatorsPtr = nullptr, const Position* pos = nullptr); - - void loadPlayersRecord(); - void checkPlayersRecord(); - - void sendGuildMotd(uint32_t playerId); - void kickPlayer(uint32_t playerId, bool displayEffect); - void playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category); - void playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment); - void playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice); - void playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); - - bool internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem); - void internalCloseTrade(Player* player); - bool playerBroadcastMessage(Player* player, const std::string& text) const; - void broadcastMessage(const std::string& text, MessageClasses type) const; - - //Implementation of player invoked events - void playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, - const Position& toPos, uint8_t count); - void playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos); - void playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile); - void playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count); - void playerMoveItem(Player* player, const Position& fromPos, - uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder); - void playerEquipItem(uint32_t playerId, uint16_t spriteId); - void playerMove(uint32_t playerId, Direction direction); - void playerCreatePrivateChannel(uint32_t playerId); - void playerChannelInvite(uint32_t playerId, const std::string& name); - void playerChannelExclude(uint32_t playerId, const std::string& name); - void playerRequestChannels(uint32_t playerId); - void playerOpenChannel(uint32_t playerId, uint16_t channelId); - void playerCloseChannel(uint32_t playerId, uint16_t channelId); - void playerOpenPrivateChannel(uint32_t playerId, std::string& receiver); - void playerCloseNpcChannel(uint32_t playerId); - void playerReceivePing(uint32_t playerId); - void playerReceivePingBack(uint32_t playerId); - void playerAutoWalk(uint32_t playerId, const std::vector& listDir); - void playerStopAutoWalk(uint32_t playerId); - void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, - uint16_t fromSpriteId, const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId); - void playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId); - void playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId); - void playerCloseContainer(uint32_t playerId, uint8_t cid); - void playerMoveUpContainer(uint32_t playerId, uint8_t cid); - void playerUpdateContainer(uint32_t playerId, uint8_t cid); - void playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId); - void playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text); - void playerBrowseField(uint32_t playerId, const Position& pos); - void playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index); - void playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text); - void playerWrapItem(uint32_t playerId, const Position& position, uint8_t stackPos, const uint16_t spriteId); - void playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, - uint32_t tradePlayerId, uint16_t spriteId); - void playerAcceptTrade(uint32_t playerId); - void playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index); - void playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, - bool ignoreCap = false, bool inBackpacks = false); - void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, - uint8_t amount, bool ignoreEquipped = false); - void playerCloseShop(uint32_t playerId); - void playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count); - void playerCloseTrade(uint32_t playerId); - void playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId); - void playerFollowCreature(uint32_t playerId, uint32_t creatureId); - void playerCancelAttackAndFollow(uint32_t playerId); - void playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool chaseMode, bool secureMode); - void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos); - void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); - void playerRequestAddVip(uint32_t playerId, const std::string& name); - void playerRequestRemoveVip(uint32_t playerId, uint32_t guid); - void playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); - void playerTurn(uint32_t playerId, Direction dir); - void playerRequestOutfit(uint32_t playerId); - void playerShowQuestLog(uint32_t playerId); - void playerShowQuestLine(uint32_t playerId, uint16_t questId); - void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, - const std::string& receiver, const std::string& text); - void playerChangeOutfit(uint32_t playerId, Outfit_t outfit); - void playerInviteToParty(uint32_t playerId, uint32_t invitedId); - void playerJoinParty(uint32_t playerId, uint32_t leaderId); - void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId); - void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); - void playerLeaveParty(uint32_t playerId); - void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); - void playerToggleMount(uint32_t playerId, bool mount); - void playerLeaveMarket(uint32_t playerId); - void playerBrowseMarket(uint32_t playerId, uint16_t spriteId); - void playerBrowseMarketOwnOffers(uint32_t playerId); - void playerBrowseMarketOwnHistory(uint32_t playerId); - void playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous); - void playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter); - void playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount); - - void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer); - - std::forward_list getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest* depotChest, Inbox* inbox); - - void cleanup(); - void shutdown(); - void ReleaseCreature(Creature* creature); - void ReleaseItem(Item* item); - - bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, - int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const; - bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const; - - void changeSpeed(Creature* creature, int32_t varSpeedDelta); - void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit); - void internalCreatureChangeVisible(Creature* creature, bool visible); - void changeLight(const Creature* creature); - void updateCreatureSkull(const Creature* creature); - void updatePlayerShield(Player* player); - void updatePlayerHelpers(const Player& player); - void updateCreatureType(Creature* creature); - void updateCreatureWalkthrough(const Creature* creature); - - GameState_t getGameState() const; - void setGameState(GameState_t newState); - void saveGameState(); - - //Events - void checkCreatureWalk(uint32_t creatureId); - void updateCreatureWalk(uint32_t creatureId); - void checkCreatureAttack(uint32_t creatureId); - void checkCreatures(size_t index); - void checkLight(); - - bool combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field, bool ignoreResistances = false); - - void combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect); - - bool combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage); - bool combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage); - - //animation help functions - void addCreatureHealth(const Creature* target); - static void addCreatureHealth(const SpectatorVec& spectators, const Creature* target); - void addMagicEffect(const Position& pos, uint8_t effect); - static void addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect); - void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect); - static void addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect); - - void startDecay(Item* item); - - int16_t getWorldTime() { return worldTime; } - void updateWorldTime(); - - void loadMotdNum(); - void saveMotdNum() const; - const std::string& getMotdHash() const { return motdHash; } - uint32_t getMotdNum() const { return motdNum; } - void incrementMotdNum() { motdNum++; } - - void sendOfflineTrainingDialog(Player* player); - - const std::unordered_map& getPlayers() const { return players; } - const std::map& getNpcs() const { return npcs; } - - void addPlayer(Player* player); - void removePlayer(Player* player); - - void addNpc(Npc* npc); - void removeNpc(Npc* npc); - - void addMonster(Monster* monster); - void removeMonster(Monster* monster); - - Guild* getGuild(uint32_t id) const; - void addGuild(Guild* guild); - void removeGuild(uint32_t guildId); - void decreaseBrowseFieldRef(const Position& pos); - - std::unordered_map browseFields; - - void internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable); - - BedItem* getBedBySleeper(uint32_t guid) const; - void setBedSleeper(BedItem* bed, uint32_t guid); - void removeBedSleeper(uint32_t guid); - - Item* getUniqueItem(uint16_t uniqueId); - bool addUniqueItem(uint16_t uniqueId, Item* item); - void removeUniqueItem(uint16_t uniqueId); + bool reload(ReloadTypes_t reloadType); - bool reload(ReloadTypes_t reloadType); + Groups groups; + Map map; + Mounts mounts; + Raids raids; + Quests quests; - Groups groups; - Map map; - Mounts mounts; - Raids raids; - Quests quests; - - std::forward_list toDecayItems; - - std::unordered_set getTilesToClean() const { - return tilesToClean; - } - void addTileToClean(Tile* tile) { - tilesToClean.emplace(tile); - } - void removeTileToClean(Tile* tile) { - tilesToClean.erase(tile); - } - void clearTilesToClean() { - tilesToClean.clear(); - } + std::forward_list toDecayItems; - private: - bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text); - void playerWhisper(Player* player, const std::string& text); - bool playerYell(Player* player, const std::string& text); - bool playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text); - void playerSpeakToNpc(Player* player, const std::string& text); + std::unordered_set getTilesToClean() const { return tilesToClean; } + void addTileToClean(Tile* tile) { tilesToClean.emplace(tile); } + void removeTileToClean(Tile* tile) { tilesToClean.erase(tile); } + void clearTilesToClean() { tilesToClean.clear(); } - void checkDecay(); - void internalDecayItem(Item* item); +private: + bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text); + void playerWhisper(Player* player, const std::string& text); + bool playerYell(Player* player, const std::string& text); + bool playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text); + void playerSpeakToNpc(Player* player, const std::string& text); - std::unordered_map players; - std::unordered_map mappedPlayerNames; - std::unordered_map mappedPlayerGuids; - std::unordered_map guilds; - std::unordered_map uniqueItems; - std::map stages; + void checkDecay(); + void internalDecayItem(Item* item); - std::list decayItems[EVENT_DECAY_BUCKETS]; - std::list checkCreatureLists[EVENT_CREATURECOUNT]; + std::unordered_map players; + std::unordered_map mappedPlayerNames; + std::unordered_map mappedPlayerGuids; + std::unordered_map guilds; + std::unordered_map uniqueItems; + std::map stages; + std::unordered_map> accountStorageMap; - std::vector ToReleaseCreatures; - std::vector ToReleaseItems; + std::list decayItems[EVENT_DECAY_BUCKETS]; + std::list checkCreatureLists[EVENT_CREATURECOUNT]; - size_t lastBucket = 0; + std::vector ToReleaseCreatures; + std::vector ToReleaseItems; - WildcardTreeNode wildcardTree { false }; + size_t lastBucket = 0; - std::map npcs; - std::map monsters; + WildcardTreeNode wildcardTree{false}; - //list of items that are in trading state, mapped to the player - std::map tradeItems; + std::map npcs; + std::map monsters; - std::map bedSleepersMap; + // list of items that are in trading state, mapped to the player + std::map tradeItems; - std::unordered_set tilesToClean; + std::map bedSleepersMap; - ModalWindow offlineTrainingWindow { std::numeric_limits::max(), "Choose a Skill", "Please choose a skill:" }; + std::unordered_set tilesToClean; - static constexpr uint8_t LIGHT_DAY = 250; - static constexpr uint8_t LIGHT_NIGHT = 40; - // 1h realtime = 1day worldtime - // 2.5s realtime = 1min worldtime - // worldTime is calculated in minutes - static constexpr int16_t GAME_SUNRISE = 360; - static constexpr int16_t GAME_DAYTIME = 480; - static constexpr int16_t GAME_SUNSET = 1080; - static constexpr int16_t GAME_NIGHTTIME = 1200; - static constexpr float LIGHT_CHANGE_SUNRISE = static_cast(float(float(LIGHT_DAY - LIGHT_NIGHT) / float(GAME_DAYTIME - GAME_SUNRISE)) * 100) / 100.0f; - static constexpr float LIGHT_CHANGE_SUNSET = static_cast(float(float(LIGHT_DAY - LIGHT_NIGHT) / float(GAME_NIGHTTIME - GAME_SUNSET)) * 100) / 100.0f; + ModalWindow offlineTrainingWindow{std::numeric_limits::max(), "Choose a Skill", "Please choose a skill:"}; - uint8_t lightLevel = LIGHT_DAY; - uint8_t lightColor = 215; - int16_t worldTime = 0; + static constexpr uint8_t LIGHT_DAY = 250; + static constexpr uint8_t LIGHT_NIGHT = 40; + // 1h realtime = 1day worldtime + // 2.5s realtime = 1min worldtime + // worldTime is calculated in minutes + static constexpr int16_t GAME_SUNRISE = 360; + static constexpr int16_t GAME_DAYTIME = 480; + static constexpr int16_t GAME_SUNSET = 1080; + static constexpr int16_t GAME_NIGHTTIME = 1200; + static constexpr float LIGHT_CHANGE_SUNRISE = + static_cast(float(float(LIGHT_DAY - LIGHT_NIGHT) / float(GAME_DAYTIME - GAME_SUNRISE)) * 100) / 100.0f; + static constexpr float LIGHT_CHANGE_SUNSET = + static_cast(float(float(LIGHT_DAY - LIGHT_NIGHT) / float(GAME_NIGHTTIME - GAME_SUNSET)) * 100) / 100.0f; - GameState_t gameState = GAME_STATE_NORMAL; - WorldType_t worldType = WORLD_TYPE_PVP; + uint8_t lightLevel = LIGHT_DAY; + uint8_t lightColor = 215; + int16_t worldTime = 0; - ServiceManager* serviceManager = nullptr; + GameState_t gameState = GAME_STATE_NORMAL; + WorldType_t worldType = WORLD_TYPE_PVP; - void updatePlayersRecord() const; - uint32_t playersRecord = 0; + ServiceManager* serviceManager = nullptr; - std::string motdHash; - uint32_t motdNum = 0; + void updatePlayersRecord() const; + uint32_t playersRecord = 0; - uint32_t lastStageLevel = 0; - bool stagesEnabled = false; - bool useLastStageLevel = false; + uint32_t lastStageLevel = 0; + bool stagesEnabled = false; + bool useLastStageLevel = false; }; -#endif +#endif // FS_GAME_H diff --git a/src/globalevent.cpp b/src/globalevent.cpp index daa233efa2..6f700e7c1d 100644 --- a/src/globalevent.cpp +++ b/src/globalevent.cpp @@ -1,46 +1,24 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "configmanager.h" #include "globalevent.h" -#include "tools.h" -#include "scheduler.h" + +#include "configmanager.h" #include "pugicast.h" +#include "scheduler.h" +#include "tools.h" extern ConfigManager g_config; -GlobalEvents::GlobalEvents() : - scriptInterface("GlobalEvent Interface") -{ - scriptInterface.initState(); -} +GlobalEvents::GlobalEvents() : scriptInterface("GlobalEvent Interface") { scriptInterface.initState(); } -GlobalEvents::~GlobalEvents() -{ - clear(false); -} +GlobalEvents::~GlobalEvents() { clear(false); } void GlobalEvents::clearMap(GlobalEventMap& map, bool fromLua) { - for (auto it = map.begin(); it != map.end(); ) { + for (auto it = map.begin(); it != map.end();) { if (fromLua == it->second.fromLua) { it = map.erase(it); } else { @@ -65,7 +43,7 @@ void GlobalEvents::clear(bool fromLua) Event_ptr GlobalEvents::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "globalevent") != 0) { + if (!caseInsensitiveEqual(nodeName, "globalevent")) { return nullptr; } return Event_ptr(new GlobalEvent(&scriptInterface)); @@ -73,12 +51,12 @@ Event_ptr GlobalEvents::getEvent(const std::string& nodeName) bool GlobalEvents::registerEvent(Event_ptr event, const pugi::xml_node&) { - GlobalEvent_ptr globalEvent{static_cast(event.release())}; //event is guaranteed to be a GlobalEvent + GlobalEvent_ptr globalEvent{static_cast(event.release())}; // event is guaranteed to be a GlobalEvent if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { auto result = timerMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { if (timerEventId == 0) { - timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this))); + timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, [this]() { timer(); })); } return true; } @@ -91,24 +69,25 @@ bool GlobalEvents::registerEvent(Event_ptr event, const pugi::xml_node&) auto result = thinkMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { if (thinkEventId == 0) { - thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this))); + thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, [this]() { think(); })); } return true; } } - std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl; + std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " + << globalEvent->getName() << std::endl; return false; } bool GlobalEvents::registerLuaEvent(GlobalEvent* event) { - GlobalEvent_ptr globalEvent{ event }; + GlobalEvent_ptr globalEvent{event}; if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { auto result = timerMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { if (timerEventId == 0) { - timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this))); + timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, [this]() { timer(); })); } return true; } @@ -121,20 +100,18 @@ bool GlobalEvents::registerLuaEvent(GlobalEvent* event) auto result = thinkMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { if (thinkEventId == 0) { - thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this))); + thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, [this]() { think(); })); } return true; } } - std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl; + std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " + << globalEvent->getName() << std::endl; return false; } -void GlobalEvents::startup() const -{ - execute(GLOBALEVENT_STARTUP); -} +void GlobalEvents::startup() const { execute(GLOBALEVENT_STARTUP); } void GlobalEvents::timer() { @@ -172,8 +149,8 @@ void GlobalEvents::timer() } if (nextScheduledTime != std::numeric_limits::max()) { - timerEventId = g_scheduler.addEvent(createSchedulerTask(std::max(1000, nextScheduledTime * 1000), - std::bind(&GlobalEvents::timer, this))); + timerEventId = g_scheduler.addEvent( + createSchedulerTask(std::max(1000, nextScheduledTime * 1000), [this]() { timer(); })); } } @@ -194,7 +171,8 @@ void GlobalEvents::think() } if (!globalEvent.executeEvent()) { - std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent.getName() << std::endl; + std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent.getName() + << std::endl; } nextExecutionTime = globalEvent.getInterval(); @@ -206,7 +184,7 @@ void GlobalEvents::think() } if (nextScheduledTime != std::numeric_limits::max()) { - thinkEventId = g_scheduler.addEvent(createSchedulerTask(nextScheduledTime, std::bind(&GlobalEvents::think, this))); + thinkEventId = g_scheduler.addEvent(createSchedulerTask(nextScheduledTime, [this]() { think(); })); } } @@ -224,8 +202,10 @@ GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) { // TODO: This should be better implemented. Maybe have a map for every type. switch (type) { - case GLOBALEVENT_NONE: return thinkMap; - case GLOBALEVENT_TIMER: return timerMap; + case GLOBALEVENT_NONE: + return thinkMap; + case GLOBALEVENT_TIMER: + return timerMap; case GLOBALEVENT_STARTUP: case GLOBALEVENT_SHUTDOWN: case GLOBALEVENT_RECORD: { @@ -237,7 +217,8 @@ GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) } return retMap; } - default: return GlobalEventMap(); + default: + return GlobalEventMap(); } } @@ -260,7 +241,8 @@ bool GlobalEvent::configureEvent(const pugi::xml_node& node) int32_t hour = params.front(); if (hour < 0 || hour > 23) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << attr.as_string() + << "\" for globalevent with name: " << name << std::endl; return false; } @@ -271,14 +253,16 @@ bool GlobalEvent::configureEvent(const pugi::xml_node& node) if (params.size() > 1) { min = params[1]; if (min < 0 || min > 59) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << attr.as_string() + << "\" for globalevent with name: " << name << std::endl; return false; } if (params.size() > 2) { sec = params[2]; if (sec < 0 || sec > 59) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << attr.as_string() + << "\" for globalevent with name: " << name << std::endl; return false; } } @@ -299,21 +283,23 @@ bool GlobalEvent::configureEvent(const pugi::xml_node& node) eventType = GLOBALEVENT_TIMER; } else if ((attr = node.attribute("type"))) { const char* value = attr.value(); - if (strcasecmp(value, "startup") == 0) { + if (caseInsensitiveEqual(value, "startup")) { eventType = GLOBALEVENT_STARTUP; - } else if (strcasecmp(value, "shutdown") == 0) { + } else if (caseInsensitiveEqual(value, "shutdown")) { eventType = GLOBALEVENT_SHUTDOWN; - } else if (strcasecmp(value, "record") == 0) { + } else if (caseInsensitiveEqual(value, "record")) { eventType = GLOBALEVENT_RECORD; } else { - std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() << "\" for globalevent with name " << name << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() + << "\" for globalevent with name " << name << std::endl; return false; } } else if ((attr = node.attribute("interval"))) { interval = std::max(SCHEDULER_MINTICKS, pugi::cast(attr.value())); nextExecution = OTSYS_TIME() + interval; } else { - std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << name << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << name + << std::endl; return false; } return true; @@ -322,17 +308,22 @@ bool GlobalEvent::configureEvent(const pugi::xml_node& node) std::string GlobalEvent::getScriptEventName() const { switch (eventType) { - case GLOBALEVENT_STARTUP: return "onStartup"; - case GLOBALEVENT_SHUTDOWN: return "onShutdown"; - case GLOBALEVENT_RECORD: return "onRecord"; - case GLOBALEVENT_TIMER: return "onTime"; - default: return "onThink"; + case GLOBALEVENT_STARTUP: + return "onStartup"; + case GLOBALEVENT_SHUTDOWN: + return "onShutdown"; + case GLOBALEVENT_RECORD: + return "onRecord"; + case GLOBALEVENT_TIMER: + return "onTime"; + default: + return "onThink"; } } bool GlobalEvent::executeRecord(uint32_t current, uint32_t old) { - //onRecord(current, old) + // onRecord(current, old) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - GlobalEvent::executeRecord] Call stack overflow" << std::endl; return false; diff --git a/src/globalevent.h b/src/globalevent.h index 6c65482246..28566ccdc9 100644 --- a/src/globalevent.h +++ b/src/globalevent.h @@ -1,29 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C -#define FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_GLOBALEVENT_H +#define FS_GLOBALEVENT_H + #include "baseevents.h" +#include "luascript.h" -#include "const.h" +class GlobalEvent; +using GlobalEvent_ptr = std::unique_ptr; +using GlobalEventMap = std::map; -enum GlobalEvent_t { +enum GlobalEvent_t +{ GLOBALEVENT_NONE, GLOBALEVENT_TIMER, @@ -32,95 +21,71 @@ enum GlobalEvent_t { GLOBALEVENT_RECORD, }; -class GlobalEvent; -using GlobalEvent_ptr = std::unique_ptr; -using GlobalEventMap = std::map; - class GlobalEvents final : public BaseEvents { - public: - GlobalEvents(); - ~GlobalEvents(); +public: + GlobalEvents(); + ~GlobalEvents(); - // non-copyable - GlobalEvents(const GlobalEvents&) = delete; - GlobalEvents& operator=(const GlobalEvents&) = delete; + // non-copyable + GlobalEvents(const GlobalEvents&) = delete; + GlobalEvents& operator=(const GlobalEvents&) = delete; - void startup() const; + void startup() const; - void timer(); - void think(); - void execute(GlobalEvent_t type) const; + void timer(); + void think(); + void execute(GlobalEvent_t type) const; - GlobalEventMap getEventMap(GlobalEvent_t type); - static void clearMap(GlobalEventMap& map, bool fromLua); + GlobalEventMap getEventMap(GlobalEvent_t type); + static void clearMap(GlobalEventMap& map, bool fromLua); - bool registerLuaEvent(GlobalEvent* event); - void clear(bool fromLua) override final; + bool registerLuaEvent(GlobalEvent* event); + void clear(bool fromLua) override final; - private: - std::string getScriptBaseName() const override { - return "globalevents"; - } +private: + std::string getScriptBaseName() const override { return "globalevents"; } - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - LuaScriptInterface& getScriptInterface() override { - return scriptInterface; - } - LuaScriptInterface scriptInterface; + LuaScriptInterface& getScriptInterface() override { return scriptInterface; } + LuaScriptInterface scriptInterface; - GlobalEventMap thinkMap, serverMap, timerMap; - int32_t thinkEventId = 0, timerEventId = 0; + GlobalEventMap thinkMap, serverMap, timerMap; + int32_t thinkEventId = 0, timerEventId = 0; }; class GlobalEvent final : public Event { - public: - explicit GlobalEvent(LuaScriptInterface* interface); - - bool configureEvent(const pugi::xml_node& node) override; - - bool executeRecord(uint32_t current, uint32_t old); - bool executeEvent() const; - - GlobalEvent_t getEventType() const { - return eventType; - } - void setEventType(GlobalEvent_t type) { - eventType = type; - } - - const std::string& getName() const { - return name; - } - void setName(std::string eventName) { - name = eventName; - } - - uint32_t getInterval() const { - return interval; - } - void setInterval(uint32_t eventInterval) { - interval |= eventInterval; - } - - int64_t getNextExecution() const { - return nextExecution; - } - void setNextExecution(int64_t time) { - nextExecution = time; - } - - private: - GlobalEvent_t eventType = GLOBALEVENT_NONE; - - std::string getScriptEventName() const override; - - std::string name; - int64_t nextExecution = 0; - uint32_t interval = 0; +public: + explicit GlobalEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) override; + + bool executeRecord(uint32_t current, uint32_t old); + bool executeEvent() const; + + GlobalEvent_t getEventType() const { return eventType; } + void setEventType(GlobalEvent_t type) { eventType = type; } + + const std::string& getName() const { return name; } + void setName(std::string eventName) { name = eventName; } + + uint32_t getInterval() const { return interval; } + void setInterval(uint32_t eventInterval) { interval |= eventInterval; } + + int64_t getNextExecution() const { return nextExecution; } + void setNextExecution(int64_t time) { nextExecution = time; } + +private: + GlobalEvent_t eventType = GLOBALEVENT_NONE; + + std::string getScriptEventName() const override; + + std::string name; + int64_t nextExecution = 0; + uint32_t interval = 0; }; -#endif +#endif // FS_GLOBALEVENT_H diff --git a/src/groups.cpp b/src/groups.cpp index de945517b3..d40f07a6d6 100644 --- a/src/groups.cpp +++ b/src/groups.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -25,44 +9,45 @@ #include "tools.h" const std::unordered_map ParsePlayerFlagMap = { - {"cannotusecombat", PlayerFlag_CannotUseCombat}, - {"cannotattackplayer", PlayerFlag_CannotAttackPlayer}, - {"cannotattackmonster", PlayerFlag_CannotAttackMonster}, - {"cannotbeattacked", PlayerFlag_CannotBeAttacked}, - {"canconvinceall", PlayerFlag_CanConvinceAll}, - {"cansummonall", PlayerFlag_CanSummonAll}, - {"canillusionall", PlayerFlag_CanIllusionAll}, - {"cansenseinvisibility", PlayerFlag_CanSenseInvisibility}, - {"ignoredbymonsters", PlayerFlag_IgnoredByMonsters}, - {"notgaininfight", PlayerFlag_NotGainInFight}, - {"hasinfinitemana", PlayerFlag_HasInfiniteMana}, - {"hasinfinitesoul", PlayerFlag_HasInfiniteSoul}, - {"hasnoexhaustion", PlayerFlag_HasNoExhaustion}, - {"cannotusespells", PlayerFlag_CannotUseSpells}, - {"cannotpickupitem", PlayerFlag_CannotPickupItem}, - {"canalwayslogin", PlayerFlag_CanAlwaysLogin}, - {"canbroadcast", PlayerFlag_CanBroadcast}, - {"canedithouses", PlayerFlag_CanEditHouses}, - {"cannotbebanned", PlayerFlag_CannotBeBanned}, - {"cannotbepushed", PlayerFlag_CannotBePushed}, - {"hasinfinitecapacity", PlayerFlag_HasInfiniteCapacity}, - {"cannotpushallcreatures", PlayerFlag_CanPushAllCreatures}, - {"cantalkredprivate", PlayerFlag_CanTalkRedPrivate}, - {"cantalkredchannel", PlayerFlag_CanTalkRedChannel}, - {"talkorangehelpchannel", PlayerFlag_TalkOrangeHelpChannel}, - {"notgainexperience", PlayerFlag_NotGainExperience}, - {"notgainmana", PlayerFlag_NotGainMana}, - {"notgainhealth", PlayerFlag_NotGainHealth}, - {"notgainskill", PlayerFlag_NotGainSkill}, - {"setmaxspeed", PlayerFlag_SetMaxSpeed}, - {"specialvip", PlayerFlag_SpecialVIP}, - {"notgenerateloot", PlayerFlag_NotGenerateLoot}, - {"ignoreprotectionzone", PlayerFlag_IgnoreProtectionZone}, - {"ignorespellcheck", PlayerFlag_IgnoreSpellCheck}, - {"ignoreweaponcheck", PlayerFlag_IgnoreWeaponCheck}, - {"cannotbemuted", PlayerFlag_CannotBeMuted}, - {"isalwayspremium", PlayerFlag_IsAlwaysPremium} -}; + {"cannotusecombat", PlayerFlag_CannotUseCombat}, + {"cannotattackplayer", PlayerFlag_CannotAttackPlayer}, + {"cannotattackmonster", PlayerFlag_CannotAttackMonster}, + {"cannotbeattacked", PlayerFlag_CannotBeAttacked}, + {"canconvinceall", PlayerFlag_CanConvinceAll}, + {"cansummonall", PlayerFlag_CanSummonAll}, + {"canillusionall", PlayerFlag_CanIllusionAll}, + {"cansenseinvisibility", PlayerFlag_CanSenseInvisibility}, + {"ignoredbymonsters", PlayerFlag_IgnoredByMonsters}, + {"notgaininfight", PlayerFlag_NotGainInFight}, + {"hasinfinitemana", PlayerFlag_HasInfiniteMana}, + {"hasinfinitesoul", PlayerFlag_HasInfiniteSoul}, + {"hasnoexhaustion", PlayerFlag_HasNoExhaustion}, + {"cannotusespells", PlayerFlag_CannotUseSpells}, + {"cannotpickupitem", PlayerFlag_CannotPickupItem}, + {"canalwayslogin", PlayerFlag_CanAlwaysLogin}, + {"canbroadcast", PlayerFlag_CanBroadcast}, + {"canedithouses", PlayerFlag_CanEditHouses}, + {"cannotbebanned", PlayerFlag_CannotBeBanned}, + {"cannotbepushed", PlayerFlag_CannotBePushed}, + {"hasinfinitecapacity", PlayerFlag_HasInfiniteCapacity}, + {"canpushallcreatures", PlayerFlag_CanPushAllCreatures}, + {"cantalkredprivate", PlayerFlag_CanTalkRedPrivate}, + {"cantalkredchannel", PlayerFlag_CanTalkRedChannel}, + {"talkorangehelpchannel", PlayerFlag_TalkOrangeHelpChannel}, + {"notgainexperience", PlayerFlag_NotGainExperience}, + {"notgainmana", PlayerFlag_NotGainMana}, + {"notgainhealth", PlayerFlag_NotGainHealth}, + {"notgainskill", PlayerFlag_NotGainSkill}, + {"setmaxspeed", PlayerFlag_SetMaxSpeed}, + {"specialvip", PlayerFlag_SpecialVIP}, + {"notgenerateloot", PlayerFlag_NotGenerateLoot}, + {"ignoreprotectionzone", PlayerFlag_IgnoreProtectionZone}, + {"ignorespellcheck", PlayerFlag_IgnoreSpellCheck}, + {"ignoreweaponcheck", PlayerFlag_IgnoreWeaponCheck}, + {"cannotbemuted", PlayerFlag_CannotBeMuted}, + {"isalwayspremium", PlayerFlag_IsAlwaysPremium}, + {"ignoreyellcheck", PlayerFlag_IgnoreYellCheck}, + {"ignoresendprivatecheck", PlayerFlag_IgnoreSendPrivateCheck}}; bool Groups::load() { @@ -75,7 +60,7 @@ bool Groups::load() for (auto groupNode : doc.child("groups").children()) { Group group; - group.id = pugi::cast(groupNode.attribute("id").value()); + group.id = pugi::cast(groupNode.attribute("id").value()); group.name = groupNode.attribute("name").as_string(); group.access = groupNode.attribute("access").as_bool(); group.maxDepotItems = pugi::cast(groupNode.attribute("maxdepotitems").value()); diff --git a/src/groups.h b/src/groups.h index 3297ed5d74..574a025faa 100644 --- a/src/groups.h +++ b/src/groups.h @@ -1,26 +1,11 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3 -#define FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3 +#ifndef FS_GROUPS_H +#define FS_GROUPS_H -struct Group { +struct Group +{ std::string name; uint64_t flags; uint32_t maxDepotItems; @@ -29,13 +14,14 @@ struct Group { bool access; }; -class Groups { - public: - bool load(); - Group* getGroup(uint16_t id); +class Groups +{ +public: + bool load(); + Group* getGroup(uint16_t id); - private: - std::vector groups; +private: + std::vector groups; }; -#endif +#endif // FS_GROUPS_H diff --git a/src/guild.cpp b/src/guild.cpp index dd211103bd..66804897b1 100644 --- a/src/guild.cpp +++ b/src/guild.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -25,21 +9,11 @@ extern Game g_game; -void Guild::addMember(Player* player) -{ - membersOnline.push_back(player); - for (Player* member : membersOnline) { - g_game.updatePlayerHelpers(*member); - } -} +void Guild::addMember(Player* player) { membersOnline.push_back(player); } void Guild::removeMember(Player* player) { membersOnline.remove(player); - for (Player* member : membersOnline) { - g_game.updatePlayerHelpers(*member); - } - g_game.updatePlayerHelpers(*player); if (membersOnline.empty()) { g_game.removeGuild(id); @@ -81,3 +55,33 @@ void Guild::addRank(uint32_t rankId, const std::string& rankName, uint8_t level) { ranks.emplace_back(std::make_shared(rankId, rankName, level)); } + +Guild* IOGuild::loadGuild(uint32_t guildId) +{ + Database& db = Database::getInstance(); + if (DBResult_ptr result = db.storeQuery(fmt::format("SELECT `name` FROM `guilds` WHERE `id` = {:d}", guildId))) { + Guild* guild = new Guild(guildId, result->getString("name")); + + if ((result = db.storeQuery( + fmt::format("SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = {:d}", guildId)))) { + do { + guild->addRank(result->getNumber("id"), result->getString("name"), + result->getNumber("level")); + } while (result->next()); + } + return guild; + } + return nullptr; +} + +uint32_t IOGuild::getGuildIdByName(const std::string& name) +{ + Database& db = Database::getInstance(); + + DBResult_ptr result = + db.storeQuery(fmt::format("SELECT `id` FROM `guilds` WHERE `name` = {:s}", db.escapeString(name))); + if (!result) { + return 0; + } + return result->getNumber("id"); +} diff --git a/src/guild.h b/src/guild.h index d3343c8dea..3047f0307e 100644 --- a/src/guild.h +++ b/src/guild.h @@ -1,84 +1,59 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC -#define FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC +#ifndef FS_GUILD_H +#define FS_GUILD_H class Player; -struct GuildRank { +struct GuildRank +{ uint32_t id; std::string name; uint8_t level; - GuildRank(uint32_t id, std::string name, uint8_t level) : - id(id), name(std::move(name)), level(level) {} + GuildRank(uint32_t id, std::string name, uint8_t level) : id(id), name(std::move(name)), level(level) {} }; using GuildRank_ptr = std::shared_ptr; class Guild { - public: - Guild(uint32_t id, std::string name) : name(std::move(name)), id(id) {} - - void addMember(Player* player); - void removeMember(Player* player); - - uint32_t getId() const { - return id; - } - const std::string& getName() const { - return name; - } - const std::list& getMembersOnline() const { - return membersOnline; - } - uint32_t getMemberCount() const { - return memberCount; - } - void setMemberCount(uint32_t count) { - memberCount = count; - } - - const std::vector& getRanks() const { - return ranks; - } - GuildRank_ptr getRankById(uint32_t rankId); - GuildRank_ptr getRankByName(const std::string& name) const; - GuildRank_ptr getRankByLevel(uint8_t level) const; - void addRank(uint32_t rankId, const std::string& rankName, uint8_t level); +public: + Guild(uint32_t id, std::string name) : name(std::move(name)), id(id) {} + + void addMember(Player* player); + void removeMember(Player* player); + + uint32_t getId() const { return id; } + const std::string& getName() const { return name; } + const std::list& getMembersOnline() const { return membersOnline; } + uint32_t getMemberCount() const { return memberCount; } + void setMemberCount(uint32_t count) { memberCount = count; } + + const std::vector& getRanks() const { return ranks; } + GuildRank_ptr getRankById(uint32_t rankId); + GuildRank_ptr getRankByName(const std::string& name) const; + GuildRank_ptr getRankByLevel(uint8_t level) const; + void addRank(uint32_t rankId, const std::string& rankName, uint8_t level); + + const std::string& getMotd() const { return motd; } + void setMotd(const std::string& motd) { this->motd = motd; } + +private: + std::list membersOnline; + std::vector ranks; + std::string name; + std::string motd; + uint32_t id; + uint32_t memberCount = 0; +}; - const std::string& getMotd() const { - return motd; - } - void setMotd(const std::string& motd) { - this->motd = motd; - } +using GuildWarVector = std::vector; - private: - std::list membersOnline; - std::vector ranks; - std::string name; - std::string motd; - uint32_t id; - uint32_t memberCount = 0; -}; +namespace IOGuild { +Guild* loadGuild(uint32_t guildId); +uint32_t getGuildIdByName(const std::string& name); +}; // namespace IOGuild -#endif +#endif // FS_GUILD_H diff --git a/src/house.cpp b/src/house.cpp index 1ed2912055..06d256ddfa 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -1,31 +1,17 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "pugicast.h" - #include "house.h" -#include "iologindata.h" -#include "game.h" -#include "configmanager.h" + #include "bed.h" +#include "configmanager.h" +#include "game.h" +#include "housetile.h" +#include "inbox.h" +#include "iologindata.h" +#include "pugicast.h" extern ConfigManager g_config; extern Game g_game; @@ -38,14 +24,13 @@ void House::addTile(HouseTile* tile) houseTiles.push_back(tile); } -void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/) +void House::setOwner(uint32_t guid, bool updateDatabase /* = true*/, Player* player /* = nullptr*/) { if (updateDatabase && owner != guid) { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id; - db.executeQuery(query.str()); + db.executeQuery(fmt::format( + "UPDATE `houses` SET `owner` = {:d}, `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = {:d}", + guid, id)); } if (isLoaded && owner == guid) { @@ -55,7 +40,7 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play isLoaded = true; if (owner != 0) { - //send items to depot + // send items to depot if (player) { transferToDepot(player); } else { @@ -77,7 +62,7 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play } } - //clean access lists + // clean access lists owner = 0; ownerAccountId = 0; setAccessList(SUBOWNER_LIST, ""); @@ -87,7 +72,8 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play door->setAccessList(""); } } else { - std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); + std::string strRentPeriod = + boost::algorithm::to_lower_copy(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); time_t currentTime = time(nullptr); if (strRentPeriod == "yearly") { currentTime += 24 * 60 * 60 * 365; @@ -114,30 +100,9 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play ownerAccountId = IOLoginData::getAccountIdByPlayerName(name); } } - - updateDoorDescription(); -} - -void House::updateDoorDescription() const -{ - std::ostringstream ss; - if (owner != 0) { - ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house."; - } else { - ss << "It belongs to house '" << houseName << "'. Nobody owns this house."; - - const int32_t housePrice = g_config.getNumber(ConfigManager::HOUSE_PRICE); - if (housePrice != -1 && g_config.getBoolean(ConfigManager::HOUSE_DOOR_SHOW_PRICE)) { - ss << " It costs " << (houseTiles.size() * housePrice) << " gold coins."; - } - } - - for (const auto& it : doorSet) { - it->setSpecialDescription(ss.str()); - } } -AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) +AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) const { if (!player) { return HOUSE_OWNER; @@ -207,7 +172,7 @@ void House::setAccessList(uint32_t listId, const std::string& textlist) return; } - //kick uninvited players + // kick uninvited players for (HouseTile* tile : houseTiles) { if (CreatureVector* creatures = tile->getCreatures()) { for (int32_t i = creatures->size(); --i >= 0;) { @@ -266,7 +231,8 @@ bool House::transferToDepot(Player* player) const } for (Item* item : moveItemList) { - g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), + nullptr, FLAG_NOLIMIT); } return true; } @@ -289,17 +255,13 @@ bool House::getAccessList(uint32_t listId, std::string& list) const return door->getAccessList(list); } -bool House::isInvited(const Player* player) -{ - return getHouseAccessLevel(player) != HOUSE_NOT_INVITED; -} +bool House::isInvited(const Player* player) const { return getHouseAccessLevel(player) != HOUSE_NOT_INVITED; } void House::addDoor(Door* door) { door->incrementReferenceCounter(); doorSet.insert(door); door->setHouse(this); - updateDoorDescription(); } void House::removeDoor(Door* door) @@ -353,7 +315,7 @@ bool House::canEditAccessList(uint32_t listId, const Player* player) HouseTransferItem* House::getTransferItem() { - if (transferItem != nullptr) { + if (transferItem) { return nullptr; } @@ -381,9 +343,7 @@ HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house) transferItem->incrementReferenceCounter(); transferItem->setID(ITEM_DOCUMENT_RO); transferItem->setSubType(1); - std::ostringstream ss; - ss << "It is a house transfer document for '" << house->getName() << "'."; - transferItem->setSpecialDescription(ss.str()); + transferItem->setSpecialDescription(fmt::format("It is a house transfer document for '{:s}'.", house->getName())); return transferItem; } @@ -426,22 +386,19 @@ void AccessList::parseList(const std::string& list) std::istringstream listStream(list); std::string line; - int lineNo = 1; + uint16_t lineNo = 1; while (getline(listStream, line)) { if (++lineNo > 100) { break; } - trimString(line); - trim_left(line, '\t'); - trim_right(line, '\t'); - trimString(line); + boost::algorithm::trim(line); if (line.empty() || line.front() == '#' || line.length() > 100) { continue; } - toLowerCaseString(line); + boost::algorithm::to_lower(line); std::string::size_type at_pos = line.find("@"); if (at_pos != std::string::npos) { @@ -452,7 +409,8 @@ void AccessList::parseList(const std::string& list) } } else if (line == "*") { allowEveryone = true; - } else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) { + } else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || + line.find("?") != std::string::npos) { continue; // regexp no longer supported } else { addPlayer(line); @@ -490,7 +448,7 @@ const Guild* getGuildByName(const std::string& name) return IOGuild::loadGuild(guildId); } -} +} // namespace void AccessList::addGuild(const std::string& name) { @@ -513,7 +471,7 @@ void AccessList::addGuildRank(const std::string& name, const std::string& rankNa } } -bool AccessList::isInList(const Player* player) +bool AccessList::isInList(const Player* player) const { if (allowEveryone) { return true; @@ -528,12 +486,9 @@ bool AccessList::isInList(const Player* player) return rank && guildRankList.find(rank->id) != guildRankList.end(); } -void AccessList::getList(std::string& list) const -{ - list = this->list; -} +void AccessList::getList(std::string& list) const { list = this->list; } -Door::Door(uint16_t type) : Item(type) {} +Door::Door(uint16_t type) : Item(type) {} Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream) { @@ -551,7 +506,7 @@ Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream) void Door::setHouse(House* house) { - if (this->house != nullptr) { + if (this->house) { return; } @@ -638,15 +593,12 @@ bool Houses::loadHousesXML(const std::string& filename) house->setName(houseNode.attribute("name").as_string()); - Position entryPos( - pugi::cast(houseNode.attribute("entryx").value()), - pugi::cast(houseNode.attribute("entryy").value()), - pugi::cast(houseNode.attribute("entryz").value()) - ); + Position entryPos(pugi::cast(houseNode.attribute("entryx").value()), + pugi::cast(houseNode.attribute("entryy").value()), + pugi::cast(houseNode.attribute("entryz").value())); if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) { std::cout << "[Warning - Houses::loadHousesXML] House entry not set" - << " - Name: " << house->getName() - << " - House id: " << houseId << std::endl; + << " - Name: " << house->getName() << " - House id: " << houseId << std::endl; } house->setEntryPos(entryPos); @@ -739,9 +691,9 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const break; } - std::ostringstream ss; - ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house."; - letter->setText(ss.str()); + letter->setText(fmt::format( + "Warning! \nThe {:s} rent of {:d} gold for your house \"{:s}\" is payable. Have it within {:d} days or you will lose this house.", + period, house->getRent(), house->getName(), daysLeft)); g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); house->setPayRentWarnings(house->getPayRentWarnings() + 1); } else { diff --git a/src/house.h b/src/house.h index 1a73a02f1f..21eb585328 100644 --- a/src/house.h +++ b/src/house.h @@ -1,107 +1,82 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4 -#define FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4 - -#include -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. +#ifndef FS_HOUSE_H +#define FS_HOUSE_H + +#include "const.h" #include "container.h" -#include "housetile.h" +#include "enums.h" #include "position.h" -class House; class BedItem; +class House; +class HouseTile; class Player; class AccessList { - public: - void parseList(const std::string& list); - void addPlayer(const std::string& name); - void addGuild(const std::string& name); - void addGuildRank(const std::string& name, const std::string& rankName); +public: + void parseList(const std::string& list); + void addPlayer(const std::string& name); + void addGuild(const std::string& name); + void addGuildRank(const std::string& name, const std::string& rankName); - bool isInList(const Player* player); + bool isInList(const Player* player) const; - void getList(std::string& list) const; + void getList(std::string& list) const; - private: - std::string list; - std::unordered_set playerList; - std::unordered_set guildRankList; - bool allowEveryone = false; +private: + std::string list; + std::unordered_set playerList; + std::unordered_set guildRankList; + bool allowEveryone = false; }; class Door final : public Item { - public: - explicit Door(uint16_t type); +public: + explicit Door(uint16_t type); - // non-copyable - Door(const Door&) = delete; - Door& operator=(const Door&) = delete; + // non-copyable + Door(const Door&) = delete; + Door& operator=(const Door&) = delete; - Door* getDoor() override { - return this; - } - const Door* getDoor() const override { - return this; - } + Door* getDoor() override { return this; } + const Door* getDoor() const override { return this; } - House* getHouse() { - return house; - } + House* getHouse() { return house; } - //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - void serializeAttr(PropWriteStream&) const override {} + // serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream&) const override {} - void setDoorId(uint32_t doorId) { - setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId); - } - uint32_t getDoorId() const { - return getIntAttr(ITEM_ATTRIBUTE_DOORID); - } + void setDoorId(uint32_t doorId) { setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId); } + uint32_t getDoorId() const { return getIntAttr(ITEM_ATTRIBUTE_DOORID); } - bool canUse(const Player* player); + bool canUse(const Player* player); - void setAccessList(const std::string& textlist); - bool getAccessList(std::string& list) const; + void setAccessList(const std::string& textlist); + bool getAccessList(std::string& list) const; - void onRemoved() override; + void onRemoved() override; - private: - void setHouse(House* house); +private: + void setHouse(House* house); - House* house = nullptr; - std::unique_ptr accessList; - friend class House; + House* house = nullptr; + std::unique_ptr accessList; + friend class House; }; -enum AccessList_t { +enum AccessList_t +{ GUEST_LIST = 0x100, SUBOWNER_LIST = 0x101, }; -enum AccessHouseLevel_t { +enum AccessHouseLevel_t +{ HOUSE_NOT_INVITED = 0, HOUSE_GUEST = 1, HOUSE_SUBOWNER = 2, @@ -113,151 +88,119 @@ using HouseBedItemList = std::list; class HouseTransferItem final : public Item { - public: - static HouseTransferItem* createHouseTransferItem(House* house); +public: + static HouseTransferItem* createHouseTransferItem(House* house); - explicit HouseTransferItem(House* house) : Item(0), house(house) {} + explicit HouseTransferItem(House* house) : Item(0), house(house) {} - void onTradeEvent(TradeEvents_t event, Player* owner) override; - bool canTransform() const override { - return false; - } + void onTradeEvent(TradeEvents_t event, Player* owner) override; + bool canTransform() const override { return false; } - private: - House* house; +private: + House* house; }; class House { - public: - explicit House(uint32_t houseId); +public: + explicit House(uint32_t houseId); - void addTile(HouseTile* tile); - void updateDoorDescription() const; + void addTile(HouseTile* tile); - bool canEditAccessList(uint32_t listId, const Player* player); - // listId special values: - // GUEST_LIST guest list - // SUBOWNER_LIST subowner list - void setAccessList(uint32_t listId, const std::string& textlist); - bool getAccessList(uint32_t listId, std::string& list) const; + bool canEditAccessList(uint32_t listId, const Player* player); + // listId special values: + // GUEST_LIST guest list + // SUBOWNER_LIST subowner list + void setAccessList(uint32_t listId, const std::string& textlist); + bool getAccessList(uint32_t listId, std::string& list) const; - bool isInvited(const Player* player); + bool isInvited(const Player* player) const; - AccessHouseLevel_t getHouseAccessLevel(const Player* player); - bool kickPlayer(Player* player, Player* target); + AccessHouseLevel_t getHouseAccessLevel(const Player* player) const; + bool kickPlayer(Player* player, Player* target); - void setEntryPos(Position pos) { - posEntry = pos; - } - const Position& getEntryPosition() const { - return posEntry; - } + void setEntryPos(Position pos) { posEntry = pos; } + const Position& getEntryPosition() const { return posEntry; } - void setName(std::string houseName) { - this->houseName = houseName; - } - const std::string& getName() const { - return houseName; - } + void setName(std::string houseName) { this->houseName = houseName; } + const std::string& getName() const { return houseName; } - void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr); - uint32_t getOwner() const { - return owner; - } + const std::string& getOwnerName() const { return ownerName; } - void setPaidUntil(time_t paid) { - paidUntil = paid; - } - time_t getPaidUntil() const { - return paidUntil; - } + void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr); + uint32_t getOwner() const { return owner; } - void setRent(uint32_t rent) { - this->rent = rent; - } - uint32_t getRent() const { - return rent; - } + void setPaidUntil(time_t paid) { paidUntil = paid; } + time_t getPaidUntil() const { return paidUntil; } - void setPayRentWarnings(uint32_t warnings) { - rentWarnings = warnings; - } - uint32_t getPayRentWarnings() const { - return rentWarnings; - } + void setRent(uint32_t rent) { this->rent = rent; } + uint32_t getRent() const { return rent; } - void setTownId(uint32_t townId) { - this->townId = townId; - } - uint32_t getTownId() const { - return townId; - } + void setPayRentWarnings(uint32_t warnings) { rentWarnings = warnings; } + uint32_t getPayRentWarnings() const { return rentWarnings; } - uint32_t getId() const { - return id; - } + void setTownId(uint32_t townId) { this->townId = townId; } + uint32_t getTownId() const { return townId; } - void addDoor(Door* door); - void removeDoor(Door* door); - Door* getDoorByNumber(uint32_t doorId) const; - Door* getDoorByPosition(const Position& pos); + uint32_t getId() const { return id; } - HouseTransferItem* getTransferItem(); - void resetTransferItem(); - bool executeTransfer(HouseTransferItem* item, Player* newOwner); + void addDoor(Door* door); + void removeDoor(Door* door); + Door* getDoorByNumber(uint32_t doorId) const; + Door* getDoorByPosition(const Position& pos); - const HouseTileList& getTiles() const { - return houseTiles; - } + HouseTransferItem* getTransferItem(); + void resetTransferItem(); + bool executeTransfer(HouseTransferItem* item, Player* newOwner); - const std::set& getDoors() const { - return doorSet; - } + const HouseTileList& getTiles() const { return houseTiles; } - void addBed(BedItem* bed); - const HouseBedItemList& getBeds() const { - return bedsList; - } - uint32_t getBedCount() { - return static_cast(std::ceil(bedsList.size() / 2.)); //each bed takes 2 sqms of space, ceil is just for bad maps - } + const std::set& getDoors() const { return doorSet; } + + void addBed(BedItem* bed); + const HouseBedItemList& getBeds() const { return bedsList; } + uint32_t getBedCount() + { + return static_cast( + std::ceil(bedsList.size() / 2.)); // each bed takes 2 sqms of space, ceil is just for bad maps + } - private: - bool transferToDepot() const; - bool transferToDepot(Player* player) const; +private: + bool transferToDepot() const; + bool transferToDepot(Player* player) const; - AccessList guestList; - AccessList subOwnerList; + AccessList guestList; + AccessList subOwnerList; - Container transfer_container{ITEM_LOCKER1}; + Container transfer_container{ITEM_LOCKER}; - HouseTileList houseTiles; - std::set doorSet; - HouseBedItemList bedsList; + HouseTileList houseTiles; + std::set doorSet; + HouseBedItemList bedsList; - std::string houseName; - std::string ownerName; + std::string houseName; + std::string ownerName; - HouseTransferItem* transferItem = nullptr; + HouseTransferItem* transferItem = nullptr; - time_t paidUntil = 0; + time_t paidUntil = 0; - uint32_t id; - uint32_t owner = 0; - uint32_t ownerAccountId = 0; - uint32_t rentWarnings = 0; - uint32_t rent = 0; - uint32_t townId = 0; + uint32_t id; + uint32_t owner = 0; + uint32_t ownerAccountId = 0; + uint32_t rentWarnings = 0; + uint32_t rent = 0; + uint32_t townId = 0; - Position posEntry = {}; + Position posEntry = {}; - bool isLoaded = false; + bool isLoaded = false; }; using HouseMap = std::map; -enum RentPeriod_t { +enum RentPeriod_t +{ RENTPERIOD_DAILY, RENTPERIOD_WEEKLY, RENTPERIOD_MONTHLY, @@ -267,49 +210,50 @@ enum RentPeriod_t { class Houses { - public: - Houses() = default; - ~Houses() { - for (const auto& it : houseMap) { - delete it.second; - } +public: + Houses() = default; + ~Houses() + { + for (const auto& it : houseMap) { + delete it.second; } + } - // non-copyable - Houses(const Houses&) = delete; - Houses& operator=(const Houses&) = delete; - - House* addHouse(uint32_t id) { - auto it = houseMap.find(id); - if (it != houseMap.end()) { - return it->second; - } + // non-copyable + Houses(const Houses&) = delete; + Houses& operator=(const Houses&) = delete; - House* house = new House(id); - houseMap[id] = house; - return house; + House* addHouse(uint32_t id) + { + auto it = houseMap.find(id); + if (it != houseMap.end()) { + return it->second; } - House* getHouse(uint32_t houseId) { - auto it = houseMap.find(houseId); - if (it == houseMap.end()) { - return nullptr; - } - return it->second; + House* house = new House(id); + houseMap[id] = house; + return house; + } + + House* getHouse(uint32_t houseId) + { + auto it = houseMap.find(houseId); + if (it == houseMap.end()) { + return nullptr; } + return it->second; + } - House* getHouseByPlayerId(uint32_t playerId); + House* getHouseByPlayerId(uint32_t playerId); - bool loadHousesXML(const std::string& filename); + bool loadHousesXML(const std::string& filename); - void payHouses(RentPeriod_t rentPeriod) const; + void payHouses(RentPeriod_t rentPeriod) const; - const HouseMap& getHouses() const { - return houseMap; - } + const HouseMap& getHouses() const { return houseMap; } - private: - HouseMap houseMap; +private: + HouseMap houseMap; }; -#endif +#endif // FS_HOUSE_H diff --git a/src/housetile.cpp b/src/housetile.cpp index 4c0b504a58..fcff89311e 100644 --- a/src/housetile.cpp +++ b/src/housetile.cpp @@ -1,34 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "housetile.h" -#include "house.h" -#include "game.h" + #include "configmanager.h" +#include "game.h" +#include "house.h" extern Game g_game; extern ConfigManager g_config; -HouseTile::HouseTile(int32_t x, int32_t y, int32_t z, House* house) : - DynamicTile(x, y, z), house(house) {} +HouseTile::HouseTile(int32_t x, int32_t y, int32_t z, House* house) : DynamicTile(x, y, z), house(house) {} void HouseTile::addThing(int32_t index, Thing* thing) { @@ -75,7 +59,8 @@ void HouseTile::updateHouse(Item* item) } } -ReturnValue HouseTile::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor/* = nullptr*/) const +ReturnValue HouseTile::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor /* = nullptr*/) const { if (const Creature* creature = thing.getCreature()) { if (const Player* player = creature->getPlayer()) { @@ -90,10 +75,9 @@ ReturnValue HouseTile::queryAdd(int32_t index, const Thing& thing, uint32_t coun return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE; } - if (actor) { - Player* actorPlayer = actor->getPlayer(); - if (!house->isInvited(actorPlayer)) { - return RETURNVALUE_CANNOTTHROW; + if (actor && g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { + if (!house->isInvited(actor->getPlayer())) { + return RETURNVALUE_PLAYERISNOTINVITED; } } } @@ -109,8 +93,7 @@ Tile* HouseTile::queryDestination(int32_t& index, const Thing& thing, Item** des Tile* destTile = g_game.map.getTile(entryPos); if (!destTile) { std::cout << "Error: [HouseTile::queryDestination] House entry not correct" - << " - Name: " << house->getName() - << " - House id: " << house->getId() + << " - Name: " << house->getName() << " - House id: " << house->getId() << " - Tile not found: " << entryPos << std::endl; destTile = g_game.map.getTile(player->getTemplePosition()); @@ -129,7 +112,8 @@ Tile* HouseTile::queryDestination(int32_t& index, const Thing& thing, Item** des return Tile::queryDestination(index, thing, destItem, flags); } -ReturnValue HouseTile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor /*= nullptr*/) const +ReturnValue HouseTile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor /*= nullptr*/) const { const Item* item = thing.getItem(); if (!item) { @@ -137,9 +121,8 @@ ReturnValue HouseTile::queryRemove(const Thing& thing, uint32_t count, uint32_t } if (actor && g_config.getBoolean(ConfigManager::ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS)) { - Player* actorPlayer = actor->getPlayer(); - if (!house->isInvited(actorPlayer)) { - return RETURNVALUE_NOTPOSSIBLE; + if (!house->isInvited(actor->getPlayer())) { + return RETURNVALUE_PLAYERISNOTINVITED; } } return Tile::queryRemove(thing, count, flags); diff --git a/src/housetile.h b/src/housetile.h index 7220f82991..c87f935b20 100644 --- a/src/housetile.h +++ b/src/housetile.h @@ -1,24 +1,8 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B -#define FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_HOUSETILE_H +#define FS_HOUSETILE_H #include "tile.h" @@ -26,29 +10,27 @@ class House; class HouseTile final : public DynamicTile { - public: - HouseTile(int32_t x, int32_t y, int32_t z, House* house); +public: + HouseTile(int32_t x, int32_t y, int32_t z, House* house); - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; - Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) override; + Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; - void addThing(int32_t index, Thing* thing) override; - void internalAddThing(uint32_t index, Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; + void internalAddThing(uint32_t index, Thing* thing) override; - House* getHouse() { - return house; - } + House* getHouse() const { return house; } - private: - void updateHouse(Item* item); +private: + void updateHouse(Item* item); - House* house; + House* house; }; -#endif +#endif // FS_HOUSETILE_H diff --git a/src/inbox.cpp b/src/inbox.cpp index 42dff7d86d..fcfe1ccad6 100644 --- a/src/inbox.cpp +++ b/src/inbox.cpp @@ -1,31 +1,15 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "inbox.h" + #include "tools.h" Inbox::Inbox(uint16_t type) : Container(type, 30, false, true) {} -ReturnValue Inbox::queryAdd(int32_t, const Thing& thing, uint32_t, - uint32_t flags, Creature*) const +ReturnValue Inbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const { if (!hasBitSet(FLAG_NOLIMIT, flags)) { return RETURNVALUE_CONTAINERNOTENOUGHROOM; @@ -50,7 +34,7 @@ ReturnValue Inbox::queryAdd(int32_t, const Thing& thing, uint32_t, void Inbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) { Cylinder* parent = getParent(); - if (parent != nullptr) { + if (parent) { parent->postAddNotification(thing, oldParent, index, LINK_PARENT); } } @@ -58,7 +42,7 @@ void Inbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t void Inbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) { Cylinder* parent = getParent(); - if (parent != nullptr) { + if (parent) { parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); } } diff --git a/src/inbox.h b/src/inbox.h index 8ad746a6b9..693b7e8bf0 100644 --- a/src/inbox.h +++ b/src/inbox.h @@ -1,49 +1,30 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_INBOX_H_C3EF10190329447883B9C3479234EE5C -#define FS_INBOX_H_C3EF10190329447883B9C3479234EE5C +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_INBOX_H +#define FS_INBOX_H #include "container.h" class Inbox final : public Container { - public: - explicit Inbox(uint16_t type); +public: + explicit Inbox(uint16_t type); - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; - //overrides - bool canRemove() const override { - return false; - } + // overrides + bool canRemove() const override { return false; } - Cylinder* getParent() const override; - Cylinder* getRealParent() const override { - return parent; - } + Cylinder* getParent() const override; + Cylinder* getRealParent() const override { return parent; } }; -#endif - +#endif // FS_INBOX_H diff --git a/src/ioguild.cpp b/src/ioguild.cpp deleted file mode 100644 index ed48b14560..0000000000 --- a/src/ioguild.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "database.h" -#include "guild.h" -#include "ioguild.h" - -Guild* IOGuild::loadGuild(uint32_t guildId) -{ - Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId; - if (DBResult_ptr result = db.storeQuery(query.str())) { - Guild* guild = new Guild(guildId, result->getString("name")); - - query.str(std::string()); - query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = " << guildId; - - if ((result = db.storeQuery(query.str()))) { - do { - guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); - } while (result->next()); - } - return guild; - } - return nullptr; -} - -uint32_t IOGuild::getGuildIdByName(const std::string& name) -{ - Database& db = Database::getInstance(); - - std::ostringstream query; - query << "SELECT `id` FROM `guilds` WHERE `name` = " << db.escapeString(name); - - DBResult_ptr result = db.storeQuery(query.str()); - if (!result) { - return 0; - } - return result->getNumber("id"); -} - -void IOGuild::getWarList(uint32_t guildId, GuildWarVector& guildWarVector) -{ - std::ostringstream query; - query << "SELECT `guild1`, `guild2` FROM `guild_wars` WHERE (`guild1` = " << guildId << " OR `guild2` = " << guildId << ") AND `ended` = 0 AND `status` = 1"; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - return; - } - - do { - uint32_t guild1 = result->getNumber("guild1"); - if (guildId != guild1) { - guildWarVector.push_back(guild1); - } else { - guildWarVector.push_back(result->getNumber("guild2")); - } - } while (result->next()); -} diff --git a/src/ioguild.h b/src/ioguild.h deleted file mode 100644 index 66e43392f7..0000000000 --- a/src/ioguild.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF -#define FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF - -class Guild; -using GuildWarVector = std::vector; - -class IOGuild -{ - public: - static Guild* loadGuild(uint32_t guildId); - static uint32_t getGuildIdByName(const std::string& name); - static void getWarList(uint32_t guildId, GuildWarVector& guildWarVector); -}; - -#endif diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 960810a737..764f375fc5 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -1,27 +1,16 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "iologindata.h" + +#include "condition.h" #include "configmanager.h" +#include "depotchest.h" #include "game.h" +#include "inbox.h" +#include "storeinbox.h" extern ConfigManager g_config; extern Game g_game; @@ -30,9 +19,8 @@ Account IOLoginData::loadAccount(uint32_t accno) { Account account; - std::ostringstream query; - query << "SELECT `id`, `name`, `password`, `type`, `premium_ends_at` FROM `accounts` WHERE `id` = " << accno; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `id`, `name`, `password`, `type`, `premium_ends_at` FROM `accounts` WHERE `id` = {:d}", accno)); if (!result) { return account; } @@ -44,13 +32,6 @@ Account IOLoginData::loadAccount(uint32_t accno) return account; } -bool IOLoginData::saveAccount(const Account& acc) -{ - std::ostringstream query; - query << "UPDATE `accounts` SET `premium_ends_at` = " << acc.premiumEndsAt << " WHERE `id` = " << acc.id; - return Database::getInstance().executeQuery(query.str()); -} - std::string decodeSecret(const std::string& secret) { // simple base32 decoding @@ -65,8 +46,8 @@ std::string decodeSecret(const std::string& secret) } else if (ch >= '2' && ch <= '7') { buffer |= ch - 24; } else { - // if a key is broken, return empty and the comparison - // will always be false since the token must not be empty + // if a key is broken, return empty and the comparison will always be false since the token must not be + // empty return {}; } @@ -84,9 +65,9 @@ bool IOLoginData::loginserverAuthentication(const std::string& name, const std:: { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id`, `name`, `password`, `secret`, `type`, `premium_ends_at` FROM `accounts` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `id`, `name`, `password`, `secret`, `type`, `premium_ends_at` FROM `accounts` WHERE `name` = {:s} OR `email` = {:s}", + db.escapeString(name), db.escapeString(name))); if (!result) { return false; } @@ -101,40 +82,41 @@ bool IOLoginData::loginserverAuthentication(const std::string& name, const std:: account.accountType = static_cast(result->getNumber("type")); account.premiumEndsAt = result->getNumber("premium_ends_at"); - query.str(std::string()); - query << "SELECT `name`, `deletion` FROM `players` WHERE `account_id` = " << account.id; - result = db.storeQuery(query.str()); + result = db.storeQuery(fmt::format( + "SELECT `name` FROM `players` WHERE `account_id` = {:d} AND `deletion` = 0 ORDER BY `name` ASC", account.id)); if (result) { do { - if (result->getNumber("deletion") == 0) { - account.characters.push_back(result->getString("name")); - } + account.characters.push_back(result->getString("name")); } while (result->next()); - std::sort(account.characters.begin(), account.characters.end()); } return true; } -uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime) +uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, const std::string& password, + std::string& characterName, std::string& token, uint32_t tokenTime) { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "SELECT `id`, `password`, `secret` FROM `accounts` WHERE `name` = " << db.escapeString(accountName); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery( + fmt::format("SELECT `id`, `password`, `secret` FROM `accounts` WHERE `name` = {:s} OR `email` = {:s}", + db.escapeString(accountName), db.escapeString(accountName))); if (!result) { return 0; } - std::string secret = decodeSecret(result->getString("secret")); - if (!secret.empty()) { - if (token.empty()) { - return 0; - } + // two-factor auth + if (g_config.getBoolean(ConfigManager::TWO_FACTOR_AUTH)) { + std::string secret = decodeSecret(result->getString("secret")); + if (!secret.empty()) { + if (token.empty()) { + return 0; + } - bool tokenValid = token == generateToken(secret, tokenTime) || token == generateToken(secret, tokenTime - 1) || token == generateToken(secret, tokenTime + 1); - if (!tokenValid) { - return 0; + bool tokenValid = token == generateToken(secret, tokenTime) || + token == generateToken(secret, tokenTime - 1) || + token == generateToken(secret, tokenTime + 1); + if (!tokenValid) { + return 0; + } } } @@ -144,16 +126,13 @@ uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, co uint32_t accountId = result->getNumber("id"); - query.str(std::string()); - query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db.escapeString(characterName); - result = db.storeQuery(query.str()); + result = db.storeQuery( + fmt::format("SELECT `name` FROM `players` WHERE `name` = {:s} AND `account_id` = {:d} AND `deletion` = 0", + db.escapeString(characterName), accountId)); if (!result) { return 0; } - if (result->getNumber("account_id") != accountId || result->getNumber("deletion") != 0) { - return 0; - } characterName = result->getString("name"); return accountId; } @@ -162,9 +141,19 @@ uint32_t IOLoginData::getAccountIdByPlayerName(const std::string& playerName) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `account_id` FROM `players` WHERE `name` = " << db.escapeString(playerName); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery( + fmt::format("SELECT `account_id` FROM `players` WHERE `name` = {:s}", db.escapeString(playerName))); + if (!result) { + return 0; + } + return result->getNumber("account_id"); +} + +uint32_t IOLoginData::getAccountIdByPlayerId(uint32_t playerId) +{ + Database& db = Database::getInstance(); + + DBResult_ptr result = db.storeQuery(fmt::format("SELECT `account_id` FROM `players` WHERE `id` = {:d}", playerId)); if (!result) { return 0; } @@ -173,9 +162,8 @@ uint32_t IOLoginData::getAccountIdByPlayerName(const std::string& playerName) AccountType_t IOLoginData::getAccountType(uint32_t accountId) { - std::ostringstream query; - query << "SELECT `type` FROM `accounts` WHERE `id` = " << accountId; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = + Database::getInstance().storeQuery(fmt::format("SELECT `type` FROM `accounts` WHERE `id` = {:d}", accountId)); if (!result) { return ACCOUNT_TYPE_NORMAL; } @@ -184,9 +172,8 @@ AccountType_t IOLoginData::getAccountType(uint32_t accountId) void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType) { - std::ostringstream query; - query << "UPDATE `accounts` SET `type` = " << static_cast(accountType) << " WHERE `id` = " << accountId; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery(fmt::format("UPDATE `accounts` SET `type` = {:d} WHERE `id` = {:d}", + static_cast(accountType), accountId)); } void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) @@ -195,34 +182,30 @@ void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) return; } - std::ostringstream query; if (login) { - query << "INSERT INTO `players_online` VALUES (" << guid << ')'; + Database::getInstance().executeQuery(fmt::format("INSERT INTO `players_online` VALUES ({:d})", guid)); } else { - query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; + Database::getInstance().executeQuery( + fmt::format("DELETE FROM `players_online` WHERE `player_id` = {:d}", guid)); } - Database::getInstance().executeQuery(query.str()); } bool IOLoginData::preloadPlayer(Player* player, const std::string& name) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `p`.`id`, `p`.`account_id`, `p`.`group_id`, `p`.`deletion`,`a`.`type`,`a`.`premium_ends_at` FROM `players` as `p` JOIN `accounts` as `a` ON `a`.`id` = `p`.`account_id` WHERE `p`.`name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `p`.`id`, `p`.`account_id`, `p`.`group_id`, `a`.`type`, `a`.`premium_ends_at` FROM `players` as `p` JOIN `accounts` as `a` ON `a`.`id` = `p`.`account_id` WHERE `p`.`name` = {:s} AND `p`.`deletion` = 0", + db.escapeString(name))); if (!result) { return false; } - if (result->getNumber("deletion") != 0) { - return false; - } - player->setGUID(result->getNumber("id")); Group* group = g_game.groups.getGroup(result->getNumber("group_id")); if (!group) { - std::cout << "[Error - IOLoginData::preloadPlayer] " << player->name << " has Group ID " << result->getNumber("group_id") << " which doesn't exist." << std::endl; + std::cout << "[Error - IOLoginData::preloadPlayer] " << player->name << " has Group ID " + << result->getNumber("group_id") << " which doesn't exist." << std::endl; return false; } player->setGroup(group); @@ -235,17 +218,42 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name) bool IOLoginData::loadPlayerById(Player* player, uint32_t id) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = " << id; - return loadPlayer(player, db.storeQuery(query.str())); + return loadPlayer( + player, + db.storeQuery(fmt::format( + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", + id))); } bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = " << db.escapeString(name); - return loadPlayer(player, db.storeQuery(query.str())); + return loadPlayer( + player, + db.storeQuery(fmt::format( + "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", + db.escapeString(name)))); +} + +static GuildWarVector getWarList(uint32_t guildId) +{ + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `guild1`, `guild2` FROM `guild_wars` WHERE (`guild1` = {:d} OR `guild2` = {:d}) AND `ended` = 0 AND `status` = 1", + guildId, guildId)); + if (!result) { + return {}; + } + + GuildWarVector guildWarVector; + do { + uint32_t guild1 = result->getNumber("guild1"); + if (guildId != guild1) { + guildWarVector.push_back(guild1); + } else { + guildWarVector.push_back(result->getNumber("guild2")); + } + } while (result->next()); + return guildWarVector; } bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) @@ -269,7 +277,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) Group* group = g_game.groups.getGroup(result->getNumber("group_id")); if (!group) { - std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Group ID " << result->getNumber("group_id") << " which doesn't exist" << std::endl; + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Group ID " + << result->getNumber("group_id") << " which doesn't exist" << std::endl; return false; } player->setGroup(group); @@ -315,7 +324,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } if (!player->setVocation(result->getNumber("vocation"))) { - std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Vocation ID " << result->getNumber("vocation") << " which doesn't exist" << std::endl; + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Vocation ID " + << result->getNumber("vocation") << " which doesn't exist" << std::endl; return false; } @@ -341,13 +351,18 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->defaultOutfit.lookLegs = result->getNumber("looklegs"); player->defaultOutfit.lookFeet = result->getNumber("lookfeet"); player->defaultOutfit.lookAddons = result->getNumber("lookaddons"); + player->defaultOutfit.lookMount = result->getNumber("lookmount"); + player->defaultOutfit.lookMountHead = result->getNumber("lookmounthead"); + player->defaultOutfit.lookMountBody = result->getNumber("lookmountbody"); + player->defaultOutfit.lookMountLegs = result->getNumber("lookmountlegs"); + player->defaultOutfit.lookMountFeet = result->getNumber("lookmountfeet"); player->currentOutfit = player->defaultOutfit; - player->direction = static_cast (result->getNumber("direction")); + player->direction = static_cast(result->getNumber("direction")); if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); if (skullSeconds > 0) { - //ensure that we round up the number of ticks + // ensure that we round up the number of ticks player->skullTicks = (skullSeconds + 2); uint16_t skull = result->getNumber("skull"); @@ -371,7 +386,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) Town* town = g_game.map.towns.getTown(result->getNumber("town_id")); if (!town) { - std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " << result->getNumber("town_id") << " which doesn't exist" << std::endl; + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " + << result->getNumber("town_id") << " which doesn't exist" << std::endl; return false; } @@ -384,8 +400,11 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->staminaMinutes = result->getNumber("stamina"); - static const std::string skillNames[] = {"skill_fist", "skill_club", "skill_sword", "skill_axe", "skill_dist", "skill_shielding", "skill_fishing"}; - static const std::string skillNameTries[] = {"skill_fist_tries", "skill_club_tries", "skill_sword_tries", "skill_axe_tries", "skill_dist_tries", "skill_shielding_tries", "skill_fishing_tries"}; + static const std::string skillNames[] = {"skill_fist", "skill_club", "skill_sword", "skill_axe", + "skill_dist", "skill_shielding", "skill_fishing"}; + static const std::string skillNameTries[] = {"skill_fist_tries", "skill_club_tries", "skill_sword_tries", + "skill_axe_tries", "skill_dist_tries", "skill_shielding_tries", + "skill_fishing_tries"}; static constexpr size_t size = sizeof(skillNames) / sizeof(std::string); for (uint8_t i = 0; i < size; ++i) { uint16_t skillLevel = result->getNumber(skillNames[i]); @@ -400,9 +419,9 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->skills[i].percent = Player::getPercentLevel(skillTries, nextSkillTries); } - std::ostringstream query; - query << "SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery( + fmt::format("SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = {:d}", + player->getGUID())))) { uint32_t guildId = result->getNumber("guild_id"); uint32_t playerRankId = result->getNumber("rank_id"); player->guildNick = result->getString("nick"); @@ -413,7 +432,8 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) if (guild) { g_game.addGuild(guild); } else { - std::cout << "[Warning - IOLoginData::loadPlayer] " << player->name << " has Guild ID " << guildId << " which doesn't exist" << std::endl; + std::cout << "[Warning - IOLoginData::loadPlayer] " << player->name << " has Guild ID " << guildId + << " which doesn't exist" << std::endl; } } @@ -421,11 +441,10 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->guild = guild; GuildRank_ptr rank = guild->getRankById(playerRankId); if (!rank) { - query.str(std::string()); - query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId; - - if ((result = db.storeQuery(query.str()))) { - guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); + if ((result = db.storeQuery(fmt::format( + "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = {:d}", playerRankId)))) { + guild->addRank(result->getNumber("id"), result->getString("name"), + result->getNumber("level")); } rank = guild->getRankById(playerRankId); @@ -435,37 +454,44 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } player->guildRank = rank; + player->guildWarVector = getWarList(guildId); - IOGuild::getWarList(guildId, player->guildWarVector); - - query.str(std::string()); - query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId; - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery(fmt::format( + "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = {:d}", guildId)))) { guild->setMemberCount(result->getNumber("members")); } } } - query.str(std::string()); - query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery(fmt::format("SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = {:d}", + player->getGUID())))) { do { player->learnedInstantSpellList.emplace_front(result->getString("name")); } while (result->next()); } - //load inventory items + // load inventory items ItemMap itemMap; + std::map openContainersList; - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery(fmt::format( + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + player->getGUID())))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { const std::pair& pair = it->second; Item* item = pair.first; int32_t pid = pair.second; + + Container* itemContainer = item->getContainer(); + if (itemContainer) { + uint8_t cid = item->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (cid > 0) { + openContainersList.emplace(cid, itemContainer); + } + } + if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { player->internalAddThing(pid, item); } else { @@ -482,12 +508,17 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } - //load depot items + for (auto& it : openContainersList) { + player->addContainer(it.first - 1, it.second); + player->onSendContainer(it.second); + } + + // load depot items itemMap.clear(); - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery(fmt::format( + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + player->getGUID())))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -514,12 +545,12 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } - //load inbox items + // load inbox items itemMap.clear(); - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery(fmt::format( + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + player->getGUID())))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -544,12 +575,12 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } - //load store inbox items + // load store inbox items itemMap.clear(); - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_storeinboxitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; - if ((result = db.storeQuery(query.str()))) { + if ((result = db.storeQuery(fmt::format( + "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_storeinboxitems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", + player->getGUID())))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -574,19 +605,17 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } - //load storage map - query.str(std::string()); - query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + // load storage map + if ((result = db.storeQuery( + fmt::format("SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = {:d}", player->getGUID())))) { do { player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); } while (result->next()); } - //load vip list - query.str(std::string()); - query << "SELECT `player_id` FROM `account_viplist` WHERE `account_id` = " << player->getAccount(); - if ((result = db.storeQuery(query.str()))) { + // load vip list + if ((result = db.storeQuery(fmt::format("SELECT `player_id` FROM `account_viplist` WHERE `account_id` = {:d}", + player->getAccount())))) { do { player->addVIPInternal(result->getNumber("player_id")); } while (result->next()); @@ -598,14 +627,15 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) return true; } -bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) +bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, + PropWriteStream& propWriteStream) { - std::ostringstream ss; - using ContainerBlock = std::pair; - std::list queue; + std::vector containers; + containers.reserve(32); int32_t runningId = 100; + const auto& openContainers = player->getOpenContainers(); Database& db = Database::getInstance(); for (const auto& it : itemList) { @@ -613,34 +643,66 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, Item* item = it.second; ++runningId; + if (Container* container = item->getContainer()) { + if (container->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)) { + container->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, 0); + } + + if (!openContainers.empty()) { + for (const auto& its : openContainers) { + auto openContainer = its.second; + auto opcontainer = openContainer.container; + + if (opcontainer == container) { + container->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, static_cast(its.first) + 1); + break; + } + } + } + + containers.emplace_back(container, runningId); + } + propWriteStream.clear(); item->serializeAttr(propWriteStream); size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); - if (!query_insert.addRow(ss)) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", player->getGUID(), pid, runningId, + item->getID(), item->getSubType(), + db.escapeBlob(attributes, attributesSize)))) { return false; } - - if (Container* container = item->getContainer()) { - queue.emplace_back(container, runningId); - } } - while (!queue.empty()) { - const ContainerBlock& cb = queue.front(); + for (size_t i = 0; i < containers.size(); i++) { + const ContainerBlock& cb = containers[i]; Container* container = cb.first; int32_t parentId = cb.second; - queue.pop_front(); for (Item* item : container->getItemList()) { ++runningId; Container* subContainer = item->getContainer(); if (subContainer) { - queue.emplace_back(subContainer, runningId); + containers.emplace_back(subContainer, runningId); + + if (subContainer->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)) { + subContainer->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, 0); + } + + if (!openContainers.empty()) { + for (const auto& it : openContainers) { + auto openContainer = it.second; + auto opcontainer = openContainer.container; + + if (opcontainer == subContainer) { + subContainer->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, it.first + 1); + break; + } + } + } } propWriteStream.clear(); @@ -649,8 +711,9 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); - if (!query_insert.addRow(ss)) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", player->getGUID(), parentId, + runningId, item->getID(), item->getSubType(), + db.escapeBlob(attributes, attributesSize)))) { return false; } } @@ -666,20 +729,18 @@ bool IOLoginData::savePlayer(Player* player) Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = + db.storeQuery(fmt::format("SELECT `save` FROM `players` WHERE `id` = {:d}", player->getGUID())); if (!result) { return false; } if (result->getNumber("save") == 0) { - query.str(std::string()); - query << "UPDATE `players` SET `lastlogin` = " << player->lastLoginSaved << ", `lastip` = " << player->lastIP << " WHERE `id` = " << player->getGUID(); - return db.executeQuery(query.str()); + return db.executeQuery(fmt::format("UPDATE `players` SET `lastlogin` = {:d}, `lastip` = {:d} WHERE `id` = {:d}", + player->lastLoginSaved, player->lastIP, player->getGUID())); } - //serialize conditions + // serialize conditions PropWriteStream propWriteStream; for (Condition* condition : player->conditions) { if (condition->isPersistent()) { @@ -691,8 +752,8 @@ bool IOLoginData::savePlayer(Player* player) size_t conditionsSize; const char* conditions = propWriteStream.getStream(conditionsSize); - //First, an UPDATE query to write the player itself - query.str(std::string()); + // First, an UPDATE query to write the player itself + std::ostringstream query; query << "UPDATE `players` SET "; query << "`level` = " << player->level << ','; query << "`group_id` = " << player->group->id << ','; @@ -706,6 +767,11 @@ bool IOLoginData::savePlayer(Player* player) query << "`looklegs` = " << static_cast(player->defaultOutfit.lookLegs) << ','; query << "`looktype` = " << player->defaultOutfit.lookType << ','; query << "`lookaddons` = " << static_cast(player->defaultOutfit.lookAddons) << ','; + query << "`lookmount` = " << player->defaultOutfit.lookMount << ','; + query << "`lookmounthead` = " << static_cast(player->defaultOutfit.lookMountHead) << ','; + query << "`lookmountbody` = " << static_cast(player->defaultOutfit.lookMountBody) << ','; + query << "`lookmountlegs` = " << static_cast(player->defaultOutfit.lookMountLegs) << ','; + query << "`lookmountfeet` = " << static_cast(player->defaultOutfit.lookMountFeet) << ','; query << "`maglevel` = " << player->magLevel << ','; query << "`mana` = " << player->mana << ','; query << "`manamax` = " << player->manaMax << ','; @@ -768,12 +834,12 @@ bool IOLoginData::savePlayer(Player* player) query << "`skill_shielding_tries` = " << player->skills[SKILL_SHIELD].tries << ','; query << "`skill_fishing` = " << player->skills[SKILL_FISHING].level << ','; query << "`skill_fishing_tries` = " << player->skills[SKILL_FISHING].tries << ','; - query << "`direction` = " << static_cast (player->getDirection()) << ','; + query << "`direction` = " << static_cast(player->getDirection()) << ','; if (!player->isOffline()) { query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ','; } - query << "`blessings` = " << static_cast(player->blessings); + query << "`blessings` = " << player->blessings.to_ulong(); query << " WHERE `id` = " << player->getGUID(); DBTransaction transaction; @@ -786,18 +852,13 @@ bool IOLoginData::savePlayer(Player* player) } // learned spells - query.str(std::string()); - query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + if (!db.executeQuery(fmt::format("DELETE FROM `player_spells` WHERE `player_id` = {:d}", player->getGUID()))) { return false; } - query.str(std::string()); - - DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); + DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name`) VALUES "); for (const std::string& spellName : player->learnedInstantSpellList) { - query << player->getGUID() << ',' << db.escapeString(spellName); - if (!spellsQuery.addRow(query)) { + if (!spellsQuery.addRow(fmt::format("{:d}, {:s}", player->getGUID(), db.escapeString(spellName)))) { return false; } } @@ -806,13 +867,13 @@ bool IOLoginData::savePlayer(Player* player) return false; } - //item saving - query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + // item saving + if (!db.executeQuery(fmt::format("DELETE FROM `player_items` WHERE `player_id` = {:d}", player->getGUID()))) { return false; } - DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + DBInsert itemsQuery( + "INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); ItemBlockList itemList; for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { @@ -826,38 +887,32 @@ bool IOLoginData::savePlayer(Player* player) return false; } - if (player->lastDepotId != -1) { - //save depot items - query.str(std::string()); - query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); - - if (!db.executeQuery(query.str())) { - return false; - } + // save depot items + if (!db.executeQuery(fmt::format("DELETE FROM `player_depotitems` WHERE `player_id` = {:d}", player->getGUID()))) { + return false; + } - DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - itemList.clear(); + DBInsert depotQuery( + "INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + itemList.clear(); - for (const auto& it : player->depotChests) { - DepotChest* depotChest = it.second; - for (Item* item : depotChest->getItemList()) { - itemList.emplace_back(it.first, item); - } + for (const auto& it : player->depotChests) { + for (Item* item : it.second->getItemList()) { + itemList.emplace_back(it.first, item); } + } - if (!saveItems(player, itemList, depotQuery, propWriteStream)) { - return false; - } + if (!saveItems(player, itemList, depotQuery, propWriteStream)) { + return false; } - //save inbox items - query.str(std::string()); - query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + // save inbox items + if (!db.executeQuery(fmt::format("DELETE FROM `player_inboxitems` WHERE `player_id` = {:d}", player->getGUID()))) { return false; } - DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + DBInsert inboxQuery( + "INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); itemList.clear(); for (Item* item : player->getInbox()->getItemList()) { @@ -868,14 +923,14 @@ bool IOLoginData::savePlayer(Player* player) return false; } - //save store inbox items - query.str(std::string()); - query << "DELETE FROM `player_storeinboxitems` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + // save store inbox items + if (!db.executeQuery( + fmt::format("DELETE FROM `player_storeinboxitems` WHERE `player_id` = {:d}", player->getGUID()))) { return false; } - DBInsert storeInboxQuery("INSERT INTO `player_storeinboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + DBInsert storeInboxQuery( + "INSERT INTO `player_storeinboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); itemList.clear(); for (Item* item : player->getStoreInbox()->getItemList()) { @@ -886,20 +941,15 @@ bool IOLoginData::savePlayer(Player* player) return false; } - query.str(std::string()); - query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + if (!db.executeQuery(fmt::format("DELETE FROM `player_storage` WHERE `player_id` = {:d}", player->getGUID()))) { return false; } - query.str(std::string()); - DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); player->genReservedStorageRange(); for (const auto& it : player->storageMap) { - query << player->getGUID() << ',' << it.first << ',' << it.second; - if (!storageQuery.addRow(query)) { + if (!storageQuery.addRow(fmt::format("{:d}, {:d}, {:d}", player->getGUID(), it.first, it.second))) { return false; } } @@ -908,15 +958,14 @@ bool IOLoginData::savePlayer(Player* player) return false; } - //End the transaction + // End the transaction return transaction.commit(); } std::string IOLoginData::getNameByGuid(uint32_t guid) { - std::ostringstream query; - query << "SELECT `name` FROM `players` WHERE `id` = " << guid; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = + Database::getInstance().storeQuery(fmt::format("SELECT `name` FROM `players` WHERE `id` = {:d}", guid)); if (!result) { return std::string(); } @@ -927,9 +976,8 @@ uint32_t IOLoginData::getGuidByName(const std::string& name) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `id` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = + db.storeQuery(fmt::format("SELECT `id` FROM `players` WHERE `name` = {:s}", db.escapeString(name))); if (!result) { return 0; } @@ -940,9 +988,8 @@ bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = {:s}", db.escapeString(name))); if (!result) { return false; } @@ -966,10 +1013,8 @@ bool IOLoginData::formatPlayerName(std::string& name) { Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name` FROM `players` WHERE `name` = " << db.escapeString(name); - - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = + db.storeQuery(fmt::format("SELECT `name` FROM `players` WHERE `name` = {:s}", db.escapeString(name))); if (!result) { return false; } @@ -1006,72 +1051,61 @@ void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result) void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) { - std::ostringstream query; - query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery( + fmt::format("UPDATE `players` SET `balance` = `balance` + {:d} WHERE `id` = {:d}", bankBalance, guid)); } bool IOLoginData::hasBiddedOnHouse(uint32_t guid) { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1"; - return db.storeQuery(query.str()).get() != nullptr; + return db.storeQuery(fmt::format("SELECT `id` FROM `houses` WHERE `highest_bidder` = {:d} LIMIT 1", guid)).get(); } std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) { std::forward_list entries; - std::ostringstream query; - query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = " << accountId; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = {:d}", + accountId)); if (result) { do { - entries.emplace_front( - result->getNumber("player_id"), - result->getString("name"), - result->getString("description"), - result->getNumber("icon"), - result->getNumber("notify") != 0 - ); + entries.emplace_front(result->getNumber("player_id"), result->getString("name"), + result->getString("description"), result->getNumber("icon"), + result->getNumber("notify") != 0); } while (result->next()); } return entries; } -void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, + bool notify) { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES (" << accountId << ',' << guid << ',' << db.escapeString(description) << ',' << icon << ',' << notify << ')'; - db.executeQuery(query.str()); + db.executeQuery(fmt::format( + "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES ({:d}, {:d}, {:s}, {:d}, {:d})", + accountId, guid, db.escapeString(description), icon, notify)); } -void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, + bool notify) { Database& db = Database::getInstance(); - - std::ostringstream query; - query << "UPDATE `account_viplist` SET `description` = " << db.escapeString(description) << ", `icon` = " << icon << ", `notify` = " << notify << " WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; - db.executeQuery(query.str()); + db.executeQuery(fmt::format( + "UPDATE `account_viplist` SET `description` = {:s}, `icon` = {:d}, `notify` = {:d} WHERE `account_id` = {:d} AND `player_id` = {:d}", + db.escapeString(description), icon, notify, accountId, guid)); } void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) { - std::ostringstream query; - query << "DELETE FROM `account_viplist` WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery( + fmt::format("DELETE FROM `account_viplist` WHERE `account_id` = {:d} AND `player_id` = {:d}", accountId, guid)); } void IOLoginData::updatePremiumTime(uint32_t accountId, time_t endTime) { - std::ostringstream query; - query << "UPDATE `accounts` SET `premium_ends_at` = " << endTime << " WHERE `id` = " << accountId; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery( + fmt::format("UPDATE `accounts` SET `premium_ends_at` = {:d} WHERE `id` = {:d}", endTime, accountId)); } std::vector IOLoginData::getUnjustifiedDates(const std::string& name, time_t offsetTime) diff --git a/src/iologindata.h b/src/iologindata.h index 9b27757e39..0c91a2fa9e 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -1,70 +1,62 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF -#define FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF +#ifndef FS_IOLOGINDATA_H +#define FS_IOLOGINDATA_H #include "account.h" -#include "player.h" #include "database.h" +class Item; +class Player; +class PropWriteStream; +struct VIPEntry; + using ItemBlockList = std::list>; class IOLoginData { - public: - static Account loadAccount(uint32_t accno); - static bool saveAccount(const Account& acc); - - static bool loginserverAuthentication(const std::string& name, const std::string& password, Account& account); - static uint32_t gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime); - static uint32_t getAccountIdByPlayerName(const std::string& playerName); - - static AccountType_t getAccountType(uint32_t accountId); - static void setAccountType(uint32_t accountId, AccountType_t accountType); - static void updateOnlineStatus(uint32_t guid, bool login); - static bool preloadPlayer(Player* player, const std::string& name); - - static bool loadPlayerById(Player* player, uint32_t id); - static bool loadPlayerByName(Player* player, const std::string& name); - static bool loadPlayer(Player* player, DBResult_ptr result); - static bool savePlayer(Player* player); - static uint32_t getGuidByName(const std::string& name); - static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name); - static std::string getNameByGuid(uint32_t guid); - static bool formatPlayerName(std::string& name); - static void increaseBankBalance(uint32_t guid, uint64_t bankBalance); - static bool hasBiddedOnHouse(uint32_t guid); - - static std::forward_list getVIPEntries(uint32_t accountId); - static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); - static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); - static void removeVIPEntry(uint32_t accountId, uint32_t guid); - - static void updatePremiumTime(uint32_t accountId, time_t endTime); - static std::vector getUnjustifiedDates(const std::string& name, time_t offsetTime); - - private: - using ItemMap = std::map>; - - static void loadItems(ItemMap& itemMap, DBResult_ptr result); - static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); +public: + static Account loadAccount(uint32_t accno); + + static bool loginserverAuthentication(const std::string& name, const std::string& password, Account& account); + static uint32_t gameworldAuthentication(const std::string& accountName, const std::string& password, + std::string& characterName, std::string& token, uint32_t tokenTime); + static uint32_t getAccountIdByPlayerName(const std::string& playerName); + static uint32_t getAccountIdByPlayerId(uint32_t playerId); + + static AccountType_t getAccountType(uint32_t accountId); + static void setAccountType(uint32_t accountId, AccountType_t accountType); + static void updateOnlineStatus(uint32_t guid, bool login); + static bool preloadPlayer(Player* player, const std::string& name); + + static bool loadPlayerById(Player* player, uint32_t id); + static bool loadPlayerByName(Player* player, const std::string& name); + static bool loadPlayer(Player* player, DBResult_ptr result); + static bool savePlayer(Player* player); + static uint32_t getGuidByName(const std::string& name); + static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name); + static std::string getNameByGuid(uint32_t guid); + static bool formatPlayerName(std::string& name); + static void increaseBankBalance(uint32_t guid, uint64_t bankBalance); + static bool hasBiddedOnHouse(uint32_t guid); + + static std::forward_list getVIPEntries(uint32_t accountId); + static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, + bool notify); + static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, + bool notify); + static void removeVIPEntry(uint32_t accountId, uint32_t guid); + + static void updatePremiumTime(uint32_t accountId, time_t endTime); + static std::vector getUnjustifiedDates(const std::string& name, time_t offsetTime); + +private: + using ItemMap = std::map>; + + static void loadItems(ItemMap& itemMap, DBResult_ptr result); + static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, + PropWriteStream& propWriteStream); }; -#endif +#endif // FS_IOLOGINDATA_H diff --git a/src/iomap.cpp b/src/iomap.cpp index bae55087fa..c85e9713af 100644 --- a/src/iomap.cpp +++ b/src/iomap.cpp @@ -1,50 +1,34 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "iomap.h" -#include "bed.h" +#include "housetile.h" /* - OTBM_ROOTV1 - | - |--- OTBM_MAP_DATA - | | - | |--- OTBM_TILE_AREA - | | |--- OTBM_TILE - | | |--- OTBM_TILE_SQUARE (not implemented) - | | |--- OTBM_TILE_REF (not implemented) - | | |--- OTBM_HOUSETILE - | | - | |--- OTBM_SPAWNS (not implemented) - | | |--- OTBM_SPAWN_AREA (not implemented) - | | |--- OTBM_MONSTER (not implemented) - | | - | |--- OTBM_TOWNS - | | |--- OTBM_TOWN - | | - | |--- OTBM_WAYPOINTS - | |--- OTBM_WAYPOINT - | - |--- OTBM_ITEM_DEF (not implemented) + OTBM_ROOTV1 + | + |--- OTBM_MAP_DATA + | | + | |--- OTBM_TILE_AREA + | | |--- OTBM_TILE + | | |--- OTBM_TILE_SQUARE (not implemented) + | | |--- OTBM_TILE_REF (not implemented) + | | |--- OTBM_HOUSETILE + | | + | |--- OTBM_SPAWNS (not implemented) + | | |--- OTBM_SPAWN_AREA (not implemented) + | | |--- OTBM_MONSTER (not implemented) + | | + | |--- OTBM_TOWNS + | | |--- OTBM_TOWN + | | + | |--- OTBM_WAYPOINTS + | |--- OTBM_WAYPOINT + | + |--- OTBM_ITEM_DEF (not implemented) */ Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z) @@ -87,10 +71,11 @@ bool IOMap::loadMap(Map* map, const std::string& fileName) uint32_t headerVersion = root_header.version; if (headerVersion == 0) { - //In otbm version 1 the count variable after splashes/fluidcontainers and stackables - //are saved as attributes instead, this solves a lot of problems with items - //that are changed (stackable/charges/fluidcontainer/splash) during an update. - setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + // In otbm version 1 the count variable after splashes/fluidcontainers and stackables are saved as + // attributes instead, this solves a lot of problems with items that are changed + // (stackable/charges/fluidcontainer/splash) during an update. + setLastErrorString( + "This map need to be upgraded by using the latest map editor version to be able to load correctly."); return false; } @@ -100,12 +85,14 @@ bool IOMap::loadMap(Map* map, const std::string& fileName) } if (root_header.majorVersionItems < 3) { - setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + setLastErrorString( + "This map need to be upgraded by using the latest map editor version to be able to load correctly."); return false; } if (root_header.majorVersionItems > Item::items.majorVersion) { - setLastErrorString("The map was saved with a different items.otb version, an upgraded items.otb is required."); + setLastErrorString( + "The map was saved with a different items.otb version, an upgraded items.otb is required."); return false; } @@ -255,17 +242,14 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma if (tileNode.type == OTBM_HOUSETILE) { uint32_t houseId; if (!propStream.read(houseId)) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; - setLastErrorString(ss.str()); + setLastErrorString(fmt::format("[x:{:d}, y:{:d}, z:{:d}] Could not read house id.", x, y, z)); return false; } house = map.houses.addHouse(houseId); if (!house) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not create house id: " << houseId; - setLastErrorString(ss.str()); + setLastErrorString( + fmt::format("[x:{:d}, y:{:d}, z:{:d}] Could not create house id: {:d}", x, y, z, houseId)); return false; } @@ -275,15 +259,13 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma } uint8_t attribute; - //read tile attributes + // read tile attributes while (propStream.read(attribute)) { switch (attribute) { case OTBM_ATTR_TILE_FLAGS: { uint32_t flags; if (!propStream.read(flags)) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags."; - setLastErrorString(ss.str()); + setLastErrorString(fmt::format("[x:{:d}, y:{:d}, z:{:d}] Failed to read tile flags.", x, y, z)); return false; } @@ -304,14 +286,14 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma case OTBM_ATTR_ITEM: { Item* item = Item::CreateItem(propStream); if (!item) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item."; - setLastErrorString(ss.str()); + setLastErrorString(fmt::format("[x:{:d}, y:{:d}, z:{:d}] Failed to create item.", x, y, z)); return false; } if (isHouseTile && item->isMoveable()) { - std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl; + std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() + << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y + << ", z: " << z << "]." << std::endl; delete item; } else { if (item->getItemCount() == 0) { @@ -336,18 +318,14 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma } default: - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute."; - setLastErrorString(ss.str()); + setLastErrorString(fmt::format("[x:{:d}, y:{:d}, z:{:d}] Unknown tile attribute.", x, y, z)); return false; } } for (auto& itemNode : tileNode.children) { if (itemNode.type != OTBM_ITEM) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type."; - setLastErrorString(ss.str()); + setLastErrorString(fmt::format("[x:{:d}, y:{:d}, z:{:d}] Unknown node type.", x, y, z)); return false; } @@ -359,22 +337,21 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma Item* item = Item::CreateItem(stream); if (!item) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item."; - setLastErrorString(ss.str()); + setLastErrorString(fmt::format("[x:{:d}, y:{:d}, z:{:d}] Failed to create item.", x, y, z)); return false; } if (!item->unserializeItemNode(loader, itemNode, stream)) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to load item " << item->getID() << '.'; - setLastErrorString(ss.str()); + setLastErrorString( + fmt::format("[x:{:d}, y:{:d}, z:{:d}] Failed to load item {:d}.", x, y, z, item->getID())); delete item; return false; } if (isHouseTile && item->isMoveable()) { - std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl; + std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() + << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y + << ", z: " << z << "]." << std::endl; delete item; } else { if (item->getItemCount() == 0) { @@ -483,4 +460,3 @@ bool IOMap::parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, } return true; } - diff --git a/src/iomap.h b/src/iomap.h index a00d8a4c34..1488ae556e 100644 --- a/src/iomap.h +++ b/src/iomap.h @@ -1,34 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4 -#define FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4 - -#include "item.h" -#include "map.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_IOMAP_H +#define FS_IOMAP_H + +#include "configmanager.h" #include "house.h" +#include "map.h" #include "spawn.h" -#include "configmanager.h" extern ConfigManager g_config; -enum OTBM_AttrTypes_t { +enum OTBM_AttrTypes_t +{ OTBM_ATTR_DESCRIPTION = 1, OTBM_ATTR_EXT_FILE = 2, OTBM_ATTR_TILE_FLAGS = 3, @@ -53,7 +37,8 @@ enum OTBM_AttrTypes_t { OTBM_ATTR_CHARGES = 22, }; -enum OTBM_NodeTypes_t { +enum OTBM_NodeTypes_t +{ OTBM_ROOTV1 = 1, OTBM_MAP_DATA = 2, OTBM_ITEM_DEF = 3, @@ -72,7 +57,8 @@ enum OTBM_NodeTypes_t { OTBM_WAYPOINT = 16, }; -enum OTBM_TileFlag_t : uint32_t { +enum OTBM_TileFlag_t : uint32_t +{ OTBM_TILEFLAG_PROTECTIONZONE = 1 << 0, OTBM_TILEFLAG_NOPVPZONE = 1 << 2, OTBM_TILEFLAG_NOLOGOUT = 1 << 3, @@ -81,7 +67,8 @@ enum OTBM_TileFlag_t : uint32_t { #pragma pack(1) -struct OTBM_root_header { +struct OTBM_root_header +{ uint32_t version; uint16_t width; uint16_t height; @@ -89,13 +76,15 @@ struct OTBM_root_header { uint32_t minorVersionItems; }; -struct OTBM_Destination_coords { +struct OTBM_Destination_coords +{ uint16_t x; uint16_t y; uint8_t z; }; -struct OTBM_Tile_coords { +struct OTBM_Tile_coords +{ uint8_t x; uint8_t y; }; @@ -106,53 +95,49 @@ class IOMap { static Tile* createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z); - public: - bool loadMap(Map* map, const std::string& fileName); - - /* Load the spawns - * \param map pointer to the Map class - * \returns Returns true if the spawns were loaded successfully - */ - static bool loadSpawns(Map* map) { - if (map->spawnfile.empty()) { - //OTBM file doesn't tell us about the spawnfile, - //lets guess it is mapname-spawn.xml. - map->spawnfile = g_config.getString(ConfigManager::MAP_NAME); - map->spawnfile += "-spawn.xml"; - } - - return map->spawns.loadFromXml(map->spawnfile); +public: + bool loadMap(Map* map, const std::string& fileName); + + /* Load the spawns + * \param map pointer to the Map class + * \returns Returns true if the spawns were loaded successfully + */ + static bool loadSpawns(Map* map) + { + if (map->spawnfile.empty()) { + // OTBM file doesn't tell us about the spawnfile, lets guess it is mapname-spawn.xml. + map->spawnfile = g_config.getString(ConfigManager::MAP_NAME); + map->spawnfile += "-spawn.xml"; } - /* Load the houses (not house tile-data) - * \param map pointer to the Map class - * \returns Returns true if the houses were loaded successfully - */ - static bool loadHouses(Map* map) { - if (map->housefile.empty()) { - //OTBM file doesn't tell us about the housefile, - //lets guess it is mapname-house.xml. - map->housefile = g_config.getString(ConfigManager::MAP_NAME); - map->housefile += "-house.xml"; - } - - return map->houses.loadHousesXML(map->housefile); + return map->spawns.loadFromXml(map->spawnfile); + } + + /* Load the houses (not house tile-data) + * \param map pointer to the Map class + * \returns Returns true if the houses were loaded successfully + */ + static bool loadHouses(Map* map) + { + if (map->housefile.empty()) { + // OTBM file doesn't tell us about the housefile, lets guess it is mapname-house.xml. + map->housefile = g_config.getString(ConfigManager::MAP_NAME); + map->housefile += "-house.xml"; } - const std::string& getLastErrorString() const { - return errorString; - } + return map->houses.loadHousesXML(map->housefile); + } - void setLastErrorString(std::string error) { - errorString = error; - } + const std::string& getLastErrorString() const { return errorString; } + + void setLastErrorString(std::string error) { errorString = error; } - private: - bool parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName); - bool parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map); - bool parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map); - bool parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map); - std::string errorString; +private: + bool parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName); + bool parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map); + bool parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map); + bool parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map); + std::string errorString; }; -#endif +#endif // FS_IOMAP_H diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp index af606c2147..e999110d5d 100644 --- a/src/iomapserialize.cpp +++ b/src/iomapserialize.cpp @@ -1,27 +1,13 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "iomapserialize.h" -#include "game.h" + #include "bed.h" +#include "game.h" +#include "housetile.h" extern Game g_game; @@ -68,15 +54,14 @@ bool IOMapSerialize::saveHouseItems() { int64_t start = OTSYS_TIME(); Database& db = Database::getInstance(); - std::ostringstream query; - //Start the transaction + // Start the transaction DBTransaction transaction; if (!transaction.begin()) { return false; } - //clear old tile data + // clear old tile data if (!db.executeQuery("DELETE FROM `tile_store`")) { return false; } @@ -85,7 +70,7 @@ bool IOMapSerialize::saveHouseItems() PropWriteStream stream; for (const auto& it : g_game.map.houses.getHouses()) { - //save house items + // save house items House* house = it.second; for (HouseTile* tile : house->getTiles()) { saveTile(stream, tile); @@ -93,8 +78,8 @@ bool IOMapSerialize::saveHouseItems() size_t attributesSize; const char* attributes = stream.getStream(attributesSize); if (attributesSize > 0) { - query << house->getId() << ',' << db.escapeBlob(attributes, attributesSize); - if (!stmt.addRow(query)) { + if (!stmt.addRow( + fmt::format("{:d}, {:s}", house->getId(), db.escapeBlob(attributes, attributesSize)))) { return false; } stream.clear(); @@ -106,10 +91,9 @@ bool IOMapSerialize::saveHouseItems() return false; } - //End the transaction + // End the transaction bool success = transaction.commit(); - std::cout << "> Saved house items in: " << - (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; + std::cout << "> Saved house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; return success; } @@ -117,7 +101,8 @@ bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container) { while (container->serializationCount > 0) { if (!loadItem(propStream, container)) { - std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl; + std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " + << container->getID() << std::endl; return false; } container->serializationCount--; @@ -125,7 +110,8 @@ bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container) uint8_t endAttr; if (!propStream.read(endAttr) || endAttr != 0) { - std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl; + std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " + << container->getID() << std::endl; return false; } return true; @@ -139,13 +125,13 @@ bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) } Tile* tile = nullptr; - if (parent->getParent() == nullptr) { + if (!parent->getParent()) { tile = parent->getTile(); } const ItemType& iType = Item::items[id]; if (iType.moveable || iType.forceSerialize || !tile) { - //create a new item + // create a new item Item* item = Item::CreateItem(id); if (item) { if (item->unserializeAttr(propStream)) { @@ -193,7 +179,7 @@ bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; } } else { - //The map changed since the last save, just read the attributes + // The map changed since the last save, just read the attributes std::unique_ptr dummy(Item::CreateItem(id)); if (dummy) { dummy->unserializeAttr(propStream); @@ -247,7 +233,8 @@ void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) const ItemType& it = Item::items[item->getID()]; // Note that these are NEGATED, ie. these are the items that will be saved. - if (!(it.moveable || it.forceSerialize || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) { + if (!(it.moveable || it.forceSerialize || item->getDoor() || + (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) { continue; } @@ -311,21 +298,22 @@ bool IOMapSerialize::saveHouseInfo() return false; } - std::ostringstream query; for (const auto& it : g_game.map.houses.getHouses()) { House* house = it.second; - query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getId(); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id` FROM `houses` WHERE `id` = {:d}", house->getId())); if (result) { - query.str(std::string()); - query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db.escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId(); + db.executeQuery(fmt::format( + "UPDATE `houses` SET `owner` = {:d}, `paid` = {:d}, `warnings` = {:d}, `name` = {:s}, `town_id` = {:d}, `rent` = {:d}, `size` = {:d}, `beds` = {:d} WHERE `id` = {:d}", + house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), + db.escapeString(house->getName()), house->getTownId(), house->getRent(), house->getTiles().size(), + house->getBedCount(), house->getId())); } else { - query.str(std::string()); - query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db.escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')'; + db.executeQuery(fmt::format( + "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES ({:d}, {:d}, {:d}, {:d}, {:s}, {:d}, {:d}, {:d}, {:d})", + house->getId(), house->getOwner(), house->getPaidUntil(), house->getPayRentWarnings(), + db.escapeString(house->getName()), house->getTownId(), house->getRent(), house->getTiles().size(), + house->getBedCount())); } - - db.executeQuery(query.str()); - query.str(std::string()); } DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES "); @@ -335,8 +323,7 @@ bool IOMapSerialize::saveHouseInfo() std::string listText; if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << GUEST_LIST << ',' << db.escapeString(listText); - if (!stmt.addRow(query)) { + if (!stmt.addRow(fmt::format("{:d}, {}, {:s}", house->getId(), GUEST_LIST, db.escapeString(listText)))) { return false; } @@ -344,8 +331,7 @@ bool IOMapSerialize::saveHouseInfo() } if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << SUBOWNER_LIST << ',' << db.escapeString(listText); - if (!stmt.addRow(query)) { + if (!stmt.addRow(fmt::format("{:d}, {}, {:s}", house->getId(), SUBOWNER_LIST, db.escapeString(listText)))) { return false; } @@ -354,8 +340,8 @@ bool IOMapSerialize::saveHouseInfo() for (Door* door : house->getDoors()) { if (door->getAccessList(listText) && !listText.empty()) { - query << house->getId() << ',' << door->getDoorId() << ',' << db.escapeString(listText); - if (!stmt.addRow(query)) { + if (!stmt.addRow(fmt::format("{:d}, {:d}, {:s}", house->getId(), door->getDoorId(), + db.escapeString(listText)))) { return false; } @@ -370,3 +356,44 @@ bool IOMapSerialize::saveHouseInfo() return transaction.commit(); } + +bool IOMapSerialize::saveHouse(House* house) +{ + Database& db = Database::getInstance(); + + // Start the transaction + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + uint32_t houseId = house->getId(); + + // clear old tile data + if (!db.executeQuery(fmt::format("DELETE FROM `tile_store` WHERE `house_id` = {:d}", houseId))) { + return false; + } + + DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + + PropWriteStream stream; + for (HouseTile* tile : house->getTiles()) { + saveTile(stream, tile); + + size_t attributesSize; + const char* attributes = stream.getStream(attributesSize); + if (attributesSize > 0) { + if (!stmt.addRow(fmt::format("{:d}, {:s}", houseId, db.escapeBlob(attributes, attributesSize)))) { + return false; + } + stream.clear(); + } + } + + if (!stmt.execute()) { + return false; + } + + // End the transaction + return transaction.commit(); +} diff --git a/src/iomapserialize.h b/src/iomapserialize.h index 9840f2462e..76335ad206 100644 --- a/src/iomapserialize.h +++ b/src/iomapserialize.h @@ -1,42 +1,34 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D -#define FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D +#ifndef FS_IOMAPSERIALIZE_H +#define FS_IOMAPSERIALIZE_H -#include "database.h" -#include "map.h" +class Container; +class Cylinder; +class House; +class Item; +class Map; +class PropStream; +class PropWriteStream; +class Tile; class IOMapSerialize { - public: - static void loadHouseItems(Map* map); - static bool saveHouseItems(); - static bool loadHouseInfo(); - static bool saveHouseInfo(); +public: + static void loadHouseItems(Map* map); + static bool saveHouseItems(); + static bool loadHouseInfo(); + static bool saveHouseInfo(); - private: - static void saveItem(PropWriteStream& stream, const Item* item); - static void saveTile(PropWriteStream& stream, const Tile* tile); + static bool saveHouse(House* house); - static bool loadContainer(PropStream& propStream, Container* container); - static bool loadItem(PropStream& propStream, Cylinder* parent); +private: + static void saveItem(PropWriteStream& stream, const Item* item); + static void saveTile(PropWriteStream& stream, const Tile* tile); + + static bool loadContainer(PropStream& propStream, Container* container); + static bool loadItem(PropStream& propStream, Cylinder* parent); }; -#endif +#endif // FS_IOMAPSERIALIZE_H diff --git a/src/iomarket.cpp b/src/iomarket.cpp index 5607afb083..713c3b9821 100644 --- a/src/iomarket.cpp +++ b/src/iomarket.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -23,8 +7,9 @@ #include "configmanager.h" #include "databasetasks.h" -#include "iologindata.h" #include "game.h" +#include "inbox.h" +#include "iologindata.h" #include "scheduler.h" extern ConfigManager g_config; @@ -34,10 +19,9 @@ MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId { MarketOfferList offerList; - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = " << action << " AND `itemtype` = " << itemId; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `id`, `amount`, `price`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = {:d} AND `itemtype` = {:d}", + action, itemId)); if (!result) { return offerList; } @@ -47,7 +31,7 @@ MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId do { MarketOffer offer; offer.amount = result->getNumber("amount"); - offer.price = result->getNumber("price"); + offer.price = result->getNumber("price"); offer.timestamp = result->getNumber("created") + marketOfferDuration; offer.counter = result->getNumber("id") & 0xFFFF; if (result->getNumber("anonymous") == 0) { @@ -66,10 +50,9 @@ MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `created`, `itemtype` FROM `market_offers` WHERE `player_id` = " << playerId << " AND `sale` = " << action; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `id`, `amount`, `price`, `created`, `itemtype` FROM `market_offers` WHERE `player_id` = {:d} AND `sale` = {:d}", + playerId, action)); if (!result) { return offerList; } @@ -77,7 +60,7 @@ MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) do { MarketOffer offer; offer.amount = result->getNumber("amount"); - offer.price = result->getNumber("price"); + offer.price = result->getNumber("price"); offer.timestamp = result->getNumber("created") + marketOfferDuration; offer.counter = result->getNumber("id") & 0xFFFF; offer.itemId = result->getNumber("itemtype"); @@ -90,10 +73,9 @@ HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t p { HistoryMarketOfferList offerList; - std::ostringstream query; - query << "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = " << playerId << " AND `sale` = " << action; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = {:d} AND `sale` = {:d}", + playerId, action)); if (!result) { return offerList; } @@ -102,7 +84,7 @@ HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t p HistoryMarketOffer offer; offer.itemId = result->getNumber("itemtype"); offer.amount = result->getNumber("amount"); - offer.price = result->getNumber("price"); + offer.price = result->getNumber("price"); offer.timestamp = result->getNumber("expires_at"); MarketOfferState_t offerState = static_cast(result->getNumber("state")); @@ -150,7 +132,8 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) while (tmpAmount > 0) { uint16_t stackCount = std::min(100, tmpAmount); Item* item = Item::CreateItem(itemType.id, stackCount); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { delete item; break; } @@ -167,7 +150,8 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) for (uint16_t i = 0; i < amount; ++i) { Item* item = Item::CreateItem(itemType.id, subType); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != + RETURNVALUE_NOERROR) { delete item; break; } @@ -195,24 +179,26 @@ void IOMarket::checkExpiredOffers() { const time_t lastExpireDate = time(nullptr) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale` FROM `market_offers` WHERE `created` <= " << lastExpireDate; - g_databaseTasks.addTask(query.str(), IOMarket::processExpiredOffers, true); + g_databaseTasks.addTask( + fmt::format( + "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale` FROM `market_offers` WHERE `created` <= {:d}", + lastExpireDate), + IOMarket::processExpiredOffers, true); - int32_t checkExpiredMarketOffersEachMinutes = g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES); + int32_t checkExpiredMarketOffersEachMinutes = + g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES); if (checkExpiredMarketOffersEachMinutes <= 0) { return; } - g_scheduler.addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, std::bind(IOMarket::checkExpiredOffers))); + g_scheduler.addEvent( + createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, &IOMarket::checkExpiredOffers)); } uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId) { - std::ostringstream query; - query << "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = " << playerId; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery( + fmt::format("SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = {:d}", playerId)); if (!result) { return 0; } @@ -225,10 +211,9 @@ MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) const int32_t created = timestamp - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - std::ostringstream query; - query << "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = " << created << " AND (`id` & 65535) = " << counter << " LIMIT 1"; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = {:d} AND (`id` & 65535) = {:d} LIMIT 1", + created, counter)); if (!result) { offer.id = 0; offer.playerId = 0; @@ -240,7 +225,7 @@ MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) offer.amount = result->getNumber("amount"); offer.counter = result->getNumber("id") & 0xFFFF; offer.timestamp = result->getNumber("created"); - offer.price = result->getNumber("price"); + offer.price = result->getNumber("price"); offer.itemId = result->getNumber("itemtype"); offer.playerId = result->getNumber("player_id"); if (result->getNumber("anonymous") == 0) { @@ -251,34 +236,31 @@ MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) return offer; } -void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous) +void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint64_t price, + bool anonymous) { - std::ostringstream query; - query << "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES (" << playerId << ',' << action << ',' << itemId << ',' << amount << ',' << price << ',' << time(nullptr) << ',' << anonymous << ')'; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery(fmt::format( + "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES ({:d}, {:d}, {:d}, {:d}, {:d}, {:d}, {:d})", + playerId, action, itemId, amount, price, time(nullptr), anonymous)); } void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount) { - std::ostringstream query; - query << "UPDATE `market_offers` SET `amount` = `amount` - " << amount << " WHERE `id` = " << offerId; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery( + fmt::format("UPDATE `market_offers` SET `amount` = `amount` - {:d} WHERE `id` = {:d}", amount, offerId)); } void IOMarket::deleteOffer(uint32_t offerId) { - std::ostringstream query; - query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; - Database::getInstance().executeQuery(query.str()); + Database::getInstance().executeQuery(fmt::format("DELETE FROM `market_offers` WHERE `id` = {:d}", offerId)); } -void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state) +void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint64_t price, + time_t timestamp, MarketOfferState_t state) { - std::ostringstream query; - query << "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES (" - << playerId << ',' << type << ',' << itemId << ',' << amount << ',' << price << ',' - << timestamp << ',' << time(nullptr) << ',' << state << ')'; - g_databaseTasks.addTask(query.str()); + g_databaseTasks.addTask(fmt::format( + "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES ({:d}, {:d}, {:d}, {:d}, {:d}, {:d}, {:d}, {:d})", + playerId, type, itemId, amount, price, timestamp, time(nullptr), state)); } bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state) @@ -287,29 +269,29 @@ bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state) Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = " << offerId; - - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(fmt::format( + "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = {:d}", + offerId)); if (!result) { return false; } - query.str(std::string()); - query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; - if (!db.executeQuery(query.str())) { + if (!db.executeQuery(fmt::format("DELETE FROM `market_offers` WHERE `id` = {:d}", offerId))) { return false; } - appendHistory(result->getNumber("player_id"), static_cast(result->getNumber("sale")), result->getNumber("itemtype"), result->getNumber("amount"), result->getNumber("price"), result->getNumber("created") + marketOfferDuration, state); + appendHistory( + result->getNumber("player_id"), static_cast(result->getNumber("sale")), + result->getNumber("itemtype"), result->getNumber("amount"), + result->getNumber("price"), result->getNumber("created") + marketOfferDuration, state); return true; } void IOMarket::updateStatistics() { - std::ostringstream query; - query << "SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = " << OFFERSTATE_ACCEPTED << " GROUP BY `itemtype`, `sale`"; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(fmt::format( + "SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = {:d} GROUP BY `itemtype`, `sale`", + OFFERSTATE_ACCEPTED)); if (!result) { return; } @@ -323,9 +305,9 @@ void IOMarket::updateStatistics() } statistics->numTransactions = result->getNumber("num"); - statistics->lowestPrice = result->getNumber("min"); + statistics->lowestPrice = result->getNumber("min"); statistics->totalPrice = result->getNumber("sum"); - statistics->highestPrice = result->getNumber("max"); + statistics->highestPrice = result->getNumber("max"); } while (result->next()); } diff --git a/src/iomarket.h b/src/iomarket.h index 3491e6f2fa..77e766f239 100644 --- a/src/iomarket.h +++ b/src/iomarket.h @@ -1,63 +1,50 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9 -#define FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_IOMARKET_H +#define FS_IOMARKET_H -#include "enums.h" #include "database.h" +#include "enums.h" class IOMarket { - public: - static IOMarket& getInstance() { - static IOMarket instance; - return instance; - } +public: + static IOMarket& getInstance() + { + static IOMarket instance; + return instance; + } - static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId); - static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId); - static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId); + static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId); + static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId); + static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId); - static void processExpiredOffers(DBResult_ptr result, bool); - static void checkExpiredOffers(); + static void processExpiredOffers(DBResult_ptr result, bool); + static void checkExpiredOffers(); - static uint32_t getPlayerOfferCount(uint32_t playerId); - static MarketOfferEx getOfferByCounter(uint32_t timestamp, uint16_t counter); + static uint32_t getPlayerOfferCount(uint32_t playerId); + static MarketOfferEx getOfferByCounter(uint32_t timestamp, uint16_t counter); - static void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous); - static void acceptOffer(uint32_t offerId, uint16_t amount); - static void deleteOffer(uint32_t offerId); + static void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint64_t price, + bool anonymous); + static void acceptOffer(uint32_t offerId, uint16_t amount); + static void deleteOffer(uint32_t offerId); - static void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state); - static bool moveOfferToHistory(uint32_t offerId, MarketOfferState_t state); + static void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint64_t price, + time_t timestamp, MarketOfferState_t state); + static bool moveOfferToHistory(uint32_t offerId, MarketOfferState_t state); - void updateStatistics(); + void updateStatistics(); - MarketStatistics* getPurchaseStatistics(uint16_t itemId); - MarketStatistics* getSaleStatistics(uint16_t itemId); + MarketStatistics* getPurchaseStatistics(uint16_t itemId); + MarketStatistics* getSaleStatistics(uint16_t itemId); - private: - IOMarket() = default; +private: + IOMarket() = default; - std::map purchaseStatistics; - std::map saleStatistics; + std::map purchaseStatistics; + std::map saleStatistics; }; -#endif +#endif // FS_IOMARKET_H diff --git a/src/item.cpp b/src/item.cpp index 36949b83ca..86ea4ba508 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1,35 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "item.h" + +#include "bed.h" +#include "combat.h" #include "container.h" +#include "game.h" +#include "house.h" +#include "mailbox.h" +#include "podium.h" #include "teleport.h" #include "trashholder.h" -#include "mailbox.h" -#include "house.h" -#include "game.h" -#include "bed.h" -#include "actions.h" -#include "spells.h" +class Spells; extern Game g_game; extern Spells* g_spells; @@ -67,18 +53,8 @@ Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) newItem = new Mailbox(type); } else if (it.isBed()) { newItem = new BedItem(type); - } else if (it.id >= 2210 && it.id <= 2212) { // magic rings - newItem = new Item(type - 3, count); - } else if (it.id == 2215 || it.id == 2216) { // magic rings - newItem = new Item(type - 2, count); - } else if (it.id >= 2202 && it.id <= 2206) { // magic rings - newItem = new Item(type - 37, count); - } else if (it.id == 2640) { // soft boots - newItem = new Item(6132, count); - } else if (it.id == 6301) { // death ring - newItem = new Item(6300, count); - } else if (it.id == 18528) { // prismatic ring - newItem = new Item(18408, count); + } else if (it.isPodium()) { + newItem = new Podium(type); } else { newItem = new Item(type, count); } @@ -92,7 +68,8 @@ Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) Container* Item::CreateItemAsContainer(const uint16_t type, uint16_t size) { const ItemType& it = Item::items[type]; - if (it.id == 0 || it.group == ITEM_GROUP_DEPRECATED || it.stackable || it.useable || it.moveable || it.pickupable || it.isDepot() || it.isSplash() || it.isDoor()) { + if (it.id == 0 || it.group == ITEM_GROUP_DEPRECATED || it.stackable || it.useable || it.moveable || it.pickupable || + it.isDepot() || it.isSplash() || it.isDoor()) { return nullptr; } @@ -144,8 +121,7 @@ Item* Item::CreateItem(PropStream& propStream) return Item::CreateItem(id, 0); } -Item::Item(const uint16_t type, uint16_t count /*= 0*/) : - id(type) +Item::Item(const uint16_t type, uint16_t count /*= 0*/) : id(type) { const ItemType& it = items[id]; @@ -168,8 +144,7 @@ Item::Item(const uint16_t type, uint16_t count /*= 0*/) : setDefaultDuration(); } -Item::Item(const Item& i) : - Thing(), id(i.id), count(i.count), loadedFromMap(i.loadedFromMap) +Item::Item(const Item& i) : Thing(), id(i.id), count(i.count), loadedFromMap(i.loadedFromMap) { if (i.attributes) { attributes.reset(new ItemAttributes(*i.attributes)); @@ -210,7 +185,13 @@ bool Item::equals(const Item* otherItem) const const auto& attributeList = attributes->attributes; const auto& otherAttributeList = otherAttributes->attributes; for (const auto& attribute : attributeList) { - if (ItemAttributes::isStrAttrType(attribute.type)) { + if (ItemAttributes::isIntAttrType(attribute.type)) { + for (const auto& otherAttribute : otherAttributeList) { + if (attribute.type == otherAttribute.type && attribute.value.integer != otherAttribute.value.integer) { + return false; + } + } + } else if (ItemAttributes::isStrAttrType(attribute.type)) { for (const auto& otherAttribute : otherAttributeList) { if (attribute.type == otherAttribute.type && *attribute.value.string != *otherAttribute.value.string) { return false; @@ -218,7 +199,7 @@ bool Item::equals(const Item* otherItem) const } } else { for (const auto& otherAttribute : otherAttributeList) { - if (attribute.type == otherAttribute.type && attribute.value.integer != otherAttribute.value.integer) { + if (attribute.type == otherAttribute.type && *attribute.value.custom != *otherAttribute.value.custom) { return false; } } @@ -280,7 +261,7 @@ Cylinder* Item::getTopParent() return prevaux; } - while (aux->getParent() != nullptr) { + while (aux->getParent()) { prevaux = aux; aux = aux->getParent(); } @@ -299,7 +280,7 @@ const Cylinder* Item::getTopParent() const return prevaux; } - while (aux->getParent() != nullptr) { + while (aux->getParent()) { prevaux = aux; aux = aux->getParent(); } @@ -313,7 +294,7 @@ const Cylinder* Item::getTopParent() const Tile* Item::getTile() { Cylinder* cylinder = getTopParent(); - //get root cylinder + // get root cylinder if (cylinder && cylinder->getParent()) { cylinder = cylinder->getParent(); } @@ -323,7 +304,7 @@ Tile* Item::getTile() const Tile* Item::getTile() const { const Cylinder* cylinder = getTopParent(); - //get root cylinder + // get root cylinder if (cylinder && cylinder->getParent()) { cylinder = cylinder->getParent(); } @@ -343,18 +324,7 @@ uint16_t Item::getSubType() const return count; } -Player* Item::getHoldingPlayer() const -{ - Cylinder* p = getParent(); - while (p) { - if (p->getCreature()) { - return p->getCreature()->getPlayer(); - } - - p = p->getParent(); - } - return nullptr; -} +const Player* Item::getHoldingPlayer() const { return dynamic_cast(getTopParent()); } void Item::setSubType(uint16_t n) { @@ -526,6 +496,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } + case ATTR_ATTACK_SPEED: { + uint32_t attackSpeed; + if (!propStream.read(attackSpeed)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_ATTACK_SPEED, attackSpeed); + break; + } + case ATTR_DEFENSE: { int32_t defense; if (!propStream.read(defense)) { @@ -606,11 +586,59 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - //these should be handled through derived classes - //If these are called then something has changed in the items.xml since the map was saved - //just read the values + case ATTR_OPENCONTAINER: { + uint8_t openContainer; + if (!propStream.read(openContainer)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, openContainer); + break; + } + + case ATTR_REFLECT: { + uint16_t size; + if (!propStream.read(size)) { + return ATTR_READ_ERROR; + } + + for (uint16_t i = 0; i < size; ++i) { + CombatType_t combatType; + Reflect reflect; + + if (!propStream.read(combatType) || !propStream.read(reflect.percent) || + !propStream.read(reflect.chance)) { + return ATTR_READ_ERROR; + } + + getAttributes()->reflect[combatType] = reflect; + } + break; + } + + case ATTR_BOOST: { + uint16_t size; + if (!propStream.read(size)) { + return ATTR_READ_ERROR; + } + + for (uint16_t i = 0; i < size; ++i) { + CombatType_t combatType; + uint16_t percent; + + if (!propStream.read(combatType) || !propStream.read(percent)) { + return ATTR_READ_ERROR; + } + + getAttributes()->boostPercent[combatType] = percent; + } + break; + } + + // these should be handled through derived classes If these are called then something has changed in the + // items.xml since the map was saved just read the values - //Depot class + // Depot class case ATTR_DEPOT_ID: { if (!propStream.skip(2)) { return ATTR_READ_ERROR; @@ -618,7 +646,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - //Door class + // Door class case ATTR_HOUSEDOORID: { if (!propStream.skip(1)) { return ATTR_READ_ERROR; @@ -626,7 +654,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - //Bed class + // Bed class case ATTR_SLEEPERGUID: { if (!propStream.skip(4)) { return ATTR_READ_ERROR; @@ -641,7 +669,15 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - //Teleport class + // Podium class + case ATTR_PODIUMOUTFIT: { + if (!propStream.skip(15)) { + return ATTR_READ_ERROR; + } + break; + } + + // Teleport class case ATTR_TELE_DEST: { if (!propStream.skip(5)) { return ATTR_READ_ERROR; @@ -649,7 +685,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - //Container class + // Container class case ATTR_CONTAINER_ITEMS: { return ATTR_READ_ERROR; } @@ -786,6 +822,11 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ATTACK)); } + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK_SPEED)) { + propWriteStream.write(ATTR_ATTACK_SPEED); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ATTACK_SPEED)); + } + if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { propWriteStream.write(ATTR_DEFENSE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); @@ -826,11 +867,16 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_STOREITEM)); } + if (hasAttribute(ITEM_ATTRIBUTE_OPENCONTAINER)) { + propWriteStream.write(ATTR_OPENCONTAINER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)); + } + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { const ItemAttributes::CustomAttributeMap* customAttrMap = attributes->getCustomAttributeMap(); propWriteStream.write(ATTR_CUSTOM_ATTRIBUTES); propWriteStream.write(static_cast(customAttrMap->size())); - for (const auto &entry : *customAttrMap) { + for (const auto& entry : *customAttrMap) { // Serializing key type and value propWriteStream.writeString(entry.first); @@ -838,25 +884,63 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const entry.second.serialize(propWriteStream); } } + + if (attributes) { + const auto& reflects = attributes->reflect; + if (!reflects.empty()) { + propWriteStream.write(ATTR_REFLECT); + propWriteStream.write(reflects.size()); + + for (const auto& reflect : reflects) { + propWriteStream.write(reflect.first); + propWriteStream.write(reflect.second.percent); + propWriteStream.write(reflect.second.chance); + } + } + + const auto& boosts = attributes->boostPercent; + if (!boosts.empty()) { + propWriteStream.write(ATTR_BOOST); + propWriteStream.write(boosts.size()); + + for (const auto& boost : boosts) { + propWriteStream.write(boost.first); + propWriteStream.write(boost.second); + } + } + } } bool Item::hasProperty(ITEMPROPERTY prop) const { const ItemType& it = items[id]; switch (prop) { - case CONST_PROP_BLOCKSOLID: return it.blockSolid; - case CONST_PROP_MOVEABLE: return it.moveable && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID); - case CONST_PROP_HASHEIGHT: return it.hasHeight; - case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile; - case CONST_PROP_BLOCKPATH: return it.blockPathFind; - case CONST_PROP_ISVERTICAL: return it.isVertical; - case CONST_PROP_ISHORIZONTAL: return it.isHorizontal; - case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); - case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); - case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); - case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind; - case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical; - default: return false; + case CONST_PROP_BLOCKSOLID: + return it.blockSolid; + case CONST_PROP_MOVEABLE: + return it.moveable && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID); + case CONST_PROP_HASHEIGHT: + return it.hasHeight; + case CONST_PROP_BLOCKPROJECTILE: + return it.blockProjectile; + case CONST_PROP_BLOCKPATH: + return it.blockPathFind; + case CONST_PROP_ISVERTICAL: + return it.isVertical; + case CONST_PROP_ISHORIZONTAL: + return it.isHorizontal; + case CONST_PROP_IMMOVABLEBLOCKSOLID: + return it.blockSolid && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); + case CONST_PROP_IMMOVABLEBLOCKPATH: + return it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); + case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: + return !it.isMagicField() && it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); + case CONST_PROP_NOFIELDBLOCKPATH: + return !it.isMagicField() && it.blockPathFind; + case CONST_PROP_SUPPORTHANGABLE: + return it.isHorizontal || it.isVertical; + default: + return false; } } @@ -869,639 +953,14 @@ uint32_t Item::getWeight() const return weight; } -std::string Item::getDescription(const ItemType& it, int32_t lookDistance, - const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) -{ - const std::string* text = nullptr; - - std::ostringstream s; - s << getNameDescription(it, item, subType, addArticle); - - if (item) { - subType = item->getSubType(); - } - - if (it.isRune()) { - if (it.runeLevel > 0 || it.runeMagLevel > 0) { - if (RuneSpell* rune = g_spells->getRuneSpell(it.id)) { - int32_t tmpSubType = subType; - if (item) { - tmpSubType = item->getSubType(); - } - s << ". " << (it.stackable && tmpSubType > 1 ? "They" : "It") << " can only be used by "; - - const VocSpellMap& vocMap = rune->getVocMap(); - std::vector showVocMap; - - // vocations are usually listed with the unpromoted and promoted version, the latter being - // hidden from description, so `total / 2` is most likely the amount of vocations to be shown. - showVocMap.reserve(vocMap.size() / 2); - for (const auto& voc : vocMap) { - if (voc.second) { - showVocMap.push_back(g_vocations.getVocation(voc.first)); - } - } - - if (!showVocMap.empty()) { - auto vocIt = showVocMap.begin(), vocLast = (showVocMap.end() - 1); - while (vocIt != vocLast) { - s << asLowerCaseString((*vocIt)->getVocName()) << "s"; - if (++vocIt == vocLast) { - s << " and "; - } else { - s << ", "; - } - } - s << asLowerCaseString((*vocLast)->getVocName()) << "s"; - } else { - s << "players"; - } - - s << " with"; - - if (it.runeLevel > 0) { - s << " level " << it.runeLevel; - } - - if (it.runeMagLevel > 0) { - if (it.runeLevel > 0) { - s << " and"; - } - - s << " magic level " << it.runeMagLevel; - } - - s << " or higher"; - } - } - } else if (it.weaponType != WEAPON_NONE) { - bool begin = true; - if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { - s << " (Range:" << static_cast(item ? item->getShootRange() : it.shootRange); - - int32_t attack; - int8_t hitChance; - if (item) { - attack = item->getAttack(); - hitChance = item->getHitChance(); - } else { - attack = it.attack; - hitChance = it.hitChance; - } - - if (attack != 0) { - s << ", Atk" << std::showpos << attack << std::noshowpos; - } - - if (hitChance != 0) { - s << ", Hit%" << std::showpos << static_cast(hitChance) << std::noshowpos; - } - - begin = false; - } else if (it.weaponType != WEAPON_AMMO) { - - int32_t attack, defense, extraDefense; - if (item) { - attack = item->getAttack(); - defense = item->getDefense(); - extraDefense = item->getExtraDefense(); - } else { - attack = it.attack; - defense = it.defense; - extraDefense = it.extraDefense; - } - - if (attack != 0) { - begin = false; - s << " (Atk:" << attack; - - if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { - s << " physical + " << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); - } - } - - if (defense != 0 || extraDefense != 0) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "Def:" << defense; - if (extraDefense != 0) { - s << ' ' << std::showpos << extraDefense << std::noshowpos; - } - } - } - - if (it.abilities) { - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { - continue; - } - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; - } - - for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; i++) { - if (!it.abilities->specialSkills[i]) { - continue; - } - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << getSpecialSkillName(i) << ' ' << std::showpos << it.abilities->specialSkills[i] << '%' << std::noshowpos; - } - - if (it.abilities->stats[STAT_MAGICPOINTS]) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - } - - int16_t show = it.abilities->absorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all " << std::showpos << show << std::noshowpos << '%'; - } - - show = it.abilities->fieldAbsorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->fieldAbsorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all fields " << std::showpos << show << std::noshowpos << '%'; - } - - if (it.abilities->speed) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - } - } - - if (!begin) { - s << ')'; - } - } else if (it.armor != 0 || (item && item->getArmor() != 0) || it.showAttributes) { - bool begin = true; - - int32_t armor = (item ? item->getArmor() : it.armor); - if (armor != 0) { - s << " (Arm:" << armor; - begin = false; - } - - if (it.abilities) { - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { - continue; - } - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; - } - - if (it.abilities->stats[STAT_MAGICPOINTS]) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - } - - int16_t show = it.abilities->absorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (!show) { - bool protectionBegin = true; - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (protectionBegin) { - protectionBegin = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all " << std::showpos << show << std::noshowpos << '%'; - } - - show = it.abilities->fieldAbsorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (!show) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->fieldAbsorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all fields " << std::showpos << show << std::noshowpos << '%'; - } - - if (it.abilities->speed) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - } - } - - if (!begin) { - s << ')'; - } - } else if (it.isContainer() || (item && item->getContainer())) { - uint32_t volume = 0; - if (!item || !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - if (it.isContainer()) { - volume = it.maxItems; - } else { - volume = item->getContainer()->capacity(); - } - } - - if (volume != 0) { - s << " (Vol:" << volume << ')'; - } - } else { - bool found = true; - - if (it.abilities) { - if (it.abilities->speed > 0) { - s << " (speed " << std::showpos << (it.abilities->speed / 2) << std::noshowpos << ')'; - } else if (hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) { - s << " (hard drinking)"; - } else if (it.abilities->invisible) { - s << " (invisibility)"; - } else if (it.abilities->regeneration) { - s << " (faster regeneration)"; - } else if (it.abilities->manaShield) { - s << " (mana shield)"; - } else { - found = false; - } - } else { - found = false; - } - - if (!found) { - if (it.isKey()) { - int32_t keyNumber = (item ? item->getActionId() : 0); - if (keyNumber != 0) { - s << " (Key:" << std::setfill('0') << std::setw(4) << keyNumber << ')'; - } - } else if (it.isFluidContainer()) { - if (subType > 0) { - const std::string& itemName = items[subType].name; - s << " of " << (!itemName.empty() ? itemName : "unknown"); - } else { - s << ". It is empty"; - } - } else if (it.isSplash()) { - s << " of "; - - if (subType > 0 && !items[subType].name.empty()) { - s << items[subType].name; - } else { - s << "unknown"; - } - } else if (it.allowDistRead && (it.id < 7369 || it.id > 7371)) { - s << ".\n"; - - if (lookDistance <= 4) { - if (item) { - text = &item->getText(); - if (!text->empty()) { - const std::string& writer = item->getWriter(); - if (!writer.empty()) { - s << writer << " wrote"; - time_t date = item->getDate(); - if (date != 0) { - s << " on " << formatDateShort(date); - } - s << ": "; - } else { - s << "You read: "; - } - s << *text; - } else { - s << "Nothing is written on it"; - } - } else { - s << "Nothing is written on it"; - } - } else { - s << "You are too far away to read it"; - } - } else if (it.levelDoor != 0 && item) { - uint16_t actionId = item->getActionId(); - if (actionId >= it.levelDoor) { - s << " for level " << (actionId - it.levelDoor); - } - } - } - } - - if (it.showCharges) { - s << " that has " << subType << " charge" << (subType != 1 ? "s" : "") << " left"; - } - - if (it.showDuration) { - if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { - uint32_t duration = item->getDuration() / 1000; - s << " that will expire in "; - - if (duration >= 86400) { - uint16_t days = duration / 86400; - uint16_t hours = (duration % 86400) / 3600; - s << days << " day" << (days != 1 ? "s" : ""); - - if (hours > 0) { - s << " and " << hours << " hour" << (hours != 1 ? "s" : ""); - } - } else if (duration >= 3600) { - uint16_t hours = duration / 3600; - uint16_t minutes = (duration % 3600) / 60; - s << hours << " hour" << (hours != 1 ? "s" : ""); - - if (minutes > 0) { - s << " and " << minutes << " minute" << (minutes != 1 ? "s" : ""); - } - } else if (duration >= 60) { - uint16_t minutes = duration / 60; - s << minutes << " minute" << (minutes != 1 ? "s" : ""); - uint16_t seconds = duration % 60; - - if (seconds > 0) { - s << " and " << seconds << " second" << (seconds != 1 ? "s" : ""); - } - } else { - s << duration << " second" << (duration != 1 ? "s" : ""); - } - } else { - s << " that is brand-new"; - } - } - - if (!it.allowDistRead || (it.id >= 7369 && it.id <= 7371)) { - s << '.'; - } else { - if (!text && item) { - text = &item->getText(); - } - - if (!text || text->empty()) { - s << '.'; - } - } - - if (it.wieldInfo != 0) { - s << "\nIt can only be wielded properly by "; - - if (it.wieldInfo & WIELDINFO_PREMIUM) { - s << "premium "; - } - - if (!it.vocationString.empty()) { - s << it.vocationString; - } else { - s << "players"; - } - - if (it.wieldInfo & WIELDINFO_LEVEL) { - s << " of level " << it.minReqLevel << " or higher"; - } - - if (it.wieldInfo & WIELDINFO_MAGLV) { - if (it.wieldInfo & WIELDINFO_LEVEL) { - s << " and"; - } else { - s << " of"; - } - - s << " magic level " << it.minReqMagicLevel << " or higher"; - } - - s << '.'; - } - - if (lookDistance <= 1) { - if (item) { - const uint32_t weight = item->getWeight(); - if (weight != 0 && it.pickupable) { - s << '\n' << getWeightDescription(it, weight, item->getItemCount()); - } - } else if (it.weight != 0 && it.pickupable) { - s << '\n' << getWeightDescription(it, it.weight); - } - } - - if (item) { - const std::string& specialDescription = item->getSpecialDescription(); - if (!specialDescription.empty()) { - s << '\n' << specialDescription; - } else if (lookDistance <= 1 && !it.description.empty()) { - s << '\n' << it.description; - } - } else if (lookDistance <= 1 && !it.description.empty()) { - s << '\n' << it.description; - } - - if (it.allowDistRead && it.id >= 7369 && it.id <= 7371) { - if (!text && item) { - text = &item->getText(); - } - - if (text && !text->empty()) { - s << '\n' << *text; - } - } - return s.str(); -} - -std::string Item::getDescription(int32_t lookDistance) const +std::string Item::getDescription(int32_t) const { - const ItemType& it = items[id]; - return getDescription(it, lookDistance, this); + // item descriptions moved to lua + return ""; } -std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, + bool addArticle /*= true*/) { if (item) { subType = item->getSubType(); @@ -1597,8 +1056,7 @@ bool Item::canDecay() const return false; } - const ItemType& it = Item::items[id]; - if (getDecayTo() < 0 || it.decayTime == 0) { + if (getDecayTo() < 0 || getDecayTime() == 0) { return false; } @@ -1609,33 +1067,51 @@ bool Item::canDecay() const return true; } -uint32_t Item::getWorth() const +uint32_t Item::getWorth() const { return items[id].worth * count; } + +LightInfo Item::getLightInfo() const { - switch (id) { - case ITEM_GOLD_COIN: - return count; + const ItemType& it = items[id]; + return {it.lightLevel, it.lightColor}; +} - case ITEM_PLATINUM_COIN: - return count * 100; +Reflect Item::getReflect(CombatType_t combatType, bool total /* = true */) const +{ + const ItemType& it = Item::items[id]; - case ITEM_CRYSTAL_COIN: - return count * 10000; + Reflect reflect; + if (attributes) { + reflect += attributes->getReflect(combatType); + } - default: - return 0; + if (total && it.abilities) { + reflect += it.abilities->reflect[combatTypeToIndex(combatType)]; } + + return reflect; } -LightInfo Item::getLightInfo() const +uint16_t Item::getBoostPercent(CombatType_t combatType, bool total /* = true */) const { - const ItemType& it = items[id]; - return {it.lightLevel, it.lightColor}; + const ItemType& it = Item::items[id]; + + uint16_t boostPercent = 0; + if (attributes) { + boostPercent += attributes->getBoostPercent(combatType); + } + + if (total && it.abilities) { + boostPercent += it.abilities->boostPercent[combatTypeToIndex(combatType)]; + } + + return boostPercent; } std::string ItemAttributes::emptyString; int64_t ItemAttributes::emptyInt; double ItemAttributes::emptyDouble; bool ItemAttributes::emptyBool; +Reflect ItemAttributes::emptyReflect; const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const { @@ -1706,18 +1182,15 @@ void ItemAttributes::setIntAttr(itemAttrTypes type, int64_t value) return; } - getAttr(type).value.integer = value; -} - -void ItemAttributes::increaseIntAttr(itemAttrTypes type, int64_t value) -{ - if (!isIntAttrType(type)) { - return; + if (type == ITEM_ATTRIBUTE_ATTACK_SPEED && value < 100) { + value = 100; } - getAttr(type).value.integer += value; + getAttr(type).value.integer = value; } +void ItemAttributes::increaseIntAttr(itemAttrTypes type, int64_t value) { setIntAttr(type, getIntAttr(type) + value); } + const ItemAttributes::Attribute* ItemAttributes::getExistingAttr(itemAttrTypes type) const { if (hasAttribute(type)) { @@ -1745,17 +1218,27 @@ ItemAttributes::Attribute& ItemAttributes::getAttr(itemAttrTypes type) return attributes.back(); } -void Item::startDecaying() -{ - g_game.startDecay(this); -} +void Item::startDecaying() { g_game.startDecay(this); } bool Item::hasMarketAttributes() const { - if (attributes == nullptr) { + if (!attributes) { return true; } + // discard items with custom boost and reflect + for (uint16_t i = 0; i < COMBAT_COUNT; ++i) { + if (getBoostPercent(indexToCombatType(i), false) > 0) { + return false; + } + + Reflect tmpReflect = getReflect(indexToCombatType(i), false); + if (tmpReflect.chance != 0 || tmpReflect.percent != 0) { + return false; + } + } + + // discard items with other modified attributes for (const auto& attr : attributes->getList()) { if (attr.type == ITEM_ATTRIBUTE_CHARGES) { uint16_t charges = static_cast(attr.value.integer); @@ -1774,8 +1257,9 @@ bool Item::hasMarketAttributes() const return true; } -template<> -const std::string& ItemAttributes::CustomAttribute::get() { +template <> +const std::string& ItemAttributes::CustomAttribute::get() +{ if (value.type() == typeid(std::string)) { return boost::get(value); } @@ -1783,8 +1267,9 @@ const std::string& ItemAttributes::CustomAttribute::get() { return emptyString; } -template<> -const int64_t& ItemAttributes::CustomAttribute::get() { +template <> +const int64_t& ItemAttributes::CustomAttribute::get() +{ if (value.type() == typeid(int64_t)) { return boost::get(value); } @@ -1792,8 +1277,9 @@ const int64_t& ItemAttributes::CustomAttribute::get() { return emptyInt; } -template<> -const double& ItemAttributes::CustomAttribute::get() { +template <> +const double& ItemAttributes::CustomAttribute::get() +{ if (value.type() == typeid(double)) { return boost::get(value); } @@ -1801,8 +1287,9 @@ const double& ItemAttributes::CustomAttribute::get() { return emptyDouble; } -template<> -const bool& ItemAttributes::CustomAttribute::get() { +template <> +const bool& ItemAttributes::CustomAttribute::get() +{ if (value.type() == typeid(bool)) { return boost::get(value); } diff --git a/src/item.h b/src/item.h index f516bd5e1a..5a088e251f 100644 --- a/src/item.h +++ b/src/item.h @@ -1,47 +1,26 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562 -#define FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_ITEM_H +#define FS_ITEM_H #include "cylinder.h" -#include "thing.h" #include "items.h" #include "luascript.h" -#include "tools.h" -#include - -#include -#include +#include "thing.h" -class Creature; -class Player; +class BedItem; class Container; -class Depot; -class Teleport; -class TrashHolder; -class Mailbox; class Door; class MagicField; -class BedItem; +class Mailbox; +class Player; +class Podium; +class Teleport; +class TrashHolder; -enum ITEMPROPERTY { +enum ITEMPROPERTY +{ CONST_PROP_BLOCKSOLID = 0, CONST_PROP_HASHEIGHT, CONST_PROP_BLOCKPROJECTILE, @@ -56,20 +35,23 @@ enum ITEMPROPERTY { CONST_PROP_SUPPORTHANGABLE, }; -enum TradeEvents_t { +enum TradeEvents_t +{ ON_TRADE_TRANSFER, ON_TRADE_CANCEL, }; -enum ItemDecayState_t : uint8_t { +enum ItemDecayState_t : uint8_t +{ DECAYING_FALSE = 0, DECAYING_TRUE, DECAYING_PENDING, }; -enum AttrTypes_t { - //ATTR_DESCRIPTION = 1, - //ATTR_EXT_FILE = 2, +enum AttrTypes_t +{ + // ATTR_DESCRIPTION = 1, + // ATTR_EXT_FILE = 2, ATTR_TILE_FLAGS = 3, ATTR_ACTION_ID = 4, ATTR_UNIQUE_ID = 5, @@ -78,9 +60,9 @@ enum AttrTypes_t { ATTR_TELE_DEST = 8, ATTR_ITEM = 9, ATTR_DEPOT_ID = 10, - //ATTR_EXT_SPAWN_FILE = 11, + // ATTR_EXT_SPAWN_FILE = 11, ATTR_RUNE_CHARGES = 12, - //ATTR_EXT_HOUSE_FILE = 13, + // ATTR_EXT_HOUSE_FILE = 13, ATTR_HOUSEDOORID = 14, ATTR_COUNT = 15, ATTR_DURATION = 16, @@ -104,10 +86,17 @@ enum AttrTypes_t { ATTR_CUSTOM_ATTRIBUTES = 34, ATTR_DECAYTO = 35, ATTR_WRAPID = 36, - ATTR_STOREITEM = 37 + ATTR_STOREITEM = 37, + ATTR_ATTACK_SPEED = 38, + ATTR_OPENCONTAINER = 39, + ATTR_PODIUMOUTFIT = 40, + // ATTR_TIER = 41, // mapeditor + ATTR_REFLECT = 42, + ATTR_BOOST = 43, }; -enum Attr_ReadValue { +enum Attr_ReadValue +{ ATTR_READ_CONTINUE, ATTR_READ_ERROR, ATTR_READ_END, @@ -115,941 +104,841 @@ enum Attr_ReadValue { class ItemAttributes { - public: - ItemAttributes() = default; +public: + ItemAttributes() = default; - void setSpecialDescription(const std::string& desc) { - setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); - } - const std::string& getSpecialDescription() const { - return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); - } + void setSpecialDescription(const std::string& desc) { setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); } + const std::string& getSpecialDescription() const { return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); } - void setText(const std::string& text) { - setStrAttr(ITEM_ATTRIBUTE_TEXT, text); - } - void resetText() { - removeAttribute(ITEM_ATTRIBUTE_TEXT); - } - const std::string& getText() const { - return getStrAttr(ITEM_ATTRIBUTE_TEXT); - } + void setText(const std::string& text) { setStrAttr(ITEM_ATTRIBUTE_TEXT, text); } + void resetText() { removeAttribute(ITEM_ATTRIBUTE_TEXT); } + const std::string& getText() const { return getStrAttr(ITEM_ATTRIBUTE_TEXT); } - void setDate(int32_t n) { - setIntAttr(ITEM_ATTRIBUTE_DATE, n); - } - void resetDate() { - removeAttribute(ITEM_ATTRIBUTE_DATE); - } - time_t getDate() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); - } + void setDate(int32_t n) { setIntAttr(ITEM_ATTRIBUTE_DATE, n); } + void resetDate() { removeAttribute(ITEM_ATTRIBUTE_DATE); } + time_t getDate() const { return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); } - void setWriter(const std::string& writer) { - setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); - } - void resetWriter() { - removeAttribute(ITEM_ATTRIBUTE_WRITER); - } - const std::string& getWriter() const { - return getStrAttr(ITEM_ATTRIBUTE_WRITER); - } + void setWriter(const std::string& writer) { setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); } + void resetWriter() { removeAttribute(ITEM_ATTRIBUTE_WRITER); } + const std::string& getWriter() const { return getStrAttr(ITEM_ATTRIBUTE_WRITER); } - void setActionId(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); - } - uint16_t getActionId() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); - } + void setActionId(uint16_t n) { setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); } + uint16_t getActionId() const { return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); } - void setUniqueId(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_UNIQUEID, n); - } - uint16_t getUniqueId() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); - } + void setUniqueId(uint16_t n) { setIntAttr(ITEM_ATTRIBUTE_UNIQUEID, n); } + uint16_t getUniqueId() const { return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); } - void setCharges(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); - } - uint16_t getCharges() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); - } + void setCharges(uint16_t n) { setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); } + uint16_t getCharges() const { return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); } - void setFluidType(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); - } - uint16_t getFluidType() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); - } + void setFluidType(uint16_t n) { setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); } + uint16_t getFluidType() const { return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); } - void setOwner(uint32_t owner) { - setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); - } - uint32_t getOwner() const { - return getIntAttr(ITEM_ATTRIBUTE_OWNER); - } + void setOwner(uint32_t owner) { setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); } + uint32_t getOwner() const { return getIntAttr(ITEM_ATTRIBUTE_OWNER); } - void setCorpseOwner(uint32_t corpseOwner) { - setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); - } - uint32_t getCorpseOwner() const { - return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); - } + void setCorpseOwner(uint32_t corpseOwner) { setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); } + uint32_t getCorpseOwner() const { return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); } - void setDuration(int32_t time) { - setIntAttr(ITEM_ATTRIBUTE_DURATION, time); - } - void decreaseDuration(int32_t time) { - increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); - } - uint32_t getDuration() const { - return getIntAttr(ITEM_ATTRIBUTE_DURATION); - } + void setDuration(int32_t time) { setIntAttr(ITEM_ATTRIBUTE_DURATION, time); } + void decreaseDuration(int32_t time) { increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); } + uint32_t getDuration() const { return getIntAttr(ITEM_ATTRIBUTE_DURATION); } - void setDecaying(ItemDecayState_t decayState) { - setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); - } - ItemDecayState_t getDecaying() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); - } + void setDecaying(ItemDecayState_t decayState) { setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); } + ItemDecayState_t getDecaying() const + { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + } - struct CustomAttribute - { - typedef boost::variant VariantAttribute; - VariantAttribute value; + struct CustomAttribute + { + typedef boost::variant VariantAttribute; + VariantAttribute value; - CustomAttribute() : value(boost::blank()) {} + CustomAttribute() : value(boost::blank()) {} - template - explicit CustomAttribute(const T& v) : value(v) {} + bool operator==(const CustomAttribute& otherAttr) const { return value == otherAttr.value; } - template - void set(const T& v) { - value = v; - } + bool operator!=(const CustomAttribute& otherAttr) const { return value != otherAttr.value; } - template - const T& get(); + template + explicit CustomAttribute(const T& v) : value(v) + {} - struct PushLuaVisitor : public boost::static_visitor<> { - lua_State* L; + template + void set(const T& v) + { + value = v; + } - explicit PushLuaVisitor(lua_State* L) : boost::static_visitor<>(), L(L) {} + template + const T& get(); - void operator()(const boost::blank&) const { - lua_pushnil(L); - } + struct PushLuaVisitor : public boost::static_visitor<> + { + lua_State* L; - void operator()(const std::string& v) const { - LuaScriptInterface::pushString(L, v); - } + explicit PushLuaVisitor(lua_State* L) : boost::static_visitor<>(), L(L) {} - void operator()(bool v) const { - LuaScriptInterface::pushBoolean(L, v); - } + void operator()(const boost::blank&) const { lua_pushnil(L); } - void operator()(const int64_t& v) const { - lua_pushnumber(L, v); - } + void operator()(const std::string& v) const { LuaScriptInterface::pushString(L, v); } - void operator()(const double& v) const { - lua_pushnumber(L, v); - } - }; + void operator()(bool v) const { LuaScriptInterface::pushBoolean(L, v); } - void pushToLua(lua_State* L) const { - boost::apply_visitor(PushLuaVisitor(L), value); - } + void operator()(const int64_t& v) const { lua_pushnumber(L, v); } + + void operator()(const double& v) const { lua_pushnumber(L, v); } + }; - struct SerializeVisitor : public boost::static_visitor<> { - PropWriteStream& propWriteStream; + void pushToLua(lua_State* L) const { boost::apply_visitor(PushLuaVisitor(L), value); } - explicit SerializeVisitor(PropWriteStream& propWriteStream) : boost::static_visitor<>(), propWriteStream(propWriteStream) {} + struct SerializeVisitor : public boost::static_visitor<> + { + PropWriteStream& propWriteStream; - void operator()(const boost::blank&) const { - } + explicit SerializeVisitor(PropWriteStream& propWriteStream) : + boost::static_visitor<>(), propWriteStream(propWriteStream) + {} - void operator()(const std::string& v) const { - propWriteStream.writeString(v); - } + void operator()(const boost::blank&) const {} - template - void operator()(const T& v) const { - propWriteStream.write(v); - } - }; + void operator()(const std::string& v) const { propWriteStream.writeString(v); } - void serialize(PropWriteStream& propWriteStream) const { - propWriteStream.write(static_cast(value.which())); - boost::apply_visitor(SerializeVisitor(propWriteStream), value); + template + void operator()(const T& v) const + { + propWriteStream.write(v); } + }; - bool unserialize(PropStream& propStream) { - // This is hard-coded so it's not general, depends on the position of the variants. - uint8_t pos; - if (!propStream.read(pos)) { - return false; - } + void serialize(PropWriteStream& propWriteStream) const + { + propWriteStream.write(static_cast(value.which())); + boost::apply_visitor(SerializeVisitor(propWriteStream), value); + } - switch (pos) { - case 1: { // std::string - std::string tmp; - if (!propStream.readString(tmp)) { - return false; - } - value = tmp; - break; - } + bool unserialize(PropStream& propStream) + { + // This is hard-coded so it's not general, depends on the position of the variants. + uint8_t pos; + if (!propStream.read(pos)) { + return false; + } - case 2: { // int64_t - int64_t tmp; - if (!propStream.read(tmp)) { - return false; - } - value = tmp; - break; + switch (pos) { + case 1: { // std::string + std::string tmp; + if (!propStream.readString(tmp)) { + return false; } + value = tmp; + break; + } - case 3: { // double - double tmp; - if (!propStream.read(tmp)) { - return false; - } - value = tmp; - break; + case 2: { // int64_t + int64_t tmp; + if (!propStream.read(tmp)) { + return false; } + value = tmp; + break; + } - case 4: { // bool - bool tmp; - if (!propStream.read(tmp)) { - return false; - } - value = tmp; - break; + case 3: { // double + double tmp; + if (!propStream.read(tmp)) { + return false; } + value = tmp; + break; + } - default: { - value = boost::blank(); + case 4: { // bool + bool tmp; + if (!propStream.read(tmp)) { return false; } + value = tmp; + break; } - return true; - } - }; - private: - bool hasAttribute(itemAttrTypes type) const { - return (type & attributeBits) != 0; + default: { + value = boost::blank(); + return false; + } + } + return true; } - void removeAttribute(itemAttrTypes type); + }; + +private: + bool hasAttribute(itemAttrTypes type) const { return (type & attributeBits) != 0; } + void removeAttribute(itemAttrTypes type); - static std::string emptyString; - static int64_t emptyInt; - static double emptyDouble; - static bool emptyBool; + static std::string emptyString; + static int64_t emptyInt; + static double emptyDouble; + static bool emptyBool; + static Reflect emptyReflect; - typedef std::unordered_map CustomAttributeMap; + typedef std::unordered_map CustomAttributeMap; - struct Attribute + struct Attribute + { + union { - union { - int64_t integer; - std::string* string; - CustomAttributeMap* custom; - } value; - itemAttrTypes type; - - explicit Attribute(itemAttrTypes type) : type(type) { + int64_t integer; + std::string* string; + CustomAttributeMap* custom; + } value; + itemAttrTypes type; + + explicit Attribute(itemAttrTypes type) : type(type) { memset(&value, 0, sizeof(value)); } + Attribute(const Attribute& i) + { + type = i.type; + if (ItemAttributes::isIntAttrType(type)) { + value.integer = i.value.integer; + } else if (ItemAttributes::isStrAttrType(type)) { + value.string = new std::string(*i.value.string); + } else if (ItemAttributes::isCustomAttrType(type)) { + value.custom = new CustomAttributeMap(*i.value.custom); + } else { memset(&value, 0, sizeof(value)); } - Attribute(const Attribute& i) { - type = i.type; - if (ItemAttributes::isIntAttrType(type)) { - value.integer = i.value.integer; - } else if (ItemAttributes::isStrAttrType(type)) { - value.string = new std::string(*i.value.string); - } else if (ItemAttributes::isCustomAttrType(type)) { - value.custom = new CustomAttributeMap(*i.value.custom); - } else { - memset(&value, 0, sizeof(value)); - } - } - Attribute(Attribute&& attribute) : value(attribute.value), type(attribute.type) { - memset(&attribute.value, 0, sizeof(value)); - attribute.type = ITEM_ATTRIBUTE_NONE; + } + Attribute(Attribute&& attribute) : value(attribute.value), type(attribute.type) + { + memset(&attribute.value, 0, sizeof(value)); + attribute.type = ITEM_ATTRIBUTE_NONE; + } + ~Attribute() + { + if (ItemAttributes::isStrAttrType(type)) { + delete value.string; + } else if (ItemAttributes::isCustomAttrType(type)) { + delete value.custom; } - ~Attribute() { + } + Attribute& operator=(Attribute other) + { + Attribute::swap(*this, other); + return *this; + } + Attribute& operator=(Attribute&& other) + { + if (this != &other) { if (ItemAttributes::isStrAttrType(type)) { delete value.string; } else if (ItemAttributes::isCustomAttrType(type)) { delete value.custom; } - } - Attribute& operator=(Attribute other) { - Attribute::swap(*this, other); - return *this; - } - Attribute& operator=(Attribute&& other) { - if (this != &other) { - if (ItemAttributes::isStrAttrType(type)) { - delete value.string; - } else if (ItemAttributes::isCustomAttrType(type)) { - delete value.custom; - } - - value = other.value; - type = other.type; - - memset(&other.value, 0, sizeof(value)); - other.type = ITEM_ATTRIBUTE_NONE; - } - return *this; - } - - static void swap(Attribute& first, Attribute& second) { - std::swap(first.value, second.value); - std::swap(first.type, second.type); - } - }; - - std::vector attributes; - uint32_t attributeBits = 0; - - const std::string& getStrAttr(itemAttrTypes type) const; - void setStrAttr(itemAttrTypes type, const std::string& value); - int64_t getIntAttr(itemAttrTypes type) const; - void setIntAttr(itemAttrTypes type, int64_t value); - void increaseIntAttr(itemAttrTypes type, int64_t value); + value = other.value; + type = other.type; - const Attribute* getExistingAttr(itemAttrTypes type) const; - Attribute& getAttr(itemAttrTypes type); - - CustomAttributeMap* getCustomAttributeMap() { - if (!hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - return nullptr; + memset(&other.value, 0, sizeof(value)); + other.type = ITEM_ATTRIBUTE_NONE; } - - return getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom; + return *this; } - template - void setCustomAttribute(int64_t key, R value) { - auto tmp = std::to_string(key); - setCustomAttribute(tmp, value); + static void swap(Attribute& first, Attribute& second) + { + std::swap(first.value, second.value); + std::swap(first.type, second.type); } + }; - void setCustomAttribute(int64_t key, CustomAttribute& value) { - auto tmp = std::to_string(key); - setCustomAttribute(tmp, value); - } + std::vector attributes; + uint32_t attributeBits = 0; - template - void setCustomAttribute(std::string& key, R value) { - toLowerCaseString(key); - if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - removeCustomAttribute(key); - } else { - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); - } - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->emplace(key, value); - } + std::map reflect; + std::map boostPercent; - void setCustomAttribute(std::string& key, CustomAttribute& value) { - toLowerCaseString(key); - if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - removeCustomAttribute(key); - } else { - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); - } - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->insert(std::make_pair(std::move(key), std::move(value))); - } + const Reflect& getReflect(CombatType_t combatType) + { + auto it = reflect.find(combatType); + return it != reflect.end() ? it->second : emptyReflect; + } + int16_t getBoostPercent(CombatType_t combatType) + { + auto it = boostPercent.find(combatType); + return it != boostPercent.end() ? it->second : 0; + } - const CustomAttribute* getCustomAttribute(int64_t key) { - auto tmp = std::to_string(key); - return getCustomAttribute(tmp); - } + const std::string& getStrAttr(itemAttrTypes type) const; + void setStrAttr(itemAttrTypes type, const std::string& value); - const CustomAttribute* getCustomAttribute(const std::string& key) { - if (const CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { - auto it = customAttrMap->find(asLowerCaseString(key)); - if (it != customAttrMap->end()) { - return &(it->second); - } - } - return nullptr; - } + int64_t getIntAttr(itemAttrTypes type) const; + void setIntAttr(itemAttrTypes type, int64_t value); + void increaseIntAttr(itemAttrTypes type, int64_t value); - bool removeCustomAttribute(int64_t key) { - auto tmp = std::to_string(key); - return removeCustomAttribute(tmp); + const Attribute* getExistingAttr(itemAttrTypes type) const; + Attribute& getAttr(itemAttrTypes type); + + CustomAttributeMap* getCustomAttributeMap() + { + if (!hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + return nullptr; } - bool removeCustomAttribute(const std::string& key) { - if (CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { - auto it = customAttrMap->find(asLowerCaseString(key)); - if (it != customAttrMap->end()) { - customAttrMap->erase(it); - return true; - } + return getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom; + } + + template + void setCustomAttribute(int64_t key, R value) + { + auto tmp = std::to_string(key); + setCustomAttribute(tmp, value); + } + + void setCustomAttribute(int64_t key, CustomAttribute& value) + { + auto tmp = std::to_string(key); + setCustomAttribute(tmp, value); + } + + template + void setCustomAttribute(std::string& key, R value) + { + boost::algorithm::to_lower(key); + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + removeCustomAttribute(key); + } else { + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); + } + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->emplace(key, value); + } + + void setCustomAttribute(std::string& key, CustomAttribute& value) + { + boost::algorithm::to_lower(key); + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + removeCustomAttribute(key); + } else { + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); + } + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->insert(std::make_pair(std::move(key), std::move(value))); + } + + const CustomAttribute* getCustomAttribute(int64_t key) + { + auto tmp = std::to_string(key); + return getCustomAttribute(tmp); + } + + const CustomAttribute* getCustomAttribute(const std::string& key) + { + if (const CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { + auto it = customAttrMap->find(boost::algorithm::to_lower_copy(key)); + if (it != customAttrMap->end()) { + return &(it->second); + } + } + return nullptr; + } + + bool removeCustomAttribute(int64_t key) + { + auto tmp = std::to_string(key); + return removeCustomAttribute(tmp); + } + + bool removeCustomAttribute(const std::string& key) + { + if (CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { + auto it = customAttrMap->find(boost::algorithm::to_lower_copy(key)); + if (it != customAttrMap->end()) { + customAttrMap->erase(it); + return true; } - return false; } + return false; + } - const static uint32_t intAttributeTypes = ITEM_ATTRIBUTE_ACTIONID | ITEM_ATTRIBUTE_UNIQUEID | ITEM_ATTRIBUTE_DATE - | ITEM_ATTRIBUTE_WEIGHT | ITEM_ATTRIBUTE_ATTACK | ITEM_ATTRIBUTE_DEFENSE | ITEM_ATTRIBUTE_EXTRADEFENSE - | ITEM_ATTRIBUTE_ARMOR | ITEM_ATTRIBUTE_HITCHANCE | ITEM_ATTRIBUTE_SHOOTRANGE | ITEM_ATTRIBUTE_OWNER - | ITEM_ATTRIBUTE_DURATION | ITEM_ATTRIBUTE_DECAYSTATE | ITEM_ATTRIBUTE_CORPSEOWNER | ITEM_ATTRIBUTE_CHARGES - | ITEM_ATTRIBUTE_FLUIDTYPE | ITEM_ATTRIBUTE_DOORID | ITEM_ATTRIBUTE_DECAYTO | ITEM_ATTRIBUTE_WRAPID | ITEM_ATTRIBUTE_STOREITEM; - const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | ITEM_ATTRIBUTE_WRITER - | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME; - - public: - static bool isIntAttrType(itemAttrTypes type) { - return (type & intAttributeTypes) == type; - } - static bool isStrAttrType(itemAttrTypes type) { - return (type & stringAttributeTypes) == type; - } - inline static bool isCustomAttrType(itemAttrTypes type) { - return (type & ITEM_ATTRIBUTE_CUSTOM) == type; - } + const static uint32_t intAttributeTypes = + ITEM_ATTRIBUTE_ACTIONID | ITEM_ATTRIBUTE_UNIQUEID | ITEM_ATTRIBUTE_DATE | ITEM_ATTRIBUTE_WEIGHT | + ITEM_ATTRIBUTE_ATTACK | ITEM_ATTRIBUTE_DEFENSE | ITEM_ATTRIBUTE_EXTRADEFENSE | ITEM_ATTRIBUTE_ARMOR | + ITEM_ATTRIBUTE_HITCHANCE | ITEM_ATTRIBUTE_SHOOTRANGE | ITEM_ATTRIBUTE_OWNER | ITEM_ATTRIBUTE_DURATION | + ITEM_ATTRIBUTE_DECAYSTATE | ITEM_ATTRIBUTE_CORPSEOWNER | ITEM_ATTRIBUTE_CHARGES | ITEM_ATTRIBUTE_FLUIDTYPE | + ITEM_ATTRIBUTE_DOORID | ITEM_ATTRIBUTE_DECAYTO | ITEM_ATTRIBUTE_WRAPID | ITEM_ATTRIBUTE_STOREITEM | + ITEM_ATTRIBUTE_ATTACK_SPEED | ITEM_ATTRIBUTE_OPENCONTAINER; + const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | + ITEM_ATTRIBUTE_WRITER | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | + ITEM_ATTRIBUTE_PLURALNAME; - const std::vector& getList() const { - return attributes; - } +public: + static bool isIntAttrType(itemAttrTypes type) { return (type & intAttributeTypes) == type; } + static bool isStrAttrType(itemAttrTypes type) { return (type & stringAttributeTypes) == type; } + inline static bool isCustomAttrType(itemAttrTypes type) { return (type & ITEM_ATTRIBUTE_CUSTOM) == type; } + + const std::vector& getList() const { return attributes; } friend class Item; }; class Item : virtual public Thing { - public: - //Factory member to create item of right type based on type - static Item* CreateItem(const uint16_t type, uint16_t count = 0); - static Container* CreateItemAsContainer(const uint16_t type, uint16_t size); - static Item* CreateItem(PropStream& propStream); - static Items items; - - // Constructor for items - Item(const uint16_t type, uint16_t count = 0); - Item(const Item& i); - virtual Item* clone() const; - - virtual ~Item() = default; - - // non-assignable - Item& operator=(const Item&) = delete; - - bool equals(const Item* otherItem) const; - - Item* getItem() override final { - return this; - } - const Item* getItem() const override final { - return this; - } - virtual Teleport* getTeleport() { - return nullptr; - } - virtual const Teleport* getTeleport() const { - return nullptr; - } - virtual TrashHolder* getTrashHolder() { - return nullptr; - } - virtual const TrashHolder* getTrashHolder() const { - return nullptr; - } - virtual Mailbox* getMailbox() { - return nullptr; - } - virtual const Mailbox* getMailbox() const { - return nullptr; - } - virtual Door* getDoor() { - return nullptr; - } - virtual const Door* getDoor() const { - return nullptr; - } - virtual MagicField* getMagicField() { - return nullptr; - } - virtual const MagicField* getMagicField() const { - return nullptr; - } - virtual BedItem* getBed() { - return nullptr; - } - virtual const BedItem* getBed() const { - return nullptr; - } - - const std::string& getStrAttr(itemAttrTypes type) const { - if (!attributes) { - return ItemAttributes::emptyString; - } - return attributes->getStrAttr(type); - } - void setStrAttr(itemAttrTypes type, const std::string& value) { - getAttributes()->setStrAttr(type, value); - } - - int64_t getIntAttr(itemAttrTypes type) const { - if (!attributes) { - return 0; - } - return attributes->getIntAttr(type); - } - void setIntAttr(itemAttrTypes type, int64_t value) { - getAttributes()->setIntAttr(type, value); - } - void increaseIntAttr(itemAttrTypes type, int64_t value) { - getAttributes()->increaseIntAttr(type, value); - } - - void removeAttribute(itemAttrTypes type) { - if (attributes) { - attributes->removeAttribute(type); - } - } - bool hasAttribute(itemAttrTypes type) const { - if (!attributes) { - return false; - } - return attributes->hasAttribute(type); - } - - template - void setCustomAttribute(std::string& key, R value) { - getAttributes()->setCustomAttribute(key, value); - } - - void setCustomAttribute(std::string& key, ItemAttributes::CustomAttribute& value) { - getAttributes()->setCustomAttribute(key, value); - } - - const ItemAttributes::CustomAttribute* getCustomAttribute(int64_t key) { - if (!attributes) { - return nullptr; - } - return getAttributes()->getCustomAttribute(key); - } - - const ItemAttributes::CustomAttribute* getCustomAttribute(const std::string& key) { - if (!attributes) { - return nullptr; - } - return getAttributes()->getCustomAttribute(key); - } - - bool removeCustomAttribute(int64_t key) { - if (!attributes) { - return false; - } - return getAttributes()->removeCustomAttribute(key); - } - - bool removeCustomAttribute(const std::string& key) { - if (!attributes) { - return false; - } - return getAttributes()->removeCustomAttribute(key); - } - - void setSpecialDescription(const std::string& desc) { - setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); - } - const std::string& getSpecialDescription() const { - return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); - } - - void setText(const std::string& text) { - setStrAttr(ITEM_ATTRIBUTE_TEXT, text); - } - void resetText() { - removeAttribute(ITEM_ATTRIBUTE_TEXT); - } - const std::string& getText() const { - return getStrAttr(ITEM_ATTRIBUTE_TEXT); - } - - void setDate(int32_t n) { - setIntAttr(ITEM_ATTRIBUTE_DATE, n); - } - void resetDate() { - removeAttribute(ITEM_ATTRIBUTE_DATE); - } - time_t getDate() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); - } - - void setWriter(const std::string& writer) { - setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); - } - void resetWriter() { - removeAttribute(ITEM_ATTRIBUTE_WRITER); - } - const std::string& getWriter() const { - return getStrAttr(ITEM_ATTRIBUTE_WRITER); - } - - void setActionId(uint16_t n) { - if (n < 100) { - n = 100; - } - - setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); - } - uint16_t getActionId() const { - if (!attributes) { - return 0; - } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); - } - - uint16_t getUniqueId() const { - if (!attributes) { - return 0; - } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); - } - - void setCharges(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); - } - uint16_t getCharges() const { - if (!attributes) { - return 0; - } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); +public: + // Factory member to create item of right type based on type + static Item* CreateItem(const uint16_t type, uint16_t count = 0); + static Container* CreateItemAsContainer(const uint16_t type, uint16_t size); + static Item* CreateItem(PropStream& propStream); + static Items items; + + // Constructor for items + Item(const uint16_t type, uint16_t count = 0); + Item(const Item& i); + virtual Item* clone() const; + + virtual ~Item() = default; + + // non-assignable + Item& operator=(const Item&) = delete; + + bool equals(const Item* otherItem) const; + + Item* getItem() override final { return this; } + const Item* getItem() const override final { return this; } + virtual Teleport* getTeleport() { return nullptr; } + virtual const Teleport* getTeleport() const { return nullptr; } + virtual TrashHolder* getTrashHolder() { return nullptr; } + virtual const TrashHolder* getTrashHolder() const { return nullptr; } + virtual Mailbox* getMailbox() { return nullptr; } + virtual const Mailbox* getMailbox() const { return nullptr; } + virtual Door* getDoor() { return nullptr; } + virtual const Door* getDoor() const { return nullptr; } + virtual MagicField* getMagicField() { return nullptr; } + virtual const MagicField* getMagicField() const { return nullptr; } + virtual BedItem* getBed() { return nullptr; } + virtual const BedItem* getBed() const { return nullptr; } + virtual Podium* getPodium() { return nullptr; } + virtual const Podium* getPodium() const { return nullptr; } + + const std::string& getStrAttr(itemAttrTypes type) const + { + if (!attributes) { + return ItemAttributes::emptyString; + } + return attributes->getStrAttr(type); + } + void setStrAttr(itemAttrTypes type, const std::string& value) { getAttributes()->setStrAttr(type, value); } + + int64_t getIntAttr(itemAttrTypes type) const + { + if (!attributes) { + return 0; } + return attributes->getIntAttr(type); + } + void setIntAttr(itemAttrTypes type, int64_t value) { getAttributes()->setIntAttr(type, value); } + void increaseIntAttr(itemAttrTypes type, int64_t value) { getAttributes()->increaseIntAttr(type, value); } - void setFluidType(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); + void removeAttribute(itemAttrTypes type) + { + if (attributes) { + attributes->removeAttribute(type); } - uint16_t getFluidType() const { - if (!attributes) { - return 0; - } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); + } + bool hasAttribute(itemAttrTypes type) const + { + if (!attributes) { + return false; } + return attributes->hasAttribute(type); + } - void setOwner(uint32_t owner) { - setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); - } - uint32_t getOwner() const { - if (!attributes) { - return 0; - } - return getIntAttr(ITEM_ATTRIBUTE_OWNER); - } + template + void setCustomAttribute(std::string& key, R value) + { + getAttributes()->setCustomAttribute(key, value); + } - void setCorpseOwner(uint32_t corpseOwner) { - setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); - } - uint32_t getCorpseOwner() const { - if (!attributes) { - return 0; - } - return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); - } + void setCustomAttribute(std::string& key, ItemAttributes::CustomAttribute& value) + { + getAttributes()->setCustomAttribute(key, value); + } - void setDuration(int32_t time) { - setIntAttr(ITEM_ATTRIBUTE_DURATION, time); - } - void decreaseDuration(int32_t time) { - increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); - } - uint32_t getDuration() const { - if (!attributes) { - return 0; - } - return getIntAttr(ITEM_ATTRIBUTE_DURATION); - } - - void setDecaying(ItemDecayState_t decayState) { - setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); - } - ItemDecayState_t getDecaying() const { - if (!attributes) { - return DECAYING_FALSE; - } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + const ItemAttributes::CustomAttribute* getCustomAttribute(int64_t key) + { + if (!attributes) { + return nullptr; } + return getAttributes()->getCustomAttribute(key); + } - void setDecayTo(int32_t decayTo) { - setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo); - } - int32_t getDecayTo() const { - if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) { - return getIntAttr(ITEM_ATTRIBUTE_DECAYTO); - } - return items[id].decayTo; + const ItemAttributes::CustomAttribute* getCustomAttribute(const std::string& key) + { + if (!attributes) { + return nullptr; } + return getAttributes()->getCustomAttribute(key); + } - static std::string getDescription(const ItemType& it, int32_t lookDistance, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true); - static std::string getNameDescription(const ItemType& it, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true); - static std::string getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count = 1); - - std::string getDescription(int32_t lookDistance) const override final; - std::string getNameDescription() const; - std::string getWeightDescription() const; - - //serialization - virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); - bool unserializeAttr(PropStream& propStream); - virtual bool unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream); - - virtual void serializeAttr(PropWriteStream& propWriteStream) const; - - bool isPushable() const override final { - return isMoveable(); - } - int32_t getThrowRange() const override final { - return (isPickupable() ? 15 : 2); + bool removeCustomAttribute(int64_t key) + { + if (!attributes) { + return false; } + return getAttributes()->removeCustomAttribute(key); + } - uint16_t getID() const { - return id; - } - uint16_t getClientID() const { - return items[id].clientId; + bool removeCustomAttribute(const std::string& key) + { + if (!attributes) { + return false; } - void setID(uint16_t newid); + return getAttributes()->removeCustomAttribute(key); + } - // Returns the player that is holding this item in his inventory - Player* getHoldingPlayer() const; + void setSpecialDescription(const std::string& desc) { setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); } + const std::string& getSpecialDescription() const { return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); } - WeaponType_t getWeaponType() const { - return items[id].weaponType; - } - Ammo_t getAmmoType() const { - return items[id].ammoType; - } - uint8_t getShootRange() const { - if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { - return getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE); - } - return items[id].shootRange; - } + void setText(const std::string& text) { setStrAttr(ITEM_ATTRIBUTE_TEXT, text); } + void resetText() { removeAttribute(ITEM_ATTRIBUTE_TEXT); } + const std::string& getText() const { return getStrAttr(ITEM_ATTRIBUTE_TEXT); } - virtual uint32_t getWeight() const; - uint32_t getBaseWeight() const { - if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { - return getIntAttr(ITEM_ATTRIBUTE_WEIGHT); - } - return items[id].weight; - } - int32_t getAttack() const { - if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { - return getIntAttr(ITEM_ATTRIBUTE_ATTACK); - } - return items[id].attack; - } - int32_t getArmor() const { - if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { - return getIntAttr(ITEM_ATTRIBUTE_ARMOR); - } - return items[id].armor; - } - int32_t getDefense() const { - if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { - return getIntAttr(ITEM_ATTRIBUTE_DEFENSE); - } - return items[id].defense; - } - int32_t getExtraDefense() const { - if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) { - return getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE); - } - return items[id].extraDefense; - } - int32_t getSlotPosition() const { - return items[id].slotPosition; - } - int8_t getHitChance() const { - if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) { - return getIntAttr(ITEM_ATTRIBUTE_HITCHANCE); - } - return items[id].hitChance; - } + void setDate(int32_t n) { setIntAttr(ITEM_ATTRIBUTE_DATE, n); } + void resetDate() { removeAttribute(ITEM_ATTRIBUTE_DATE); } + time_t getDate() const { return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); } - uint32_t getWorth() const; - LightInfo getLightInfo() const; + void setWriter(const std::string& writer) { setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); } + void resetWriter() { removeAttribute(ITEM_ATTRIBUTE_WRITER); } + const std::string& getWriter() const { return getStrAttr(ITEM_ATTRIBUTE_WRITER); } - bool hasProperty(ITEMPROPERTY prop) const; - bool isBlocking() const { - return items[id].blockSolid; - } - bool isStackable() const { - return items[id].stackable; - } - bool isAlwaysOnTop() const { - return items[id].alwaysOnTop; - } - bool isGroundTile() const { - return items[id].isGroundTile(); - } - bool isMagicField() const { - return items[id].isMagicField(); - } - bool isMoveable() const { - return items[id].moveable; - } - bool isPickupable() const { - return items[id].pickupable; - } - bool isUseable() const { - return items[id].useable; - } - bool isHangable() const { - return items[id].isHangable; - } - bool isRotatable() const { - const ItemType& it = items[id]; - return it.rotatable && it.rotateTo; - } - bool hasWalkStack() const { - return items[id].walkStack; + void setActionId(uint16_t n) + { + if (n < 100) { + n = 100; } - void setStoreItem(bool storeItem) { - setIntAttr(ITEM_ATTRIBUTE_STOREITEM, static_cast(storeItem)); - } - bool isStoreItem() const { - if (hasAttribute(ITEM_ATTRIBUTE_STOREITEM)) { - return getIntAttr(ITEM_ATTRIBUTE_STOREITEM) == 1; - } - return items[id].storeItem; - } - const std::string& getName() const { - if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { - return getStrAttr(ITEM_ATTRIBUTE_NAME); - } - return items[id].name; - } - const std::string getPluralName() const { - if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { - return getStrAttr(ITEM_ATTRIBUTE_PLURALNAME); - } - return items[id].getPluralName(); - } - const std::string& getArticle() const { - if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { - return getStrAttr(ITEM_ATTRIBUTE_ARTICLE); - } - return items[id].article; + setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); + } + uint16_t getActionId() const + { + if (!attributes) { + return 0; } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); + } - // get the number of items - uint16_t getItemCount() const { - return count; - } - void setItemCount(uint8_t n) { - count = n; + uint16_t getUniqueId() const + { + if (!attributes) { + return 0; } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); + } - static uint32_t countByType(const Item* i, int32_t subType) { - if (subType == -1 || subType == i->getSubType()) { - return i->getItemCount(); - } - + void setCharges(uint16_t n) { setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); } + uint16_t getCharges() const + { + if (!attributes) { return 0; } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); + } - void setDefaultSubtype(); - uint16_t getSubType() const; - void setSubType(uint16_t n); - - void setUniqueId(uint16_t n); - - void setDefaultDuration() { - uint32_t duration = getDefaultDuration(); - if (duration != 0) { - setDuration(duration); - } - } - uint32_t getDefaultDuration() const { - return items[id].decayTime * 1000; + void setFluidType(uint16_t n) { setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); } + uint16_t getFluidType() const + { + if (!attributes) { + return 0; } - bool canDecay() const; + return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); + } - virtual bool canRemove() const { - return true; - } - virtual bool canTransform() const { - return true; + void setOwner(uint32_t owner) { setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); } + uint32_t getOwner() const + { + if (!attributes) { + return 0; } - virtual void onRemoved(); - virtual void onTradeEvent(TradeEvents_t, Player*) {} - - virtual void startDecaying(); + return getIntAttr(ITEM_ATTRIBUTE_OWNER); + } - bool isLoadedFromMap() const { - return loadedFromMap; - } - void setLoadedFromMap(bool value) { - loadedFromMap = value; - } - bool isCleanable() const { - return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); + void setCorpseOwner(uint32_t corpseOwner) { setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); } + uint32_t getCorpseOwner() const + { + if (!attributes) { + return 0; } + return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); + } - bool hasMarketAttributes() const; - - std::unique_ptr& getAttributes() { - if (!attributes) { - attributes.reset(new ItemAttributes()); - } - return attributes; + void setDuration(int32_t time) { setIntAttr(ITEM_ATTRIBUTE_DURATION, time); } + void decreaseDuration(int32_t time) { increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); } + uint32_t getDuration() const + { + if (!attributes) { + return 0; } + return getIntAttr(ITEM_ATTRIBUTE_DURATION); + } - void incrementReferenceCounter() { - ++referenceCounter; - } - void decrementReferenceCounter() { - if (--referenceCounter == 0) { - delete this; - } + void setDecaying(ItemDecayState_t decayState) { setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); } + ItemDecayState_t getDecaying() const + { + if (!attributes) { + return DECAYING_FALSE; } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + } - Cylinder* getParent() const override { - return parent; - } - void setParent(Cylinder* cylinder) override { - parent = cylinder; - } - Cylinder* getTopParent(); - const Cylinder* getTopParent() const; - Tile* getTile() override; - const Tile* getTile() const override; - bool isRemoved() const override { - return !parent || parent->isRemoved(); + int32_t getDecayTime() const + { + if (hasAttribute(ITEM_ATTRIBUTE_DURATION)) { + return getIntAttr(ITEM_ATTRIBUTE_DURATION); } + return items[id].decayTime; + } + + void setDecayTo(int32_t decayTo) { setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo); } + int32_t getDecayTo() const + { + if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) { + return getIntAttr(ITEM_ATTRIBUTE_DECAYTO); + } + return items[id].decayTo; + } + + static std::string getNameDescription(const ItemType& it, const Item* item = nullptr, int32_t subType = -1, + bool addArticle = true); + static std::string getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count = 1); + + std::string getDescription(int32_t lookDistance) const override final; + std::string getNameDescription() const; + std::string getWeightDescription() const; + + // serialization + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + bool unserializeAttr(PropStream& propStream); + virtual bool unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream); + + virtual void serializeAttr(PropWriteStream& propWriteStream) const; + + bool isPushable() const override final { return isMoveable(); } + int32_t getThrowRange() const override final { return (isPickupable() ? 15 : 2); } + + uint16_t getID() const { return id; } + uint16_t getClientID() const { return items[id].clientId; } + void setID(uint16_t newid); + + // Returns the player that is holding this item in his inventory + const Player* getHoldingPlayer() const; + + WeaponType_t getWeaponType() const { return items[id].weaponType; } + Ammo_t getAmmoType() const { return items[id].ammoType; } + uint8_t getShootRange() const + { + if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { + return getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE); + } + return items[id].shootRange; + } + + virtual uint32_t getWeight() const; + uint32_t getBaseWeight() const + { + if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { + return getIntAttr(ITEM_ATTRIBUTE_WEIGHT); + } + return items[id].weight; + } + int32_t getAttack() const + { + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { + return getIntAttr(ITEM_ATTRIBUTE_ATTACK); + } + return items[id].attack; + } + uint32_t getAttackSpeed() const + { + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK_SPEED)) { + return getIntAttr(ITEM_ATTRIBUTE_ATTACK_SPEED); + } + return items[id].attackSpeed; + } + int32_t getArmor() const + { + if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { + return getIntAttr(ITEM_ATTRIBUTE_ARMOR); + } + return items[id].armor; + } + int32_t getDefense() const + { + if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { + return getIntAttr(ITEM_ATTRIBUTE_DEFENSE); + } + return items[id].defense; + } + int32_t getExtraDefense() const + { + if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) { + return getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE); + } + return items[id].extraDefense; + } + int32_t getSlotPosition() const { return items[id].slotPosition; } + int8_t getHitChance() const + { + if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) { + return getIntAttr(ITEM_ATTRIBUTE_HITCHANCE); + } + return items[id].hitChance; + } + + uint32_t getWorth() const; + LightInfo getLightInfo() const; + + void setReflect(CombatType_t combatType, const Reflect& reflect) { getAttributes()->reflect[combatType] = reflect; } + Reflect getReflect(CombatType_t combatType, bool total = true) const; + + void setBoostPercent(CombatType_t combatType, uint16_t value) { getAttributes()->boostPercent[combatType] = value; } + uint16_t getBoostPercent(CombatType_t combatType, bool total = true) const; + + bool hasProperty(ITEMPROPERTY prop) const; + bool isBlocking() const { return items[id].blockSolid; } + bool isStackable() const { return items[id].stackable; } + bool isAlwaysOnTop() const { return items[id].alwaysOnTop; } + bool isGroundTile() const { return items[id].isGroundTile(); } + bool isMagicField() const { return items[id].isMagicField(); } + bool isMoveable() const { return items[id].moveable; } + bool isPickupable() const { return items[id].pickupable; } + bool isUseable() const { return items[id].useable; } + bool isHangable() const { return items[id].isHangable; } + bool isRotatable() const + { + const ItemType& it = items[id]; + return it.rotatable && it.rotateTo; + } + bool isPodium() const { return items[id].isPodium(); } + bool hasWalkStack() const { return items[id].walkStack; } + bool isSupply() const { return items[id].isSupply(); } + + void setStoreItem(bool storeItem) { setIntAttr(ITEM_ATTRIBUTE_STOREITEM, static_cast(storeItem)); } + bool isStoreItem() const + { + if (hasAttribute(ITEM_ATTRIBUTE_STOREITEM)) { + return getIntAttr(ITEM_ATTRIBUTE_STOREITEM) == 1; + } + return items[id].storeItem; + } + const std::string& getName() const + { + if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { + return getStrAttr(ITEM_ATTRIBUTE_NAME); + } + return items[id].name; + } + const std::string getPluralName() const + { + if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { + return getStrAttr(ITEM_ATTRIBUTE_PLURALNAME); + } + return items[id].getPluralName(); + } + const std::string& getArticle() const + { + if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { + return getStrAttr(ITEM_ATTRIBUTE_ARTICLE); + } + return items[id].article; + } + + // get the number of items + uint16_t getItemCount() const { return count; } + void setItemCount(uint8_t n) { count = n; } + + static uint32_t countByType(const Item* i, int32_t subType) + { + if (subType == -1 || subType == i->getSubType()) { + return i->getItemCount(); + } + + return 0; + } + + void setDefaultSubtype(); + uint16_t getSubType() const; + void setSubType(uint16_t n); + + void setUniqueId(uint16_t n); + + void setDefaultDuration() + { + uint32_t duration = getDefaultDuration(); + if (duration != 0) { + setDuration(duration); + } + } + uint32_t getDefaultDuration() const { return items[id].decayTime * 1000; } + bool canDecay() const; + + virtual bool canRemove() const { return true; } + virtual bool canTransform() const { return true; } + virtual void onRemoved(); + virtual void onTradeEvent(TradeEvents_t, Player*) {} + + virtual void startDecaying(); + + bool isLoadedFromMap() const { return loadedFromMap; } + void setLoadedFromMap(bool value) { loadedFromMap = value; } + bool isCleanable() const + { + return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) && + !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); + } + + bool hasMarketAttributes() const; + + std::unique_ptr& getAttributes() + { + if (!attributes) { + attributes.reset(new ItemAttributes()); + } + return attributes; + } + + void incrementReferenceCounter() { ++referenceCounter; } + void decrementReferenceCounter() + { + if (--referenceCounter == 0) { + delete this; + } + } + + Cylinder* getParent() const override { return parent; } + void setParent(Cylinder* cylinder) override { parent = cylinder; } + Cylinder* getTopParent(); + const Cylinder* getTopParent() const; + Tile* getTile() override; + const Tile* getTile() const override; + bool isRemoved() const override { return !parent || parent->isRemoved(); } - protected: - Cylinder* parent = nullptr; +protected: + Cylinder* parent = nullptr; - uint16_t id; // the same id as in ItemType + uint16_t id; // the same id as in ItemType - private: - std::string getWeightDescription(uint32_t weight) const; +private: + std::string getWeightDescription(uint32_t weight) const; - std::unique_ptr attributes; + std::unique_ptr attributes; - uint32_t referenceCounter = 0; + uint32_t referenceCounter = 0; - uint8_t count = 1; // number of stacked items + uint8_t count = 1; // number of stacked items - bool loadedFromMap = false; + bool loadedFromMap = false; - //Don't add variables here, use the ItemAttribute class. + // Don't add variables here, use the ItemAttribute class. }; using ItemList = std::list; using ItemDeque = std::deque; -#endif +#endif // FS_ITEM_H diff --git a/src/itemloader.h b/src/itemloader.h index 159ee2a970..93ade877af 100644 --- a/src/itemloader.h +++ b/src/itemloader.h @@ -1,50 +1,37 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_ITEMLOADER_H_107F1D3EECC94CD0A0F528843010D5D4 -#define FS_ITEMLOADER_H_107F1D3EECC94CD0A0F528843010D5D4 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_ITEMLOADER_H +#define FS_ITEMLOADER_H #include "fileloader.h" -enum itemgroup_t { +enum itemgroup_t +{ ITEM_GROUP_NONE, ITEM_GROUP_GROUND, ITEM_GROUP_CONTAINER, - ITEM_GROUP_WEAPON, //deprecated - ITEM_GROUP_AMMUNITION, //deprecated - ITEM_GROUP_ARMOR, //deprecated + ITEM_GROUP_WEAPON, // deprecated + ITEM_GROUP_AMMUNITION, // deprecated + ITEM_GROUP_ARMOR, // deprecated ITEM_GROUP_CHARGES, - ITEM_GROUP_TELEPORT, //deprecated - ITEM_GROUP_MAGICFIELD, //deprecated - ITEM_GROUP_WRITEABLE, //deprecated - ITEM_GROUP_KEY, //deprecated + ITEM_GROUP_TELEPORT, // deprecated + ITEM_GROUP_MAGICFIELD, // deprecated + ITEM_GROUP_WRITEABLE, // deprecated + ITEM_GROUP_KEY, // deprecated ITEM_GROUP_SPLASH, ITEM_GROUP_FLUID, - ITEM_GROUP_DOOR, //deprecated + ITEM_GROUP_DOOR, // deprecated ITEM_GROUP_DEPRECATED, + ITEM_GROUP_PODIUM, ITEM_GROUP_LAST }; /////////OTB specific////////////// -enum clientVersion_t { +enum clientVersion_t +{ CLIENT_VERSION_750 = 1, CLIENT_VERSION_755 = 2, CLIENT_VERSION_760 = 3, @@ -103,13 +90,22 @@ enum clientVersion_t { CLIENT_VERSION_1035 = 55, CLIENT_VERSION_1076 = 56, CLIENT_VERSION_1098 = 57, + CLIENT_VERSION_1100 = 58, + CLIENT_VERSION_1272 = 59, + CLIENT_VERSION_1281 = 60, + CLIENT_VERSION_1285 = 61, + CLIENT_VERSION_1286 = 62, + + CLIENT_VERSION_LAST = CLIENT_VERSION_1286 }; -enum rootattrib_ { +enum rootattrib_ +{ ROOT_ATTR_VERSION = 0x01, }; -enum itemattrib_t { +enum itemattrib_t +{ ITEM_ATTR_FIRST = 0x10, ITEM_ATTR_SERVERID = ITEM_ATTR_FIRST, ITEM_ATTR_CLIENTID, @@ -133,22 +129,24 @@ enum itemattrib_t { ITEM_ATTR_08, ITEM_ATTR_LIGHT, - //1-byte aligned - ITEM_ATTR_DECAY2, //deprecated - ITEM_ATTR_WEAPON2, //deprecated - ITEM_ATTR_AMU2, //deprecated - ITEM_ATTR_ARMOR2, //deprecated - ITEM_ATTR_WRITEABLE2, //deprecated + // 1-byte aligned + ITEM_ATTR_DECAY2, // deprecated + ITEM_ATTR_WEAPON2, // deprecated + ITEM_ATTR_AMU2, // deprecated + ITEM_ATTR_ARMOR2, // deprecated + ITEM_ATTR_WRITEABLE2, // deprecated ITEM_ATTR_LIGHT2, ITEM_ATTR_TOPORDER, - ITEM_ATTR_WRITEABLE3, //deprecated + ITEM_ATTR_WRITEABLE3, // deprecated ITEM_ATTR_WAREID, + ITEM_ATTR_CLASSIFICATION, ITEM_ATTR_LAST }; -enum itemflags_t { +enum itemflags_t +{ FLAG_BLOCK_SOLID = 1 << 0, FLAG_BLOCK_PROJECTILE = 1 << 1, FLAG_BLOCK_PATHFIND = 1 << 2, @@ -157,11 +155,11 @@ enum itemflags_t { FLAG_PICKUPABLE = 1 << 5, FLAG_MOVEABLE = 1 << 6, FLAG_STACKABLE = 1 << 7, - FLAG_FLOORCHANGEDOWN = 1 << 8, // unused - FLAG_FLOORCHANGENORTH = 1 << 9, // unused - FLAG_FLOORCHANGEEAST = 1 << 10, // unused + FLAG_FLOORCHANGEDOWN = 1 << 8, // unused + FLAG_FLOORCHANGENORTH = 1 << 9, // unused + FLAG_FLOORCHANGEEAST = 1 << 10, // unused FLAG_FLOORCHANGESOUTH = 1 << 11, // unused - FLAG_FLOORCHANGEWEST = 1 << 12, // unused + FLAG_FLOORCHANGEWEST = 1 << 12, // unused FLAG_ALWAYSONTOP = 1 << 13, FLAG_READABLE = 1 << 14, FLAG_ROTATABLE = 1 << 15, @@ -170,29 +168,34 @@ enum itemflags_t { FLAG_HORIZONTAL = 1 << 18, FLAG_CANNOTDECAY = 1 << 19, // unused FLAG_ALLOWDISTREAD = 1 << 20, - FLAG_UNUSED = 1 << 21, // unused + FLAG_UNUSED = 1 << 21, // unused FLAG_CLIENTCHARGES = 1 << 22, /* deprecated */ FLAG_LOOKTHROUGH = 1 << 23, FLAG_ANIMATION = 1 << 24, FLAG_FULLTILE = 1 << 25, // unused FLAG_FORCEUSE = 1 << 26, + FLAG_AMMO = 1 << 27, // unused + FLAG_REPORTABLE = 1 << 28, // unused }; -//1-byte aligned structs +// 1-byte aligned structs #pragma pack(1) -struct VERSIONINFO { +struct VERSIONINFO +{ uint32_t dwMajorVersion; uint32_t dwMinorVersion; uint32_t dwBuildNumber; uint8_t CSDVersion[128]; }; -struct lightBlock2 { +struct lightBlock2 +{ uint16_t lightLevel; uint16_t lightColor; }; #pragma pack() /////////OTB specific////////////// -#endif + +#endif // FS_ITEMLOADER_H diff --git a/src/items.cpp b/src/items.cpp index 39abb4a81f..af2329bd1e 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1,223 +1,259 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "items.h" -#include "spells.h" -#include "movement.h" -#include "weapons.h" +#include "movement.h" #include "pugicast.h" +#include "weapons.h" extern MoveEvents* g_moveEvents; extern Weapons* g_weapons; const std::unordered_map ItemParseAttributesMap = { - {"type", ITEM_PARSE_TYPE}, - {"description", ITEM_PARSE_DESCRIPTION}, - {"runespellname", ITEM_PARSE_RUNESPELLNAME}, - {"weight", ITEM_PARSE_WEIGHT}, - {"showcount", ITEM_PARSE_SHOWCOUNT}, - {"armor", ITEM_PARSE_ARMOR}, - {"defense", ITEM_PARSE_DEFENSE}, - {"extradef", ITEM_PARSE_EXTRADEF}, - {"attack", ITEM_PARSE_ATTACK}, - {"rotateto", ITEM_PARSE_ROTATETO}, - {"moveable", ITEM_PARSE_MOVEABLE}, - {"movable", ITEM_PARSE_MOVEABLE}, - {"blockprojectile", ITEM_PARSE_BLOCKPROJECTILE}, - {"allowpickupable", ITEM_PARSE_PICKUPABLE}, - {"pickupable", ITEM_PARSE_PICKUPABLE}, - {"forceserialize", ITEM_PARSE_FORCESERIALIZE}, - {"forcesave", ITEM_PARSE_FORCESERIALIZE}, - {"floorchange", ITEM_PARSE_FLOORCHANGE}, - {"corpsetype", ITEM_PARSE_CORPSETYPE}, - {"containersize", ITEM_PARSE_CONTAINERSIZE}, - {"fluidsource", ITEM_PARSE_FLUIDSOURCE}, - {"readable", ITEM_PARSE_READABLE}, - {"writeable", ITEM_PARSE_WRITEABLE}, - {"maxtextlen", ITEM_PARSE_MAXTEXTLEN}, - {"writeonceitemid", ITEM_PARSE_WRITEONCEITEMID}, - {"weapontype", ITEM_PARSE_WEAPONTYPE}, - {"slottype", ITEM_PARSE_SLOTTYPE}, - {"ammotype", ITEM_PARSE_AMMOTYPE}, - {"shoottype", ITEM_PARSE_SHOOTTYPE}, - {"effect", ITEM_PARSE_EFFECT}, - {"range", ITEM_PARSE_RANGE}, - {"stopduration", ITEM_PARSE_STOPDURATION}, - {"decayto", ITEM_PARSE_DECAYTO}, - {"transformequipto", ITEM_PARSE_TRANSFORMEQUIPTO}, - {"transformdeequipto", ITEM_PARSE_TRANSFORMDEEQUIPTO}, - {"duration", ITEM_PARSE_DURATION}, - {"showduration", ITEM_PARSE_SHOWDURATION}, - {"charges", ITEM_PARSE_CHARGES}, - {"showcharges", ITEM_PARSE_SHOWCHARGES}, - {"showattributes", ITEM_PARSE_SHOWATTRIBUTES}, - {"hitchance", ITEM_PARSE_HITCHANCE}, - {"maxhitchance", ITEM_PARSE_MAXHITCHANCE}, - {"invisible", ITEM_PARSE_INVISIBLE}, - {"speed", ITEM_PARSE_SPEED}, - {"healthgain", ITEM_PARSE_HEALTHGAIN}, - {"healthticks", ITEM_PARSE_HEALTHTICKS}, - {"managain", ITEM_PARSE_MANAGAIN}, - {"manaticks", ITEM_PARSE_MANATICKS}, - {"manashield", ITEM_PARSE_MANASHIELD}, - {"skillsword", ITEM_PARSE_SKILLSWORD}, - {"skillaxe", ITEM_PARSE_SKILLAXE}, - {"skillclub", ITEM_PARSE_SKILLCLUB}, - {"skilldist", ITEM_PARSE_SKILLDIST}, - {"skillfish", ITEM_PARSE_SKILLFISH}, - {"skillshield", ITEM_PARSE_SKILLSHIELD}, - {"skillfist", ITEM_PARSE_SKILLFIST}, - {"maxhitpoints", ITEM_PARSE_MAXHITPOINTS}, - {"maxhitpointspercent", ITEM_PARSE_MAXHITPOINTSPERCENT}, - {"maxmanapoints", ITEM_PARSE_MAXMANAPOINTS}, - {"maxmanapointspercent", ITEM_PARSE_MAXMANAPOINTSPERCENT}, - {"magicpoints", ITEM_PARSE_MAGICPOINTS}, - {"magiclevelpoints", ITEM_PARSE_MAGICPOINTS}, - {"magicpointspercent", ITEM_PARSE_MAGICPOINTSPERCENT}, - {"criticalhitchance", ITEM_PARSE_CRITICALHITCHANCE}, - {"criticalhitamount", ITEM_PARSE_CRITICALHITAMOUNT}, - {"lifeleechchance", ITEM_PARSE_LIFELEECHCHANCE}, - {"lifeleechamount", ITEM_PARSE_LIFELEECHAMOUNT}, - {"manaleechchance", ITEM_PARSE_MANALEECHCHANCE}, - {"manaleechamount", ITEM_PARSE_MANALEECHAMOUNT}, - {"fieldabsorbpercentenergy", ITEM_PARSE_FIELDABSORBPERCENTENERGY}, - {"fieldabsorbpercentfire", ITEM_PARSE_FIELDABSORBPERCENTFIRE}, - {"fieldabsorbpercentpoison", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, - {"fieldabsorbpercentearth", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, - {"absorbpercentall", ITEM_PARSE_ABSORBPERCENTALL}, - {"absorbpercentallelements", ITEM_PARSE_ABSORBPERCENTALL}, - {"absorbpercentelements", ITEM_PARSE_ABSORBPERCENTELEMENTS}, - {"absorbpercentmagic", ITEM_PARSE_ABSORBPERCENTMAGIC}, - {"absorbpercentenergy", ITEM_PARSE_ABSORBPERCENTENERGY}, - {"absorbpercentfire", ITEM_PARSE_ABSORBPERCENTFIRE}, - {"absorbpercentpoison", ITEM_PARSE_ABSORBPERCENTPOISON}, - {"absorbpercentearth", ITEM_PARSE_ABSORBPERCENTPOISON}, - {"absorbpercentice", ITEM_PARSE_ABSORBPERCENTICE}, - {"absorbpercentholy", ITEM_PARSE_ABSORBPERCENTHOLY}, - {"absorbpercentdeath", ITEM_PARSE_ABSORBPERCENTDEATH}, - {"absorbpercentlifedrain", ITEM_PARSE_ABSORBPERCENTLIFEDRAIN}, - {"absorbpercentmanadrain", ITEM_PARSE_ABSORBPERCENTMANADRAIN}, - {"absorbpercentdrown", ITEM_PARSE_ABSORBPERCENTDROWN}, - {"absorbpercentphysical", ITEM_PARSE_ABSORBPERCENTPHYSICAL}, - {"absorbpercenthealing", ITEM_PARSE_ABSORBPERCENTHEALING}, - {"absorbpercentundefined", ITEM_PARSE_ABSORBPERCENTUNDEFINED}, - {"suppressdrunk", ITEM_PARSE_SUPPRESSDRUNK}, - {"suppressenergy", ITEM_PARSE_SUPPRESSENERGY}, - {"suppressfire", ITEM_PARSE_SUPPRESSFIRE}, - {"suppresspoison", ITEM_PARSE_SUPPRESSPOISON}, - {"suppressdrown", ITEM_PARSE_SUPPRESSDROWN}, - {"suppressphysical", ITEM_PARSE_SUPPRESSPHYSICAL}, - {"suppressfreeze", ITEM_PARSE_SUPPRESSFREEZE}, - {"suppressdazzle", ITEM_PARSE_SUPPRESSDAZZLE}, - {"suppresscurse", ITEM_PARSE_SUPPRESSCURSE}, - {"field", ITEM_PARSE_FIELD}, - {"replaceable", ITEM_PARSE_REPLACEABLE}, - {"partnerdirection", ITEM_PARSE_PARTNERDIRECTION}, - {"leveldoor", ITEM_PARSE_LEVELDOOR}, - {"maletransformto", ITEM_PARSE_MALETRANSFORMTO}, - {"malesleeper", ITEM_PARSE_MALETRANSFORMTO}, - {"femaletransformto", ITEM_PARSE_FEMALETRANSFORMTO}, - {"femalesleeper", ITEM_PARSE_FEMALETRANSFORMTO}, - {"transformto", ITEM_PARSE_TRANSFORMTO}, - {"destroyto", ITEM_PARSE_DESTROYTO}, - {"elementice", ITEM_PARSE_ELEMENTICE}, - {"elementearth", ITEM_PARSE_ELEMENTEARTH}, - {"elementfire", ITEM_PARSE_ELEMENTFIRE}, - {"elementenergy", ITEM_PARSE_ELEMENTENERGY}, - {"elementdeath", ITEM_PARSE_ELEMENTDEATH}, - {"elementholy", ITEM_PARSE_ELEMENTHOLY}, - {"walkstack", ITEM_PARSE_WALKSTACK}, - {"blocking", ITEM_PARSE_BLOCKING}, - {"allowdistread", ITEM_PARSE_ALLOWDISTREAD}, - {"storeitem", ITEM_PARSE_STOREITEM}, + {"type", ITEM_PARSE_TYPE}, + {"description", ITEM_PARSE_DESCRIPTION}, + {"runespellname", ITEM_PARSE_RUNESPELLNAME}, + {"weight", ITEM_PARSE_WEIGHT}, + {"showcount", ITEM_PARSE_SHOWCOUNT}, + {"armor", ITEM_PARSE_ARMOR}, + {"defense", ITEM_PARSE_DEFENSE}, + {"extradef", ITEM_PARSE_EXTRADEF}, + {"attack", ITEM_PARSE_ATTACK}, + {"attackspeed", ITEM_PARSE_ATTACK_SPEED}, + {"rotateto", ITEM_PARSE_ROTATETO}, + {"moveable", ITEM_PARSE_MOVEABLE}, + {"movable", ITEM_PARSE_MOVEABLE}, + {"blockprojectile", ITEM_PARSE_BLOCKPROJECTILE}, + {"allowpickupable", ITEM_PARSE_PICKUPABLE}, + {"pickupable", ITEM_PARSE_PICKUPABLE}, + {"forceserialize", ITEM_PARSE_FORCESERIALIZE}, + {"forcesave", ITEM_PARSE_FORCESERIALIZE}, + {"floorchange", ITEM_PARSE_FLOORCHANGE}, + {"corpsetype", ITEM_PARSE_CORPSETYPE}, + {"containersize", ITEM_PARSE_CONTAINERSIZE}, + {"fluidsource", ITEM_PARSE_FLUIDSOURCE}, + {"readable", ITEM_PARSE_READABLE}, + {"writeable", ITEM_PARSE_WRITEABLE}, + {"maxtextlen", ITEM_PARSE_MAXTEXTLEN}, + {"writeonceitemid", ITEM_PARSE_WRITEONCEITEMID}, + {"weapontype", ITEM_PARSE_WEAPONTYPE}, + {"slottype", ITEM_PARSE_SLOTTYPE}, + {"ammotype", ITEM_PARSE_AMMOTYPE}, + {"shoottype", ITEM_PARSE_SHOOTTYPE}, + {"effect", ITEM_PARSE_EFFECT}, + {"range", ITEM_PARSE_RANGE}, + {"stopduration", ITEM_PARSE_STOPDURATION}, + {"decayto", ITEM_PARSE_DECAYTO}, + {"transformequipto", ITEM_PARSE_TRANSFORMEQUIPTO}, + {"transformdeequipto", ITEM_PARSE_TRANSFORMDEEQUIPTO}, + {"duration", ITEM_PARSE_DURATION}, + {"showduration", ITEM_PARSE_SHOWDURATION}, + {"charges", ITEM_PARSE_CHARGES}, + {"showcharges", ITEM_PARSE_SHOWCHARGES}, + {"showattributes", ITEM_PARSE_SHOWATTRIBUTES}, + {"hitchance", ITEM_PARSE_HITCHANCE}, + {"maxhitchance", ITEM_PARSE_MAXHITCHANCE}, + {"invisible", ITEM_PARSE_INVISIBLE}, + {"speed", ITEM_PARSE_SPEED}, + {"healthgain", ITEM_PARSE_HEALTHGAIN}, + {"healthticks", ITEM_PARSE_HEALTHTICKS}, + {"managain", ITEM_PARSE_MANAGAIN}, + {"manaticks", ITEM_PARSE_MANATICKS}, + {"manashield", ITEM_PARSE_MANASHIELD}, + {"skillsword", ITEM_PARSE_SKILLSWORD}, + {"skillaxe", ITEM_PARSE_SKILLAXE}, + {"skillclub", ITEM_PARSE_SKILLCLUB}, + {"skilldist", ITEM_PARSE_SKILLDIST}, + {"skillfish", ITEM_PARSE_SKILLFISH}, + {"skillshield", ITEM_PARSE_SKILLSHIELD}, + {"skillfist", ITEM_PARSE_SKILLFIST}, + {"maxhitpoints", ITEM_PARSE_MAXHITPOINTS}, + {"maxhitpointspercent", ITEM_PARSE_MAXHITPOINTSPERCENT}, + {"maxmanapoints", ITEM_PARSE_MAXMANAPOINTS}, + {"maxmanapointspercent", ITEM_PARSE_MAXMANAPOINTSPERCENT}, + {"magicpoints", ITEM_PARSE_MAGICPOINTS}, + {"magiclevelpoints", ITEM_PARSE_MAGICPOINTS}, + {"magicpointspercent", ITEM_PARSE_MAGICPOINTSPERCENT}, + {"criticalhitchance", ITEM_PARSE_CRITICALHITCHANCE}, + {"criticalhitamount", ITEM_PARSE_CRITICALHITAMOUNT}, + {"lifeleechchance", ITEM_PARSE_LIFELEECHCHANCE}, + {"lifeleechamount", ITEM_PARSE_LIFELEECHAMOUNT}, + {"manaleechchance", ITEM_PARSE_MANALEECHCHANCE}, + {"manaleechamount", ITEM_PARSE_MANALEECHAMOUNT}, + {"fieldabsorbpercentenergy", ITEM_PARSE_FIELDABSORBPERCENTENERGY}, + {"fieldabsorbpercentfire", ITEM_PARSE_FIELDABSORBPERCENTFIRE}, + {"fieldabsorbpercentpoison", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, + {"fieldabsorbpercentearth", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, + {"absorbpercentall", ITEM_PARSE_ABSORBPERCENTALL}, + {"absorbpercentallelements", ITEM_PARSE_ABSORBPERCENTALL}, + {"absorbpercentelements", ITEM_PARSE_ABSORBPERCENTELEMENTS}, + {"absorbpercentmagic", ITEM_PARSE_ABSORBPERCENTMAGIC}, + {"absorbpercentenergy", ITEM_PARSE_ABSORBPERCENTENERGY}, + {"absorbpercentfire", ITEM_PARSE_ABSORBPERCENTFIRE}, + {"absorbpercentpoison", ITEM_PARSE_ABSORBPERCENTPOISON}, + {"absorbpercentearth", ITEM_PARSE_ABSORBPERCENTPOISON}, + {"absorbpercentice", ITEM_PARSE_ABSORBPERCENTICE}, + {"absorbpercentholy", ITEM_PARSE_ABSORBPERCENTHOLY}, + {"absorbpercentdeath", ITEM_PARSE_ABSORBPERCENTDEATH}, + {"absorbpercentlifedrain", ITEM_PARSE_ABSORBPERCENTLIFEDRAIN}, + {"absorbpercentmanadrain", ITEM_PARSE_ABSORBPERCENTMANADRAIN}, + {"absorbpercentdrown", ITEM_PARSE_ABSORBPERCENTDROWN}, + {"absorbpercentphysical", ITEM_PARSE_ABSORBPERCENTPHYSICAL}, + {"absorbpercenthealing", ITEM_PARSE_ABSORBPERCENTHEALING}, + {"absorbpercentundefined", ITEM_PARSE_ABSORBPERCENTUNDEFINED}, + {"reflectpercentall", ITEM_PARSE_REFLECTPERCENTALL}, + {"reflectpercentallelements", ITEM_PARSE_REFLECTPERCENTALL}, + {"reflectpercentelements", ITEM_PARSE_REFLECTPERCENTELEMENTS}, + {"reflectpercentmagic", ITEM_PARSE_REFLECTPERCENTMAGIC}, + {"reflectpercentenergy", ITEM_PARSE_REFLECTPERCENTENERGY}, + {"reflectpercentfire", ITEM_PARSE_REFLECTPERCENTFIRE}, + {"reflectpercentpoison", ITEM_PARSE_REFLECTPERCENTEARTH}, + {"reflectpercentearth", ITEM_PARSE_REFLECTPERCENTEARTH}, + {"reflectpercentice", ITEM_PARSE_REFLECTPERCENTICE}, + {"reflectpercentholy", ITEM_PARSE_REFLECTPERCENTHOLY}, + {"reflectpercentdeath", ITEM_PARSE_REFLECTPERCENTDEATH}, + {"reflectpercentlifedrain", ITEM_PARSE_REFLECTPERCENTLIFEDRAIN}, + {"reflectpercentmanadrain", ITEM_PARSE_REFLECTPERCENTMANADRAIN}, + {"reflectpercentdrown", ITEM_PARSE_REFLECTPERCENTDROWN}, + {"reflectpercentphysical", ITEM_PARSE_REFLECTPERCENTPHYSICAL}, + {"reflectpercenthealing", ITEM_PARSE_REFLECTPERCENTHEALING}, + {"reflectchanceall", ITEM_PARSE_REFLECTCHANCEALL}, + {"reflectchanceallelements", ITEM_PARSE_REFLECTCHANCEALL}, + {"reflectchanceelements", ITEM_PARSE_REFLECTCHANCEELEMENTS}, + {"reflectchancemagic", ITEM_PARSE_REFLECTCHANCEMAGIC}, + {"reflectchanceenergy", ITEM_PARSE_REFLECTCHANCEENERGY}, + {"reflectchancefire", ITEM_PARSE_REFLECTCHANCEFIRE}, + {"reflectchancepoison", ITEM_PARSE_REFLECTCHANCEEARTH}, + {"reflectchanceearth", ITEM_PARSE_REFLECTCHANCEEARTH}, + {"reflectchanceice", ITEM_PARSE_REFLECTCHANCEICE}, + {"reflectchanceholy", ITEM_PARSE_REFLECTCHANCEHOLY}, + {"reflectchancedeath", ITEM_PARSE_REFLECTCHANCEDEATH}, + {"reflectchancelifedrain", ITEM_PARSE_REFLECTCHANCELIFEDRAIN}, + {"reflectchancemanadrain", ITEM_PARSE_REFLECTCHANCEMANADRAIN}, + {"reflectchancedrown", ITEM_PARSE_REFLECTCHANCEDROWN}, + {"reflectchancephysical", ITEM_PARSE_REFLECTCHANCEPHYSICAL}, + {"reflectchancehealing", ITEM_PARSE_REFLECTCHANCEHEALING}, + {"boostpercentall", ITEM_PARSE_BOOSTPERCENTALL}, + {"boostpercentallelements", ITEM_PARSE_BOOSTPERCENTALL}, + {"boostpercentelements", ITEM_PARSE_BOOSTPERCENTELEMENTS}, + {"boostpercentmagic", ITEM_PARSE_BOOSTPERCENTMAGIC}, + {"boostpercentenergy", ITEM_PARSE_BOOSTPERCENTENERGY}, + {"boostpercentfire", ITEM_PARSE_BOOSTPERCENTFIRE}, + {"boostpercentpoison", ITEM_PARSE_BOOSTPERCENTEARTH}, + {"boostpercentearth", ITEM_PARSE_BOOSTPERCENTEARTH}, + {"boostpercentice", ITEM_PARSE_BOOSTPERCENTICE}, + {"boostpercentholy", ITEM_PARSE_BOOSTPERCENTHOLY}, + {"boostpercentdeath", ITEM_PARSE_BOOSTPERCENTDEATH}, + {"boostpercentlifedrain", ITEM_PARSE_BOOSTPERCENTLIFEDRAIN}, + {"boostpercentmanadrain", ITEM_PARSE_BOOSTPERCENTMANADRAIN}, + {"boostpercentdrown", ITEM_PARSE_BOOSTPERCENTDROWN}, + {"boostpercentphysical", ITEM_PARSE_BOOSTPERCENTPHYSICAL}, + {"boostpercenthealing", ITEM_PARSE_BOOSTPERCENTHEALING}, + {"magiclevelenergy", ITEM_PARSE_MAGICLEVELENERGY}, + {"magiclevelfire", ITEM_PARSE_MAGICLEVELFIRE}, + {"magiclevelpoison", ITEM_PARSE_MAGICLEVELPOISON}, + {"magiclevelearth", ITEM_PARSE_MAGICLEVELPOISON}, + {"magiclevelice", ITEM_PARSE_MAGICLEVELICE}, + {"magiclevelholy", ITEM_PARSE_MAGICLEVELHOLY}, + {"magicleveldeath", ITEM_PARSE_MAGICLEVELDEATH}, + {"magiclevellifedrain", ITEM_PARSE_MAGICLEVELLIFEDRAIN}, + {"magiclevelmanadrain", ITEM_PARSE_MAGICLEVELMANADRAIN}, + {"magicleveldrown", ITEM_PARSE_MAGICLEVELDROWN}, + {"magiclevelphysical", ITEM_PARSE_MAGICLEVELPHYSICAL}, + {"magiclevelhealing", ITEM_PARSE_MAGICLEVELHEALING}, + {"magiclevelundefined", ITEM_PARSE_MAGICLEVELUNDEFINED}, + {"suppressdrunk", ITEM_PARSE_SUPPRESSDRUNK}, + {"suppressenergy", ITEM_PARSE_SUPPRESSENERGY}, + {"suppressfire", ITEM_PARSE_SUPPRESSFIRE}, + {"suppresspoison", ITEM_PARSE_SUPPRESSPOISON}, + {"suppressdrown", ITEM_PARSE_SUPPRESSDROWN}, + {"suppressphysical", ITEM_PARSE_SUPPRESSPHYSICAL}, + {"suppressfreeze", ITEM_PARSE_SUPPRESSFREEZE}, + {"suppressdazzle", ITEM_PARSE_SUPPRESSDAZZLE}, + {"suppresscurse", ITEM_PARSE_SUPPRESSCURSE}, + {"field", ITEM_PARSE_FIELD}, + {"replaceable", ITEM_PARSE_REPLACEABLE}, + {"partnerdirection", ITEM_PARSE_PARTNERDIRECTION}, + {"leveldoor", ITEM_PARSE_LEVELDOOR}, + {"maletransformto", ITEM_PARSE_MALETRANSFORMTO}, + {"malesleeper", ITEM_PARSE_MALETRANSFORMTO}, + {"femaletransformto", ITEM_PARSE_FEMALETRANSFORMTO}, + {"femalesleeper", ITEM_PARSE_FEMALETRANSFORMTO}, + {"transformto", ITEM_PARSE_TRANSFORMTO}, + {"destroyto", ITEM_PARSE_DESTROYTO}, + {"elementice", ITEM_PARSE_ELEMENTICE}, + {"elementearth", ITEM_PARSE_ELEMENTEARTH}, + {"elementfire", ITEM_PARSE_ELEMENTFIRE}, + {"elementenergy", ITEM_PARSE_ELEMENTENERGY}, + {"elementdeath", ITEM_PARSE_ELEMENTDEATH}, + {"elementholy", ITEM_PARSE_ELEMENTHOLY}, + {"walkstack", ITEM_PARSE_WALKSTACK}, + {"blocking", ITEM_PARSE_BLOCKING}, + {"allowdistread", ITEM_PARSE_ALLOWDISTREAD}, + {"storeitem", ITEM_PARSE_STOREITEM}, + {"worth", ITEM_PARSE_WORTH}, + {"supply", ITEM_PARSE_SUPPLY}, }; -const std::unordered_map ItemTypesMap = { - {"key", ITEM_TYPE_KEY}, - {"magicfield", ITEM_TYPE_MAGICFIELD}, - {"container", ITEM_TYPE_CONTAINER}, - {"depot", ITEM_TYPE_DEPOT}, - {"mailbox", ITEM_TYPE_MAILBOX}, - {"trashholder", ITEM_TYPE_TRASHHOLDER}, - {"teleport", ITEM_TYPE_TELEPORT}, - {"door", ITEM_TYPE_DOOR}, - {"bed", ITEM_TYPE_BED}, - {"rune", ITEM_TYPE_RUNE}, -}; +const std::unordered_map ItemTypesMap = {{"key", ITEM_TYPE_KEY}, + {"magicfield", ITEM_TYPE_MAGICFIELD}, + {"container", ITEM_TYPE_CONTAINER}, + {"depot", ITEM_TYPE_DEPOT}, + {"mailbox", ITEM_TYPE_MAILBOX}, + {"trashholder", ITEM_TYPE_TRASHHOLDER}, + {"teleport", ITEM_TYPE_TELEPORT}, + {"door", ITEM_TYPE_DOOR}, + {"bed", ITEM_TYPE_BED}, + {"rune", ITEM_TYPE_RUNE}, + {"podium", ITEM_TYPE_PODIUM}}; const std::unordered_map TileStatesMap = { - {"down", TILESTATE_FLOORCHANGE_DOWN}, - {"north", TILESTATE_FLOORCHANGE_NORTH}, - {"south", TILESTATE_FLOORCHANGE_SOUTH}, - {"southalt", TILESTATE_FLOORCHANGE_SOUTH_ALT}, - {"west", TILESTATE_FLOORCHANGE_WEST}, - {"east", TILESTATE_FLOORCHANGE_EAST}, - {"eastalt", TILESTATE_FLOORCHANGE_EAST_ALT}, + {"down", TILESTATE_FLOORCHANGE_DOWN}, {"north", TILESTATE_FLOORCHANGE_NORTH}, + {"south", TILESTATE_FLOORCHANGE_SOUTH}, {"southalt", TILESTATE_FLOORCHANGE_SOUTH_ALT}, + {"west", TILESTATE_FLOORCHANGE_WEST}, {"east", TILESTATE_FLOORCHANGE_EAST}, + {"eastalt", TILESTATE_FLOORCHANGE_EAST_ALT}, }; const std::unordered_map RaceTypesMap = { - {"venom", RACE_VENOM}, - {"blood", RACE_BLOOD}, - {"undead", RACE_UNDEAD}, - {"fire", RACE_FIRE}, - {"energy", RACE_ENERGY}, + {"venom", RACE_VENOM}, {"blood", RACE_BLOOD}, {"undead", RACE_UNDEAD}, + {"fire", RACE_FIRE}, {"energy", RACE_ENERGY}, {"ink", RACE_INK}, }; const std::unordered_map WeaponTypesMap = { - {"sword", WEAPON_SWORD}, - {"club", WEAPON_CLUB}, - {"axe", WEAPON_AXE}, - {"shield", WEAPON_SHIELD}, - {"distance", WEAPON_DISTANCE}, - {"wand", WEAPON_WAND}, - {"ammunition", WEAPON_AMMO}, + {"sword", WEAPON_SWORD}, {"club", WEAPON_CLUB}, {"axe", WEAPON_AXE}, {"shield", WEAPON_SHIELD}, + {"distance", WEAPON_DISTANCE}, {"wand", WEAPON_WAND}, {"ammunition", WEAPON_AMMO}, }; const std::unordered_map FluidTypesMap = { - {"water", FLUID_WATER}, - {"blood", FLUID_BLOOD}, - {"beer", FLUID_BEER}, - {"slime", FLUID_SLIME}, - {"lemonade", FLUID_LEMONADE}, - {"milk", FLUID_MILK}, - {"mana", FLUID_MANA}, - {"life", FLUID_LIFE}, - {"oil", FLUID_OIL}, - {"urine", FLUID_URINE}, - {"coconut", FLUID_COCONUTMILK}, - {"wine", FLUID_WINE}, - {"mud", FLUID_MUD}, - {"fruitjuice", FLUID_FRUITJUICE}, - {"lava", FLUID_LAVA}, - {"rum", FLUID_RUM}, - {"swamp", FLUID_SWAMP}, - {"tea", FLUID_TEA}, - {"mead", FLUID_MEAD}, + {"water", FLUID_WATER}, + {"blood", FLUID_BLOOD}, + {"beer", FLUID_BEER}, + {"slime", FLUID_SLIME}, + {"lemonade", FLUID_LEMONADE}, + {"milk", FLUID_MILK}, + {"mana", FLUID_MANA}, + {"life", FLUID_LIFE}, + {"oil", FLUID_OIL}, + {"urine", FLUID_URINE}, + {"coconut", FLUID_COCONUTMILK}, + {"wine", FLUID_WINE}, + {"mud", FLUID_MUD}, + {"fruitjuice", FLUID_FRUITJUICE}, + {"lava", FLUID_LAVA}, + {"rum", FLUID_RUM}, + {"swamp", FLUID_SWAMP}, + {"tea", FLUID_TEA}, + {"mead", FLUID_MEAD}, + {"ink", FLUID_INK}, }; Items::Items() { - items.reserve(30000); - nameToItems.reserve(30000); + items.reserve(45000); + nameToItems.reserve(45000); } void Items::clear() @@ -225,6 +261,8 @@ void Items::clear() items.clear(); clientIdToServerIdMap.clear(); nameToItems.clear(); + currencyItems.clear(); + inventory.clear(); } bool Items::reload() @@ -242,7 +280,7 @@ bool Items::reload() return true; } -constexpr auto OTBI = OTB::Identifier{{'O','T', 'B', 'I'}}; +constexpr auto OTBI = OTB::Identifier{{'O', 'T', 'B', 'I'}}; bool Items::loadFromOtb(const std::string& file) { @@ -252,9 +290,9 @@ bool Items::loadFromOtb(const std::string& file) PropStream props; if (loader.getProps(root, props)) { - //4 byte flags - //attributes - //0x01 = version data + // 4 byte flags + // attributes + // 0x01 = version data uint32_t flags; if (!props.read(flags)) { return false; @@ -280,9 +318,9 @@ bool Items::loadFromOtb(const std::string& file) return false; } - majorVersion = vi.dwMajorVersion; //items otb format file version - minorVersion = vi.dwMinorVersion; //client version - buildNumber = vi.dwBuildNumber; //revision + majorVersion = vi.dwMajorVersion; // items otb format file version + minorVersion = vi.dwMinorVersion; // client version + buildNumber = vi.dwBuildNumber; // revision } } @@ -291,7 +329,7 @@ bool Items::loadFromOtb(const std::string& file) } else if (majorVersion != 3) { std::cout << "Old version detected, a newer version of items.otb is required." << std::endl; return false; - } else if (minorVersion < CLIENT_VERSION_1098) { + } else if (minorVersion < CLIENT_VERSION_LAST) { std::cout << "A newer version of items.otb is required." << std::endl; return false; } @@ -314,6 +352,7 @@ bool Items::loadFromOtb(const std::string& file) uint8_t lightLevel = 0; uint8_t lightColor = 0; uint8_t alwaysOnTopOrder = 0; + uint8_t classification = 0; uint8_t attrib; while (stream.read(attrib)) { @@ -331,10 +370,6 @@ bool Items::loadFromOtb(const std::string& file) if (!stream.read(serverId)) { return false; } - - if (serverId > 30000 && serverId < 30100) { - serverId -= 30000; - } break; } @@ -397,8 +432,19 @@ bool Items::loadFromOtb(const std::string& file) break; } + case ITEM_ATTR_CLASSIFICATION: { + if (datalen != sizeof(uint8_t)) { + return false; + } + + if (!stream.read(classification)) { + return false; + } + break; + } + default: { - //skip unknown attributes + // skip unknown attributes if (!stream.skip(datalen)) { return false; } @@ -421,15 +467,15 @@ bool Items::loadFromOtb(const std::string& file) iType.type = ITEM_TYPE_CONTAINER; break; case ITEM_GROUP_DOOR: - //not used + // not used iType.type = ITEM_TYPE_DOOR; break; case ITEM_GROUP_MAGICFIELD: - //not used + // not used iType.type = ITEM_TYPE_MAGICFIELD; break; case ITEM_GROUP_TELEPORT: - //not used + // not used iType.type = ITEM_TYPE_TELEPORT; break; case ITEM_GROUP_NONE: @@ -439,6 +485,9 @@ bool Items::loadFromOtb(const std::string& file) case ITEM_GROUP_CHARGES: case ITEM_GROUP_DEPRECATED: break; + case ITEM_GROUP_PODIUM: + iType.type = ITEM_TYPE_PODIUM; + break; default: return false; } @@ -470,6 +519,7 @@ bool Items::loadFromOtb(const std::string& file) iType.lightLevel = lightLevel; iType.lightColor = lightColor; iType.wareId = wareId; + iType.classification = classification; iType.alwaysOnTopOrder = alwaysOnTopOrder; } @@ -501,7 +551,8 @@ bool Items::loadFromXml() pugi::xml_attribute toIdAttribute = itemNode.attribute("toid"); if (!toIdAttribute) { - std::cout << "[Warning - Items::loadFromXml] fromid (" << fromIdAttribute.value() << ") without toid" << std::endl; + std::cout << "[Warning - Items::loadFromXml] fromid (" << fromIdAttribute.value() << ") without toid" + << std::endl; continue; } @@ -512,32 +563,9 @@ bool Items::loadFromXml() } } - buildInventoryList(); return true; } -void Items::buildInventoryList() -{ - inventory.reserve(items.size()); - for (const auto& type: items) { - if (type.weaponType != WEAPON_NONE || type.ammoType != AMMO_NONE || - type.attack != 0 || type.defense != 0 || - type.extraDefense != 0 || type.armor != 0 || - type.slotPosition & SLOTP_NECKLACE || - type.slotPosition & SLOTP_RING || - type.slotPosition & SLOTP_AMMO || - type.slotPosition & SLOTP_FEET || - type.slotPosition & SLOTP_HEAD || - type.slotPosition & SLOTP_ARMOR || - type.slotPosition & SLOTP_LEGS) - { - inventory.push_back(type.clientId); - } - } - inventory.shrink_to_fit(); - std::sort(inventory.begin(), inventory.end()); -} - void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) { if (id > 0 && id < 100) { @@ -557,7 +585,12 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) it.name = itemNode.attribute("name").as_string(); - nameToItems.insert({ asLowerCaseString(it.name), id }); + if (!it.name.empty()) { + std::string lowerCaseName = boost::algorithm::to_lower_copy(it.name); + if (nameToItems.find(lowerCaseName) == nameToItems.end()) { + nameToItems.emplace(std::move(lowerCaseName), id); + } + } pugi::xml_attribute articleAttribute = itemNode.attribute("article"); if (articleAttribute) { @@ -582,13 +615,13 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) continue; } - std::string tmpStrValue = asLowerCaseString(keyAttribute.as_string()); + std::string tmpStrValue = boost::algorithm::to_lower_copy(keyAttribute.as_string()); auto parseAttribute = ItemParseAttributesMap.find(tmpStrValue); if (parseAttribute != ItemParseAttributesMap.end()) { ItemParseAttributes_t parseType = parseAttribute->second; switch (parseType) { case ITEM_PARSE_TYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); auto it2 = ItemTypesMap.find(tmpStrValue); if (it2 != ItemTypesMap.end()) { it.type = it2->second; @@ -596,7 +629,8 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) it.group = ITEM_GROUP_CONTAINER; } } else { - std::cout << "[Warning - Items::parseItemNode] Unknown type: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown type: " << valueAttribute.as_string() + << std::endl; } break; } @@ -621,6 +655,11 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } + case ITEM_PARSE_SUPPLY: { + it.supply = valueAttribute.as_bool(); + break; + } + case ITEM_PARSE_ARMOR: { it.armor = pugi::cast(valueAttribute.value()); break; @@ -641,6 +680,16 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } + case ITEM_PARSE_ATTACK_SPEED: { + it.attackSpeed = pugi::cast(valueAttribute.value()); + if (it.attackSpeed > 0 && it.attackSpeed < 100) { + std::cout << "[Warning - Items::parseItemNode] AttackSpeed lower than 100 for item: " << it.id + << std::endl; + it.attackSpeed = 100; + } + break; + } + case ITEM_PARSE_ROTATETO: { it.rotateTo = pugi::cast(valueAttribute.value()); break; @@ -667,23 +716,25 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_FLOORCHANGE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); auto it2 = TileStatesMap.find(tmpStrValue); if (it2 != TileStatesMap.end()) { it.floorChange |= it2->second; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown floorChange: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown floorChange: " + << valueAttribute.as_string() << std::endl; } break; } case ITEM_PARSE_CORPSETYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); auto it2 = RaceTypesMap.find(tmpStrValue); if (it2 != RaceTypesMap.end()) { it.corpseType = it2->second; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown corpseType: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown corpseType: " + << valueAttribute.as_string() << std::endl; } break; } @@ -694,12 +745,13 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_FLUIDSOURCE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); auto it2 = FluidTypesMap.find(tmpStrValue); if (it2 != FluidTypesMap.end()) { it.fluidSource = it2->second; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown fluidSource: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown fluidSource: " + << valueAttribute.as_string() << std::endl; } break; } @@ -726,18 +778,19 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_WEAPONTYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); auto it2 = WeaponTypesMap.find(tmpStrValue); if (it2 != WeaponTypesMap.end()) { it.weaponType = it2->second; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown weaponType: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown weaponType: " + << valueAttribute.as_string() << std::endl; } break; } case ITEM_PARSE_SLOTTYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); if (tmpStrValue == "head") { it.slotPosition |= SLOTP_HEAD; } else if (tmpStrValue == "body") { @@ -763,35 +816,41 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } else if (tmpStrValue == "hand") { it.slotPosition |= SLOTP_HAND; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown slotType: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown slotType: " << valueAttribute.as_string() + << std::endl; } break; } case ITEM_PARSE_AMMOTYPE: { - it.ammoType = getAmmoType(asLowerCaseString(valueAttribute.as_string())); + it.ammoType = getAmmoType(boost::algorithm::to_lower_copy(valueAttribute.as_string())); if (it.ammoType == AMMO_NONE) { - std::cout << "[Warning - Items::parseItemNode] Unknown ammoType: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown ammoType: " << valueAttribute.as_string() + << std::endl; } break; } case ITEM_PARSE_SHOOTTYPE: { - ShootType_t shoot = getShootType(asLowerCaseString(valueAttribute.as_string())); + ShootType_t shoot = + getShootType(boost::algorithm::to_lower_copy(valueAttribute.as_string())); if (shoot != CONST_ANI_NONE) { it.shootType = shoot; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown shootType: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown shootType: " + << valueAttribute.as_string() << std::endl; } break; } case ITEM_PARSE_EFFECT: { - MagicEffectClasses effect = getMagicEffect(asLowerCaseString(valueAttribute.as_string())); + MagicEffectClasses effect = + getMagicEffect(boost::algorithm::to_lower_copy(valueAttribute.as_string())); if (effect != CONST_ME_NONE) { it.magicEffect = effect; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown effect: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown effect: " << valueAttribute.as_string() + << std::endl; } break; } @@ -847,7 +906,8 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_HITCHANCE: { - it.hitChance = std::min(100, std::max(-100, pugi::cast(valueAttribute.value()))); + it.hitChance = + std::min(100, std::max(-100, pugi::cast(valueAttribute.value()))); break; } @@ -931,12 +991,14 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_CRITICALHITAMOUNT: { - abilities.specialSkills[SPECIALSKILL_CRITICALHITAMOUNT] = pugi::cast(valueAttribute.value()); + abilities.specialSkills[SPECIALSKILL_CRITICALHITAMOUNT] = + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_CRITICALHITCHANCE: { - abilities.specialSkills[SPECIALSKILL_CRITICALHITCHANCE] = pugi::cast(valueAttribute.value()); + abilities.specialSkills[SPECIALSKILL_CRITICALHITCHANCE] = + pugi::cast(valueAttribute.value()); break; } @@ -991,17 +1053,20 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_FIELDABSORBPERCENTENERGY: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_FIELDABSORBPERCENTFIRE: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_FIELDABSORBPERCENTPOISON: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } @@ -1034,62 +1099,428 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_ABSORBPERCENTENERGY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTFIRE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTPOISON: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTICE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTHOLY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTDEATH: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTLIFEDRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTMANADRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTDROWN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTPHYSICAL: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTHEALING: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += + pugi::cast(valueAttribute.value()); break; } case ITEM_PARSE_ABSORBPERCENTUNDEFINED: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTALL: { + int16_t value = pugi::cast(valueAttribute.value()); + for (auto& i : abilities.reflect) { + i.percent += value; + } + break; + } + + case ITEM_PARSE_REFLECTPERCENTELEMENTS: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].percent += value; + break; + } + + case ITEM_PARSE_REFLECTPERCENTMAGIC: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].percent += value; + abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].percent += value; + break; + } + + case ITEM_PARSE_REFLECTPERCENTENERGY: { + abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTFIRE: { + abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTEARTH: { + abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTICE: { + abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTHOLY: { + abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTDEATH: { + abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTLIFEDRAIN: { + abilities.reflect[combatTypeToIndex(COMBAT_LIFEDRAIN)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTMANADRAIN: { + abilities.reflect[combatTypeToIndex(COMBAT_MANADRAIN)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTDROWN: { + abilities.reflect[combatTypeToIndex(COMBAT_DROWNDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTPHYSICAL: { + abilities.reflect[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTPERCENTHEALING: { + abilities.reflect[combatTypeToIndex(COMBAT_HEALING)].percent += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEALL: { + int16_t value = pugi::cast(valueAttribute.value()); + for (auto& i : abilities.reflect) { + i.chance += value; + } + break; + } + + case ITEM_PARSE_REFLECTCHANCEELEMENTS: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].chance += value; + break; + } + + case ITEM_PARSE_REFLECTCHANCEMAGIC: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].chance += value; + abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].chance += value; + break; + } + + case ITEM_PARSE_REFLECTCHANCEENERGY: { + abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEFIRE: { + abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEEARTH: { + abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEICE: { + abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEHOLY: { + abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEDEATH: { + abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCELIFEDRAIN: { + abilities.reflect[combatTypeToIndex(COMBAT_LIFEDRAIN)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEMANADRAIN: { + abilities.reflect[combatTypeToIndex(COMBAT_MANADRAIN)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEDROWN: { + abilities.reflect[combatTypeToIndex(COMBAT_DROWNDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEPHYSICAL: { + abilities.reflect[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_REFLECTCHANCEHEALING: { + abilities.reflect[combatTypeToIndex(COMBAT_HEALING)].chance += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTALL: { + int16_t value = pugi::cast(valueAttribute.value()); + for (auto& i : abilities.boostPercent) { + i += value; + } + break; + } + + case ITEM_PARSE_BOOSTPERCENTELEMENTS: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.boostPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; + break; + } + + case ITEM_PARSE_BOOSTPERCENTMAGIC: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.boostPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += value; + abilities.boostPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += value; + break; + } + + case ITEM_PARSE_BOOSTPERCENTENERGY: { + abilities.boostPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTFIRE: { + abilities.boostPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTEARTH: { + abilities.boostPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTICE: { + abilities.boostPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTHOLY: { + abilities.boostPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTDEATH: { + abilities.boostPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTLIFEDRAIN: { + abilities.boostPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTMANADRAIN: { + abilities.boostPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTDROWN: { + abilities.boostPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTPHYSICAL: { + abilities.boostPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_BOOSTPERCENTHEALING: { + abilities.boostPercent[combatTypeToIndex(COMBAT_HEALING)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELENERGY: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELFIRE: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_FIREDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELPOISON: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELICE: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_ICEDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELHOLY: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELDEATH: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELLIFEDRAIN: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_LIFEDRAIN)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELMANADRAIN: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_MANADRAIN)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELDROWN: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELPHYSICAL: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELHEALING: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_HEALING)] += + pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICLEVELUNDEFINED: { + abilities.specialMagicLevelSkill[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += + pugi::cast(valueAttribute.value()); break; } @@ -1163,7 +1594,7 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) CombatType_t combatType = COMBAT_NONE; ConditionDamage* conditionDamage = nullptr; - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(valueAttribute.as_string()); if (tmpStrValue == "fire") { conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); combatType = COMBAT_FIREDAMAGE; @@ -1180,7 +1611,8 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_BLEEDING); combatType = COMBAT_PHYSICALDAMAGE; } else { - std::cout << "[Warning - Items::parseItemNode] Unknown field value: " << valueAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown field value: " + << valueAttribute.as_string() << std::endl; } if (combatType != COMBAT_NONE) { @@ -1203,7 +1635,7 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) continue; } - tmpStrValue = asLowerCaseString(subKeyAttribute.as_string()); + tmpStrValue = boost::algorithm::to_lower_copy(subKeyAttribute.as_string()); if (tmpStrValue == "initdamage") { initDamage = pugi::cast(subValueAttribute.value()); } else if (tmpStrValue == "ticks") { @@ -1233,8 +1665,8 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) // initDamage = -1 (undefined, override initDamage with damage) if (initDamage > 0 || initDamage < -1) { conditionDamage->setInitDamage(-initDamage); - } else if (initDamage == -1 && damage != 0) { - conditionDamage->setInitDamage(damage); + } else if (initDamage == -1 && start != 0) { + conditionDamage->setInitDamage(start); } conditionDamage->setParam(CONDITION_PARAM_FIELD, 1); @@ -1356,19 +1788,36 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } + case ITEM_PARSE_WORTH: { + uint64_t worth = pugi::cast(valueAttribute.value()); + if (currencyItems.find(worth) != currencyItems.end()) { + std::cout << "[Warning - Items::parseItemNode] Duplicated currency worth. Item " << id + << " redefines worth " << worth << std::endl; + } else { + currencyItems.insert(CurrencyMap::value_type(worth, id)); + it.worth = worth; + } + break; + } + default: { - // It should not ever get to here, only if you add a new key to the map and don't configure a case for it. - std::cout << "[Warning - Items::parseItemNode] Not configured key value: " << keyAttribute.as_string() << std::endl; + // It should not ever get to here, only if you add a new key to the map and don't configure a case + // for it. + std::cout << "[Warning - Items::parseItemNode] Not configured key value: " + << keyAttribute.as_string() << std::endl; break; } } } else { - std::cout << "[Warning - Items::parseItemNode] Unknown key value: " << keyAttribute.as_string() << std::endl; + std::cout << "[Warning - Items::parseItemNode] Unknown key value: " << keyAttribute.as_string() + << std::endl; } } - //check bed items - if ((it.transformToFree != 0 || it.transformToOnUse[PLAYERSEX_FEMALE] != 0 || it.transformToOnUse[PLAYERSEX_MALE] != 0) && it.type != ITEM_TYPE_BED) { + // check bed items + if ((it.transformToFree != 0 || it.transformToOnUse[PLAYERSEX_FEMALE] != 0 || + it.transformToOnUse[PLAYERSEX_MALE] != 0) && + it.type != ITEM_TYPE_BED) { std::cout << "[Warning - Items::parseItemNode] Item " << it.id << " is not set as a bed-type" << std::endl; } } @@ -1401,10 +1850,12 @@ const ItemType& Items::getItemIdByClientId(uint16_t spriteId) const uint16_t Items::getItemIdByName(const std::string& name) { - auto result = nameToItems.find(asLowerCaseString(name)); - - if (result == nameToItems.end()) + if (name.empty()) { return 0; + } + + auto result = nameToItems.find(boost::algorithm::to_lower_copy(name)); + if (result == nameToItems.end()) return 0; return result->second; } diff --git a/src/items.h b/src/items.h index 9f45528fe8..480200a8c4 100644 --- a/src/items.h +++ b/src/items.h @@ -1,31 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C -#define FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_ITEMS_H +#define FS_ITEMS_H #include "const.h" #include "enums.h" #include "itemloader.h" #include "position.h" -enum SlotPositionBits : uint32_t { +class ConditionDamage; + +enum SlotPositionBits : uint32_t +{ SLOTP_WHEREEVER = 0xFFFFFFFF, SLOTP_HEAD = 1 << 0, SLOTP_NECKLACE = 1 << 1, @@ -42,7 +29,8 @@ enum SlotPositionBits : uint32_t { SLOTP_HAND = (SLOTP_LEFT | SLOTP_RIGHT) }; -enum ItemTypes_t { +enum ItemTypes_t +{ ITEM_TYPE_NONE, ITEM_TYPE_DEPOT, ITEM_TYPE_MAILBOX, @@ -54,10 +42,12 @@ enum ItemTypes_t { ITEM_TYPE_BED, ITEM_TYPE_KEY, ITEM_TYPE_RUNE, + ITEM_TYPE_PODIUM, ITEM_TYPE_LAST }; -enum ItemParseAttributes_t { +enum ItemParseAttributes_t +{ ITEM_PARSE_TYPE, ITEM_PARSE_DESCRIPTION, ITEM_PARSE_RUNESPELLNAME, @@ -67,6 +57,7 @@ enum ItemParseAttributes_t { ITEM_PARSE_DEFENSE, ITEM_PARSE_EXTRADEF, ITEM_PARSE_ATTACK, + ITEM_PARSE_ATTACK_SPEED, ITEM_PARSE_ROTATETO, ITEM_PARSE_MOVEABLE, ITEM_PARSE_BLOCKPROJECTILE, @@ -141,6 +132,18 @@ enum ItemParseAttributes_t { ITEM_PARSE_ABSORBPERCENTPHYSICAL, ITEM_PARSE_ABSORBPERCENTHEALING, ITEM_PARSE_ABSORBPERCENTUNDEFINED, + ITEM_PARSE_MAGICLEVELENERGY, + ITEM_PARSE_MAGICLEVELFIRE, + ITEM_PARSE_MAGICLEVELPOISON, + ITEM_PARSE_MAGICLEVELICE, + ITEM_PARSE_MAGICLEVELHOLY, + ITEM_PARSE_MAGICLEVELDEATH, + ITEM_PARSE_MAGICLEVELLIFEDRAIN, + ITEM_PARSE_MAGICLEVELMANADRAIN, + ITEM_PARSE_MAGICLEVELDROWN, + ITEM_PARSE_MAGICLEVELPHYSICAL, + ITEM_PARSE_MAGICLEVELHEALING, + ITEM_PARSE_MAGICLEVELUNDEFINED, ITEM_PARSE_SUPPRESSDRUNK, ITEM_PARSE_SUPPRESSENERGY, ITEM_PARSE_SUPPRESSFIRE, @@ -168,9 +171,54 @@ enum ItemParseAttributes_t { ITEM_PARSE_BLOCKING, ITEM_PARSE_ALLOWDISTREAD, ITEM_PARSE_STOREITEM, + ITEM_PARSE_WORTH, + ITEM_PARSE_REFLECTPERCENTALL, + ITEM_PARSE_REFLECTPERCENTELEMENTS, + ITEM_PARSE_REFLECTPERCENTMAGIC, + ITEM_PARSE_REFLECTPERCENTENERGY, + ITEM_PARSE_REFLECTPERCENTFIRE, + ITEM_PARSE_REFLECTPERCENTEARTH, + ITEM_PARSE_REFLECTPERCENTICE, + ITEM_PARSE_REFLECTPERCENTHOLY, + ITEM_PARSE_REFLECTPERCENTDEATH, + ITEM_PARSE_REFLECTPERCENTLIFEDRAIN, + ITEM_PARSE_REFLECTPERCENTMANADRAIN, + ITEM_PARSE_REFLECTPERCENTDROWN, + ITEM_PARSE_REFLECTPERCENTPHYSICAL, + ITEM_PARSE_REFLECTPERCENTHEALING, + ITEM_PARSE_REFLECTCHANCEALL, + ITEM_PARSE_REFLECTCHANCEELEMENTS, + ITEM_PARSE_REFLECTCHANCEMAGIC, + ITEM_PARSE_REFLECTCHANCEENERGY, + ITEM_PARSE_REFLECTCHANCEFIRE, + ITEM_PARSE_REFLECTCHANCEEARTH, + ITEM_PARSE_REFLECTCHANCEICE, + ITEM_PARSE_REFLECTCHANCEHOLY, + ITEM_PARSE_REFLECTCHANCEDEATH, + ITEM_PARSE_REFLECTCHANCELIFEDRAIN, + ITEM_PARSE_REFLECTCHANCEMANADRAIN, + ITEM_PARSE_REFLECTCHANCEDROWN, + ITEM_PARSE_REFLECTCHANCEPHYSICAL, + ITEM_PARSE_REFLECTCHANCEHEALING, + ITEM_PARSE_BOOSTPERCENTALL, + ITEM_PARSE_BOOSTPERCENTELEMENTS, + ITEM_PARSE_BOOSTPERCENTMAGIC, + ITEM_PARSE_BOOSTPERCENTENERGY, + ITEM_PARSE_BOOSTPERCENTFIRE, + ITEM_PARSE_BOOSTPERCENTEARTH, + ITEM_PARSE_BOOSTPERCENTICE, + ITEM_PARSE_BOOSTPERCENTHOLY, + ITEM_PARSE_BOOSTPERCENTDEATH, + ITEM_PARSE_BOOSTPERCENTLIFEDRAIN, + ITEM_PARSE_BOOSTPERCENTMANADRAIN, + ITEM_PARSE_BOOSTPERCENTDROWN, + ITEM_PARSE_BOOSTPERCENTPHYSICAL, + ITEM_PARSE_BOOSTPERCENTHEALING, + ITEM_PARSE_SUPPLY, }; -struct Abilities { +struct Abilities +{ uint32_t healthGain = 0; uint32_t healthTicks = 0; uint32_t manaGain = 0; @@ -179,23 +227,27 @@ struct Abilities { uint32_t conditionImmunities = 0; uint32_t conditionSuppressions = 0; - //stats modifiers - int32_t stats[STAT_LAST + 1] = { 0 }; - int32_t statsPercent[STAT_LAST + 1] = { 0 }; - - //extra skill modifiers - int32_t skills[SKILL_LAST + 1] = { 0 }; - int32_t specialSkills[SPECIALSKILL_LAST + 1] = { 0 }; + // stats modifiers + std::array stats = {0}; + std::array statsPercent = {0}; + // extra skill modifiers + std::array skills = {0}; + std::array specialSkills = {0}; + std::array specialMagicLevelSkill = {0}; int32_t speed = 0; // field damage abilities modifiers - int16_t fieldAbsorbPercent[COMBAT_COUNT] = { 0 }; + std::array fieldAbsorbPercent = {0}; + + // damage abilities modifiers + std::array absorbPercent = {0}; + + std::array reflect; - //damage abilities modifiers - int16_t absorbPercent[COMBAT_COUNT] = { 0 }; + int16_t boostPercent[COMBAT_COUNT] = {0}; - //elemental damage + // elemental damage uint16_t elementDamage = 0; CombatType_t elementType = COMBAT_NONE; @@ -204,266 +256,234 @@ struct Abilities { bool regeneration = false; }; -class ConditionDamage; - class ItemType { - public: - ItemType() = default; - - //non-copyable - ItemType(const ItemType& other) = delete; - ItemType& operator=(const ItemType& other) = delete; - - ItemType(ItemType&& other) = default; - ItemType& operator=(ItemType&& other) = default; - - bool isGroundTile() const { - return group == ITEM_GROUP_GROUND; - } - bool isContainer() const { - return group == ITEM_GROUP_CONTAINER; - } - bool isSplash() const { - return group == ITEM_GROUP_SPLASH; - } - bool isFluidContainer() const { - return group == ITEM_GROUP_FLUID; +public: + ItemType() = default; + + // non-copyable + ItemType(const ItemType& other) = delete; + ItemType& operator=(const ItemType& other) = delete; + + ItemType(ItemType&& other) = default; + ItemType& operator=(ItemType&& other) = default; + + bool isGroundTile() const { return group == ITEM_GROUP_GROUND; } + bool isContainer() const { return group == ITEM_GROUP_CONTAINER; } + bool isSplash() const { return group == ITEM_GROUP_SPLASH; } + bool isFluidContainer() const { return group == ITEM_GROUP_FLUID; } + + bool isDoor() const { return (type == ITEM_TYPE_DOOR); } + bool isMagicField() const { return (type == ITEM_TYPE_MAGICFIELD); } + bool isTeleport() const { return (type == ITEM_TYPE_TELEPORT); } + bool isKey() const { return (type == ITEM_TYPE_KEY); } + bool isDepot() const { return (type == ITEM_TYPE_DEPOT); } + bool isMailbox() const { return (type == ITEM_TYPE_MAILBOX); } + bool isTrashHolder() const { return (type == ITEM_TYPE_TRASHHOLDER); } + bool isBed() const { return (type == ITEM_TYPE_BED); } + bool isRune() const { return (type == ITEM_TYPE_RUNE); } + bool isPodium() const { return (type == ITEM_TYPE_PODIUM); } + bool isPickupable() const { return (allowPickupable || pickupable); } + bool isUseable() const { return (useable); } + bool hasSubType() const { return (isFluidContainer() || isSplash() || stackable || charges != 0); } + bool isSupply() const { return supply; } + + Abilities& getAbilities() + { + if (!abilities) { + abilities.reset(new Abilities()); } + return *abilities; + } - bool isDoor() const { - return (type == ITEM_TYPE_DOOR); - } - bool isMagicField() const { - return (type == ITEM_TYPE_MAGICFIELD); - } - bool isTeleport() const { - return (type == ITEM_TYPE_TELEPORT); - } - bool isKey() const { - return (type == ITEM_TYPE_KEY); - } - bool isDepot() const { - return (type == ITEM_TYPE_DEPOT); - } - bool isMailbox() const { - return (type == ITEM_TYPE_MAILBOX); - } - bool isTrashHolder() const { - return (type == ITEM_TYPE_TRASHHOLDER); - } - bool isBed() const { - return (type == ITEM_TYPE_BED); - } - bool isRune() const { - return (type == ITEM_TYPE_RUNE); - } - bool isPickupable() const { - return (allowPickupable || pickupable); - } - bool isUseable() const { - return (useable); - } - bool hasSubType() const { - return (isFluidContainer() || isSplash() || stackable || charges != 0); + std::string getPluralName() const + { + if (!pluralName.empty()) { + return pluralName; } - Abilities& getAbilities() { - if (!abilities) { - abilities.reset(new Abilities()); - } - return *abilities; + if (showCount == 0) { + return name; } - std::string getPluralName() const { - if (!pluralName.empty()) { - return pluralName; - } - - if (showCount == 0) { - return name; - } - - if (name.empty() || name.back() == 's') { - return name; - } - - std::string str; - str.reserve(name.length() + 1); - str.assign(name); - str += 's'; - return str; + if (name.empty() || name.back() == 's') { + return name; } - itemgroup_t group = ITEM_GROUP_NONE; - ItemTypes_t type = ITEM_TYPE_NONE; - uint16_t id = 0; - uint16_t clientId = 0; - bool stackable = false; - bool isAnimation = false; - - std::string name; - std::string article; - std::string pluralName; - std::string description; - std::string runeSpellName; - std::string vocationString; - - std::unique_ptr abilities; - std::unique_ptr conditionDamage; - - uint32_t weight = 0; - uint32_t levelDoor = 0; - uint32_t decayTime = 0; - uint32_t wieldInfo = 0; - uint32_t minReqLevel = 0; - uint32_t minReqMagicLevel = 0; - uint32_t charges = 0; - int32_t maxHitChance = -1; - int32_t decayTo = -1; - int32_t attack = 0; - int32_t defense = 0; - int32_t extraDefense = 0; - int32_t armor = 0; - uint16_t rotateTo = 0; - int32_t runeMagLevel = 0; - int32_t runeLevel = 0; - - CombatType_t combatType = COMBAT_NONE; - - uint16_t transformToOnUse[2] = {0, 0}; - uint16_t transformToFree = 0; - uint16_t destroyTo = 0; - uint16_t maxTextLen = 0; - uint16_t writeOnceItemId = 0; - uint16_t transformEquipTo = 0; - uint16_t transformDeEquipTo = 0; - uint16_t maxItems = 8; - uint16_t slotPosition = SLOTP_HAND; - uint16_t speed = 0; - uint16_t wareId = 0; - - MagicEffectClasses magicEffect = CONST_ME_NONE; - Direction bedPartnerDir = DIRECTION_NONE; - WeaponType_t weaponType = WEAPON_NONE; - Ammo_t ammoType = AMMO_NONE; - ShootType_t shootType = CONST_ANI_NONE; - RaceType_t corpseType = RACE_NONE; - FluidTypes_t fluidSource = FLUID_NONE; - - uint8_t floorChange = 0; - uint8_t alwaysOnTopOrder = 0; - uint8_t lightLevel = 0; - uint8_t lightColor = 0; - uint8_t shootRange = 1; - int8_t hitChance = 0; - - bool storeItem = false; - bool forceUse = false; - bool forceSerialize = false; - bool hasHeight = false; - bool walkStack = true; - bool blockSolid = false; - bool blockPickupable = false; - bool blockProjectile = false; - bool blockPathFind = false; - bool allowPickupable = false; - bool showDuration = false; - bool showCharges = false; - bool showAttributes = false; - bool replaceable = true; - bool pickupable = false; - bool rotatable = false; - bool useable = false; - bool moveable = false; - bool alwaysOnTop = false; - bool canReadText = false; - bool canWriteText = false; - bool isVertical = false; - bool isHorizontal = false; - bool isHangable = false; - bool allowDistRead = false; - bool lookThrough = false; - bool stopTime = false; - bool showCount = true; + std::string str; + str.reserve(name.length() + 1); + str.assign(name); + str += 's'; + return str; + } + + itemgroup_t group = ITEM_GROUP_NONE; + ItemTypes_t type = ITEM_TYPE_NONE; + uint16_t id = 0; + uint16_t clientId = 0; + bool stackable = false; + bool isAnimation = false; + + std::string name; + std::string article; + std::string pluralName; + std::string description; + std::string runeSpellName; + std::string vocationString; + + std::unique_ptr abilities; + std::unique_ptr conditionDamage; + + uint32_t attackSpeed = 0; + uint32_t weight = 0; + uint32_t levelDoor = 0; + uint32_t decayTime = 0; + uint32_t wieldInfo = 0; + uint32_t minReqLevel = 0; + uint32_t minReqMagicLevel = 0; + uint32_t charges = 0; + int32_t maxHitChance = -1; + int32_t decayTo = -1; + int32_t attack = 0; + int32_t defense = 0; + int32_t extraDefense = 0; + int32_t armor = 0; + uint16_t rotateTo = 0; + int32_t runeMagLevel = 0; + int32_t runeLevel = 0; + uint64_t worth = 0; + + CombatType_t combatType = COMBAT_NONE; + + uint16_t transformToOnUse[2] = {0, 0}; + uint16_t transformToFree = 0; + uint16_t destroyTo = 0; + uint16_t maxTextLen = 0; + uint16_t writeOnceItemId = 0; + uint16_t transformEquipTo = 0; + uint16_t transformDeEquipTo = 0; + uint16_t maxItems = 8; + uint16_t slotPosition = SLOTP_HAND; + uint16_t speed = 0; + uint16_t wareId = 0; + + MagicEffectClasses magicEffect = CONST_ME_NONE; + Direction bedPartnerDir = DIRECTION_NONE; + WeaponType_t weaponType = WEAPON_NONE; + Ammo_t ammoType = AMMO_NONE; + ShootType_t shootType = CONST_ANI_NONE; + RaceType_t corpseType = RACE_NONE; + FluidTypes_t fluidSource = FLUID_NONE; + + uint8_t floorChange = 0; + uint8_t alwaysOnTopOrder = 0; + uint8_t lightLevel = 0; + uint8_t lightColor = 0; + uint8_t shootRange = 1; + uint8_t classification = 0; + int8_t hitChance = 0; + + bool storeItem = false; + bool forceUse = false; + bool forceSerialize = false; + bool hasHeight = false; + bool walkStack = true; + bool blockSolid = false; + bool blockPickupable = false; + bool blockProjectile = false; + bool blockPathFind = false; + bool allowPickupable = false; + bool showDuration = false; + bool showCharges = false; + bool showAttributes = false; + bool replaceable = true; + bool pickupable = false; + bool rotatable = false; + bool useable = false; + bool moveable = false; + bool alwaysOnTop = false; + bool canReadText = false; + bool canWriteText = false; + bool isVertical = false; + bool isHorizontal = false; + bool isHangable = false; + bool allowDistRead = false; + bool lookThrough = false; + bool stopTime = false; + bool showCount = true; + bool supply = false; }; class Items { - public: - using NameMap = std::unordered_multimap; - using InventoryVector = std::vector; +public: + using NameMap = std::unordered_map; + using InventoryVector = std::vector; - Items(); + using CurrencyMap = std::map>; - // non-copyable - Items(const Items&) = delete; - Items& operator=(const Items&) = delete; + Items(); - bool reload(); - void clear(); + // non-copyable + Items(const Items&) = delete; + Items& operator=(const Items&) = delete; - bool loadFromOtb(const std::string& file); + bool reload(); + void clear(); - const ItemType& operator[](size_t id) const { - return getItemType(id); - } - const ItemType& getItemType(size_t id) const; - ItemType& getItemType(size_t id); - const ItemType& getItemIdByClientId(uint16_t spriteId) const; + bool loadFromOtb(const std::string& file); - uint16_t getItemIdByName(const std::string& name); + const ItemType& operator[](size_t id) const { return getItemType(id); } + const ItemType& getItemType(size_t id) const; + ItemType& getItemType(size_t id); + const ItemType& getItemIdByClientId(uint16_t spriteId) const; - uint32_t majorVersion = 0; - uint32_t minorVersion = 0; - uint32_t buildNumber = 0; + uint16_t getItemIdByName(const std::string& name); - bool loadFromXml(); - void parseItemNode(const pugi::xml_node& itemNode, uint16_t id); + uint32_t majorVersion = 0; + uint32_t minorVersion = 0; + uint32_t buildNumber = 0; - void buildInventoryList(); - const InventoryVector& getInventory() const { - return inventory; + bool loadFromXml(); + void parseItemNode(const pugi::xml_node& itemNode, uint16_t id); + + size_t size() const { return items.size(); } + + NameMap nameToItems; + CurrencyMap currencyItems; + +private: + std::vector items; + InventoryVector inventory; + class ClientIdToServerIdMap + { + public: + ClientIdToServerIdMap() { vec.reserve(45000); } + + void emplace(uint16_t clientId, uint16_t serverId) + { + if (clientId >= vec.size()) { + vec.resize(clientId + 1, 0); + } + if (vec[clientId] == 0) { + vec[clientId] = serverId; + } } - size_t size() const { - return items.size(); + uint16_t getServerId(uint16_t clientId) const + { + uint16_t serverId = 0; + if (clientId < vec.size()) { + serverId = vec[clientId]; + } + return serverId; } - NameMap nameToItems; + void clear() { vec.clear(); } private: - std::vector items; - InventoryVector inventory; - class ClientIdToServerIdMap - { - public: - ClientIdToServerIdMap() { - vec.reserve(30000); - } - - void emplace(uint16_t clientId, uint16_t serverId) { - if (clientId >= vec.size()) { - vec.resize(clientId + 1, 0); - } - if (vec[clientId] == 0) { - vec[clientId] = serverId; - } - } - - uint16_t getServerId(uint16_t clientId) const { - uint16_t serverId = 0; - if (clientId < vec.size()) { - serverId = vec[clientId]; - } - return serverId; - } - - void clear() { - vec.clear(); - } - private: - std::vector vec; - } clientIdToServerIdMap; + std::vector vec; + } clientIdToServerIdMap; }; -#endif + +#endif // FS_ITEMS_H diff --git a/src/lockfree.h b/src/lockfree.h index b555139d05..e5d1e6e709 100644 --- a/src/lockfree.h +++ b/src/lockfree.h @@ -1,42 +1,26 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008 -#define FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008 +#ifndef FS_LOCKFREE_H +#define FS_LOCKFREE_H -#if _MSC_FULL_VER >= 190023918 // Workaround for VS2015 Update 2. Boost.Lockfree is a header-only library, so this should be safe to do. +#if _MSC_FULL_VER >= 190023918 // Workaround for VS2015 Update 2. Boost.Lockfree is a header-only library, so this + // should be safe to do. #define _ENABLE_ATOMIC_ALIGNMENT_FIX #endif -#include - /* * we use this to avoid instantiating multiple free lists for objects of the * same size and it can be replaced by a variable template in C++14 * - * template - * boost::lockfree::stack lockfreeFreeList; + * template + * boost::lockfree::stack + * lockfreeFreeList; */ -template +template struct LockfreeFreeList { - using FreeList = boost::lockfree::stack>; + using FreeList = boost::lockfree::stack>; static FreeList& get() { static FreeList freeList; @@ -44,34 +28,43 @@ struct LockfreeFreeList } }; -template -class LockfreePoolingAllocator : public std::allocator +template +class LockfreePoolingAllocator { - public: - LockfreePoolingAllocator() = default; +public: + template + struct rebind + { + using other = LockfreePoolingAllocator; + }; - template ::value>::type> - explicit constexpr LockfreePoolingAllocator(const U&) {} - using value_type = T; + LockfreePoolingAllocator() = default; - T* allocate(size_t) const { - auto& inst = LockfreeFreeList::get(); - void* p; // NOTE: p doesn't have to be initialized - if (!inst.pop(p)) { - //Acquire memory without calling the constructor of T - p = operator new (sizeof(T)); - } - return static_cast(p); + template + explicit constexpr LockfreePoolingAllocator(const LockfreePoolingAllocator&) + {} + using value_type = T; + + T* allocate(size_t) const + { + auto& inst = LockfreeFreeList::get(); + void* p; // NOTE: p doesn't have to be initialized + if (!inst.pop(p)) { + // Acquire memory without calling the constructor of T + p = operator new(sizeof(T)); } + return static_cast(p); + } - void deallocate(T* p, size_t) const { - auto& inst = LockfreeFreeList::get(); - if (!inst.bounded_push(p)) { - //Release memory without calling the destructor of T - //(it has already been called at this point) - operator delete(p); - } + void deallocate(T* p, size_t) const + { + auto& inst = LockfreeFreeList::get(); + if (!inst.bounded_push(p)) { + // Release memory without calling the destructor of T + //(it has already been called at this point) + operator delete(p); } + } }; -#endif +#endif // FS_LOCKFREE_H diff --git a/src/luascript.cpp b/src/luascript.cpp index 98be8132db..7d0859a4fd 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -1,47 +1,43 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include -#include - #include "luascript.h" + +#include "bed.h" #include "chat.h" -#include "player.h" -#include "game.h" -#include "protocolstatus.h" -#include "spells.h" -#include "iologindata.h" #include "configmanager.h" -#include "teleport.h" #include "databasemanager.h" -#include "bed.h" -#include "monster.h" -#include "scheduler.h" #include "databasetasks.h" +#include "depotchest.h" #include "events.h" -#include "movement.h" +#include "game.h" #include "globalevent.h" +#include "housetile.h" +#include "inbox.h" +#include "iologindata.h" +#include "iomapserialize.h" +#include "iomarket.h" +#include "luavariant.h" +#include "monster.h" +#include "movement.h" +#include "npc.h" +#include "outfit.h" +#include "party.h" +#include "player.h" +#include "podium.h" +#include "protocolstatus.h" +#include "scheduler.h" #include "script.h" +#include "spectators.h" +#include "spells.h" +#include "storeinbox.h" +#include "teleport.h" #include "weapons.h" +#include + extern Chat* g_chat; extern Game g_game; extern Monsters g_monsters; @@ -64,15 +60,9 @@ std::multimap ScriptEnvironment::tempItems; LuaEnvironment g_luaEnvironment; -ScriptEnvironment::ScriptEnvironment() -{ - resetEnv(); -} +ScriptEnvironment::ScriptEnvironment() { resetEnv(); } -ScriptEnvironment::~ScriptEnvironment() -{ - resetEnv(); -} +ScriptEnvironment::~ScriptEnvironment() { resetEnv(); } void ScriptEnvironment::resetEnv() { @@ -87,7 +77,7 @@ void ScriptEnvironment::resetEnv() auto it = pair.first; while (it != pair.second) { Item* item = it->second; - if (item->getParent() == VirtualCylinder::virtualCylinder) { + if (item && item->getParent() == VirtualCylinder::virtualCylinder) { g_game.ReleaseItem(item); } it = tempItems.erase(it); @@ -97,7 +87,7 @@ void ScriptEnvironment::resetEnv() bool ScriptEnvironment::setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface) { if (this->callbackId != 0) { - //nested callbacks are not allowed + // nested callbacks are not allowed if (interface) { reportErrorFunc(interface->getLuaState(), "Nested callbacks!"); } @@ -109,7 +99,8 @@ bool ScriptEnvironment::setCallbackId(int32_t callbackId, LuaScriptInterface* sc return true; } -void ScriptEnvironment::getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const +void ScriptEnvironment::getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, + bool& timerEvent) const { scriptId = this->scriptId; scriptInterface = interface; @@ -153,7 +144,7 @@ void ScriptEnvironment::insertItem(uint32_t uid, Item* item) Thing* ScriptEnvironment::getThingByUID(uint32_t uid) { - if (uid >= 0x10000000) { + if (uid >= CREATURE_ID_MIN) { return g_game.getCreatureByID(uid); } @@ -206,10 +197,7 @@ void ScriptEnvironment::removeItemByUID(uint32_t uid) } } -void ScriptEnvironment::addTempItem(Item* item) -{ - tempItems.emplace(this, item); -} +void ScriptEnvironment::addTempItem(Item* item) { tempItems.emplace(this, item); } void ScriptEnvironment::removeTempItem(Item* item) { @@ -250,20 +238,34 @@ DBResult_ptr ScriptEnvironment::getResultByID(uint32_t id) std::string LuaScriptInterface::getErrorDesc(ErrorCode_t code) { switch (code) { - case LUA_ERROR_PLAYER_NOT_FOUND: return "Player not found"; - case LUA_ERROR_CREATURE_NOT_FOUND: return "Creature not found"; - case LUA_ERROR_ITEM_NOT_FOUND: return "Item not found"; - case LUA_ERROR_THING_NOT_FOUND: return "Thing not found"; - case LUA_ERROR_TILE_NOT_FOUND: return "Tile not found"; - case LUA_ERROR_HOUSE_NOT_FOUND: return "House not found"; - case LUA_ERROR_COMBAT_NOT_FOUND: return "Combat not found"; - case LUA_ERROR_CONDITION_NOT_FOUND: return "Condition not found"; - case LUA_ERROR_AREA_NOT_FOUND: return "Area not found"; - case LUA_ERROR_CONTAINER_NOT_FOUND: return "Container not found"; - case LUA_ERROR_VARIANT_NOT_FOUND: return "Variant not found"; - case LUA_ERROR_VARIANT_UNKNOWN: return "Unknown variant type"; - case LUA_ERROR_SPELL_NOT_FOUND: return "Spell not found"; - default: return "Bad error code"; + case LUA_ERROR_PLAYER_NOT_FOUND: + return "Player not found"; + case LUA_ERROR_CREATURE_NOT_FOUND: + return "Creature not found"; + case LUA_ERROR_ITEM_NOT_FOUND: + return "Item not found"; + case LUA_ERROR_THING_NOT_FOUND: + return "Thing not found"; + case LUA_ERROR_TILE_NOT_FOUND: + return "Tile not found"; + case LUA_ERROR_HOUSE_NOT_FOUND: + return "House not found"; + case LUA_ERROR_COMBAT_NOT_FOUND: + return "Combat not found"; + case LUA_ERROR_CONDITION_NOT_FOUND: + return "Condition not found"; + case LUA_ERROR_AREA_NOT_FOUND: + return "Area not found"; + case LUA_ERROR_CONTAINER_NOT_FOUND: + return "Container not found"; + case LUA_ERROR_VARIANT_NOT_FOUND: + return "Variant not found"; + case LUA_ERROR_VARIANT_UNKNOWN: + return "Unknown variant type"; + case LUA_ERROR_SPELL_NOT_FOUND: + return "Spell not found"; + default: + return "Bad error code"; } } @@ -277,10 +279,7 @@ LuaScriptInterface::LuaScriptInterface(std::string interfaceName) : interfaceNam } } -LuaScriptInterface::~LuaScriptInterface() -{ - closeState(); -} +LuaScriptInterface::~LuaScriptInterface() { closeState(); } bool LuaScriptInterface::reInitState() { @@ -305,21 +304,23 @@ int LuaScriptInterface::protectedCall(lua_State* L, int nargs, int nresults) int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = nullptr*/) { - //loads file as a chunk at stack top + // loads file as a chunk at stack top int ret = luaL_loadfile(luaState, file.c_str()); if (ret != 0) { lastLuaError = popString(luaState); return -1; } - //check that it is loaded as a function + // check that it is loaded as a function if (!isFunction(luaState, -1)) { + lua_pop(luaState, 1); return -1; } loadingFile = file; if (!reserveScriptEnv()) { + lua_pop(luaState, 1); return -1; } @@ -327,7 +328,7 @@ int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = null env->setScriptId(EVENT_ID_LOADING, this); env->setNpc(npc); - //execute it + // execute it ret = protectedCall(luaState, 0, 0); if (ret != 0) { reportError(nullptr, popString(luaState)); @@ -341,26 +342,26 @@ int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = null int32_t LuaScriptInterface::getEvent(const std::string& eventName) { - //get our events table + // get our events table lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); if (!isTable(luaState, -1)) { lua_pop(luaState, 1); return -1; } - //get current event function pointer + // get current event function pointer lua_getglobal(luaState, eventName.c_str()); if (!isFunction(luaState, -1)) { lua_pop(luaState, 2); return -1; } - //save in our events table + // save in our events table lua_pushvalue(luaState, -1); lua_rawseti(luaState, -3, runningEventId); lua_pop(luaState, 2); - //reset global value of this event + // reset global value of this event lua_pushnil(luaState); lua_setglobal(luaState, eventName.c_str()); @@ -370,19 +371,19 @@ int32_t LuaScriptInterface::getEvent(const std::string& eventName) int32_t LuaScriptInterface::getEvent() { - //check if function is on the stack + // check if function is on the stack if (!isFunction(luaState, -1)) { return -1; } - //get our events table + // get our events table lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); if (!isTable(luaState, -1)) { lua_pop(luaState, 1); return -1; } - //save in our events table + // save in our events table lua_pushvalue(luaState, -2); lua_rawseti(luaState, -2, runningEventId); lua_pop(luaState, 2); @@ -393,14 +394,14 @@ int32_t LuaScriptInterface::getEvent() int32_t LuaScriptInterface::getMetaEvent(const std::string& globalName, const std::string& eventName) { - //get our events table + // get our events table lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); if (!isTable(luaState, -1)) { lua_pop(luaState, 1); return -1; } - //get current event function pointer + // get current event function pointer lua_getglobal(luaState, globalName.c_str()); lua_getfield(luaState, -1, eventName.c_str()); if (!isFunction(luaState, -1)) { @@ -408,12 +409,12 @@ int32_t LuaScriptInterface::getMetaEvent(const std::string& globalName, const st return -1; } - //save in our events table + // save in our events table lua_pushvalue(luaState, -1); lua_rawseti(luaState, -4, runningEventId); lua_pop(luaState, 1); - //reset global value of this event + // reset global value of this event lua_pushnil(luaState); lua_setfield(luaState, -2, eventName.c_str()); lua_pop(luaState, 2); @@ -438,25 +439,12 @@ const std::string& LuaScriptInterface::getFileById(int32_t scriptId) std::string LuaScriptInterface::getStackTrace(lua_State* L, const std::string& error_desc) { - lua_getglobal(L, "debug"); - if (!isTable(L, -1)) { - lua_pop(L, 1); - return error_desc; - } - - lua_getfield(L, -1, "traceback"); - if (!isFunction(L, -1)) { - lua_pop(L, 2); - return error_desc; - } - - lua_replace(L, -2); - pushString(L, error_desc); - lua_call(L, 1, 1); + luaL_traceback(L, L, error_desc.c_str(), 1); return popString(L); } -void LuaScriptInterface::reportError(const char* function, const std::string& error_desc, lua_State* L /*= nullptr*/, bool stack_trace /*= false*/) +void LuaScriptInterface::reportError(const char* function, const std::string& error_desc, lua_State* L /*= nullptr*/, + bool stack_trace /*= false*/) { int32_t scriptId; int32_t callbackId; @@ -575,17 +563,20 @@ void LuaScriptInterface::callVoidFunction(int params) void LuaScriptInterface::pushVariant(lua_State* L, const LuaVariant& var) { lua_createtable(L, 0, 2); - setField(L, "type", var.type); - switch (var.type) { + setField(L, "type", var.type()); + switch (var.type()) { case VARIANT_NUMBER: - setField(L, "number", var.number); + setField(L, "number", var.getNumber()); break; case VARIANT_STRING: - setField(L, "string", var.text); + setField(L, "string", var.getString()); break; case VARIANT_TARGETPOSITION: + pushPosition(L, var.getTargetPosition()); + lua_setfield(L, -2, "pos"); + break; case VARIANT_POSITION: { - pushPosition(L, var.pos); + pushPosition(L, var.getPosition()); lua_setfield(L, -2, "pos"); break; } @@ -640,10 +631,7 @@ void LuaScriptInterface::pushString(lua_State* L, const std::string& value) lua_pushlstring(L, value.c_str(), value.length()); } -void LuaScriptInterface::pushCallback(lua_State* L, int32_t callback) -{ - lua_rawgeti(L, LUA_REGISTRYINDEX, callback); -} +void LuaScriptInterface::pushCallback(lua_State* L, int32_t callback) { lua_rawgeti(L, LUA_REGISTRYINDEX, callback); } std::string LuaScriptInterface::popString(lua_State* L) { @@ -656,10 +644,7 @@ std::string LuaScriptInterface::popString(lua_State* L) return str; } -int32_t LuaScriptInterface::popCallback(lua_State* L) -{ - return luaL_ref(L, LUA_REGISTRYINDEX); -} +int32_t LuaScriptInterface::popCallback(lua_State* L) { return luaL_ref(L, LUA_REGISTRYINDEX); } // Metatables void LuaScriptInterface::setMetatable(lua_State* L, int32_t index, const std::string& name) @@ -709,6 +694,8 @@ void LuaScriptInterface::setItemMetatable(lua_State* L, int32_t index, const Ite luaL_getmetatable(L, "Container"); } else if (item->getTeleport()) { luaL_getmetatable(L, "Teleport"); + } else if (item->getPodium()) { + luaL_getmetatable(L, "Podium"); } else { luaL_getmetatable(L, "Item"); } @@ -770,18 +757,23 @@ Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg) Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) { Outfit_t outfit; - outfit.lookMount = getField(L, arg, "lookMount"); - outfit.lookAddons = getField(L, arg, "lookAddons"); - outfit.lookFeet = getField(L, arg, "lookFeet"); - outfit.lookLegs = getField(L, arg, "lookLegs"); - outfit.lookBody = getField(L, arg, "lookBody"); + outfit.lookType = getField(L, arg, "lookType"); + outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); + outfit.lookHead = getField(L, arg, "lookHead"); + outfit.lookBody = getField(L, arg, "lookBody"); + outfit.lookLegs = getField(L, arg, "lookLegs"); + outfit.lookFeet = getField(L, arg, "lookFeet"); + outfit.lookAddons = getField(L, arg, "lookAddons"); - outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); - outfit.lookType = getField(L, arg, "lookType"); + outfit.lookMount = getField(L, arg, "lookMount"); + outfit.lookMountHead = getField(L, arg, "lookMountHead"); + outfit.lookMountBody = getField(L, arg, "lookMountBody"); + outfit.lookMountLegs = getField(L, arg, "lookMountLegs"); + outfit.lookMountFeet = getField(L, arg, "lookMountFeet"); - lua_pop(L, 8); + lua_pop(L, 12); return outfit; } @@ -795,32 +787,37 @@ Outfit LuaScriptInterface::getOutfitClass(lua_State* L, int32_t arg) return Outfit(name, lookType, premium, unlocked); } -LuaVariant LuaScriptInterface::getVariant(lua_State* L, int32_t arg) +static LuaVariant getVariant(lua_State* L, int32_t arg) { LuaVariant var; - switch (var.type = getField(L, arg, "type")) { + switch (LuaScriptInterface::getField(L, arg, "type")) { case VARIANT_NUMBER: { - var.number = getField(L, arg, "number"); + var.setNumber(LuaScriptInterface::getField(L, arg, "number")); lua_pop(L, 2); break; } case VARIANT_STRING: { - var.text = getFieldString(L, arg, "string"); + var.setString(LuaScriptInterface::getFieldString(L, arg, "string")); lua_pop(L, 2); break; } case VARIANT_POSITION: + lua_getfield(L, arg, "pos"); + var.setPosition(LuaScriptInterface::getPosition(L, lua_gettop(L))); + lua_pop(L, 2); + break; + case VARIANT_TARGETPOSITION: { lua_getfield(L, arg, "pos"); - var.pos = getPosition(L, lua_gettop(L)); + var.setTargetPosition(LuaScriptInterface::getPosition(L, lua_gettop(L))); lua_pop(L, 2); break; } default: { - var.type = VARIANT_NONE; + var = {}; lua_pop(L, 1); break; } @@ -835,12 +832,20 @@ InstantSpell* LuaScriptInterface::getInstantSpell(lua_State* L, int32_t arg) return spell; } +Reflect LuaScriptInterface::getReflect(lua_State* L, int32_t arg) +{ + uint16_t percent = getField(L, arg, "percent"); + uint16_t chance = getField(L, arg, "chance"); + lua_pop(L, 2); + return Reflect(percent, chance); +} + Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) { Thing* thing; if (lua_getmetatable(L, arg) != 0) { lua_rawgeti(L, -1, 't'); - switch(getNumber(L, -1)) { + switch (getNumber(L, -1)) { case LuaData_Item: thing = getUserdata(L, arg); break; @@ -850,6 +855,9 @@ Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) case LuaData_Teleport: thing = getUserdata(L, arg); break; + case LuaData_Podium: + thing = getUserdata(L, arg); + break; case LuaData_Player: thing = getUserdata(L, arg); break; @@ -906,10 +914,7 @@ LuaDataType LuaScriptInterface::getUserdataType(lua_State* L, int32_t arg) } // Push -void LuaScriptInterface::pushBoolean(lua_State* L, bool value) -{ - lua_pushboolean(L, value ? 1 : 0); -} +void LuaScriptInterface::pushBoolean(lua_State* L, bool value) { lua_pushboolean(L, value ? 1 : 0); } void LuaScriptInterface::pushCombatDamage(lua_State* L, const CombatDamage& damage) { @@ -922,7 +927,7 @@ void LuaScriptInterface::pushCombatDamage(lua_State* L, const CombatDamage& dama void LuaScriptInterface::pushInstantSpell(lua_State* L, const InstantSpell& spell) { - lua_createtable(L, 0, 6); + lua_createtable(L, 0, 7); setField(L, "name", spell.getName()); setField(L, "words", spell.getWords()); @@ -930,11 +935,12 @@ void LuaScriptInterface::pushInstantSpell(lua_State* L, const InstantSpell& spel setField(L, "mlevel", spell.getMagicLevel()); setField(L, "mana", spell.getMana()); setField(L, "manapercent", spell.getManaPercent()); + setField(L, "params", spell.getHasParam()); setMetatable(L, -1, "Spell"); } -void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, int32_t stackpos/* = 0*/) +void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, int32_t stackpos /* = 0*/) { lua_createtable(L, 0, 4); @@ -948,7 +954,7 @@ void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, in void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) { - lua_createtable(L, 0, 8); + lua_createtable(L, 0, 12); setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); setField(L, "lookHead", outfit.lookHead); @@ -957,6 +963,10 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookFeet", outfit.lookFeet); setField(L, "lookAddons", outfit.lookAddons); setField(L, "lookMount", outfit.lookMount); + setField(L, "lookMountHead", outfit.lookMountHead); + setField(L, "lookMountBody", outfit.lookMountBody); + setField(L, "lookMountLegs", outfit.lookMountLegs); + setField(L, "lookMountFeet", outfit.lookMountFeet); } void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit* outfit) @@ -969,6 +979,16 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit* outfit) setMetatable(L, -1, "Outfit"); } +void LuaScriptInterface::pushMount(lua_State* L, const Mount* mount) +{ + lua_createtable(L, 0, 5); + setField(L, "name", mount->name); + setField(L, "speed", mount->speed); + setField(L, "clientId", mount->clientId); + setField(L, "id", mount->id); + setField(L, "premium", mount->premium); +} + void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lootList) { lua_createtable(L, lootList.size(), 0); @@ -991,914 +1011,1065 @@ void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lo } } -#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); } -#define registerEnumIn(tableName, value) { std::string enumName = #value; registerVariable(tableName, enumName.substr(enumName.find_last_of(':') + 1), value); } +void LuaScriptInterface::pushReflect(lua_State* L, const Reflect& reflect) +{ + lua_createtable(L, 0, 2); + setField(L, "percent", reflect.percent); + setField(L, "chance", reflect.chance); +} + +#define registerEnum(value) \ + { \ + std::string enumName = #value; \ + registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); \ + } +#define registerEnumIn(tableName, value) \ + { \ + std::string enumName = #value; \ + registerVariable(tableName, enumName.substr(enumName.find_last_of(':') + 1), value); \ + } void LuaScriptInterface::registerFunctions() { - //doPlayerAddItem(uid, itemid, count/subtype) - //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) - //Returns uid of the created item + // doPlayerAddItem(uid, itemid, count/subtype) + // doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) Returns uid of the created item lua_register(luaState, "doPlayerAddItem", LuaScriptInterface::luaDoPlayerAddItem); - //isValidUID(uid) + // isValidUID(uid) lua_register(luaState, "isValidUID", LuaScriptInterface::luaIsValidUID); - //isDepot(uid) + // isDepot(uid) lua_register(luaState, "isDepot", LuaScriptInterface::luaIsDepot); - //isMovable(uid) + // isMovable(uid) lua_register(luaState, "isMovable", LuaScriptInterface::luaIsMoveable); - //doAddContainerItem(uid, itemid, count/subtype) + // doAddContainerItem(uid, itemid, count/subtype) lua_register(luaState, "doAddContainerItem", LuaScriptInterface::luaDoAddContainerItem); - //getDepotId(uid) + // getDepotId(uid) lua_register(luaState, "getDepotId", LuaScriptInterface::luaGetDepotId); - //getWorldTime() + // getWorldTime() lua_register(luaState, "getWorldTime", LuaScriptInterface::luaGetWorldTime); - //getWorldLight() + // getWorldLight() lua_register(luaState, "getWorldLight", LuaScriptInterface::luaGetWorldLight); - //setWorldLight(level, color) + // setWorldLight(level, color) lua_register(luaState, "setWorldLight", LuaScriptInterface::luaSetWorldLight); - //getWorldUpTime() + // getWorldUpTime() lua_register(luaState, "getWorldUpTime", LuaScriptInterface::luaGetWorldUpTime); // getSubTypeName(subType) lua_register(luaState, "getSubTypeName", LuaScriptInterface::luaGetSubTypeName); - //createCombatArea( {area}, {extArea} ) + // createCombatArea({area}, {extArea}) lua_register(luaState, "createCombatArea", LuaScriptInterface::luaCreateCombatArea); - //doAreaCombat(cid, type, pos, area, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = false[, ignoreResistances = false]]]]) + // doAreaCombat(cid, type, pos, area, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = + // false[, ignoreResistances = false]]]]) lua_register(luaState, "doAreaCombat", LuaScriptInterface::luaDoAreaCombat); - //doTargetCombat(cid, target, type, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = false[, ignoreResistances = false]]]]) + // doTargetCombat(cid, target, type, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = + // false[, ignoreResistances = false]]]]) lua_register(luaState, "doTargetCombat", LuaScriptInterface::luaDoTargetCombat); - //doChallengeCreature(cid, target) + // doChallengeCreature(cid, target[, force = false]) lua_register(luaState, "doChallengeCreature", LuaScriptInterface::luaDoChallengeCreature); - //addEvent(callback, delay, ...) + // addEvent(callback, delay, ...) lua_register(luaState, "addEvent", LuaScriptInterface::luaAddEvent); - //stopEvent(eventid) + // stopEvent(eventid) lua_register(luaState, "stopEvent", LuaScriptInterface::luaStopEvent); - //saveServer() + // saveServer() lua_register(luaState, "saveServer", LuaScriptInterface::luaSaveServer); - //cleanMap() + // cleanMap() lua_register(luaState, "cleanMap", LuaScriptInterface::luaCleanMap); - //debugPrint(text) + // debugPrint(text) lua_register(luaState, "debugPrint", LuaScriptInterface::luaDebugPrint); - //isInWar(cid, target) + // isInWar(cid, target) lua_register(luaState, "isInWar", LuaScriptInterface::luaIsInWar); - //getWaypointPosition(name) + // getWaypointPosition(name) lua_register(luaState, "getWaypointPositionByName", LuaScriptInterface::luaGetWaypointPositionByName); - //sendChannelMessage(channelId, type, message) + // sendChannelMessage(channelId, type, message) lua_register(luaState, "sendChannelMessage", LuaScriptInterface::luaSendChannelMessage); - //sendGuildChannelMessage(guildId, type, message) + // sendGuildChannelMessage(guildId, type, message) lua_register(luaState, "sendGuildChannelMessage", LuaScriptInterface::luaSendGuildChannelMessage); - //isScriptsInterface() + // isScriptsInterface() lua_register(luaState, "isScriptsInterface", LuaScriptInterface::luaIsScriptsInterface); #ifndef LUAJIT_VERSION - //bit operations for Lua, based on bitlib project release 24 - //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift + // bit operations for Lua, based on bitlib project release 24 + // bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift luaL_register(luaState, "bit", LuaScriptInterface::luaBitReg); + lua_pop(luaState, 1); #endif - //configManager table + // configManager table luaL_register(luaState, "configManager", LuaScriptInterface::luaConfigManagerTable); + lua_pop(luaState, 1); - //db table + // db table luaL_register(luaState, "db", LuaScriptInterface::luaDatabaseTable); + lua_pop(luaState, 1); - //result table + // result table luaL_register(luaState, "result", LuaScriptInterface::luaResultTable); + lua_pop(luaState, 1); /* New functions */ - //registerClass(className, baseClass, newFunction) - //registerTable(tableName) - //registerMethod(className, functionName, function) - //registerMetaMethod(className, functionName, function) - //registerGlobalMethod(functionName, function) - //registerVariable(tableName, name, value) - //registerGlobalVariable(name, value) - //registerEnum(value) - //registerEnumIn(tableName, value) + // registerClass(className, baseClass, newFunction) + // registerTable(tableName) + // registerMethod(className, functionName, function) + // registerMetaMethod(className, functionName, function) + // registerGlobalMethod(functionName, function) + // registerVariable(tableName, name, value) + // registerGlobalVariable(name, value) + // registerEnum(value) + // registerEnumIn(tableName, value) // Enums - registerEnum(ACCOUNT_TYPE_NORMAL) - registerEnum(ACCOUNT_TYPE_TUTOR) - registerEnum(ACCOUNT_TYPE_SENIORTUTOR) - registerEnum(ACCOUNT_TYPE_GAMEMASTER) - registerEnum(ACCOUNT_TYPE_GOD) - - registerEnum(BUG_CATEGORY_MAP) - registerEnum(BUG_CATEGORY_TYPO) - registerEnum(BUG_CATEGORY_TECHNICAL) - registerEnum(BUG_CATEGORY_OTHER) - - registerEnum(CALLBACK_PARAM_LEVELMAGICVALUE) - registerEnum(CALLBACK_PARAM_SKILLVALUE) - registerEnum(CALLBACK_PARAM_TARGETTILE) - registerEnum(CALLBACK_PARAM_TARGETCREATURE) - - registerEnum(COMBAT_FORMULA_UNDEFINED) - registerEnum(COMBAT_FORMULA_LEVELMAGIC) - registerEnum(COMBAT_FORMULA_SKILL) - registerEnum(COMBAT_FORMULA_DAMAGE) - - registerEnum(DIRECTION_NORTH) - registerEnum(DIRECTION_EAST) - registerEnum(DIRECTION_SOUTH) - registerEnum(DIRECTION_WEST) - registerEnum(DIRECTION_SOUTHWEST) - registerEnum(DIRECTION_SOUTHEAST) - registerEnum(DIRECTION_NORTHWEST) - registerEnum(DIRECTION_NORTHEAST) - - registerEnum(COMBAT_NONE) - registerEnum(COMBAT_PHYSICALDAMAGE) - registerEnum(COMBAT_ENERGYDAMAGE) - registerEnum(COMBAT_EARTHDAMAGE) - registerEnum(COMBAT_FIREDAMAGE) - registerEnum(COMBAT_UNDEFINEDDAMAGE) - registerEnum(COMBAT_LIFEDRAIN) - registerEnum(COMBAT_MANADRAIN) - registerEnum(COMBAT_HEALING) - registerEnum(COMBAT_DROWNDAMAGE) - registerEnum(COMBAT_ICEDAMAGE) - registerEnum(COMBAT_HOLYDAMAGE) - registerEnum(COMBAT_DEATHDAMAGE) - - registerEnum(COMBAT_PARAM_TYPE) - registerEnum(COMBAT_PARAM_EFFECT) - registerEnum(COMBAT_PARAM_DISTANCEEFFECT) - registerEnum(COMBAT_PARAM_BLOCKSHIELD) - registerEnum(COMBAT_PARAM_BLOCKARMOR) - registerEnum(COMBAT_PARAM_TARGETCASTERORTOPMOST) - registerEnum(COMBAT_PARAM_CREATEITEM) - registerEnum(COMBAT_PARAM_AGGRESSIVE) - registerEnum(COMBAT_PARAM_DISPEL) - registerEnum(COMBAT_PARAM_USECHARGES) - - registerEnum(CONDITION_NONE) - registerEnum(CONDITION_POISON) - registerEnum(CONDITION_FIRE) - registerEnum(CONDITION_ENERGY) - registerEnum(CONDITION_BLEEDING) - registerEnum(CONDITION_HASTE) - registerEnum(CONDITION_PARALYZE) - registerEnum(CONDITION_OUTFIT) - registerEnum(CONDITION_INVISIBLE) - registerEnum(CONDITION_LIGHT) - registerEnum(CONDITION_MANASHIELD) - registerEnum(CONDITION_INFIGHT) - registerEnum(CONDITION_DRUNK) - registerEnum(CONDITION_EXHAUST_WEAPON) - registerEnum(CONDITION_REGENERATION) - registerEnum(CONDITION_SOUL) - registerEnum(CONDITION_DROWN) - registerEnum(CONDITION_MUTED) - registerEnum(CONDITION_CHANNELMUTEDTICKS) - registerEnum(CONDITION_YELLTICKS) - registerEnum(CONDITION_ATTRIBUTES) - registerEnum(CONDITION_FREEZING) - registerEnum(CONDITION_DAZZLED) - registerEnum(CONDITION_CURSED) - registerEnum(CONDITION_EXHAUST_COMBAT) - registerEnum(CONDITION_EXHAUST_HEAL) - registerEnum(CONDITION_PACIFIED) - registerEnum(CONDITION_SPELLCOOLDOWN) - registerEnum(CONDITION_SPELLGROUPCOOLDOWN) - - registerEnum(CONDITIONID_DEFAULT) - registerEnum(CONDITIONID_COMBAT) - registerEnum(CONDITIONID_HEAD) - registerEnum(CONDITIONID_NECKLACE) - registerEnum(CONDITIONID_BACKPACK) - registerEnum(CONDITIONID_ARMOR) - registerEnum(CONDITIONID_RIGHT) - registerEnum(CONDITIONID_LEFT) - registerEnum(CONDITIONID_LEGS) - registerEnum(CONDITIONID_FEET) - registerEnum(CONDITIONID_RING) - registerEnum(CONDITIONID_AMMO) - - registerEnum(CONDITION_PARAM_OWNER) - registerEnum(CONDITION_PARAM_TICKS) - registerEnum(CONDITION_PARAM_HEALTHGAIN) - registerEnum(CONDITION_PARAM_HEALTHTICKS) - registerEnum(CONDITION_PARAM_MANAGAIN) - registerEnum(CONDITION_PARAM_MANATICKS) - registerEnum(CONDITION_PARAM_DELAYED) - registerEnum(CONDITION_PARAM_SPEED) - registerEnum(CONDITION_PARAM_LIGHT_LEVEL) - registerEnum(CONDITION_PARAM_LIGHT_COLOR) - registerEnum(CONDITION_PARAM_SOULGAIN) - registerEnum(CONDITION_PARAM_SOULTICKS) - registerEnum(CONDITION_PARAM_MINVALUE) - registerEnum(CONDITION_PARAM_MAXVALUE) - registerEnum(CONDITION_PARAM_STARTVALUE) - registerEnum(CONDITION_PARAM_TICKINTERVAL) - registerEnum(CONDITION_PARAM_FORCEUPDATE) - registerEnum(CONDITION_PARAM_SKILL_MELEE) - registerEnum(CONDITION_PARAM_SKILL_FIST) - registerEnum(CONDITION_PARAM_SKILL_CLUB) - registerEnum(CONDITION_PARAM_SKILL_SWORD) - registerEnum(CONDITION_PARAM_SKILL_AXE) - registerEnum(CONDITION_PARAM_SKILL_DISTANCE) - registerEnum(CONDITION_PARAM_SKILL_SHIELD) - registerEnum(CONDITION_PARAM_SKILL_FISHING) - registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTS) - registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTS) - registerEnum(CONDITION_PARAM_STAT_MAGICPOINTS) - registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT) - registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT) - registerEnum(CONDITION_PARAM_STAT_MAGICPOINTSPERCENT) - registerEnum(CONDITION_PARAM_PERIODICDAMAGE) - registerEnum(CONDITION_PARAM_SKILL_MELEEPERCENT) - registerEnum(CONDITION_PARAM_SKILL_FISTPERCENT) - registerEnum(CONDITION_PARAM_SKILL_CLUBPERCENT) - registerEnum(CONDITION_PARAM_SKILL_SWORDPERCENT) - registerEnum(CONDITION_PARAM_SKILL_AXEPERCENT) - registerEnum(CONDITION_PARAM_SKILL_DISTANCEPERCENT) - registerEnum(CONDITION_PARAM_SKILL_SHIELDPERCENT) - registerEnum(CONDITION_PARAM_SKILL_FISHINGPERCENT) - registerEnum(CONDITION_PARAM_BUFF_SPELL) - registerEnum(CONDITION_PARAM_SUBID) - registerEnum(CONDITION_PARAM_FIELD) - registerEnum(CONDITION_PARAM_DISABLE_DEFENSE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT) - registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT) - registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT) - registerEnum(CONDITION_PARAM_AGGRESSIVE) - - registerEnum(CONST_ME_NONE) - registerEnum(CONST_ME_DRAWBLOOD) - registerEnum(CONST_ME_LOSEENERGY) - registerEnum(CONST_ME_POFF) - registerEnum(CONST_ME_BLOCKHIT) - registerEnum(CONST_ME_EXPLOSIONAREA) - registerEnum(CONST_ME_EXPLOSIONHIT) - registerEnum(CONST_ME_FIREAREA) - registerEnum(CONST_ME_YELLOW_RINGS) - registerEnum(CONST_ME_GREEN_RINGS) - registerEnum(CONST_ME_HITAREA) - registerEnum(CONST_ME_TELEPORT) - registerEnum(CONST_ME_ENERGYHIT) - registerEnum(CONST_ME_MAGIC_BLUE) - registerEnum(CONST_ME_MAGIC_RED) - registerEnum(CONST_ME_MAGIC_GREEN) - registerEnum(CONST_ME_HITBYFIRE) - registerEnum(CONST_ME_HITBYPOISON) - registerEnum(CONST_ME_MORTAREA) - registerEnum(CONST_ME_SOUND_GREEN) - registerEnum(CONST_ME_SOUND_RED) - registerEnum(CONST_ME_POISONAREA) - registerEnum(CONST_ME_SOUND_YELLOW) - registerEnum(CONST_ME_SOUND_PURPLE) - registerEnum(CONST_ME_SOUND_BLUE) - registerEnum(CONST_ME_SOUND_WHITE) - registerEnum(CONST_ME_BUBBLES) - registerEnum(CONST_ME_CRAPS) - registerEnum(CONST_ME_GIFT_WRAPS) - registerEnum(CONST_ME_FIREWORK_YELLOW) - registerEnum(CONST_ME_FIREWORK_RED) - registerEnum(CONST_ME_FIREWORK_BLUE) - registerEnum(CONST_ME_STUN) - registerEnum(CONST_ME_SLEEP) - registerEnum(CONST_ME_WATERCREATURE) - registerEnum(CONST_ME_GROUNDSHAKER) - registerEnum(CONST_ME_HEARTS) - registerEnum(CONST_ME_FIREATTACK) - registerEnum(CONST_ME_ENERGYAREA) - registerEnum(CONST_ME_SMALLCLOUDS) - registerEnum(CONST_ME_HOLYDAMAGE) - registerEnum(CONST_ME_BIGCLOUDS) - registerEnum(CONST_ME_ICEAREA) - registerEnum(CONST_ME_ICETORNADO) - registerEnum(CONST_ME_ICEATTACK) - registerEnum(CONST_ME_STONES) - registerEnum(CONST_ME_SMALLPLANTS) - registerEnum(CONST_ME_CARNIPHILA) - registerEnum(CONST_ME_PURPLEENERGY) - registerEnum(CONST_ME_YELLOWENERGY) - registerEnum(CONST_ME_HOLYAREA) - registerEnum(CONST_ME_BIGPLANTS) - registerEnum(CONST_ME_CAKE) - registerEnum(CONST_ME_GIANTICE) - registerEnum(CONST_ME_WATERSPLASH) - registerEnum(CONST_ME_PLANTATTACK) - registerEnum(CONST_ME_TUTORIALARROW) - registerEnum(CONST_ME_TUTORIALSQUARE) - registerEnum(CONST_ME_MIRRORHORIZONTAL) - registerEnum(CONST_ME_MIRRORVERTICAL) - registerEnum(CONST_ME_SKULLHORIZONTAL) - registerEnum(CONST_ME_SKULLVERTICAL) - registerEnum(CONST_ME_ASSASSIN) - registerEnum(CONST_ME_STEPSHORIZONTAL) - registerEnum(CONST_ME_BLOODYSTEPS) - registerEnum(CONST_ME_STEPSVERTICAL) - registerEnum(CONST_ME_YALAHARIGHOST) - registerEnum(CONST_ME_BATS) - registerEnum(CONST_ME_SMOKE) - registerEnum(CONST_ME_INSECTS) - registerEnum(CONST_ME_DRAGONHEAD) - registerEnum(CONST_ME_ORCSHAMAN) - registerEnum(CONST_ME_ORCSHAMAN_FIRE) - registerEnum(CONST_ME_THUNDER) - registerEnum(CONST_ME_FERUMBRAS) - registerEnum(CONST_ME_CONFETTI_HORIZONTAL) - registerEnum(CONST_ME_CONFETTI_VERTICAL) - registerEnum(CONST_ME_BLACKSMOKE) - registerEnum(CONST_ME_REDSMOKE) - registerEnum(CONST_ME_YELLOWSMOKE) - registerEnum(CONST_ME_GREENSMOKE) - registerEnum(CONST_ME_PURPLESMOKE) - registerEnum(CONST_ME_EARLY_THUNDER) - registerEnum(CONST_ME_RAGIAZ_BONECAPSULE) - registerEnum(CONST_ME_CRITICAL_DAMAGE) - registerEnum(CONST_ME_PLUNGING_FISH) - - registerEnum(CONST_ANI_NONE) - registerEnum(CONST_ANI_SPEAR) - registerEnum(CONST_ANI_BOLT) - registerEnum(CONST_ANI_ARROW) - registerEnum(CONST_ANI_FIRE) - registerEnum(CONST_ANI_ENERGY) - registerEnum(CONST_ANI_POISONARROW) - registerEnum(CONST_ANI_BURSTARROW) - registerEnum(CONST_ANI_THROWINGSTAR) - registerEnum(CONST_ANI_THROWINGKNIFE) - registerEnum(CONST_ANI_SMALLSTONE) - registerEnum(CONST_ANI_DEATH) - registerEnum(CONST_ANI_LARGEROCK) - registerEnum(CONST_ANI_SNOWBALL) - registerEnum(CONST_ANI_POWERBOLT) - registerEnum(CONST_ANI_POISON) - registerEnum(CONST_ANI_INFERNALBOLT) - registerEnum(CONST_ANI_HUNTINGSPEAR) - registerEnum(CONST_ANI_ENCHANTEDSPEAR) - registerEnum(CONST_ANI_REDSTAR) - registerEnum(CONST_ANI_GREENSTAR) - registerEnum(CONST_ANI_ROYALSPEAR) - registerEnum(CONST_ANI_SNIPERARROW) - registerEnum(CONST_ANI_ONYXARROW) - registerEnum(CONST_ANI_PIERCINGBOLT) - registerEnum(CONST_ANI_WHIRLWINDSWORD) - registerEnum(CONST_ANI_WHIRLWINDAXE) - registerEnum(CONST_ANI_WHIRLWINDCLUB) - registerEnum(CONST_ANI_ETHEREALSPEAR) - registerEnum(CONST_ANI_ICE) - registerEnum(CONST_ANI_EARTH) - registerEnum(CONST_ANI_HOLY) - registerEnum(CONST_ANI_SUDDENDEATH) - registerEnum(CONST_ANI_FLASHARROW) - registerEnum(CONST_ANI_FLAMMINGARROW) - registerEnum(CONST_ANI_SHIVERARROW) - registerEnum(CONST_ANI_ENERGYBALL) - registerEnum(CONST_ANI_SMALLICE) - registerEnum(CONST_ANI_SMALLHOLY) - registerEnum(CONST_ANI_SMALLEARTH) - registerEnum(CONST_ANI_EARTHARROW) - registerEnum(CONST_ANI_EXPLOSION) - registerEnum(CONST_ANI_CAKE) - registerEnum(CONST_ANI_TARSALARROW) - registerEnum(CONST_ANI_VORTEXBOLT) - registerEnum(CONST_ANI_PRISMATICBOLT) - registerEnum(CONST_ANI_CRYSTALLINEARROW) - registerEnum(CONST_ANI_DRILLBOLT) - registerEnum(CONST_ANI_ENVENOMEDARROW) - registerEnum(CONST_ANI_GLOOTHSPEAR) - registerEnum(CONST_ANI_SIMPLEARROW) - registerEnum(CONST_ANI_WEAPONTYPE) - - registerEnum(CONST_PROP_BLOCKSOLID) - registerEnum(CONST_PROP_HASHEIGHT) - registerEnum(CONST_PROP_BLOCKPROJECTILE) - registerEnum(CONST_PROP_BLOCKPATH) - registerEnum(CONST_PROP_ISVERTICAL) - registerEnum(CONST_PROP_ISHORIZONTAL) - registerEnum(CONST_PROP_MOVEABLE) - registerEnum(CONST_PROP_IMMOVABLEBLOCKSOLID) - registerEnum(CONST_PROP_IMMOVABLEBLOCKPATH) - registerEnum(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) - registerEnum(CONST_PROP_NOFIELDBLOCKPATH) - registerEnum(CONST_PROP_SUPPORTHANGABLE) - - registerEnum(CONST_SLOT_HEAD) - registerEnum(CONST_SLOT_NECKLACE) - registerEnum(CONST_SLOT_BACKPACK) - registerEnum(CONST_SLOT_ARMOR) - registerEnum(CONST_SLOT_RIGHT) - registerEnum(CONST_SLOT_LEFT) - registerEnum(CONST_SLOT_LEGS) - registerEnum(CONST_SLOT_FEET) - registerEnum(CONST_SLOT_RING) - registerEnum(CONST_SLOT_AMMO) - - registerEnum(CREATURE_EVENT_NONE) - registerEnum(CREATURE_EVENT_LOGIN) - registerEnum(CREATURE_EVENT_LOGOUT) - registerEnum(CREATURE_EVENT_THINK) - registerEnum(CREATURE_EVENT_PREPAREDEATH) - registerEnum(CREATURE_EVENT_DEATH) - registerEnum(CREATURE_EVENT_KILL) - registerEnum(CREATURE_EVENT_ADVANCE) - registerEnum(CREATURE_EVENT_MODALWINDOW) - registerEnum(CREATURE_EVENT_TEXTEDIT) - registerEnum(CREATURE_EVENT_HEALTHCHANGE) - registerEnum(CREATURE_EVENT_MANACHANGE) - registerEnum(CREATURE_EVENT_EXTENDED_OPCODE) - - registerEnum(GAME_STATE_STARTUP) - registerEnum(GAME_STATE_INIT) - registerEnum(GAME_STATE_NORMAL) - registerEnum(GAME_STATE_CLOSED) - registerEnum(GAME_STATE_SHUTDOWN) - registerEnum(GAME_STATE_CLOSING) - registerEnum(GAME_STATE_MAINTAIN) - - registerEnum(MESSAGE_STATUS_CONSOLE_BLUE) - registerEnum(MESSAGE_STATUS_CONSOLE_RED) - registerEnum(MESSAGE_STATUS_DEFAULT) - registerEnum(MESSAGE_STATUS_WARNING) - registerEnum(MESSAGE_EVENT_ADVANCE) - registerEnum(MESSAGE_STATUS_SMALL) - registerEnum(MESSAGE_INFO_DESCR) - registerEnum(MESSAGE_DAMAGE_DEALT) - registerEnum(MESSAGE_DAMAGE_RECEIVED) - registerEnum(MESSAGE_HEALED) - registerEnum(MESSAGE_EXPERIENCE) - registerEnum(MESSAGE_DAMAGE_OTHERS) - registerEnum(MESSAGE_HEALED_OTHERS) - registerEnum(MESSAGE_EXPERIENCE_OTHERS) - registerEnum(MESSAGE_EVENT_DEFAULT) - registerEnum(MESSAGE_GUILD) - registerEnum(MESSAGE_PARTY_MANAGEMENT) - registerEnum(MESSAGE_PARTY) - registerEnum(MESSAGE_EVENT_ORANGE) - registerEnum(MESSAGE_STATUS_CONSOLE_ORANGE) - registerEnum(MESSAGE_LOOT) - - registerEnum(CREATURETYPE_PLAYER) - registerEnum(CREATURETYPE_MONSTER) - registerEnum(CREATURETYPE_NPC) - registerEnum(CREATURETYPE_SUMMON_OWN) - registerEnum(CREATURETYPE_SUMMON_OTHERS) - - registerEnum(CLIENTOS_LINUX) - registerEnum(CLIENTOS_WINDOWS) - registerEnum(CLIENTOS_FLASH) - registerEnum(CLIENTOS_OTCLIENT_LINUX) - registerEnum(CLIENTOS_OTCLIENT_WINDOWS) - registerEnum(CLIENTOS_OTCLIENT_MAC) - - registerEnum(FIGHTMODE_ATTACK) - registerEnum(FIGHTMODE_BALANCED) - registerEnum(FIGHTMODE_DEFENSE) - - registerEnum(ITEM_ATTRIBUTE_NONE) - registerEnum(ITEM_ATTRIBUTE_ACTIONID) - registerEnum(ITEM_ATTRIBUTE_UNIQUEID) - registerEnum(ITEM_ATTRIBUTE_DESCRIPTION) - registerEnum(ITEM_ATTRIBUTE_TEXT) - registerEnum(ITEM_ATTRIBUTE_DATE) - registerEnum(ITEM_ATTRIBUTE_WRITER) - registerEnum(ITEM_ATTRIBUTE_NAME) - registerEnum(ITEM_ATTRIBUTE_ARTICLE) - registerEnum(ITEM_ATTRIBUTE_PLURALNAME) - registerEnum(ITEM_ATTRIBUTE_WEIGHT) - registerEnum(ITEM_ATTRIBUTE_ATTACK) - registerEnum(ITEM_ATTRIBUTE_DEFENSE) - registerEnum(ITEM_ATTRIBUTE_EXTRADEFENSE) - registerEnum(ITEM_ATTRIBUTE_ARMOR) - registerEnum(ITEM_ATTRIBUTE_HITCHANCE) - registerEnum(ITEM_ATTRIBUTE_SHOOTRANGE) - registerEnum(ITEM_ATTRIBUTE_OWNER) - registerEnum(ITEM_ATTRIBUTE_DURATION) - registerEnum(ITEM_ATTRIBUTE_DECAYSTATE) - registerEnum(ITEM_ATTRIBUTE_CORPSEOWNER) - registerEnum(ITEM_ATTRIBUTE_CHARGES) - registerEnum(ITEM_ATTRIBUTE_FLUIDTYPE) - registerEnum(ITEM_ATTRIBUTE_DOORID) - - registerEnum(ITEM_TYPE_DEPOT) - registerEnum(ITEM_TYPE_MAILBOX) - registerEnum(ITEM_TYPE_TRASHHOLDER) - registerEnum(ITEM_TYPE_CONTAINER) - registerEnum(ITEM_TYPE_DOOR) - registerEnum(ITEM_TYPE_MAGICFIELD) - registerEnum(ITEM_TYPE_TELEPORT) - registerEnum(ITEM_TYPE_BED) - registerEnum(ITEM_TYPE_KEY) - registerEnum(ITEM_TYPE_RUNE) - - registerEnum(ITEM_GROUP_GROUND) - registerEnum(ITEM_GROUP_CONTAINER) - registerEnum(ITEM_GROUP_WEAPON) - registerEnum(ITEM_GROUP_AMMUNITION) - registerEnum(ITEM_GROUP_ARMOR) - registerEnum(ITEM_GROUP_CHARGES) - registerEnum(ITEM_GROUP_TELEPORT) - registerEnum(ITEM_GROUP_MAGICFIELD) - registerEnum(ITEM_GROUP_WRITEABLE) - registerEnum(ITEM_GROUP_KEY) - registerEnum(ITEM_GROUP_SPLASH) - registerEnum(ITEM_GROUP_FLUID) - registerEnum(ITEM_GROUP_DOOR) - registerEnum(ITEM_GROUP_DEPRECATED) - - registerEnum(ITEM_BAG) - registerEnum(ITEM_SHOPPING_BAG) - registerEnum(ITEM_GOLD_COIN) - registerEnum(ITEM_PLATINUM_COIN) - registerEnum(ITEM_CRYSTAL_COIN) - registerEnum(ITEM_AMULETOFLOSS) - registerEnum(ITEM_PARCEL) - registerEnum(ITEM_LABEL) - registerEnum(ITEM_FIREFIELD_PVP_FULL) - registerEnum(ITEM_FIREFIELD_PVP_MEDIUM) - registerEnum(ITEM_FIREFIELD_PVP_SMALL) - registerEnum(ITEM_FIREFIELD_PERSISTENT_FULL) - registerEnum(ITEM_FIREFIELD_PERSISTENT_MEDIUM) - registerEnum(ITEM_FIREFIELD_PERSISTENT_SMALL) - registerEnum(ITEM_FIREFIELD_NOPVP) - registerEnum(ITEM_POISONFIELD_PVP) - registerEnum(ITEM_POISONFIELD_PERSISTENT) - registerEnum(ITEM_POISONFIELD_NOPVP) - registerEnum(ITEM_ENERGYFIELD_PVP) - registerEnum(ITEM_ENERGYFIELD_PERSISTENT) - registerEnum(ITEM_ENERGYFIELD_NOPVP) - registerEnum(ITEM_MAGICWALL) - registerEnum(ITEM_MAGICWALL_PERSISTENT) - registerEnum(ITEM_MAGICWALL_SAFE) - registerEnum(ITEM_WILDGROWTH) - registerEnum(ITEM_WILDGROWTH_PERSISTENT) - registerEnum(ITEM_WILDGROWTH_SAFE) - - registerEnum(WIELDINFO_NONE) - registerEnum(WIELDINFO_LEVEL) - registerEnum(WIELDINFO_MAGLV) - registerEnum(WIELDINFO_VOCREQ) - registerEnum(WIELDINFO_PREMIUM) - - registerEnum(PlayerFlag_CannotUseCombat) - registerEnum(PlayerFlag_CannotAttackPlayer) - registerEnum(PlayerFlag_CannotAttackMonster) - registerEnum(PlayerFlag_CannotBeAttacked) - registerEnum(PlayerFlag_CanConvinceAll) - registerEnum(PlayerFlag_CanSummonAll) - registerEnum(PlayerFlag_CanIllusionAll) - registerEnum(PlayerFlag_CanSenseInvisibility) - registerEnum(PlayerFlag_IgnoredByMonsters) - registerEnum(PlayerFlag_NotGainInFight) - registerEnum(PlayerFlag_HasInfiniteMana) - registerEnum(PlayerFlag_HasInfiniteSoul) - registerEnum(PlayerFlag_HasNoExhaustion) - registerEnum(PlayerFlag_CannotUseSpells) - registerEnum(PlayerFlag_CannotPickupItem) - registerEnum(PlayerFlag_CanAlwaysLogin) - registerEnum(PlayerFlag_CanBroadcast) - registerEnum(PlayerFlag_CanEditHouses) - registerEnum(PlayerFlag_CannotBeBanned) - registerEnum(PlayerFlag_CannotBePushed) - registerEnum(PlayerFlag_HasInfiniteCapacity) - registerEnum(PlayerFlag_CanPushAllCreatures) - registerEnum(PlayerFlag_CanTalkRedPrivate) - registerEnum(PlayerFlag_CanTalkRedChannel) - registerEnum(PlayerFlag_TalkOrangeHelpChannel) - registerEnum(PlayerFlag_NotGainExperience) - registerEnum(PlayerFlag_NotGainMana) - registerEnum(PlayerFlag_NotGainHealth) - registerEnum(PlayerFlag_NotGainSkill) - registerEnum(PlayerFlag_SetMaxSpeed) - registerEnum(PlayerFlag_SpecialVIP) - registerEnum(PlayerFlag_NotGenerateLoot) - registerEnum(PlayerFlag_IgnoreProtectionZone) - registerEnum(PlayerFlag_IgnoreSpellCheck) - registerEnum(PlayerFlag_IgnoreWeaponCheck) - registerEnum(PlayerFlag_CannotBeMuted) - registerEnum(PlayerFlag_IsAlwaysPremium) - - registerEnum(PLAYERSEX_FEMALE) - registerEnum(PLAYERSEX_MALE) - - registerEnum(REPORT_REASON_NAMEINAPPROPRIATE) - registerEnum(REPORT_REASON_NAMEPOORFORMATTED) - registerEnum(REPORT_REASON_NAMEADVERTISING) - registerEnum(REPORT_REASON_NAMEUNFITTING) - registerEnum(REPORT_REASON_NAMERULEVIOLATION) - registerEnum(REPORT_REASON_INSULTINGSTATEMENT) - registerEnum(REPORT_REASON_SPAMMING) - registerEnum(REPORT_REASON_ADVERTISINGSTATEMENT) - registerEnum(REPORT_REASON_UNFITTINGSTATEMENT) - registerEnum(REPORT_REASON_LANGUAGESTATEMENT) - registerEnum(REPORT_REASON_DISCLOSURE) - registerEnum(REPORT_REASON_RULEVIOLATION) - registerEnum(REPORT_REASON_STATEMENT_BUGABUSE) - registerEnum(REPORT_REASON_UNOFFICIALSOFTWARE) - registerEnum(REPORT_REASON_PRETENDING) - registerEnum(REPORT_REASON_HARASSINGOWNERS) - registerEnum(REPORT_REASON_FALSEINFO) - registerEnum(REPORT_REASON_ACCOUNTSHARING) - registerEnum(REPORT_REASON_STEALINGDATA) - registerEnum(REPORT_REASON_SERVICEATTACKING) - registerEnum(REPORT_REASON_SERVICEAGREEMENT) - - registerEnum(REPORT_TYPE_NAME) - registerEnum(REPORT_TYPE_STATEMENT) - registerEnum(REPORT_TYPE_BOT) - - registerEnum(VOCATION_NONE) - - registerEnum(SKILL_FIST) - registerEnum(SKILL_CLUB) - registerEnum(SKILL_SWORD) - registerEnum(SKILL_AXE) - registerEnum(SKILL_DISTANCE) - registerEnum(SKILL_SHIELD) - registerEnum(SKILL_FISHING) - registerEnum(SKILL_MAGLEVEL) - registerEnum(SKILL_LEVEL) - - registerEnum(SPECIALSKILL_CRITICALHITCHANCE) - registerEnum(SPECIALSKILL_CRITICALHITAMOUNT) - registerEnum(SPECIALSKILL_LIFELEECHCHANCE) - registerEnum(SPECIALSKILL_LIFELEECHAMOUNT) - registerEnum(SPECIALSKILL_MANALEECHCHANCE) - registerEnum(SPECIALSKILL_MANALEECHAMOUNT) - - registerEnum(SKULL_NONE) - registerEnum(SKULL_YELLOW) - registerEnum(SKULL_GREEN) - registerEnum(SKULL_WHITE) - registerEnum(SKULL_RED) - registerEnum(SKULL_BLACK) - registerEnum(SKULL_ORANGE) - - registerEnum(TALKTYPE_SAY) - registerEnum(TALKTYPE_WHISPER) - registerEnum(TALKTYPE_YELL) - registerEnum(TALKTYPE_PRIVATE_FROM) - registerEnum(TALKTYPE_PRIVATE_TO) - registerEnum(TALKTYPE_CHANNEL_Y) - registerEnum(TALKTYPE_CHANNEL_O) - registerEnum(TALKTYPE_PRIVATE_NP) - registerEnum(TALKTYPE_PRIVATE_PN) - registerEnum(TALKTYPE_BROADCAST) - registerEnum(TALKTYPE_CHANNEL_R1) - registerEnum(TALKTYPE_PRIVATE_RED_FROM) - registerEnum(TALKTYPE_PRIVATE_RED_TO) - registerEnum(TALKTYPE_MONSTER_SAY) - registerEnum(TALKTYPE_MONSTER_YELL) - - registerEnum(TEXTCOLOR_BLUE) - registerEnum(TEXTCOLOR_LIGHTGREEN) - registerEnum(TEXTCOLOR_LIGHTBLUE) - registerEnum(TEXTCOLOR_MAYABLUE) - registerEnum(TEXTCOLOR_DARKRED) - registerEnum(TEXTCOLOR_LIGHTGREY) - registerEnum(TEXTCOLOR_SKYBLUE) - registerEnum(TEXTCOLOR_PURPLE) - registerEnum(TEXTCOLOR_ELECTRICPURPLE) - registerEnum(TEXTCOLOR_RED) - registerEnum(TEXTCOLOR_PASTELRED) - registerEnum(TEXTCOLOR_ORANGE) - registerEnum(TEXTCOLOR_YELLOW) - registerEnum(TEXTCOLOR_WHITE_EXP) - registerEnum(TEXTCOLOR_NONE) - - registerEnum(TILESTATE_NONE) - registerEnum(TILESTATE_PROTECTIONZONE) - registerEnum(TILESTATE_NOPVPZONE) - registerEnum(TILESTATE_NOLOGOUT) - registerEnum(TILESTATE_PVPZONE) - registerEnum(TILESTATE_FLOORCHANGE) - registerEnum(TILESTATE_FLOORCHANGE_DOWN) - registerEnum(TILESTATE_FLOORCHANGE_NORTH) - registerEnum(TILESTATE_FLOORCHANGE_SOUTH) - registerEnum(TILESTATE_FLOORCHANGE_EAST) - registerEnum(TILESTATE_FLOORCHANGE_WEST) - registerEnum(TILESTATE_TELEPORT) - registerEnum(TILESTATE_MAGICFIELD) - registerEnum(TILESTATE_MAILBOX) - registerEnum(TILESTATE_TRASHHOLDER) - registerEnum(TILESTATE_BED) - registerEnum(TILESTATE_DEPOT) - registerEnum(TILESTATE_BLOCKSOLID) - registerEnum(TILESTATE_BLOCKPATH) - registerEnum(TILESTATE_IMMOVABLEBLOCKSOLID) - registerEnum(TILESTATE_IMMOVABLEBLOCKPATH) - registerEnum(TILESTATE_IMMOVABLENOFIELDBLOCKPATH) - registerEnum(TILESTATE_NOFIELDBLOCKPATH) - registerEnum(TILESTATE_FLOORCHANGE_SOUTH_ALT) - registerEnum(TILESTATE_FLOORCHANGE_EAST_ALT) - registerEnum(TILESTATE_SUPPORTS_HANGABLE) - - registerEnum(WEAPON_NONE) - registerEnum(WEAPON_SWORD) - registerEnum(WEAPON_CLUB) - registerEnum(WEAPON_AXE) - registerEnum(WEAPON_SHIELD) - registerEnum(WEAPON_DISTANCE) - registerEnum(WEAPON_WAND) - registerEnum(WEAPON_AMMO) - - registerEnum(WORLD_TYPE_NO_PVP) - registerEnum(WORLD_TYPE_PVP) - registerEnum(WORLD_TYPE_PVP_ENFORCED) + registerEnum(ACCOUNT_TYPE_NORMAL); + registerEnum(ACCOUNT_TYPE_TUTOR); + registerEnum(ACCOUNT_TYPE_SENIORTUTOR); + registerEnum(ACCOUNT_TYPE_GAMEMASTER); + registerEnum(ACCOUNT_TYPE_COMMUNITYMANAGER); + registerEnum(ACCOUNT_TYPE_GOD); + + registerEnum(AMMO_NONE); + registerEnum(AMMO_BOLT); + registerEnum(AMMO_ARROW); + registerEnum(AMMO_SPEAR); + registerEnum(AMMO_THROWINGSTAR); + registerEnum(AMMO_THROWINGKNIFE); + registerEnum(AMMO_STONE); + registerEnum(AMMO_SNOWBALL); + + registerEnum(BUG_CATEGORY_MAP); + registerEnum(BUG_CATEGORY_TYPO); + registerEnum(BUG_CATEGORY_TECHNICAL); + registerEnum(BUG_CATEGORY_OTHER); + + registerEnum(CALLBACK_PARAM_LEVELMAGICVALUE); + registerEnum(CALLBACK_PARAM_SKILLVALUE); + registerEnum(CALLBACK_PARAM_TARGETTILE); + registerEnum(CALLBACK_PARAM_TARGETCREATURE); + + registerEnum(COMBAT_FORMULA_UNDEFINED); + registerEnum(COMBAT_FORMULA_LEVELMAGIC); + registerEnum(COMBAT_FORMULA_SKILL); + registerEnum(COMBAT_FORMULA_DAMAGE); + + registerEnum(DIRECTION_NORTH); + registerEnum(DIRECTION_EAST); + registerEnum(DIRECTION_SOUTH); + registerEnum(DIRECTION_WEST); + registerEnum(DIRECTION_SOUTHWEST); + registerEnum(DIRECTION_SOUTHEAST); + registerEnum(DIRECTION_NORTHWEST); + registerEnum(DIRECTION_NORTHEAST); + + registerEnum(COMBAT_NONE); + registerEnum(COMBAT_PHYSICALDAMAGE); + registerEnum(COMBAT_ENERGYDAMAGE); + registerEnum(COMBAT_EARTHDAMAGE); + registerEnum(COMBAT_FIREDAMAGE); + registerEnum(COMBAT_UNDEFINEDDAMAGE); + registerEnum(COMBAT_LIFEDRAIN); + registerEnum(COMBAT_MANADRAIN); + registerEnum(COMBAT_HEALING); + registerEnum(COMBAT_DROWNDAMAGE); + registerEnum(COMBAT_ICEDAMAGE); + registerEnum(COMBAT_HOLYDAMAGE); + registerEnum(COMBAT_DEATHDAMAGE); + + registerEnum(COMBAT_PARAM_TYPE); + registerEnum(COMBAT_PARAM_EFFECT); + registerEnum(COMBAT_PARAM_DISTANCEEFFECT); + registerEnum(COMBAT_PARAM_BLOCKSHIELD); + registerEnum(COMBAT_PARAM_BLOCKARMOR); + registerEnum(COMBAT_PARAM_TARGETCASTERORTOPMOST); + registerEnum(COMBAT_PARAM_CREATEITEM); + registerEnum(COMBAT_PARAM_AGGRESSIVE); + registerEnum(COMBAT_PARAM_DISPEL); + registerEnum(COMBAT_PARAM_USECHARGES); + + registerEnum(CONDITION_NONE); + registerEnum(CONDITION_POISON); + registerEnum(CONDITION_FIRE); + registerEnum(CONDITION_ENERGY); + registerEnum(CONDITION_BLEEDING); + registerEnum(CONDITION_HASTE); + registerEnum(CONDITION_PARALYZE); + registerEnum(CONDITION_OUTFIT); + registerEnum(CONDITION_INVISIBLE); + registerEnum(CONDITION_LIGHT); + registerEnum(CONDITION_MANASHIELD); + registerEnum(CONDITION_INFIGHT); + registerEnum(CONDITION_DRUNK); + registerEnum(CONDITION_EXHAUST_WEAPON); + registerEnum(CONDITION_REGENERATION); + registerEnum(CONDITION_SOUL); + registerEnum(CONDITION_DROWN); + registerEnum(CONDITION_MUTED); + registerEnum(CONDITION_CHANNELMUTEDTICKS); + registerEnum(CONDITION_YELLTICKS); + registerEnum(CONDITION_ATTRIBUTES); + registerEnum(CONDITION_FREEZING); + registerEnum(CONDITION_DAZZLED); + registerEnum(CONDITION_CURSED); + registerEnum(CONDITION_EXHAUST_COMBAT); + registerEnum(CONDITION_EXHAUST_HEAL); + registerEnum(CONDITION_PACIFIED); + registerEnum(CONDITION_SPELLCOOLDOWN); + registerEnum(CONDITION_SPELLGROUPCOOLDOWN); + registerEnum(CONDITION_ROOT); + + registerEnum(CONDITIONID_DEFAULT); + registerEnum(CONDITIONID_COMBAT); + registerEnum(CONDITIONID_HEAD); + registerEnum(CONDITIONID_NECKLACE); + registerEnum(CONDITIONID_BACKPACK); + registerEnum(CONDITIONID_ARMOR); + registerEnum(CONDITIONID_RIGHT); + registerEnum(CONDITIONID_LEFT); + registerEnum(CONDITIONID_LEGS); + registerEnum(CONDITIONID_FEET); + registerEnum(CONDITIONID_RING); + registerEnum(CONDITIONID_AMMO); + + registerEnum(CONDITION_PARAM_OWNER); + registerEnum(CONDITION_PARAM_TICKS); + registerEnum(CONDITION_PARAM_DRUNKENNESS); + registerEnum(CONDITION_PARAM_HEALTHGAIN); + registerEnum(CONDITION_PARAM_HEALTHTICKS); + registerEnum(CONDITION_PARAM_MANAGAIN); + registerEnum(CONDITION_PARAM_MANATICKS); + registerEnum(CONDITION_PARAM_DELAYED); + registerEnum(CONDITION_PARAM_SPEED); + registerEnum(CONDITION_PARAM_LIGHT_LEVEL); + registerEnum(CONDITION_PARAM_LIGHT_COLOR); + registerEnum(CONDITION_PARAM_SOULGAIN); + registerEnum(CONDITION_PARAM_SOULTICKS); + registerEnum(CONDITION_PARAM_MINVALUE); + registerEnum(CONDITION_PARAM_MAXVALUE); + registerEnum(CONDITION_PARAM_STARTVALUE); + registerEnum(CONDITION_PARAM_TICKINTERVAL); + registerEnum(CONDITION_PARAM_FORCEUPDATE); + registerEnum(CONDITION_PARAM_SKILL_MELEE); + registerEnum(CONDITION_PARAM_SKILL_FIST); + registerEnum(CONDITION_PARAM_SKILL_CLUB); + registerEnum(CONDITION_PARAM_SKILL_SWORD); + registerEnum(CONDITION_PARAM_SKILL_AXE); + registerEnum(CONDITION_PARAM_SKILL_DISTANCE); + registerEnum(CONDITION_PARAM_SKILL_SHIELD); + registerEnum(CONDITION_PARAM_SKILL_FISHING); + registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTS); + registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTS); + registerEnum(CONDITION_PARAM_STAT_MAGICPOINTS); + registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT); + registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT); + registerEnum(CONDITION_PARAM_STAT_MAGICPOINTSPERCENT); + registerEnum(CONDITION_PARAM_PERIODICDAMAGE); + registerEnum(CONDITION_PARAM_SKILL_MELEEPERCENT); + registerEnum(CONDITION_PARAM_SKILL_FISTPERCENT); + registerEnum(CONDITION_PARAM_SKILL_CLUBPERCENT); + registerEnum(CONDITION_PARAM_SKILL_SWORDPERCENT); + registerEnum(CONDITION_PARAM_SKILL_AXEPERCENT); + registerEnum(CONDITION_PARAM_SKILL_DISTANCEPERCENT); + registerEnum(CONDITION_PARAM_SKILL_SHIELDPERCENT); + registerEnum(CONDITION_PARAM_SKILL_FISHINGPERCENT); + registerEnum(CONDITION_PARAM_BUFF_SPELL); + registerEnum(CONDITION_PARAM_SUBID); + registerEnum(CONDITION_PARAM_FIELD); + registerEnum(CONDITION_PARAM_DISABLE_DEFENSE); + registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE); + registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT); + registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE); + registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT); + registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE); + registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT); + registerEnum(CONDITION_PARAM_AGGRESSIVE); + + registerEnum(CONST_ME_NONE); + registerEnum(CONST_ME_DRAWBLOOD); + registerEnum(CONST_ME_LOSEENERGY); + registerEnum(CONST_ME_POFF); + registerEnum(CONST_ME_BLOCKHIT); + registerEnum(CONST_ME_EXPLOSIONAREA); + registerEnum(CONST_ME_EXPLOSIONHIT); + registerEnum(CONST_ME_FIREAREA); + registerEnum(CONST_ME_YELLOW_RINGS); + registerEnum(CONST_ME_GREEN_RINGS); + registerEnum(CONST_ME_HITAREA); + registerEnum(CONST_ME_TELEPORT); + registerEnum(CONST_ME_ENERGYHIT); + registerEnum(CONST_ME_MAGIC_BLUE); + registerEnum(CONST_ME_MAGIC_RED); + registerEnum(CONST_ME_MAGIC_GREEN); + registerEnum(CONST_ME_HITBYFIRE); + registerEnum(CONST_ME_HITBYPOISON); + registerEnum(CONST_ME_MORTAREA); + registerEnum(CONST_ME_SOUND_GREEN); + registerEnum(CONST_ME_SOUND_RED); + registerEnum(CONST_ME_POISONAREA); + registerEnum(CONST_ME_SOUND_YELLOW); + registerEnum(CONST_ME_SOUND_PURPLE); + registerEnum(CONST_ME_SOUND_BLUE); + registerEnum(CONST_ME_SOUND_WHITE); + registerEnum(CONST_ME_BUBBLES); + registerEnum(CONST_ME_CRAPS); + registerEnum(CONST_ME_GIFT_WRAPS); + registerEnum(CONST_ME_FIREWORK_YELLOW); + registerEnum(CONST_ME_FIREWORK_RED); + registerEnum(CONST_ME_FIREWORK_BLUE); + registerEnum(CONST_ME_STUN); + registerEnum(CONST_ME_SLEEP); + registerEnum(CONST_ME_WATERCREATURE); + registerEnum(CONST_ME_GROUNDSHAKER); + registerEnum(CONST_ME_HEARTS); + registerEnum(CONST_ME_FIREATTACK); + registerEnum(CONST_ME_ENERGYAREA); + registerEnum(CONST_ME_SMALLCLOUDS); + registerEnum(CONST_ME_HOLYDAMAGE); + registerEnum(CONST_ME_BIGCLOUDS); + registerEnum(CONST_ME_ICEAREA); + registerEnum(CONST_ME_ICETORNADO); + registerEnum(CONST_ME_ICEATTACK); + registerEnum(CONST_ME_STONES); + registerEnum(CONST_ME_SMALLPLANTS); + registerEnum(CONST_ME_CARNIPHILA); + registerEnum(CONST_ME_PURPLEENERGY); + registerEnum(CONST_ME_YELLOWENERGY); + registerEnum(CONST_ME_HOLYAREA); + registerEnum(CONST_ME_BIGPLANTS); + registerEnum(CONST_ME_CAKE); + registerEnum(CONST_ME_GIANTICE); + registerEnum(CONST_ME_WATERSPLASH); + registerEnum(CONST_ME_PLANTATTACK); + registerEnum(CONST_ME_TUTORIALARROW); + registerEnum(CONST_ME_TUTORIALSQUARE); + registerEnum(CONST_ME_MIRRORHORIZONTAL); + registerEnum(CONST_ME_MIRRORVERTICAL); + registerEnum(CONST_ME_SKULLHORIZONTAL); + registerEnum(CONST_ME_SKULLVERTICAL); + registerEnum(CONST_ME_ASSASSIN); + registerEnum(CONST_ME_STEPSHORIZONTAL); + registerEnum(CONST_ME_BLOODYSTEPS); + registerEnum(CONST_ME_STEPSVERTICAL); + registerEnum(CONST_ME_YALAHARIGHOST); + registerEnum(CONST_ME_BATS); + registerEnum(CONST_ME_SMOKE); + registerEnum(CONST_ME_INSECTS); + registerEnum(CONST_ME_DRAGONHEAD); + registerEnum(CONST_ME_ORCSHAMAN); + registerEnum(CONST_ME_ORCSHAMAN_FIRE); + registerEnum(CONST_ME_THUNDER); + registerEnum(CONST_ME_FERUMBRAS); + registerEnum(CONST_ME_CONFETTI_HORIZONTAL); + registerEnum(CONST_ME_CONFETTI_VERTICAL); + registerEnum(CONST_ME_BLACKSMOKE); + registerEnum(CONST_ME_REDSMOKE); + registerEnum(CONST_ME_YELLOWSMOKE); + registerEnum(CONST_ME_GREENSMOKE); + registerEnum(CONST_ME_PURPLESMOKE); + registerEnum(CONST_ME_EARLY_THUNDER); + registerEnum(CONST_ME_RAGIAZ_BONECAPSULE); + registerEnum(CONST_ME_CRITICAL_DAMAGE); + registerEnum(CONST_ME_PLUNGING_FISH); + registerEnum(CONST_ME_BLUECHAIN); + registerEnum(CONST_ME_ORANGECHAIN); + registerEnum(CONST_ME_GREENCHAIN); + registerEnum(CONST_ME_PURPLECHAIN); + registerEnum(CONST_ME_GREYCHAIN); + registerEnum(CONST_ME_YELLOWCHAIN); + registerEnum(CONST_ME_YELLOWSPARKLES); + registerEnum(CONST_ME_FAEEXPLOSION); + registerEnum(CONST_ME_FAECOMING); + registerEnum(CONST_ME_FAEGOING); + registerEnum(CONST_ME_BIGCLOUDSSINGLESPACE); + registerEnum(CONST_ME_STONESSINGLESPACE); + registerEnum(CONST_ME_BLUEGHOST); + registerEnum(CONST_ME_POINTOFINTEREST); + registerEnum(CONST_ME_MAPEFFECT); + registerEnum(CONST_ME_PINKSPARK); + registerEnum(CONST_ME_FIREWORK_GREEN); + registerEnum(CONST_ME_FIREWORK_ORANGE); + registerEnum(CONST_ME_FIREWORK_PURPLE); + registerEnum(CONST_ME_FIREWORK_TURQUOISE); + registerEnum(CONST_ME_THECUBE); + registerEnum(CONST_ME_DRAWINK); + registerEnum(CONST_ME_PRISMATICSPARKLES); + registerEnum(CONST_ME_THAIAN); + registerEnum(CONST_ME_THAIANGHOST); + registerEnum(CONST_ME_GHOSTSMOKE); + registerEnum(CONST_ME_FLOATINGBLOCK); + registerEnum(CONST_ME_BLOCK); + registerEnum(CONST_ME_ROOTING); + registerEnum(CONST_ME_GHOSTLYSCRATCH); + registerEnum(CONST_ME_GHOSTLYBITE); + registerEnum(CONST_ME_BIGSCRATCHING); + registerEnum(CONST_ME_SLASH); + registerEnum(CONST_ME_BITE); + registerEnum(CONST_ME_CHIVALRIOUSCHALLENGE); + registerEnum(CONST_ME_DIVINEDAZZLE); + registerEnum(CONST_ME_ELECTRICALSPARK); + registerEnum(CONST_ME_PURPLETELEPORT); + registerEnum(CONST_ME_REDTELEPORT); + registerEnum(CONST_ME_ORANGETELEPORT); + registerEnum(CONST_ME_GREYTELEPORT); + registerEnum(CONST_ME_LIGHTBLUETELEPORT); + registerEnum(CONST_ME_FATAL); + registerEnum(CONST_ME_DODGE); + registerEnum(CONST_ME_HOURGLASS); + registerEnum(CONST_ME_FERUMBRAS_1); + registerEnum(CONST_ME_GAZHARAGOTH); + registerEnum(CONST_ME_MAD_MAGE); + registerEnum(CONST_ME_HORESTIS); + registerEnum(CONST_ME_DEVOVORGA); + registerEnum(CONST_ME_FERUMBRAS_2); + + registerEnum(CONST_ANI_NONE); + registerEnum(CONST_ANI_SPEAR); + registerEnum(CONST_ANI_BOLT); + registerEnum(CONST_ANI_ARROW); + registerEnum(CONST_ANI_FIRE); + registerEnum(CONST_ANI_ENERGY); + registerEnum(CONST_ANI_POISONARROW); + registerEnum(CONST_ANI_BURSTARROW); + registerEnum(CONST_ANI_THROWINGSTAR); + registerEnum(CONST_ANI_THROWINGKNIFE); + registerEnum(CONST_ANI_SMALLSTONE); + registerEnum(CONST_ANI_DEATH); + registerEnum(CONST_ANI_LARGEROCK); + registerEnum(CONST_ANI_SNOWBALL); + registerEnum(CONST_ANI_POWERBOLT); + registerEnum(CONST_ANI_POISON); + registerEnum(CONST_ANI_INFERNALBOLT); + registerEnum(CONST_ANI_HUNTINGSPEAR); + registerEnum(CONST_ANI_ENCHANTEDSPEAR); + registerEnum(CONST_ANI_REDSTAR); + registerEnum(CONST_ANI_GREENSTAR); + registerEnum(CONST_ANI_ROYALSPEAR); + registerEnum(CONST_ANI_SNIPERARROW); + registerEnum(CONST_ANI_ONYXARROW); + registerEnum(CONST_ANI_PIERCINGBOLT); + registerEnum(CONST_ANI_WHIRLWINDSWORD); + registerEnum(CONST_ANI_WHIRLWINDAXE); + registerEnum(CONST_ANI_WHIRLWINDCLUB); + registerEnum(CONST_ANI_ETHEREALSPEAR); + registerEnum(CONST_ANI_ICE); + registerEnum(CONST_ANI_EARTH); + registerEnum(CONST_ANI_HOLY); + registerEnum(CONST_ANI_SUDDENDEATH); + registerEnum(CONST_ANI_FLASHARROW); + registerEnum(CONST_ANI_FLAMMINGARROW); + registerEnum(CONST_ANI_SHIVERARROW); + registerEnum(CONST_ANI_ENERGYBALL); + registerEnum(CONST_ANI_SMALLICE); + registerEnum(CONST_ANI_SMALLHOLY); + registerEnum(CONST_ANI_SMALLEARTH); + registerEnum(CONST_ANI_EARTHARROW); + registerEnum(CONST_ANI_EXPLOSION); + registerEnum(CONST_ANI_CAKE); + registerEnum(CONST_ANI_TARSALARROW); + registerEnum(CONST_ANI_VORTEXBOLT); + registerEnum(CONST_ANI_PRISMATICBOLT); + registerEnum(CONST_ANI_CRYSTALLINEARROW); + registerEnum(CONST_ANI_DRILLBOLT); + registerEnum(CONST_ANI_ENVENOMEDARROW); + registerEnum(CONST_ANI_GLOOTHSPEAR); + registerEnum(CONST_ANI_SIMPLEARROW); + registerEnum(CONST_ANI_LEAFSTAR); + registerEnum(CONST_ANI_DIAMONDARROW); + registerEnum(CONST_ANI_SPECTRALBOLT); + registerEnum(CONST_ANI_ROYALSTAR); + registerEnum(CONST_ANI_WEAPONTYPE); + + registerEnum(CONST_PROP_BLOCKSOLID); + registerEnum(CONST_PROP_HASHEIGHT); + registerEnum(CONST_PROP_BLOCKPROJECTILE); + registerEnum(CONST_PROP_BLOCKPATH); + registerEnum(CONST_PROP_ISVERTICAL); + registerEnum(CONST_PROP_ISHORIZONTAL); + registerEnum(CONST_PROP_MOVEABLE); + registerEnum(CONST_PROP_IMMOVABLEBLOCKSOLID); + registerEnum(CONST_PROP_IMMOVABLEBLOCKPATH); + registerEnum(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH); + registerEnum(CONST_PROP_NOFIELDBLOCKPATH); + registerEnum(CONST_PROP_SUPPORTHANGABLE); + + registerEnum(CONST_SLOT_HEAD); + registerEnum(CONST_SLOT_NECKLACE); + registerEnum(CONST_SLOT_BACKPACK); + registerEnum(CONST_SLOT_ARMOR); + registerEnum(CONST_SLOT_RIGHT); + registerEnum(CONST_SLOT_LEFT); + registerEnum(CONST_SLOT_LEGS); + registerEnum(CONST_SLOT_FEET); + registerEnum(CONST_SLOT_RING); + registerEnum(CONST_SLOT_AMMO); + + registerEnum(CREATURE_EVENT_NONE); + registerEnum(CREATURE_EVENT_LOGIN); + registerEnum(CREATURE_EVENT_LOGOUT); + registerEnum(CREATURE_EVENT_THINK); + registerEnum(CREATURE_EVENT_PREPAREDEATH); + registerEnum(CREATURE_EVENT_DEATH); + registerEnum(CREATURE_EVENT_KILL); + registerEnum(CREATURE_EVENT_ADVANCE); + registerEnum(CREATURE_EVENT_MODALWINDOW); + registerEnum(CREATURE_EVENT_TEXTEDIT); + registerEnum(CREATURE_EVENT_HEALTHCHANGE); + registerEnum(CREATURE_EVENT_MANACHANGE); + registerEnum(CREATURE_EVENT_EXTENDED_OPCODE); + + registerEnum(CREATURE_ID_MIN); + registerEnum(CREATURE_ID_MAX); + + registerEnum(GAME_STATE_STARTUP); + registerEnum(GAME_STATE_INIT); + registerEnum(GAME_STATE_NORMAL); + registerEnum(GAME_STATE_CLOSED); + registerEnum(GAME_STATE_SHUTDOWN); + registerEnum(GAME_STATE_CLOSING); + registerEnum(GAME_STATE_MAINTAIN); + + registerEnum(MESSAGE_STATUS_DEFAULT); + registerEnum(MESSAGE_STATUS_WARNING); + registerEnum(MESSAGE_EVENT_ADVANCE); + registerEnum(MESSAGE_STATUS_WARNING2); + registerEnum(MESSAGE_STATUS_SMALL); + registerEnum(MESSAGE_INFO_DESCR); + registerEnum(MESSAGE_DAMAGE_DEALT); + registerEnum(MESSAGE_DAMAGE_RECEIVED); + registerEnum(MESSAGE_HEALED); + registerEnum(MESSAGE_EXPERIENCE); + registerEnum(MESSAGE_DAMAGE_OTHERS); + registerEnum(MESSAGE_HEALED_OTHERS); + registerEnum(MESSAGE_EXPERIENCE_OTHERS); + registerEnum(MESSAGE_EVENT_DEFAULT); + registerEnum(MESSAGE_LOOT); + registerEnum(MESSAGE_TRADE); + registerEnum(MESSAGE_GUILD); + registerEnum(MESSAGE_PARTY_MANAGEMENT); + registerEnum(MESSAGE_PARTY); + registerEnum(MESSAGE_REPORT); + registerEnum(MESSAGE_HOTKEY_PRESSED); + registerEnum(MESSAGE_MARKET); + registerEnum(MESSAGE_BEYOND_LAST); + registerEnum(MESSAGE_TOURNAMENT_INFO); + registerEnum(MESSAGE_ATTENTION); + registerEnum(MESSAGE_BOOSTED_CREATURE); + registerEnum(MESSAGE_OFFLINE_TRAINING); + registerEnum(MESSAGE_TRANSACTION); + + registerEnum(CREATURETYPE_PLAYER); + registerEnum(CREATURETYPE_MONSTER); + registerEnum(CREATURETYPE_NPC); + registerEnum(CREATURETYPE_SUMMON_OWN); + registerEnum(CREATURETYPE_SUMMON_OTHERS); + + registerEnum(CLIENTOS_LINUX); + registerEnum(CLIENTOS_WINDOWS); + registerEnum(CLIENTOS_FLASH); + registerEnum(CLIENTOS_OTCLIENT_LINUX); + registerEnum(CLIENTOS_OTCLIENT_WINDOWS); + registerEnum(CLIENTOS_OTCLIENT_MAC); + + registerEnum(FIGHTMODE_ATTACK); + registerEnum(FIGHTMODE_BALANCED); + registerEnum(FIGHTMODE_DEFENSE); + + registerEnum(ITEM_ATTRIBUTE_NONE); + registerEnum(ITEM_ATTRIBUTE_ACTIONID); + registerEnum(ITEM_ATTRIBUTE_UNIQUEID); + registerEnum(ITEM_ATTRIBUTE_DESCRIPTION); + registerEnum(ITEM_ATTRIBUTE_TEXT); + registerEnum(ITEM_ATTRIBUTE_DATE); + registerEnum(ITEM_ATTRIBUTE_WRITER); + registerEnum(ITEM_ATTRIBUTE_NAME); + registerEnum(ITEM_ATTRIBUTE_ARTICLE); + registerEnum(ITEM_ATTRIBUTE_PLURALNAME); + registerEnum(ITEM_ATTRIBUTE_WEIGHT); + registerEnum(ITEM_ATTRIBUTE_ATTACK); + registerEnum(ITEM_ATTRIBUTE_DEFENSE); + registerEnum(ITEM_ATTRIBUTE_EXTRADEFENSE); + registerEnum(ITEM_ATTRIBUTE_ARMOR); + registerEnum(ITEM_ATTRIBUTE_HITCHANCE); + registerEnum(ITEM_ATTRIBUTE_SHOOTRANGE); + registerEnum(ITEM_ATTRIBUTE_OWNER); + registerEnum(ITEM_ATTRIBUTE_DURATION); + registerEnum(ITEM_ATTRIBUTE_DECAYSTATE); + registerEnum(ITEM_ATTRIBUTE_CORPSEOWNER); + registerEnum(ITEM_ATTRIBUTE_CHARGES); + registerEnum(ITEM_ATTRIBUTE_FLUIDTYPE); + registerEnum(ITEM_ATTRIBUTE_DOORID); + registerEnum(ITEM_ATTRIBUTE_DECAYTO); + registerEnum(ITEM_ATTRIBUTE_WRAPID); + registerEnum(ITEM_ATTRIBUTE_STOREITEM); + registerEnum(ITEM_ATTRIBUTE_ATTACK_SPEED); + registerEnum(ITEM_ATTRIBUTE_OPENCONTAINER); + + registerEnum(ITEM_TYPE_DEPOT); + registerEnum(ITEM_TYPE_MAILBOX); + registerEnum(ITEM_TYPE_TRASHHOLDER); + registerEnum(ITEM_TYPE_CONTAINER); + registerEnum(ITEM_TYPE_DOOR); + registerEnum(ITEM_TYPE_MAGICFIELD); + registerEnum(ITEM_TYPE_TELEPORT); + registerEnum(ITEM_TYPE_BED); + registerEnum(ITEM_TYPE_KEY); + registerEnum(ITEM_TYPE_RUNE); + registerEnum(ITEM_TYPE_PODIUM); + + registerEnum(ITEM_GROUP_GROUND); + registerEnum(ITEM_GROUP_CONTAINER); + registerEnum(ITEM_GROUP_WEAPON); + registerEnum(ITEM_GROUP_AMMUNITION); + registerEnum(ITEM_GROUP_ARMOR); + registerEnum(ITEM_GROUP_CHARGES); + registerEnum(ITEM_GROUP_TELEPORT); + registerEnum(ITEM_GROUP_MAGICFIELD); + registerEnum(ITEM_GROUP_WRITEABLE); + registerEnum(ITEM_GROUP_KEY); + registerEnum(ITEM_GROUP_SPLASH); + registerEnum(ITEM_GROUP_FLUID); + registerEnum(ITEM_GROUP_DOOR); + registerEnum(ITEM_GROUP_DEPRECATED); + registerEnum(ITEM_GROUP_PODIUM); + + registerEnum(ITEM_BROWSEFIELD); + registerEnum(ITEM_BAG); + registerEnum(ITEM_SHOPPING_BAG); + registerEnum(ITEM_GOLD_COIN); + registerEnum(ITEM_PLATINUM_COIN); + registerEnum(ITEM_CRYSTAL_COIN); + registerEnum(ITEM_AMULETOFLOSS); + registerEnum(ITEM_PARCEL); + registerEnum(ITEM_LABEL); + registerEnum(ITEM_FIREFIELD_PVP_FULL); + registerEnum(ITEM_FIREFIELD_PVP_MEDIUM); + registerEnum(ITEM_FIREFIELD_PVP_SMALL); + registerEnum(ITEM_FIREFIELD_PERSISTENT_FULL); + registerEnum(ITEM_FIREFIELD_PERSISTENT_MEDIUM); + registerEnum(ITEM_FIREFIELD_PERSISTENT_SMALL); + registerEnum(ITEM_FIREFIELD_NOPVP); + registerEnum(ITEM_POISONFIELD_PVP); + registerEnum(ITEM_POISONFIELD_PERSISTENT); + registerEnum(ITEM_POISONFIELD_NOPVP); + registerEnum(ITEM_ENERGYFIELD_PVP); + registerEnum(ITEM_ENERGYFIELD_PERSISTENT); + registerEnum(ITEM_ENERGYFIELD_NOPVP); + registerEnum(ITEM_MAGICWALL); + registerEnum(ITEM_MAGICWALL_PERSISTENT); + registerEnum(ITEM_MAGICWALL_SAFE); + registerEnum(ITEM_WILDGROWTH); + registerEnum(ITEM_WILDGROWTH_PERSISTENT); + registerEnum(ITEM_WILDGROWTH_SAFE); + registerEnum(ITEM_DECORATION_KIT); + + registerEnum(WIELDINFO_NONE); + registerEnum(WIELDINFO_LEVEL); + registerEnum(WIELDINFO_MAGLV); + registerEnum(WIELDINFO_VOCREQ); + registerEnum(WIELDINFO_PREMIUM); + + registerEnum(PlayerFlag_CannotUseCombat); + registerEnum(PlayerFlag_CannotAttackPlayer); + registerEnum(PlayerFlag_CannotAttackMonster); + registerEnum(PlayerFlag_CannotBeAttacked); + registerEnum(PlayerFlag_CanConvinceAll); + registerEnum(PlayerFlag_CanSummonAll); + registerEnum(PlayerFlag_CanIllusionAll); + registerEnum(PlayerFlag_CanSenseInvisibility); + registerEnum(PlayerFlag_IgnoredByMonsters); + registerEnum(PlayerFlag_NotGainInFight); + registerEnum(PlayerFlag_HasInfiniteMana); + registerEnum(PlayerFlag_HasInfiniteSoul); + registerEnum(PlayerFlag_HasNoExhaustion); + registerEnum(PlayerFlag_CannotUseSpells); + registerEnum(PlayerFlag_CannotPickupItem); + registerEnum(PlayerFlag_CanAlwaysLogin); + registerEnum(PlayerFlag_CanBroadcast); + registerEnum(PlayerFlag_CanEditHouses); + registerEnum(PlayerFlag_CannotBeBanned); + registerEnum(PlayerFlag_CannotBePushed); + registerEnum(PlayerFlag_HasInfiniteCapacity); + registerEnum(PlayerFlag_CanPushAllCreatures); + registerEnum(PlayerFlag_CanTalkRedPrivate); + registerEnum(PlayerFlag_CanTalkRedChannel); + registerEnum(PlayerFlag_TalkOrangeHelpChannel); + registerEnum(PlayerFlag_NotGainExperience); + registerEnum(PlayerFlag_NotGainMana); + registerEnum(PlayerFlag_NotGainHealth); + registerEnum(PlayerFlag_NotGainSkill); + registerEnum(PlayerFlag_SetMaxSpeed); + registerEnum(PlayerFlag_SpecialVIP); + registerEnum(PlayerFlag_NotGenerateLoot); + registerEnum(PlayerFlag_IgnoreProtectionZone); + registerEnum(PlayerFlag_IgnoreSpellCheck); + registerEnum(PlayerFlag_IgnoreWeaponCheck); + registerEnum(PlayerFlag_CannotBeMuted); + registerEnum(PlayerFlag_IsAlwaysPremium); + registerEnum(PlayerFlag_IgnoreYellCheck); + registerEnum(PlayerFlag_IgnoreSendPrivateCheck); + + registerEnum(PODIUM_SHOW_PLATFORM); + registerEnum(PODIUM_SHOW_OUTFIT); + registerEnum(PODIUM_SHOW_MOUNT); + + registerEnum(PLAYERSEX_FEMALE); + registerEnum(PLAYERSEX_MALE); + + registerEnum(REPORT_REASON_NAMEINAPPROPRIATE); + registerEnum(REPORT_REASON_NAMEPOORFORMATTED); + registerEnum(REPORT_REASON_NAMEADVERTISING); + registerEnum(REPORT_REASON_NAMEUNFITTING); + registerEnum(REPORT_REASON_NAMERULEVIOLATION); + registerEnum(REPORT_REASON_INSULTINGSTATEMENT); + registerEnum(REPORT_REASON_SPAMMING); + registerEnum(REPORT_REASON_ADVERTISINGSTATEMENT); + registerEnum(REPORT_REASON_UNFITTINGSTATEMENT); + registerEnum(REPORT_REASON_LANGUAGESTATEMENT); + registerEnum(REPORT_REASON_DISCLOSURE); + registerEnum(REPORT_REASON_RULEVIOLATION); + registerEnum(REPORT_REASON_STATEMENT_BUGABUSE); + registerEnum(REPORT_REASON_UNOFFICIALSOFTWARE); + registerEnum(REPORT_REASON_PRETENDING); + registerEnum(REPORT_REASON_HARASSINGOWNERS); + registerEnum(REPORT_REASON_FALSEINFO); + registerEnum(REPORT_REASON_ACCOUNTSHARING); + registerEnum(REPORT_REASON_STEALINGDATA); + registerEnum(REPORT_REASON_SERVICEATTACKING); + registerEnum(REPORT_REASON_SERVICEAGREEMENT); + + registerEnum(REPORT_TYPE_NAME); + registerEnum(REPORT_TYPE_STATEMENT); + registerEnum(REPORT_TYPE_BOT); + + registerEnum(VOCATION_NONE); + + registerEnum(SKILL_FIST); + registerEnum(SKILL_CLUB); + registerEnum(SKILL_SWORD); + registerEnum(SKILL_AXE); + registerEnum(SKILL_DISTANCE); + registerEnum(SKILL_SHIELD); + registerEnum(SKILL_FISHING); + registerEnum(SKILL_MAGLEVEL); + registerEnum(SKILL_LEVEL); + + registerEnum(SPECIALSKILL_CRITICALHITCHANCE); + registerEnum(SPECIALSKILL_CRITICALHITAMOUNT); + registerEnum(SPECIALSKILL_LIFELEECHCHANCE); + registerEnum(SPECIALSKILL_LIFELEECHAMOUNT); + registerEnum(SPECIALSKILL_MANALEECHCHANCE); + registerEnum(SPECIALSKILL_MANALEECHAMOUNT); + + registerEnum(STAT_MAXHITPOINTS); + registerEnum(STAT_MAXMANAPOINTS); + registerEnum(STAT_SOULPOINTS); + registerEnum(STAT_MAGICPOINTS); + + registerEnum(SKULL_NONE); + registerEnum(SKULL_YELLOW); + registerEnum(SKULL_GREEN); + registerEnum(SKULL_WHITE); + registerEnum(SKULL_RED); + registerEnum(SKULL_BLACK); + registerEnum(SKULL_ORANGE); + + registerEnum(FLUID_NONE); + registerEnum(FLUID_WATER); + registerEnum(FLUID_BLOOD); + registerEnum(FLUID_BEER); + registerEnum(FLUID_SLIME); + registerEnum(FLUID_LEMONADE); + registerEnum(FLUID_MILK); + registerEnum(FLUID_MANA); + registerEnum(FLUID_LIFE); + registerEnum(FLUID_OIL); + registerEnum(FLUID_URINE); + registerEnum(FLUID_COCONUTMILK); + registerEnum(FLUID_WINE); + registerEnum(FLUID_MUD); + registerEnum(FLUID_FRUITJUICE); + registerEnum(FLUID_LAVA); + registerEnum(FLUID_RUM); + registerEnum(FLUID_SWAMP); + registerEnum(FLUID_TEA); + registerEnum(FLUID_MEAD); + + registerEnum(TALKTYPE_SAY); + registerEnum(TALKTYPE_WHISPER); + registerEnum(TALKTYPE_YELL); + registerEnum(TALKTYPE_PRIVATE_FROM); + registerEnum(TALKTYPE_PRIVATE_TO); + registerEnum(TALKTYPE_CHANNEL_Y); + registerEnum(TALKTYPE_CHANNEL_O); + registerEnum(TALKTYPE_SPELL); + registerEnum(TALKTYPE_PRIVATE_NP); + registerEnum(TALKTYPE_PRIVATE_NP_CONSOLE); + registerEnum(TALKTYPE_PRIVATE_PN); + registerEnum(TALKTYPE_BROADCAST); + registerEnum(TALKTYPE_CHANNEL_R1); + registerEnum(TALKTYPE_PRIVATE_RED_FROM); + registerEnum(TALKTYPE_PRIVATE_RED_TO); + registerEnum(TALKTYPE_MONSTER_SAY); + registerEnum(TALKTYPE_MONSTER_YELL); + registerEnum(TALKTYPE_POTION); + + registerEnum(TEXTCOLOR_BLUE); + registerEnum(TEXTCOLOR_LIGHTGREEN); + registerEnum(TEXTCOLOR_LIGHTBLUE); + registerEnum(TEXTCOLOR_MAYABLUE); + registerEnum(TEXTCOLOR_DARKRED); + registerEnum(TEXTCOLOR_LIGHTGREY); + registerEnum(TEXTCOLOR_SKYBLUE); + registerEnum(TEXTCOLOR_PURPLE); + registerEnum(TEXTCOLOR_ELECTRICPURPLE); + registerEnum(TEXTCOLOR_RED); + registerEnum(TEXTCOLOR_PASTELRED); + registerEnum(TEXTCOLOR_ORANGE); + registerEnum(TEXTCOLOR_YELLOW); + registerEnum(TEXTCOLOR_WHITE_EXP); + registerEnum(TEXTCOLOR_NONE); + + registerEnum(TILESTATE_NONE); + registerEnum(TILESTATE_PROTECTIONZONE); + registerEnum(TILESTATE_NOPVPZONE); + registerEnum(TILESTATE_NOLOGOUT); + registerEnum(TILESTATE_PVPZONE); + registerEnum(TILESTATE_FLOORCHANGE); + registerEnum(TILESTATE_FLOORCHANGE_DOWN); + registerEnum(TILESTATE_FLOORCHANGE_NORTH); + registerEnum(TILESTATE_FLOORCHANGE_SOUTH); + registerEnum(TILESTATE_FLOORCHANGE_EAST); + registerEnum(TILESTATE_FLOORCHANGE_WEST); + registerEnum(TILESTATE_TELEPORT); + registerEnum(TILESTATE_MAGICFIELD); + registerEnum(TILESTATE_MAILBOX); + registerEnum(TILESTATE_TRASHHOLDER); + registerEnum(TILESTATE_BED); + registerEnum(TILESTATE_DEPOT); + registerEnum(TILESTATE_BLOCKSOLID); + registerEnum(TILESTATE_BLOCKPATH); + registerEnum(TILESTATE_IMMOVABLEBLOCKSOLID); + registerEnum(TILESTATE_IMMOVABLEBLOCKPATH); + registerEnum(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + registerEnum(TILESTATE_NOFIELDBLOCKPATH); + registerEnum(TILESTATE_FLOORCHANGE_SOUTH_ALT); + registerEnum(TILESTATE_FLOORCHANGE_EAST_ALT); + registerEnum(TILESTATE_SUPPORTS_HANGABLE); + + registerEnum(WEAPON_NONE); + registerEnum(WEAPON_SWORD); + registerEnum(WEAPON_CLUB); + registerEnum(WEAPON_AXE); + registerEnum(WEAPON_SHIELD); + registerEnum(WEAPON_DISTANCE); + registerEnum(WEAPON_WAND); + registerEnum(WEAPON_AMMO); + + registerEnum(WORLD_TYPE_NO_PVP); + registerEnum(WORLD_TYPE_PVP); + registerEnum(WORLD_TYPE_PVP_ENFORCED); // Use with container:addItem, container:addItemEx and possibly other functions. - registerEnum(FLAG_NOLIMIT) - registerEnum(FLAG_IGNOREBLOCKITEM) - registerEnum(FLAG_IGNOREBLOCKCREATURE) - registerEnum(FLAG_CHILDISOWNER) - registerEnum(FLAG_PATHFINDING) - registerEnum(FLAG_IGNOREFIELDDAMAGE) - registerEnum(FLAG_IGNORENOTMOVEABLE) - registerEnum(FLAG_IGNOREAUTOSTACK) + registerEnum(FLAG_NOLIMIT); + registerEnum(FLAG_IGNOREBLOCKITEM); + registerEnum(FLAG_IGNOREBLOCKCREATURE); + registerEnum(FLAG_CHILDISOWNER); + registerEnum(FLAG_PATHFINDING); + registerEnum(FLAG_IGNOREFIELDDAMAGE); + registerEnum(FLAG_IGNORENOTMOVEABLE); + registerEnum(FLAG_IGNOREAUTOSTACK); // Use with itemType:getSlotPosition - registerEnum(SLOTP_WHEREEVER) - registerEnum(SLOTP_HEAD) - registerEnum(SLOTP_NECKLACE) - registerEnum(SLOTP_BACKPACK) - registerEnum(SLOTP_ARMOR) - registerEnum(SLOTP_RIGHT) - registerEnum(SLOTP_LEFT) - registerEnum(SLOTP_LEGS) - registerEnum(SLOTP_FEET) - registerEnum(SLOTP_RING) - registerEnum(SLOTP_AMMO) - registerEnum(SLOTP_DEPOT) - registerEnum(SLOTP_TWO_HAND) + registerEnum(SLOTP_WHEREEVER); + registerEnum(SLOTP_HEAD); + registerEnum(SLOTP_NECKLACE); + registerEnum(SLOTP_BACKPACK); + registerEnum(SLOTP_ARMOR); + registerEnum(SLOTP_RIGHT); + registerEnum(SLOTP_LEFT); + registerEnum(SLOTP_LEGS); + registerEnum(SLOTP_FEET); + registerEnum(SLOTP_RING); + registerEnum(SLOTP_AMMO); + registerEnum(SLOTP_DEPOT); + registerEnum(SLOTP_TWO_HAND); // Use with combat functions - registerEnum(ORIGIN_NONE) - registerEnum(ORIGIN_CONDITION) - registerEnum(ORIGIN_SPELL) - registerEnum(ORIGIN_MELEE) - registerEnum(ORIGIN_RANGED) + registerEnum(ORIGIN_NONE); + registerEnum(ORIGIN_CONDITION); + registerEnum(ORIGIN_SPELL); + registerEnum(ORIGIN_MELEE); + registerEnum(ORIGIN_RANGED); + registerEnum(ORIGIN_WAND); // Use with house:getAccessList, house:setAccessList - registerEnum(GUEST_LIST) - registerEnum(SUBOWNER_LIST) + registerEnum(GUEST_LIST); + registerEnum(SUBOWNER_LIST); // Use with npc:setSpeechBubble - registerEnum(SPEECHBUBBLE_NONE) - registerEnum(SPEECHBUBBLE_NORMAL) - registerEnum(SPEECHBUBBLE_TRADE) - registerEnum(SPEECHBUBBLE_QUEST) - registerEnum(SPEECHBUBBLE_QUESTTRADER) + registerEnum(SPEECHBUBBLE_NONE); + registerEnum(SPEECHBUBBLE_NORMAL); + registerEnum(SPEECHBUBBLE_TRADE); + registerEnum(SPEECHBUBBLE_QUEST); + registerEnum(SPEECHBUBBLE_COMPASS); + registerEnum(SPEECHBUBBLE_NORMAL2); + registerEnum(SPEECHBUBBLE_NORMAL3); + registerEnum(SPEECHBUBBLE_HIRELING); // Use with player:addMapMark - registerEnum(MAPMARK_TICK) - registerEnum(MAPMARK_QUESTION) - registerEnum(MAPMARK_EXCLAMATION) - registerEnum(MAPMARK_STAR) - registerEnum(MAPMARK_CROSS) - registerEnum(MAPMARK_TEMPLE) - registerEnum(MAPMARK_KISS) - registerEnum(MAPMARK_SHOVEL) - registerEnum(MAPMARK_SWORD) - registerEnum(MAPMARK_FLAG) - registerEnum(MAPMARK_LOCK) - registerEnum(MAPMARK_BAG) - registerEnum(MAPMARK_SKULL) - registerEnum(MAPMARK_DOLLAR) - registerEnum(MAPMARK_REDNORTH) - registerEnum(MAPMARK_REDSOUTH) - registerEnum(MAPMARK_REDEAST) - registerEnum(MAPMARK_REDWEST) - registerEnum(MAPMARK_GREENNORTH) - registerEnum(MAPMARK_GREENSOUTH) + registerEnum(MAPMARK_TICK); + registerEnum(MAPMARK_QUESTION); + registerEnum(MAPMARK_EXCLAMATION); + registerEnum(MAPMARK_STAR); + registerEnum(MAPMARK_CROSS); + registerEnum(MAPMARK_TEMPLE); + registerEnum(MAPMARK_KISS); + registerEnum(MAPMARK_SHOVEL); + registerEnum(MAPMARK_SWORD); + registerEnum(MAPMARK_FLAG); + registerEnum(MAPMARK_LOCK); + registerEnum(MAPMARK_BAG); + registerEnum(MAPMARK_SKULL); + registerEnum(MAPMARK_DOLLAR); + registerEnum(MAPMARK_REDNORTH); + registerEnum(MAPMARK_REDSOUTH); + registerEnum(MAPMARK_REDEAST); + registerEnum(MAPMARK_REDWEST); + registerEnum(MAPMARK_GREENNORTH); + registerEnum(MAPMARK_GREENSOUTH); // Use with Game.getReturnMessage - registerEnum(RETURNVALUE_NOERROR) - registerEnum(RETURNVALUE_NOTPOSSIBLE) - registerEnum(RETURNVALUE_NOTENOUGHROOM) - registerEnum(RETURNVALUE_PLAYERISPZLOCKED) - registerEnum(RETURNVALUE_PLAYERISNOTINVITED) - registerEnum(RETURNVALUE_CANNOTTHROW) - registerEnum(RETURNVALUE_THEREISNOWAY) - registerEnum(RETURNVALUE_DESTINATIONOUTOFREACH) - registerEnum(RETURNVALUE_CREATUREBLOCK) - registerEnum(RETURNVALUE_NOTMOVEABLE) - registerEnum(RETURNVALUE_DROPTWOHANDEDITEM) - registerEnum(RETURNVALUE_BOTHHANDSNEEDTOBEFREE) - registerEnum(RETURNVALUE_CANONLYUSEONEWEAPON) - registerEnum(RETURNVALUE_NEEDEXCHANGE) - registerEnum(RETURNVALUE_CANNOTBEDRESSED) - registerEnum(RETURNVALUE_PUTTHISOBJECTINYOURHAND) - registerEnum(RETURNVALUE_PUTTHISOBJECTINBOTHHANDS) - registerEnum(RETURNVALUE_TOOFARAWAY) - registerEnum(RETURNVALUE_FIRSTGODOWNSTAIRS) - registerEnum(RETURNVALUE_FIRSTGOUPSTAIRS) - registerEnum(RETURNVALUE_CONTAINERNOTENOUGHROOM) - registerEnum(RETURNVALUE_NOTENOUGHCAPACITY) - registerEnum(RETURNVALUE_CANNOTPICKUP) - registerEnum(RETURNVALUE_THISISIMPOSSIBLE) - registerEnum(RETURNVALUE_DEPOTISFULL) - registerEnum(RETURNVALUE_CREATUREDOESNOTEXIST) - registerEnum(RETURNVALUE_CANNOTUSETHISOBJECT) - registerEnum(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) - registerEnum(RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE) - registerEnum(RETURNVALUE_YOUAREALREADYTRADING) - registerEnum(RETURNVALUE_THISPLAYERISALREADYTRADING) - registerEnum(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT) - registerEnum(RETURNVALUE_DIRECTPLAYERSHOOT) - registerEnum(RETURNVALUE_NOTENOUGHLEVEL) - registerEnum(RETURNVALUE_NOTENOUGHMAGICLEVEL) - registerEnum(RETURNVALUE_NOTENOUGHMANA) - registerEnum(RETURNVALUE_NOTENOUGHSOUL) - registerEnum(RETURNVALUE_YOUAREEXHAUSTED) - registerEnum(RETURNVALUE_YOUCANNOTUSEOBJECTSTHATFAST) - registerEnum(RETURNVALUE_PLAYERISNOTREACHABLE) - registerEnum(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES) - registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE) - registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER) - registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE) - registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE) - registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE) - registerEnum(RETURNVALUE_YOUCANONLYUSEITONCREATURES) - registerEnum(RETURNVALUE_CREATUREISNOTREACHABLE) - registerEnum(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS) - registerEnum(RETURNVALUE_YOUNEEDPREMIUMACCOUNT) - registerEnum(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL) - registerEnum(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL) - registerEnum(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL) - registerEnum(RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE) - registerEnum(RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE) - registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE) - registerEnum(RETURNVALUE_YOUCANNOTLOGOUTHERE) - registerEnum(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL) - registerEnum(RETURNVALUE_CANNOTCONJUREITEMHERE) - registerEnum(RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS) - registerEnum(RETURNVALUE_NAMEISTOOAMBIGUOUS) - registerEnum(RETURNVALUE_CANONLYUSEONESHIELD) - registerEnum(RETURNVALUE_NOPARTYMEMBERSINRANGE) - registerEnum(RETURNVALUE_YOUARENOTTHEOWNER) - registerEnum(RETURNVALUE_TRADEPLAYERFARAWAY) - registerEnum(RETURNVALUE_YOUDONTOWNTHISHOUSE) - registerEnum(RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE) - registerEnum(RETURNVALUE_TRADEPLAYERHIGHESTBIDDER) - registerEnum(RETURNVALUE_YOUCANNOTTRADETHISHOUSE) - registerEnum(RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION) - - registerEnum(RELOAD_TYPE_ALL) - registerEnum(RELOAD_TYPE_ACTIONS) - registerEnum(RELOAD_TYPE_CHAT) - registerEnum(RELOAD_TYPE_CONFIG) - registerEnum(RELOAD_TYPE_CREATURESCRIPTS) - registerEnum(RELOAD_TYPE_EVENTS) - registerEnum(RELOAD_TYPE_GLOBAL) - registerEnum(RELOAD_TYPE_GLOBALEVENTS) - registerEnum(RELOAD_TYPE_ITEMS) - registerEnum(RELOAD_TYPE_MONSTERS) - registerEnum(RELOAD_TYPE_MOUNTS) - registerEnum(RELOAD_TYPE_MOVEMENTS) - registerEnum(RELOAD_TYPE_NPCS) - registerEnum(RELOAD_TYPE_QUESTS) - registerEnum(RELOAD_TYPE_RAIDS) - registerEnum(RELOAD_TYPE_SCRIPTS) - registerEnum(RELOAD_TYPE_SPELLS) - registerEnum(RELOAD_TYPE_TALKACTIONS) - registerEnum(RELOAD_TYPE_WEAPONS) - - registerEnum(ZONE_PROTECTION) - registerEnum(ZONE_NOPVP) - registerEnum(ZONE_PVP) - registerEnum(ZONE_NOLOGOUT) - registerEnum(ZONE_NORMAL) - - registerEnum(MAX_LOOTCHANCE) - - registerEnum(SPELL_INSTANT) - registerEnum(SPELL_RUNE) - - registerEnum(MONSTERS_EVENT_THINK) - registerEnum(MONSTERS_EVENT_APPEAR) - registerEnum(MONSTERS_EVENT_DISAPPEAR) - registerEnum(MONSTERS_EVENT_MOVE) - registerEnum(MONSTERS_EVENT_SAY) + registerEnum(RETURNVALUE_NOERROR); + registerEnum(RETURNVALUE_NOTPOSSIBLE); + registerEnum(RETURNVALUE_NOTENOUGHROOM); + registerEnum(RETURNVALUE_PLAYERISPZLOCKED); + registerEnum(RETURNVALUE_PLAYERISNOTINVITED); + registerEnum(RETURNVALUE_CANNOTTHROW); + registerEnum(RETURNVALUE_THEREISNOWAY); + registerEnum(RETURNVALUE_DESTINATIONOUTOFREACH); + registerEnum(RETURNVALUE_CREATUREBLOCK); + registerEnum(RETURNVALUE_NOTMOVEABLE); + registerEnum(RETURNVALUE_DROPTWOHANDEDITEM); + registerEnum(RETURNVALUE_BOTHHANDSNEEDTOBEFREE); + registerEnum(RETURNVALUE_CANONLYUSEONEWEAPON); + registerEnum(RETURNVALUE_NEEDEXCHANGE); + registerEnum(RETURNVALUE_CANNOTBEDRESSED); + registerEnum(RETURNVALUE_PUTTHISOBJECTINYOURHAND); + registerEnum(RETURNVALUE_PUTTHISOBJECTINBOTHHANDS); + registerEnum(RETURNVALUE_TOOFARAWAY); + registerEnum(RETURNVALUE_FIRSTGODOWNSTAIRS); + registerEnum(RETURNVALUE_FIRSTGOUPSTAIRS); + registerEnum(RETURNVALUE_CONTAINERNOTENOUGHROOM); + registerEnum(RETURNVALUE_NOTENOUGHCAPACITY); + registerEnum(RETURNVALUE_CANNOTPICKUP); + registerEnum(RETURNVALUE_THISISIMPOSSIBLE); + registerEnum(RETURNVALUE_DEPOTISFULL); + registerEnum(RETURNVALUE_CREATUREDOESNOTEXIST); + registerEnum(RETURNVALUE_CANNOTUSETHISOBJECT); + registerEnum(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE); + registerEnum(RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE); + registerEnum(RETURNVALUE_YOUAREALREADYTRADING); + registerEnum(RETURNVALUE_THISPLAYERISALREADYTRADING); + registerEnum(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT); + registerEnum(RETURNVALUE_DIRECTPLAYERSHOOT); + registerEnum(RETURNVALUE_NOTENOUGHLEVEL); + registerEnum(RETURNVALUE_NOTENOUGHMAGICLEVEL); + registerEnum(RETURNVALUE_NOTENOUGHMANA); + registerEnum(RETURNVALUE_NOTENOUGHSOUL); + registerEnum(RETURNVALUE_YOUAREEXHAUSTED); + registerEnum(RETURNVALUE_YOUCANNOTUSEOBJECTSTHATFAST); + registerEnum(RETURNVALUE_PLAYERISNOTREACHABLE); + registerEnum(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); + registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); + registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER); + registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE); + registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE); + registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE); + registerEnum(RETURNVALUE_YOUCANONLYUSEITONCREATURES); + registerEnum(RETURNVALUE_CREATUREISNOTREACHABLE); + registerEnum(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); + registerEnum(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); + registerEnum(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL); + registerEnum(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL); + registerEnum(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); + registerEnum(RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE); + registerEnum(RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE); + registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE); + registerEnum(RETURNVALUE_YOUCANNOTLOGOUTHERE); + registerEnum(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL); + registerEnum(RETURNVALUE_CANNOTCONJUREITEMHERE); + registerEnum(RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS); + registerEnum(RETURNVALUE_NAMEISTOOAMBIGUOUS); + registerEnum(RETURNVALUE_CANONLYUSEONESHIELD); + registerEnum(RETURNVALUE_NOPARTYMEMBERSINRANGE); + registerEnum(RETURNVALUE_YOUARENOTTHEOWNER); + registerEnum(RETURNVALUE_TRADEPLAYERFARAWAY); + registerEnum(RETURNVALUE_YOUDONTOWNTHISHOUSE); + registerEnum(RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE); + registerEnum(RETURNVALUE_TRADEPLAYERHIGHESTBIDDER); + registerEnum(RETURNVALUE_YOUCANNOTTRADETHISHOUSE); + registerEnum(RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION); + registerEnum(RETURNVALUE_YOUCANNOTUSETHISBED); + + registerEnum(RELOAD_TYPE_ALL); + registerEnum(RELOAD_TYPE_ACTIONS); + registerEnum(RELOAD_TYPE_CHAT); + registerEnum(RELOAD_TYPE_CONFIG); + registerEnum(RELOAD_TYPE_CREATURESCRIPTS); + registerEnum(RELOAD_TYPE_EVENTS); + registerEnum(RELOAD_TYPE_GLOBAL); + registerEnum(RELOAD_TYPE_GLOBALEVENTS); + registerEnum(RELOAD_TYPE_ITEMS); + registerEnum(RELOAD_TYPE_MONSTERS); + registerEnum(RELOAD_TYPE_MOUNTS); + registerEnum(RELOAD_TYPE_MOVEMENTS); + registerEnum(RELOAD_TYPE_NPCS); + registerEnum(RELOAD_TYPE_QUESTS); + registerEnum(RELOAD_TYPE_RAIDS); + registerEnum(RELOAD_TYPE_SCRIPTS); + registerEnum(RELOAD_TYPE_SPELLS); + registerEnum(RELOAD_TYPE_TALKACTIONS); + registerEnum(RELOAD_TYPE_WEAPONS); + + registerEnum(ZONE_PROTECTION); + registerEnum(ZONE_NOPVP); + registerEnum(ZONE_PVP); + registerEnum(ZONE_NOLOGOUT); + registerEnum(ZONE_NORMAL); + + registerEnum(MAX_LOOTCHANCE); + + registerEnum(SPELL_INSTANT); + registerEnum(SPELL_RUNE); + + registerEnum(MONSTERS_EVENT_THINK); + registerEnum(MONSTERS_EVENT_APPEAR); + registerEnum(MONSTERS_EVENT_DISAPPEAR); + registerEnum(MONSTERS_EVENT_MOVE); + registerEnum(MONSTERS_EVENT_SAY); + + registerEnum(DECAYING_FALSE); + registerEnum(DECAYING_TRUE); + registerEnum(DECAYING_PENDING); // _G registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); @@ -1910,89 +2081,90 @@ void LuaScriptInterface::registerFunctions() // configKeys registerTable("configKeys"); - registerEnumIn("configKeys", ConfigManager::ALLOW_CHANGEOUTFIT) - registerEnumIn("configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT) - registerEnumIn("configKeys", ConfigManager::AIMBOT_HOTKEY_ENABLED) - registerEnumIn("configKeys", ConfigManager::REMOVE_RUNE_CHARGES) - registerEnumIn("configKeys", ConfigManager::REMOVE_WEAPON_AMMO) - registerEnumIn("configKeys", ConfigManager::REMOVE_WEAPON_CHARGES) - registerEnumIn("configKeys", ConfigManager::REMOVE_POTION_CHARGES) - registerEnumIn("configKeys", ConfigManager::EXPERIENCE_FROM_PLAYERS) - registerEnumIn("configKeys", ConfigManager::FREE_PREMIUM) - registerEnumIn("configKeys", ConfigManager::REPLACE_KICK_ON_LOGIN) - registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES) - registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS) - registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE) - registerEnumIn("configKeys", ConfigManager::MARKET_PREMIUM) - registerEnumIn("configKeys", ConfigManager::EMOTE_SPELLS) - registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM) - registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS) - registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS) - registerEnumIn("configKeys", ConfigManager::CLASSIC_EQUIPMENT_SLOTS) - registerEnumIn("configKeys", ConfigManager::CLASSIC_ATTACK_SPEED) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_MESSAGE) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_DURATION) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLEAN_MAP) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLOSE) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_SHUTDOWN) - registerEnumIn("configKeys", ConfigManager::ONLINE_OFFLINE_CHARLIST) - registerEnumIn("configKeys", ConfigManager::LUA_ITEM_DESC) - - registerEnumIn("configKeys", ConfigManager::MAP_NAME) - registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD) - registerEnumIn("configKeys", ConfigManager::SERVER_NAME) - registerEnumIn("configKeys", ConfigManager::OWNER_NAME) - registerEnumIn("configKeys", ConfigManager::OWNER_EMAIL) - registerEnumIn("configKeys", ConfigManager::URL) - registerEnumIn("configKeys", ConfigManager::LOCATION) - registerEnumIn("configKeys", ConfigManager::IP) - registerEnumIn("configKeys", ConfigManager::MOTD) - registerEnumIn("configKeys", ConfigManager::WORLD_TYPE) - registerEnumIn("configKeys", ConfigManager::MYSQL_HOST) - registerEnumIn("configKeys", ConfigManager::MYSQL_USER) - registerEnumIn("configKeys", ConfigManager::MYSQL_PASS) - registerEnumIn("configKeys", ConfigManager::MYSQL_DB) - registerEnumIn("configKeys", ConfigManager::MYSQL_SOCK) - registerEnumIn("configKeys", ConfigManager::DEFAULT_PRIORITY) - registerEnumIn("configKeys", ConfigManager::MAP_AUTHOR) - - registerEnumIn("configKeys", ConfigManager::SQL_PORT) - registerEnumIn("configKeys", ConfigManager::MAX_PLAYERS) - registerEnumIn("configKeys", ConfigManager::PZ_LOCKED) - registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRANGE) - registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRADIUS) - registerEnumIn("configKeys", ConfigManager::REMOVE_ON_DESPAWN) - registerEnumIn("configKeys", ConfigManager::RATE_EXPERIENCE) - registerEnumIn("configKeys", ConfigManager::RATE_SKILL) - registerEnumIn("configKeys", ConfigManager::RATE_LOOT) - registerEnumIn("configKeys", ConfigManager::RATE_MAGIC) - registerEnumIn("configKeys", ConfigManager::RATE_SPAWN) - registerEnumIn("configKeys", ConfigManager::HOUSE_PRICE) - registerEnumIn("configKeys", ConfigManager::RED_DAILY_LIMIT) - registerEnumIn("configKeys", ConfigManager::RED_WEEKLY_LIMIT) - registerEnumIn("configKeys", ConfigManager::RED_MONTHLY_LIMIT) - registerEnumIn("configKeys", ConfigManager::RED_SKULL_LENGTH) - registerEnumIn("configKeys", ConfigManager::BLACK_DAILY_LIMIT) - registerEnumIn("configKeys", ConfigManager::BLACK_WEEKLY_LIMIT) - registerEnumIn("configKeys", ConfigManager::BLACK_MONTHLY_LIMIT) - registerEnumIn("configKeys", ConfigManager::BLACK_SKULL_LENGTH) - registerEnumIn("configKeys", ConfigManager::MAX_MESSAGEBUFFER) - registerEnumIn("configKeys", ConfigManager::ACTIONS_DELAY_INTERVAL) - registerEnumIn("configKeys", ConfigManager::EX_ACTIONS_DELAY_INTERVAL) - registerEnumIn("configKeys", ConfigManager::KICK_AFTER_MINUTES) - registerEnumIn("configKeys", ConfigManager::PROTECTION_LEVEL) - registerEnumIn("configKeys", ConfigManager::DEATH_LOSE_PERCENT) - registerEnumIn("configKeys", ConfigManager::STATUSQUERY_TIMEOUT) - registerEnumIn("configKeys", ConfigManager::WHITE_SKULL_TIME) - registerEnumIn("configKeys", ConfigManager::GAME_PORT) - registerEnumIn("configKeys", ConfigManager::LOGIN_PORT) - registerEnumIn("configKeys", ConfigManager::STATUS_PORT) - registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY) - registerEnumIn("configKeys", ConfigManager::MARKET_OFFER_DURATION) - registerEnumIn("configKeys", ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES) - registerEnumIn("configKeys", ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER) - registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE) - registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND) + registerEnumIn("configKeys", ConfigManager::ALLOW_CHANGEOUTFIT); + registerEnumIn("configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT); + registerEnumIn("configKeys", ConfigManager::AIMBOT_HOTKEY_ENABLED); + registerEnumIn("configKeys", ConfigManager::REMOVE_RUNE_CHARGES); + registerEnumIn("configKeys", ConfigManager::REMOVE_WEAPON_AMMO); + registerEnumIn("configKeys", ConfigManager::REMOVE_WEAPON_CHARGES); + registerEnumIn("configKeys", ConfigManager::REMOVE_POTION_CHARGES); + registerEnumIn("configKeys", ConfigManager::EXPERIENCE_FROM_PLAYERS); + registerEnumIn("configKeys", ConfigManager::FREE_PREMIUM); + registerEnumIn("configKeys", ConfigManager::REPLACE_KICK_ON_LOGIN); + registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES); + registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS); + registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE); + registerEnumIn("configKeys", ConfigManager::MARKET_PREMIUM); + registerEnumIn("configKeys", ConfigManager::EMOTE_SPELLS); + registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM); + registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS); + registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS); + registerEnumIn("configKeys", ConfigManager::CLASSIC_EQUIPMENT_SLOTS); + registerEnumIn("configKeys", ConfigManager::CLASSIC_ATTACK_SPEED); + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_MESSAGE); + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_DURATION); + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLEAN_MAP); + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLOSE); + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_SHUTDOWN); + registerEnumIn("configKeys", ConfigManager::ONLINE_OFFLINE_CHARLIST); + + registerEnumIn("configKeys", ConfigManager::MAP_NAME); + registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD); + registerEnumIn("configKeys", ConfigManager::SERVER_NAME); + registerEnumIn("configKeys", ConfigManager::OWNER_NAME); + registerEnumIn("configKeys", ConfigManager::OWNER_EMAIL); + registerEnumIn("configKeys", ConfigManager::URL); + registerEnumIn("configKeys", ConfigManager::LOCATION); + registerEnumIn("configKeys", ConfigManager::IP); + registerEnumIn("configKeys", ConfigManager::WORLD_TYPE); + registerEnumIn("configKeys", ConfigManager::MYSQL_HOST); + registerEnumIn("configKeys", ConfigManager::MYSQL_USER); + registerEnumIn("configKeys", ConfigManager::MYSQL_PASS); + registerEnumIn("configKeys", ConfigManager::MYSQL_DB); + registerEnumIn("configKeys", ConfigManager::MYSQL_SOCK); + registerEnumIn("configKeys", ConfigManager::DEFAULT_PRIORITY); + registerEnumIn("configKeys", ConfigManager::MAP_AUTHOR); + + registerEnumIn("configKeys", ConfigManager::SQL_PORT); + registerEnumIn("configKeys", ConfigManager::MAX_PLAYERS); + registerEnumIn("configKeys", ConfigManager::PZ_LOCKED); + registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRANGE); + registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRADIUS); + registerEnumIn("configKeys", ConfigManager::DEFAULT_WALKTOSPAWNRADIUS); + registerEnumIn("configKeys", ConfigManager::REMOVE_ON_DESPAWN); + registerEnumIn("configKeys", ConfigManager::RATE_EXPERIENCE); + registerEnumIn("configKeys", ConfigManager::RATE_SKILL); + registerEnumIn("configKeys", ConfigManager::RATE_LOOT); + registerEnumIn("configKeys", ConfigManager::RATE_MAGIC); + registerEnumIn("configKeys", ConfigManager::RATE_SPAWN); + registerEnumIn("configKeys", ConfigManager::HOUSE_PRICE); + registerEnumIn("configKeys", ConfigManager::RED_DAILY_LIMIT); + registerEnumIn("configKeys", ConfigManager::RED_WEEKLY_LIMIT); + registerEnumIn("configKeys", ConfigManager::RED_MONTHLY_LIMIT); + registerEnumIn("configKeys", ConfigManager::RED_SKULL_LENGTH); + registerEnumIn("configKeys", ConfigManager::BLACK_DAILY_LIMIT); + registerEnumIn("configKeys", ConfigManager::BLACK_WEEKLY_LIMIT); + registerEnumIn("configKeys", ConfigManager::BLACK_MONTHLY_LIMIT); + registerEnumIn("configKeys", ConfigManager::BLACK_SKULL_LENGTH); + registerEnumIn("configKeys", ConfigManager::MAX_MESSAGEBUFFER); + registerEnumIn("configKeys", ConfigManager::ACTIONS_DELAY_INTERVAL); + registerEnumIn("configKeys", ConfigManager::EX_ACTIONS_DELAY_INTERVAL); + registerEnumIn("configKeys", ConfigManager::KICK_AFTER_MINUTES); + registerEnumIn("configKeys", ConfigManager::PROTECTION_LEVEL); + registerEnumIn("configKeys", ConfigManager::DEATH_LOSE_PERCENT); + registerEnumIn("configKeys", ConfigManager::STATUSQUERY_TIMEOUT); + registerEnumIn("configKeys", ConfigManager::WHITE_SKULL_TIME); + registerEnumIn("configKeys", ConfigManager::GAME_PORT); + registerEnumIn("configKeys", ConfigManager::LOGIN_PORT); + registerEnumIn("configKeys", ConfigManager::STATUS_PORT); + registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY); + registerEnumIn("configKeys", ConfigManager::MARKET_OFFER_DURATION); + registerEnumIn("configKeys", ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES); + registerEnumIn("configKeys", ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER); + registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE); + registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND); + registerEnumIn("configKeys", ConfigManager::PLAYER_CONSOLE_LOGS); + registerEnumIn("configKeys", ConfigManager::TWO_FACTOR_AUTH); // os registerMethod("os", "mtime", LuaScriptInterface::luaSystemTime); @@ -2009,13 +2181,19 @@ void LuaScriptInterface::registerFunctions() registerMethod("Game", "loadMap", LuaScriptInterface::luaGameLoadMap); registerMethod("Game", "getExperienceStage", LuaScriptInterface::luaGameGetExperienceStage); + registerMethod("Game", "getExperienceForLevel", LuaScriptInterface::luaGameGetExperienceForLevel); registerMethod("Game", "getMonsterCount", LuaScriptInterface::luaGameGetMonsterCount); registerMethod("Game", "getPlayerCount", LuaScriptInterface::luaGameGetPlayerCount); registerMethod("Game", "getNpcCount", LuaScriptInterface::luaGameGetNpcCount); registerMethod("Game", "getMonsterTypes", LuaScriptInterface::luaGameGetMonsterTypes); + registerMethod("Game", "getCurrencyItems", LuaScriptInterface::luaGameGetCurrencyItems); + registerMethod("Game", "getItemTypeByClientId", LuaScriptInterface::luaGameGetItemTypeByClientId); + registerMethod("Game", "getMountIdByLookType", LuaScriptInterface::luaGameGetMountIdByLookType); registerMethod("Game", "getTowns", LuaScriptInterface::luaGameGetTowns); registerMethod("Game", "getHouses", LuaScriptInterface::luaGameGetHouses); + registerMethod("Game", "getOutfits", LuaScriptInterface::luaGameGetOutfits); + registerMethod("Game", "getMounts", LuaScriptInterface::luaGameGetMounts); registerMethod("Game", "getGameState", LuaScriptInterface::luaGameGetGameState); registerMethod("Game", "setGameState", LuaScriptInterface::luaGameSetGameState); @@ -2023,6 +2201,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Game", "getWorldType", LuaScriptInterface::luaGameGetWorldType); registerMethod("Game", "setWorldType", LuaScriptInterface::luaGameSetWorldType); + registerMethod("Game", "getItemAttributeByName", LuaScriptInterface::luaGameGetItemAttributeByName); registerMethod("Game", "getReturnMessage", LuaScriptInterface::luaGameGetReturnMessage); registerMethod("Game", "createItem", LuaScriptInterface::luaGameCreateItem); @@ -2038,6 +2217,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Game", "reload", LuaScriptInterface::luaGameReload); + registerMethod("Game", "getAccountStorageValue", LuaScriptInterface::luaGameGetAccountStorageValue); + registerMethod("Game", "setAccountStorageValue", LuaScriptInterface::luaGameSetAccountStorageValue); + registerMethod("Game", "saveAccountStorageValues", LuaScriptInterface::luaGameSaveAccountStorageValues); + // Variant registerClass("Variant", "", LuaScriptInterface::luaVariantCreate); @@ -2185,6 +2368,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "getCharges", LuaScriptInterface::luaItemGetCharges); registerMethod("Item", "getFluidType", LuaScriptInterface::luaItemGetFluidType); registerMethod("Item", "getWeight", LuaScriptInterface::luaItemGetWeight); + registerMethod("Item", "getWorth", LuaScriptInterface::luaItemGetWorth); registerMethod("Item", "getSubType", LuaScriptInterface::luaItemGetSubType); @@ -2207,7 +2391,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "transform", LuaScriptInterface::luaItemTransform); registerMethod("Item", "decay", LuaScriptInterface::luaItemDecay); - registerMethod("Item", "getDescription", LuaScriptInterface::luaItemGetDescription); registerMethod("Item", "getSpecialDescription", LuaScriptInterface::luaItemGetSpecialDescription); registerMethod("Item", "hasProperty", LuaScriptInterface::luaItemHasProperty); @@ -2216,6 +2399,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "setStoreItem", LuaScriptInterface::luaItemSetStoreItem); registerMethod("Item", "isStoreItem", LuaScriptInterface::luaItemIsStoreItem); + registerMethod("Item", "setReflect", LuaScriptInterface::luaItemSetReflect); + registerMethod("Item", "getReflect", LuaScriptInterface::luaItemGetReflect); + + registerMethod("Item", "setBoostPercent", LuaScriptInterface::luaItemSetBoostPercent); + registerMethod("Item", "getBoostPercent", LuaScriptInterface::luaItemGetBoostPercent); + // Container registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); registerMetaMethod("Container", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2223,7 +2412,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Container", "getSize", LuaScriptInterface::luaContainerGetSize); registerMethod("Container", "getCapacity", LuaScriptInterface::luaContainerGetCapacity); registerMethod("Container", "getEmptySlots", LuaScriptInterface::luaContainerGetEmptySlots); - registerMethod("Container", "getContentDescription", LuaScriptInterface::luaContainerGetContentDescription); registerMethod("Container", "getItems", LuaScriptInterface::luaContainerGetItems); registerMethod("Container", "getItemHoldingCount", LuaScriptInterface::luaContainerGetItemHoldingCount); registerMethod("Container", "getItemCountById", LuaScriptInterface::luaContainerGetItemCountById); @@ -2241,6 +2429,17 @@ void LuaScriptInterface::registerFunctions() registerMethod("Teleport", "getDestination", LuaScriptInterface::luaTeleportGetDestination); registerMethod("Teleport", "setDestination", LuaScriptInterface::luaTeleportSetDestination); + // Podium + registerClass("Podium", "Item", LuaScriptInterface::luaPodiumCreate); + registerMetaMethod("Podium", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Podium", "getOutfit", LuaScriptInterface::luaPodiumGetOutfit); + registerMethod("Podium", "setOutfit", LuaScriptInterface::luaPodiumSetOutfit); + registerMethod("Podium", "hasFlag", LuaScriptInterface::luaPodiumHasFlag); + registerMethod("Podium", "setFlag", LuaScriptInterface::luaPodiumSetFlag); + registerMethod("Podium", "getDirection", LuaScriptInterface::luaPodiumGetDirection); + registerMethod("Podium", "setDirection", LuaScriptInterface::luaPodiumSetDirection); + // Creature registerClass("Creature", "", LuaScriptInterface::luaCreatureCreate); registerMetaMethod("Creature", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2258,6 +2457,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "canSee", LuaScriptInterface::luaCreatureCanSee); registerMethod("Creature", "canSeeCreature", LuaScriptInterface::luaCreatureCanSeeCreature); + registerMethod("Creature", "canSeeGhostMode", LuaScriptInterface::luaCreatureCanSeeGhostMode); + registerMethod("Creature", "canSeeInvisibility", LuaScriptInterface::luaCreatureCanSeeInvisibility); registerMethod("Creature", "getParent", LuaScriptInterface::luaCreatureGetParent); @@ -2362,6 +2563,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "setMaxMana", LuaScriptInterface::luaPlayerSetMaxMana); registerMethod("Player", "getManaSpent", LuaScriptInterface::luaPlayerGetManaSpent); registerMethod("Player", "addManaSpent", LuaScriptInterface::luaPlayerAddManaSpent); + registerMethod("Player", "removeManaSpent", LuaScriptInterface::luaPlayerRemoveManaSpent); registerMethod("Player", "getBaseMaxHealth", LuaScriptInterface::luaPlayerGetBaseMaxHealth); registerMethod("Player", "getBaseMaxMana", LuaScriptInterface::luaPlayerGetBaseMaxMana); @@ -2371,6 +2573,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getSkillPercent", LuaScriptInterface::luaPlayerGetSkillPercent); registerMethod("Player", "getSkillTries", LuaScriptInterface::luaPlayerGetSkillTries); registerMethod("Player", "addSkillTries", LuaScriptInterface::luaPlayerAddSkillTries); + registerMethod("Player", "removeSkillTries", LuaScriptInterface::luaPlayerRemoveSkillTries); registerMethod("Player", "getSpecialSkill", LuaScriptInterface::luaPlayerGetSpecialSkill); registerMethod("Player", "addSpecialSkill", LuaScriptInterface::luaPlayerAddSpecialSkill); @@ -2423,6 +2626,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "addItem", LuaScriptInterface::luaPlayerAddItem); registerMethod("Player", "addItemEx", LuaScriptInterface::luaPlayerAddItemEx); registerMethod("Player", "removeItem", LuaScriptInterface::luaPlayerRemoveItem); + registerMethod("Player", "sendSupplyUsed", LuaScriptInterface::luaPlayerSendSupplyUsed); registerMethod("Player", "getMoney", LuaScriptInterface::luaPlayerGetMoney); registerMethod("Player", "addMoney", LuaScriptInterface::luaPlayerAddMoney); @@ -2448,6 +2652,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "canWearOutfit", LuaScriptInterface::luaPlayerCanWearOutfit); registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow); + registerMethod("Player", "sendEditPodium", LuaScriptInterface::luaPlayerSendEditPodium); + registerMethod("Player", "addMount", LuaScriptInterface::luaPlayerAddMount); registerMethod("Player", "removeMount", LuaScriptInterface::luaPlayerRemoveMount); registerMethod("Player", "hasMount", LuaScriptInterface::luaPlayerHasMount); @@ -2493,6 +2699,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getStoreInbox", LuaScriptInterface::luaPlayerGetStoreInbox); + registerMethod("Player", "isNearDepotBox", LuaScriptInterface::luaPlayerIsNearDepotBox); + + registerMethod("Player", "getIdleTime", LuaScriptInterface::luaPlayerGetIdleTime); + // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2501,6 +2711,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Monster", "getType", LuaScriptInterface::luaMonsterGetType); + registerMethod("Monster", "rename", LuaScriptInterface::luaMonsterRename); + registerMethod("Monster", "getSpawnPosition", LuaScriptInterface::luaMonsterGetSpawnPosition); registerMethod("Monster", "isInSpawnRange", LuaScriptInterface::luaMonsterIsInSpawnRange); @@ -2524,6 +2736,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Monster", "selectTarget", LuaScriptInterface::luaMonsterSelectTarget); registerMethod("Monster", "searchTarget", LuaScriptInterface::luaMonsterSearchTarget); + registerMethod("Monster", "isWalkingToSpawn", LuaScriptInterface::luaMonsterIsWalkingToSpawn); + registerMethod("Monster", "walkToSpawn", LuaScriptInterface::luaMonsterWalkToSpawn); + // Npc registerClass("Npc", "Creature", LuaScriptInterface::luaNpcCreate); registerMetaMethod("Npc", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2593,6 +2808,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Vocation", "getDemotion", LuaScriptInterface::luaVocationGetDemotion); registerMethod("Vocation", "getPromotion", LuaScriptInterface::luaVocationGetPromotion); + registerMethod("Vocation", "allowsPvp", LuaScriptInterface::luaVocationAllowsPvp); + // Town registerClass("Town", "", LuaScriptInterface::luaTownCreate); registerMetaMethod("Town", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2609,8 +2826,17 @@ void LuaScriptInterface::registerFunctions() registerMethod("House", "getName", LuaScriptInterface::luaHouseGetName); registerMethod("House", "getTown", LuaScriptInterface::luaHouseGetTown); registerMethod("House", "getExitPosition", LuaScriptInterface::luaHouseGetExitPosition); + registerMethod("House", "getRent", LuaScriptInterface::luaHouseGetRent); + registerMethod("House", "setRent", LuaScriptInterface::luaHouseSetRent); + registerMethod("House", "getPaidUntil", LuaScriptInterface::luaHouseGetPaidUntil); + registerMethod("House", "setPaidUntil", LuaScriptInterface::luaHouseSetPaidUntil); + + registerMethod("House", "getPayRentWarnings", LuaScriptInterface::luaHouseGetPayRentWarnings); + registerMethod("House", "setPayRentWarnings", LuaScriptInterface::luaHouseSetPayRentWarnings); + + registerMethod("House", "getOwnerName", LuaScriptInterface::luaHouseGetOwnerName); registerMethod("House", "getOwnerGuid", LuaScriptInterface::luaHouseGetOwnerGuid); registerMethod("House", "setOwnerGuid", LuaScriptInterface::luaHouseSetOwnerGuid); registerMethod("House", "startTrade", LuaScriptInterface::luaHouseStartTrade); @@ -2632,6 +2858,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("House", "kickPlayer", LuaScriptInterface::luaHouseKickPlayer); + registerMethod("House", "save", LuaScriptInterface::luaHouseSave); + // ItemType registerClass("ItemType", "", LuaScriptInterface::luaItemTypeCreate); registerMetaMethod("ItemType", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2665,11 +2893,13 @@ void LuaScriptInterface::registerFunctions() registerMethod("ItemType", "getFluidSource", LuaScriptInterface::luaItemTypeGetFluidSource); registerMethod("ItemType", "getCapacity", LuaScriptInterface::luaItemTypeGetCapacity); registerMethod("ItemType", "getWeight", LuaScriptInterface::luaItemTypeGetWeight); + registerMethod("ItemType", "getWorth", LuaScriptInterface::luaItemTypeGetWorth); registerMethod("ItemType", "getHitChance", LuaScriptInterface::luaItemTypeGetHitChance); registerMethod("ItemType", "getShootRange", LuaScriptInterface::luaItemTypeGetShootRange); registerMethod("ItemType", "getAttack", LuaScriptInterface::luaItemTypeGetAttack); + registerMethod("ItemType", "getAttackSpeed", LuaScriptInterface::luaItemTypeGetAttackSpeed); registerMethod("ItemType", "getDefense", LuaScriptInterface::luaItemTypeGetDefense); registerMethod("ItemType", "getExtraDefense", LuaScriptInterface::luaItemTypeGetExtraDefense); registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor); @@ -2685,6 +2915,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel); registerMethod("ItemType", "getAmmoType", LuaScriptInterface::luaItemTypeGetAmmoType); registerMethod("ItemType", "getCorpseType", LuaScriptInterface::luaItemTypeGetCorpseType); + registerMethod("ItemType", "getClassification", LuaScriptInterface::luaItemTypeGetClassification); registerMethod("ItemType", "getAbilities", LuaScriptInterface::luaItemTypeGetAbilities); @@ -2696,9 +2927,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("ItemType", "getWieldInfo", LuaScriptInterface::luaItemTypeGetWieldInfo); registerMethod("ItemType", "getDuration", LuaScriptInterface::luaItemTypeGetDuration); registerMethod("ItemType", "getLevelDoor", LuaScriptInterface::luaItemTypeGetLevelDoor); + registerMethod("ItemType", "getRuneSpellName", LuaScriptInterface::luaItemTypeGetRuneSpellName); registerMethod("ItemType", "getVocationString", LuaScriptInterface::luaItemTypeGetVocationString); registerMethod("ItemType", "getMinReqLevel", LuaScriptInterface::luaItemTypeGetMinReqLevel); registerMethod("ItemType", "getMinReqMagicLevel", LuaScriptInterface::luaItemTypeGetMinReqMagicLevel); + registerMethod("ItemType", "getMarketBuyStatistics", LuaScriptInterface::luaItemTypeGetMarketBuyStatistics); + registerMethod("ItemType", "getMarketSellStatistics", LuaScriptInterface::luaItemTypeGetMarketSellStatistics); registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType); @@ -2707,8 +2941,12 @@ void LuaScriptInterface::registerFunctions() // Combat registerClass("Combat", "", LuaScriptInterface::luaCombatCreate); registerMetaMethod("Combat", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMetaMethod("Combat", "__gc", LuaScriptInterface::luaCombatDelete); + registerMethod("Combat", "delete", LuaScriptInterface::luaCombatDelete); registerMethod("Combat", "setParameter", LuaScriptInterface::luaCombatSetParameter); + registerMethod("Combat", "getParameter", LuaScriptInterface::luaCombatGetParameter); + registerMethod("Combat", "setFormula", LuaScriptInterface::luaCombatSetFormula); registerMethod("Combat", "setArea", LuaScriptInterface::luaCombatSetArea); @@ -2723,7 +2961,6 @@ void LuaScriptInterface::registerFunctions() registerClass("Condition", "", LuaScriptInterface::luaConditionCreate); registerMetaMethod("Condition", "__eq", LuaScriptInterface::luaUserdataCompare); registerMetaMethod("Condition", "__gc", LuaScriptInterface::luaConditionDelete); - registerMethod("Condition", "delete", LuaScriptInterface::luaConditionDelete); registerMethod("Condition", "getId", LuaScriptInterface::luaConditionGetId); registerMethod("Condition", "getSubId", LuaScriptInterface::luaConditionGetSubId); @@ -2737,6 +2974,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Condition", "setTicks", LuaScriptInterface::luaConditionSetTicks); registerMethod("Condition", "setParameter", LuaScriptInterface::luaConditionSetParameter); + registerMethod("Condition", "getParameter", LuaScriptInterface::luaConditionGetParameter); + registerMethod("Condition", "setFormula", LuaScriptInterface::luaConditionSetFormula); registerMethod("Condition", "setOutfit", LuaScriptInterface::luaConditionSetOutfit); @@ -2751,8 +2990,10 @@ void LuaScriptInterface::registerFunctions() registerMetaMethod("MonsterType", "__eq", LuaScriptInterface::luaUserdataCompare); registerMethod("MonsterType", "isAttackable", LuaScriptInterface::luaMonsterTypeIsAttackable); + registerMethod("MonsterType", "isChallengeable", LuaScriptInterface::luaMonsterTypeIsChallengeable); registerMethod("MonsterType", "isConvinceable", LuaScriptInterface::luaMonsterTypeIsConvinceable); registerMethod("MonsterType", "isSummonable", LuaScriptInterface::luaMonsterTypeIsSummonable); + registerMethod("MonsterType", "isIgnoringSpawnBlock", LuaScriptInterface::luaMonsterTypeIsIgnoringSpawnBlock); registerMethod("MonsterType", "isIllusionable", LuaScriptInterface::luaMonsterTypeIsIllusionable); registerMethod("MonsterType", "isHostile", LuaScriptInterface::luaMonsterTypeIsHostile); registerMethod("MonsterType", "isPushable", LuaScriptInterface::luaMonsterTypeIsPushable); @@ -2762,8 +3003,11 @@ void LuaScriptInterface::registerFunctions() registerMethod("MonsterType", "canPushItems", LuaScriptInterface::luaMonsterTypeCanPushItems); registerMethod("MonsterType", "canPushCreatures", LuaScriptInterface::luaMonsterTypeCanPushCreatures); - registerMethod("MonsterType", "name", LuaScriptInterface::luaMonsterTypeName); + registerMethod("MonsterType", "canWalkOnEnergy", LuaScriptInterface::luaMonsterTypeCanWalkOnEnergy); + registerMethod("MonsterType", "canWalkOnFire", LuaScriptInterface::luaMonsterTypeCanWalkOnFire); + registerMethod("MonsterType", "canWalkOnPoison", LuaScriptInterface::luaMonsterTypeCanWalkOnPoison); + registerMethod("MonsterType", "name", LuaScriptInterface::luaMonsterTypeName); registerMethod("MonsterType", "nameDescription", LuaScriptInterface::luaMonsterTypeNameDescription); registerMethod("MonsterType", "health", LuaScriptInterface::luaMonsterTypeHealth); @@ -2848,16 +3092,23 @@ void LuaScriptInterface::registerFunctions() registerMethod("MonsterSpell", "setCombatType", LuaScriptInterface::luaMonsterSpellSetCombatType); registerMethod("MonsterSpell", "setAttackValue", LuaScriptInterface::luaMonsterSpellSetAttackValue); registerMethod("MonsterSpell", "setNeedTarget", LuaScriptInterface::luaMonsterSpellSetNeedTarget); + registerMethod("MonsterSpell", "setNeedDirection", LuaScriptInterface::luaMonsterSpellSetNeedDirection); registerMethod("MonsterSpell", "setCombatLength", LuaScriptInterface::luaMonsterSpellSetCombatLength); registerMethod("MonsterSpell", "setCombatSpread", LuaScriptInterface::luaMonsterSpellSetCombatSpread); registerMethod("MonsterSpell", "setCombatRadius", LuaScriptInterface::luaMonsterSpellSetCombatRadius); + registerMethod("MonsterSpell", "setCombatRing", LuaScriptInterface::luaMonsterSpellSetCombatRing); registerMethod("MonsterSpell", "setConditionType", LuaScriptInterface::luaMonsterSpellSetConditionType); registerMethod("MonsterSpell", "setConditionDamage", LuaScriptInterface::luaMonsterSpellSetConditionDamage); - registerMethod("MonsterSpell", "setConditionSpeedChange", LuaScriptInterface::luaMonsterSpellSetConditionSpeedChange); + registerMethod("MonsterSpell", "setConditionSpeedChange", + LuaScriptInterface::luaMonsterSpellSetConditionSpeedChange); registerMethod("MonsterSpell", "setConditionDuration", LuaScriptInterface::luaMonsterSpellSetConditionDuration); - registerMethod("MonsterSpell", "setConditionTickInterval", LuaScriptInterface::luaMonsterSpellSetConditionTickInterval); + registerMethod("MonsterSpell", "setConditionDrunkenness", + LuaScriptInterface::luaMonsterSpellSetConditionDrunkenness); + registerMethod("MonsterSpell", "setConditionTickInterval", + LuaScriptInterface::luaMonsterSpellSetConditionTickInterval); registerMethod("MonsterSpell", "setCombatShootEffect", LuaScriptInterface::luaMonsterSpellSetCombatShootEffect); registerMethod("MonsterSpell", "setCombatEffect", LuaScriptInterface::luaMonsterSpellSetCombatEffect); + registerMethod("MonsterSpell", "setOutfit", LuaScriptInterface::luaMonsterSpellSetOutfit); // Party registerClass("Party", "", LuaScriptInterface::luaPartyCreate); @@ -2910,6 +3161,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Spell", "isSelfTarget", LuaScriptInterface::luaSpellSelfTarget); registerMethod("Spell", "isBlocking", LuaScriptInterface::luaSpellBlocking); registerMethod("Spell", "isAggressive", LuaScriptInterface::luaSpellAggressive); + registerMethod("Spell", "isPzLock", LuaScriptInterface::luaSpellPzLock); registerMethod("Spell", "vocation", LuaScriptInterface::luaSpellVocation); // only for InstantSpell @@ -2978,6 +3230,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("MoveEvent", "position", LuaScriptInterface::luaMoveEventPosition); registerMethod("MoveEvent", "premium", LuaScriptInterface::luaMoveEventPremium); registerMethod("MoveEvent", "vocation", LuaScriptInterface::luaMoveEventVocation); + registerMethod("MoveEvent", "tileItem", LuaScriptInterface::luaMoveEventTileItem); registerMethod("MoveEvent", "onEquip", LuaScriptInterface::luaMoveEventOnCallback); registerMethod("MoveEvent", "onDeEquip", LuaScriptInterface::luaMoveEventOnCallback); registerMethod("MoveEvent", "onStepIn", LuaScriptInterface::luaMoveEventOnCallback); @@ -3041,7 +3294,8 @@ void LuaScriptInterface::registerFunctions() #undef registerEnum #undef registerEnumIn -void LuaScriptInterface::registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction/* = nullptr*/) +void LuaScriptInterface::registerClass(const std::string& className, const std::string& baseClass, + lua_CFunction newFunction /* = nullptr*/) { // className = {} lua_newtable(luaState); @@ -3098,6 +3352,8 @@ void LuaScriptInterface::registerClass(const std::string& className, const std:: lua_pushnumber(luaState, LuaData_Container); } else if (className == "Teleport") { lua_pushnumber(luaState, LuaData_Teleport); + } else if (className == "Podium") { + lua_pushnumber(luaState, LuaData_Podium); } else if (className == "Player") { lua_pushnumber(luaState, LuaData_Player); } else if (className == "Monster") { @@ -3122,7 +3378,8 @@ void LuaScriptInterface::registerTable(const std::string& tableName) lua_setglobal(luaState, tableName.c_str()); } -void LuaScriptInterface::registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func) +void LuaScriptInterface::registerMethod(const std::string& globalName, const std::string& methodName, + lua_CFunction func) { // globalName.methodName = func lua_getglobal(luaState, globalName.c_str()); @@ -3133,7 +3390,8 @@ void LuaScriptInterface::registerMethod(const std::string& globalName, const std lua_pop(luaState, 1); } -void LuaScriptInterface::registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func) +void LuaScriptInterface::registerMetaMethod(const std::string& className, const std::string& methodName, + lua_CFunction func) { // className.metatable.methodName = func luaL_getmetatable(luaState, className.c_str()); @@ -3177,8 +3435,9 @@ void LuaScriptInterface::registerGlobalBoolean(const std::string& name, bool val int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) { - //doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) - //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + // doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) + // doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) Player* player = getPlayer(L, 1); if (!player) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); @@ -3196,7 +3455,7 @@ int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) auto parameters = lua_gettop(L); if (parameters > 4) { - //subtype already supplied, count then is the amount + // subtype already supplied, count then is the amount itemCount = std::max(1, count); } else if (it.hasSubType()) { if (it.stackable) { @@ -3239,7 +3498,7 @@ int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) lua_pushnumber(L, uid); return 1; } else { - //stackable item stacked with existing object, newItem will be released + // stackable item stacked with existing object, newItem will be released pushBoolean(L, false); return 1; } @@ -3252,14 +3511,14 @@ int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) int LuaScriptInterface::luaDebugPrint(lua_State* L) { - //debugPrint(text) + // debugPrint(text) reportErrorFunc(L, getString(L, -1)); return 0; } int LuaScriptInterface::luaGetWorldTime(lua_State* L) { - //getWorldTime() + // getWorldTime() int16_t time = g_game.getWorldTime(); lua_pushnumber(L, time); return 1; @@ -3267,7 +3526,7 @@ int LuaScriptInterface::luaGetWorldTime(lua_State* L) int LuaScriptInterface::luaGetWorldLight(lua_State* L) { - //getWorldLight() + // getWorldLight() LightInfo lightInfo = g_game.getWorldLightInfo(); lua_pushnumber(L, lightInfo.level); lua_pushnumber(L, lightInfo.color); @@ -3276,7 +3535,7 @@ int LuaScriptInterface::luaGetWorldLight(lua_State* L) int LuaScriptInterface::luaSetWorldLight(lua_State* L) { - //setWorldLight(level, color) + // setWorldLight(level, color) if (g_config.getBoolean(ConfigManager::DEFAULT_WORLD_LIGHT)) { pushBoolean(L, false); return 1; @@ -3292,7 +3551,7 @@ int LuaScriptInterface::luaSetWorldLight(lua_State* L) int LuaScriptInterface::luaGetWorldUpTime(lua_State* L) { - //getWorldUpTime() + // getWorldUpTime() uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; lua_pushnumber(L, uptime); return 1; @@ -3336,7 +3595,7 @@ bool LuaScriptInterface::getArea(lua_State* L, std::vector& vec, uint3 int LuaScriptInterface::luaCreateCombatArea(lua_State* L) { - //createCombatArea( {area}, {extArea} ) + // createCombatArea({area}, {extArea}) ScriptEnvironment* env = getScriptEnv(); if (env->getScriptId() != EVENT_ID_LOADING) { reportErrorFunc(L, "This function can only be used while loading the script."); @@ -3374,7 +3633,8 @@ int LuaScriptInterface::luaCreateCombatArea(lua_State* L) int LuaScriptInterface::luaDoAreaCombat(lua_State* L) { - //doAreaCombat(cid, type, pos, area, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = false[, ignoreResistances = false]]]]) + // doAreaCombat(cid, type, pos, area, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = + // false[, ignoreResistances = false]]]]) Creature* creature = getCreature(L, 1); if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); @@ -3410,7 +3670,8 @@ int LuaScriptInterface::luaDoAreaCombat(lua_State* L) int LuaScriptInterface::luaDoTargetCombat(lua_State* L) { - //doTargetCombat(cid, target, type, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = false[, ignoreResistances = false]]]]) + // doTargetCombat(cid, target, type, min, max, effect[, origin = ORIGIN_SPELL[, blockArmor = false[, blockShield = + // false[, ignoreResistances = false]]]]) Creature* creature = getCreature(L, 1); if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); @@ -3446,7 +3707,7 @@ int LuaScriptInterface::luaDoTargetCombat(lua_State* L) int LuaScriptInterface::luaDoChallengeCreature(lua_State* L) { - //doChallengeCreature(cid, target) + // doChallengeCreature(cid, target[, force = false]) Creature* creature = getCreature(L, 1); if (!creature) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); @@ -3461,21 +3722,21 @@ int LuaScriptInterface::luaDoChallengeCreature(lua_State* L) return 1; } - target->challengeCreature(creature); + target->challengeCreature(creature, getBoolean(L, 3, false)); pushBoolean(L, true); return 1; } int LuaScriptInterface::luaIsValidUID(lua_State* L) { - //isValidUID(uid) + // isValidUID(uid) pushBoolean(L, getScriptEnv()->getThingByUID(getNumber(L, -1)) != nullptr); return 1; } int LuaScriptInterface::luaIsDepot(lua_State* L) { - //isDepot(uid) + // isDepot(uid) Container* container = getScriptEnv()->getContainerByUID(getNumber(L, -1)); pushBoolean(L, container && container->getDepotLocker()); return 1; @@ -3483,8 +3744,8 @@ int LuaScriptInterface::luaIsDepot(lua_State* L) int LuaScriptInterface::luaIsMoveable(lua_State* L) { - //isMoveable(uid) - //isMovable(uid) + // isMoveable(uid) + // isMovable(uid) Thing* thing = getScriptEnv()->getThingByUID(getNumber(L, -1)); pushBoolean(L, thing && thing->isPushable()); return 1; @@ -3492,7 +3753,7 @@ int LuaScriptInterface::luaIsMoveable(lua_State* L) int LuaScriptInterface::luaDoAddContainerItem(lua_State* L) { - //doAddContainerItem(uid, itemid, count/subtype) + // doAddContainerItem(uid, itemid, count/subtype) uint32_t uid = getNumber(L, 1); ScriptEnvironment* env = getScriptEnv(); @@ -3544,7 +3805,7 @@ int LuaScriptInterface::luaDoAddContainerItem(lua_State* L) if (newItem->getParent()) { lua_pushnumber(L, env->addThing(newItem)); } else { - //stackable item stacked with existing object, newItem will be released + // stackable item stacked with existing object, newItem will be released pushBoolean(L, false); } return 1; @@ -3557,7 +3818,7 @@ int LuaScriptInterface::luaDoAddContainerItem(lua_State* L) int LuaScriptInterface::luaGetDepotId(lua_State* L) { - //getDepotId(uid) + // getDepotId(uid) uint32_t uid = getNumber(L, -1); Container* container = getScriptEnv()->getContainerByUID(uid); @@ -3580,7 +3841,7 @@ int LuaScriptInterface::luaGetDepotId(lua_State* L) int LuaScriptInterface::luaAddEvent(lua_State* L) { - //addEvent(callback, delay, ...) + // addEvent(callback, delay, ...) int parameters = lua_gettop(L); if (parameters < 2) { reportErrorFunc(L, fmt::format("Not enough parameters: {:d}.", parameters)); @@ -3600,7 +3861,8 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) return 1; } - if (g_config.getBoolean(ConfigManager::WARN_UNSAFE_SCRIPTS) || g_config.getBoolean(ConfigManager::CONVERT_UNSAFE_SCRIPTS)) { + if (g_config.getBoolean(ConfigManager::WARN_UNSAFE_SCRIPTS) || + g_config.getBoolean(ConfigManager::CONVERT_UNSAFE_SCRIPTS)) { std::vector> indexes; for (int i = 3; i <= parameters; ++i) { if (lua_getmetatable(L, i) == 0) { @@ -3650,7 +3912,8 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) switch (entry.second) { case LuaData_Item: case LuaData_Container: - case LuaData_Teleport: { + case LuaData_Teleport: + case LuaData_Podium: { lua_getglobal(L, "Item"); lua_getfield(L, -1, "getUniqueId"); break; @@ -3675,7 +3938,8 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) } LuaTimerEventDesc eventDesc; - eventDesc.parameters.reserve(parameters - 2); // safe to use -2 since we garanteed that there is at least two parameters + eventDesc.parameters.reserve(parameters - + 2); // safe to use -2 since we garanteed that there is at least two parameters for (int i = 0; i < parameters - 2; ++i) { eventDesc.parameters.push_back(luaL_ref(L, LUA_REGISTRYINDEX)); } @@ -3687,9 +3951,8 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) eventDesc.scriptId = getScriptEnv()->getScriptId(); auto& lastTimerEventId = g_luaEnvironment.lastEventTimerId; - eventDesc.eventId = g_scheduler.addEvent(createSchedulerTask( - delay, std::bind(&LuaEnvironment::executeTimerEvent, &g_luaEnvironment, lastTimerEventId) - )); + eventDesc.eventId = g_scheduler.addEvent( + createSchedulerTask(delay, [=]() { g_luaEnvironment.executeTimerEvent(lastTimerEventId); })); g_luaEnvironment.timerEvents.emplace(lastTimerEventId, std::move(eventDesc)); lua_pushnumber(L, lastTimerEventId++); @@ -3698,7 +3961,7 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) int LuaScriptInterface::luaStopEvent(lua_State* L) { - //stopEvent(eventid) + // stopEvent(eventid) uint32_t eventId = getNumber(L, 1); auto& timerEvents = g_luaEnvironment.timerEvents; @@ -3737,7 +4000,7 @@ int LuaScriptInterface::luaCleanMap(lua_State* L) int LuaScriptInterface::luaIsInWar(lua_State* L) { - //isInWar(cid, target) + // isInWar(cid, target) Player* player = getPlayer(L, 1); if (!player) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); @@ -3758,7 +4021,7 @@ int LuaScriptInterface::luaIsInWar(lua_State* L) int LuaScriptInterface::luaGetWaypointPositionByName(lua_State* L) { - //getWaypointPositionByName(name) + // getWaypointPositionByName(name) auto& waypoints = g_game.map.waypoints; auto it = waypoints.find(getString(L, -1)); @@ -3772,7 +4035,7 @@ int LuaScriptInterface::luaGetWaypointPositionByName(lua_State* L) int LuaScriptInterface::luaSendChannelMessage(lua_State* L) { - //sendChannelMessage(channelId, type, message) + // sendChannelMessage(channelId, type, message) uint32_t channelId = getNumber(L, 1); ChatChannel* channel = g_chat->getChannelById(channelId); if (!channel) { @@ -3789,7 +4052,7 @@ int LuaScriptInterface::luaSendChannelMessage(lua_State* L) int LuaScriptInterface::luaSendGuildChannelMessage(lua_State* L) { - //sendGuildChannelMessage(guildId, type, message) + // sendGuildChannelMessage(guildId, type, message) uint32_t guildId = getNumber(L, 1); ChatChannel* channel = g_chat->getGuildChannelById(guildId); if (!channel) { @@ -3806,7 +4069,7 @@ int LuaScriptInterface::luaSendGuildChannelMessage(lua_State* L) int LuaScriptInterface::luaIsScriptsInterface(lua_State* L) { - //isScriptsInterface() + // isScriptsInterface() if (getScriptEnv()->getScriptInterface() == &g_scripts->getScriptInterface()) { pushBoolean(L, true); } else { @@ -3816,32 +4079,30 @@ int LuaScriptInterface::luaIsScriptsInterface(lua_State* L) return 1; } -std::string LuaScriptInterface::escapeString(const std::string& string) +std::string LuaScriptInterface::escapeString(std::string s) { - std::string s = string; - replaceString(s, "\\", "\\\\"); - replaceString(s, "\"", "\\\""); - replaceString(s, "'", "\\'"); - replaceString(s, "[[", "\\[["); + boost::algorithm::replace_all(s, "\\", "\\\\"); + boost::algorithm::replace_all(s, "\"", "\\\""); + boost::algorithm::replace_all(s, "'", "\\'"); + boost::algorithm::replace_all(s, "[[", "\\[["); return s; } #ifndef LUAJIT_VERSION const luaL_Reg LuaScriptInterface::luaBitReg[] = { - //{"tobit", LuaScriptInterface::luaBitToBit}, - {"bnot", LuaScriptInterface::luaBitNot}, - {"band", LuaScriptInterface::luaBitAnd}, - {"bor", LuaScriptInterface::luaBitOr}, - {"bxor", LuaScriptInterface::luaBitXor}, - {"lshift", LuaScriptInterface::luaBitLeftShift}, - {"rshift", LuaScriptInterface::luaBitRightShift}, - //{"arshift", LuaScriptInterface::luaBitArithmeticalRightShift}, - //{"rol", LuaScriptInterface::luaBitRotateLeft}, - //{"ror", LuaScriptInterface::luaBitRotateRight}, - //{"bswap", LuaScriptInterface::luaBitSwapEndian}, - //{"tohex", LuaScriptInterface::luaBitToHex}, - {nullptr, nullptr} -}; + //{"tobit", LuaScriptInterface::luaBitToBit}, + {"bnot", LuaScriptInterface::luaBitNot}, + {"band", LuaScriptInterface::luaBitAnd}, + {"bor", LuaScriptInterface::luaBitOr}, + {"bxor", LuaScriptInterface::luaBitXor}, + {"lshift", LuaScriptInterface::luaBitLeftShift}, + {"rshift", LuaScriptInterface::luaBitRightShift}, + //{"arshift", LuaScriptInterface::luaBitArithmeticalRightShift}, + //{"rol", LuaScriptInterface::luaBitRotateLeft}, + //{"ror", LuaScriptInterface::luaBitRotateRight}, + //{"bswap", LuaScriptInterface::luaBitSwapEndian}, + //{"tohex", LuaScriptInterface::luaBitToHex}, + {nullptr, nullptr}}; int LuaScriptInterface::luaBitNot(lua_State* L) { @@ -3850,38 +4111,36 @@ int LuaScriptInterface::luaBitNot(lua_State* L) } #define MULTIOP(name, op) \ -int LuaScriptInterface::luaBit##name(lua_State* L) \ -{ \ - int n = lua_gettop(L); \ - uint32_t w = getNumber(L, -1); \ - for (int i = 1; i < n; ++i) \ - w op getNumber(L, i); \ - lua_pushnumber(L, w); \ - return 1; \ -} + int LuaScriptInterface::luaBit##name(lua_State* L) \ + { \ + int n = lua_gettop(L); \ + uint32_t w = getNumber(L, -1); \ + for (int i = 1; i < n; ++i) w op getNumber(L, i); \ + lua_pushnumber(L, w); \ + return 1; \ + } -MULTIOP(And, &= ) -MULTIOP(Or, |= ) -MULTIOP(Xor, ^= ) +MULTIOP(And, &=) +MULTIOP(Or, |=) +MULTIOP(Xor, ^=) #define SHIFTOP(name, op) \ -int LuaScriptInterface::luaBit##name(lua_State* L) \ -{ \ - uint32_t n1 = getNumber(L, 1), n2 = getNumber(L, 2); \ - lua_pushnumber(L, (n1 op n2)); \ - return 1; \ -} + int LuaScriptInterface::luaBit##name(lua_State* L) \ + { \ + uint32_t n1 = getNumber(L, 1), n2 = getNumber(L, 2); \ + lua_pushnumber(L, (n1 op n2)); \ + return 1; \ + } -SHIFTOP(LeftShift, << ) -SHIFTOP(RightShift, >> ) +SHIFTOP(LeftShift, <<) +SHIFTOP(RightShift, >>) #endif const luaL_Reg LuaScriptInterface::luaConfigManagerTable[] = { - {"getString", LuaScriptInterface::luaConfigManagerGetString}, - {"getNumber", LuaScriptInterface::luaConfigManagerGetNumber}, - {"getBoolean", LuaScriptInterface::luaConfigManagerGetBoolean}, - {nullptr, nullptr} -}; + {"getString", LuaScriptInterface::luaConfigManagerGetString}, + {"getNumber", LuaScriptInterface::luaConfigManagerGetNumber}, + {"getBoolean", LuaScriptInterface::luaConfigManagerGetBoolean}, + {nullptr, nullptr}}; int LuaScriptInterface::luaConfigManagerGetString(lua_State* L) { @@ -3902,16 +4161,15 @@ int LuaScriptInterface::luaConfigManagerGetBoolean(lua_State* L) } const luaL_Reg LuaScriptInterface::luaDatabaseTable[] = { - {"query", LuaScriptInterface::luaDatabaseExecute}, - {"asyncQuery", LuaScriptInterface::luaDatabaseAsyncExecute}, - {"storeQuery", LuaScriptInterface::luaDatabaseStoreQuery}, - {"asyncStoreQuery", LuaScriptInterface::luaDatabaseAsyncStoreQuery}, - {"escapeString", LuaScriptInterface::luaDatabaseEscapeString}, - {"escapeBlob", LuaScriptInterface::luaDatabaseEscapeBlob}, - {"lastInsertId", LuaScriptInterface::luaDatabaseLastInsertId}, - {"tableExists", LuaScriptInterface::luaDatabaseTableExists}, - {nullptr, nullptr} -}; + {"query", LuaScriptInterface::luaDatabaseExecute}, + {"asyncQuery", LuaScriptInterface::luaDatabaseAsyncExecute}, + {"storeQuery", LuaScriptInterface::luaDatabaseStoreQuery}, + {"asyncStoreQuery", LuaScriptInterface::luaDatabaseAsyncStoreQuery}, + {"escapeString", LuaScriptInterface::luaDatabaseEscapeString}, + {"escapeBlob", LuaScriptInterface::luaDatabaseEscapeBlob}, + {"lastInsertId", LuaScriptInterface::luaDatabaseLastInsertId}, + {"tableExists", LuaScriptInterface::luaDatabaseTableExists}, + {nullptr, nullptr}}; int LuaScriptInterface::luaDatabaseExecute(lua_State* L) { @@ -4019,13 +4277,9 @@ int LuaScriptInterface::luaDatabaseTableExists(lua_State* L) } const luaL_Reg LuaScriptInterface::luaResultTable[] = { - {"getNumber", LuaScriptInterface::luaResultGetNumber}, - {"getString", LuaScriptInterface::luaResultGetString}, - {"getStream", LuaScriptInterface::luaResultGetStream}, - {"next", LuaScriptInterface::luaResultNext}, - {"free", LuaScriptInterface::luaResultFree}, - {nullptr, nullptr} -}; + {"getNumber", LuaScriptInterface::luaResultGetNumber}, {"getString", LuaScriptInterface::luaResultGetString}, + {"getStream", LuaScriptInterface::luaResultGetStream}, {"next", LuaScriptInterface::luaResultNext}, + {"free", LuaScriptInterface::luaResultFree}, {nullptr, nullptr}}; int LuaScriptInterface::luaResultGetNumber(lua_State* L) { @@ -4148,23 +4402,25 @@ int LuaScriptInterface::luaTablePack(lua_State* L) { // table.pack(...) int i; - int n = lua_gettop(L); /* number of elements to pack */ + int n = lua_gettop(L); /* number of elements to pack */ lua_createtable(L, n, 1); /* create result table */ - lua_insert(L, 1); /* put it at index 1 */ - for (i = n; i >= 1; i--) /* assign elements */ + lua_insert(L, 1); /* put it at index 1 */ + for (i = n; i >= 1; i--) { /* assign elements */ lua_rawseti(L, 1, i); - if (luaL_callmeta(L, -1, "__index") != 0) { - lua_replace(L, -2); - } + } + if (luaL_callmeta(L, -1, "__index") != 0) { + lua_replace(L, -2); + } lua_pushinteger(L, n); - lua_setfield(L, 1, "n"); /* t.n = number of elements */ - return 1; /* return table */ + lua_setfield(L, 1, "n"); /* t.n = number of elements */ + return 1; /* return table */ } // Game int LuaScriptInterface::luaGameGetSpectators(lua_State* L) { - // Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY = 0[, maxRangeY = 0]]]]]]) + // Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY + // = 0[, maxRangeY = 0]]]]]]) const Position& position = getPosition(L, 1); bool multifloor = getBoolean(L, 2, false); bool onlyPlayers = getBoolean(L, 3, false); @@ -4210,8 +4466,7 @@ int LuaScriptInterface::luaGameLoadMap(lua_State* L) g_game.loadMap(path); } catch (const std::exception& e) { // FIXME: Should only catch some exceptions - std::cout << "[Error - LuaScriptInterface::luaGameLoadMap] Failed to load map: " - << e.what() << std::endl; + std::cout << "[Error - LuaScriptInterface::luaGameLoadMap] Failed to load map: " << e.what() << std::endl; } })); return 0; @@ -4225,6 +4480,18 @@ int LuaScriptInterface::luaGameGetExperienceStage(lua_State* L) return 1; } +int LuaScriptInterface::luaGameGetExperienceForLevel(lua_State* L) +{ + // Game.getExperienceForLevel(level) + const uint32_t level = getNumber(L, 1); + if (level == 0) { + lua_pushnumber(L, 0); + } else { + lua_pushnumber(L, Player::getExpForLevel(level)); + } + return 1; +} + int LuaScriptInterface::luaGameGetMonsterCount(lua_State* L) { // Game.getMonsterCount() @@ -4260,6 +4527,53 @@ int LuaScriptInterface::luaGameGetMonsterTypes(lua_State* L) return 1; } +int LuaScriptInterface::luaGameGetCurrencyItems(lua_State* L) +{ + // Game.getCurrencyItems() + const auto& currencyItems = Item::items.currencyItems; + size_t size = currencyItems.size(); + lua_createtable(L, size, 0); + + for (const auto& it : currencyItems) { + const ItemType& itemType = Item::items[it.second]; + pushUserdata(L, &itemType); + setMetatable(L, -1, "ItemType"); + lua_rawseti(L, -2, size--); + } + return 1; +} + +int LuaScriptInterface::luaGameGetItemTypeByClientId(lua_State* L) +{ + // Game.getItemTypeByClientId(clientId) + uint16_t spriteId = getNumber(L, 1); + const ItemType& itemType = Item::items.getItemIdByClientId(spriteId); + if (itemType.id != 0) { + pushUserdata(L, &itemType); + setMetatable(L, -1, "ItemType"); + } else { + lua_pushnil(L); + } + + return 1; +} + +int LuaScriptInterface::luaGameGetMountIdByLookType(lua_State* L) +{ + // Game.getMountIdByLookType(lookType) + Mount* mount = nullptr; + if (isNumber(L, 1)) { + mount = g_game.mounts.getMountByClientID(getNumber(L, 1)); + } + + if (mount) { + lua_pushnumber(L, mount->id); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaGameGetTowns(lua_State* L) { // Game.getTowns() @@ -4290,6 +4604,47 @@ int LuaScriptInterface::luaGameGetHouses(lua_State* L) return 1; } +int LuaScriptInterface::luaGameGetOutfits(lua_State* L) +{ + // Game.getOutfits(playerSex) + if (!isNumber(L, 1)) { + lua_pushnil(L); + return 1; + } + + PlayerSex_t playerSex = getNumber(L, 1); + if (playerSex > PLAYERSEX_LAST) { + lua_pushnil(L); + return 1; + } + + const auto& outfits = Outfits::getInstance().getOutfits(playerSex); + lua_createtable(L, outfits.size(), 0); + + int index = 0; + for (const auto& outfit : outfits) { + pushOutfit(L, &outfit); + lua_rawseti(L, -2, ++index); + } + + return 1; +} + +int LuaScriptInterface::luaGameGetMounts(lua_State* L) +{ + // Game.getMounts() + const auto& mounts = g_game.mounts.getMounts(); + lua_createtable(L, mounts.size(), 0); + + int index = 0; + for (const auto& mount : mounts) { + pushMount(L, &mount); + lua_rawseti(L, -2, ++index); + } + + return 1; +} + int LuaScriptInterface::luaGameGetGameState(lua_State* L) { // Game.getGameState() @@ -4330,6 +4685,13 @@ int LuaScriptInterface::luaGameGetReturnMessage(lua_State* L) return 1; } +int LuaScriptInterface::luaGameGetItemAttributeByName(lua_State* L) +{ + // Game.getItemAttributeByName(name) + lua_pushnumber(L, stringToItemAttribute(getString(L, 1))); + return 1; +} + int LuaScriptInterface::luaGameCreateItem(lua_State* L) { // Game.createItem(itemId[, count[, position]]) @@ -4419,7 +4781,7 @@ int LuaScriptInterface::luaGameCreateContainer(lua_State* L) int LuaScriptInterface::luaGameCreateMonster(lua_State* L) { - // Game.createMonster(monsterName, position[, extended = false[, force = false]]) + // Game.createMonster(monsterName, position[, extended = false[, force = false[, magicEffect = CONST_ME_TELEPORT]]]) Monster* monster = Monster::createMonster(getString(L, 1)); if (!monster) { lua_pushnil(L); @@ -4429,8 +4791,9 @@ int LuaScriptInterface::luaGameCreateMonster(lua_State* L) const Position& position = getPosition(L, 2); bool extended = getBoolean(L, 3, false); bool force = getBoolean(L, 4, false); + MagicEffectClasses magicEffect = getNumber(L, 5, CONST_ME_TELEPORT); if (g_events->eventMonsterOnSpawn(monster, position, false, true) || force) { - if (g_game.placeCreature(monster, position, extended, force)) { + if (g_game.placeCreature(monster, position, extended, force, magicEffect)) { pushUserdata(L, monster); setMetatable(L, -1, "Monster"); } else { @@ -4446,7 +4809,7 @@ int LuaScriptInterface::luaGameCreateMonster(lua_State* L) int LuaScriptInterface::luaGameCreateNpc(lua_State* L) { - // Game.createNpc(npcName, position[, extended = false[, force = false]]) + // Game.createNpc(npcName, position[, extended = false[, force = false[, magicEffect = CONST_ME_TELEPORT]]]) Npc* npc = Npc::createNpc(getString(L, 1)); if (!npc) { lua_pushnil(L); @@ -4456,7 +4819,8 @@ int LuaScriptInterface::luaGameCreateNpc(lua_State* L) const Position& position = getPosition(L, 2); bool extended = getBoolean(L, 3, false); bool force = getBoolean(L, 4, false); - if (g_game.placeCreature(npc, position, extended, force)) { + MagicEffectClasses magicEffect = getNumber(L, 5, CONST_ME_TELEPORT); + if (g_game.placeCreature(npc, position, extended, force, magicEffect)) { pushUserdata(L, npc); setMetatable(L, -1, "Npc"); } else { @@ -4515,7 +4879,7 @@ int LuaScriptInterface::luaGameCreateMonsterType(lua_State* L) MonsterType* monsterType = g_monsters.getMonsterType(name, false); if (!monsterType) { - monsterType = &g_monsters.monsters[asLowerCaseString(name)]; + monsterType = &g_monsters.monsters[boost::algorithm::to_lower_copy(name)]; monsterType->name = name; monsterType->nameDescription = "a " + name; } else { @@ -4581,6 +4945,38 @@ int LuaScriptInterface::luaGameReload(lua_State* L) return 1; } +int LuaScriptInterface::luaGameGetAccountStorageValue(lua_State* L) +{ + // Game.getAccountStorageValue(accountId, key) + uint32_t accountId = getNumber(L, 1); + uint32_t key = getNumber(L, 2); + + lua_pushnumber(L, g_game.getAccountStorageValue(accountId, key)); + + return 1; +} + +int LuaScriptInterface::luaGameSetAccountStorageValue(lua_State* L) +{ + // Game.setAccountStorageValue(accountId, key, value) + uint32_t accountId = getNumber(L, 1); + uint32_t key = getNumber(L, 2); + int32_t value = getNumber(L, 3); + + g_game.setAccountStorageValue(accountId, key, value); + lua_pushboolean(L, true); + + return 1; +} + +int LuaScriptInterface::luaGameSaveAccountStorageValues(lua_State* L) +{ + // Game.saveAccountStorageValues() + lua_pushboolean(L, g_game.saveAccountStorageValues()); + + return 1; +} + // Variant int LuaScriptInterface::luaVariantCreate(lua_State* L) { @@ -4588,18 +4984,14 @@ int LuaScriptInterface::luaVariantCreate(lua_State* L) LuaVariant variant; if (isUserdata(L, 2)) { if (Thing* thing = getThing(L, 2)) { - variant.type = VARIANT_TARGETPOSITION; - variant.pos = thing->getPosition(); + variant.setTargetPosition(thing->getPosition()); } } else if (isTable(L, 2)) { - variant.type = VARIANT_POSITION; - variant.pos = getPosition(L, 2); + variant.setPosition(getPosition(L, 2)); } else if (isNumber(L, 2)) { - variant.type = VARIANT_NUMBER; - variant.number = getNumber(L, 2); + variant.setNumber(getNumber(L, 2)); } else if (isString(L, 2)) { - variant.type = VARIANT_STRING; - variant.text = getString(L, 2); + variant.setString(getString(L, 2)); } pushVariant(L, variant); return 1; @@ -4609,8 +5001,8 @@ int LuaScriptInterface::luaVariantGetNumber(lua_State* L) { // Variant:getNumber() const LuaVariant& variant = getVariant(L, 1); - if (variant.type == VARIANT_NUMBER) { - lua_pushnumber(L, variant.number); + if (variant.isNumber()) { + lua_pushnumber(L, variant.getNumber()); } else { lua_pushnumber(L, 0); } @@ -4621,8 +5013,8 @@ int LuaScriptInterface::luaVariantGetString(lua_State* L) { // Variant:getString() const LuaVariant& variant = getVariant(L, 1); - if (variant.type == VARIANT_STRING) { - pushString(L, variant.text); + if (variant.isString()) { + pushString(L, variant.getString()); } else { pushString(L, std::string()); } @@ -4633,8 +5025,10 @@ int LuaScriptInterface::luaVariantGetPosition(lua_State* L) { // Variant:getPosition() const LuaVariant& variant = getVariant(L, 1); - if (variant.type == VARIANT_POSITION || variant.type == VARIANT_TARGETPOSITION) { - pushPosition(L, variant.pos); + if (variant.isPosition()) { + pushPosition(L, variant.getPosition()); + } else if (variant.isTargetPosition()) { + pushPosition(L, variant.getTargetPosition()); } else { pushPosition(L, Position()); } @@ -4714,13 +5108,9 @@ int LuaScriptInterface::luaPositionGetDistance(lua_State* L) // position:getDistance(positionEx) const Position& positionEx = getPosition(L, 2); const Position& position = getPosition(L, 1); - lua_pushnumber(L, std::max( - std::max( - std::abs(Position::getDistanceX(position, positionEx)), - std::abs(Position::getDistanceY(position, positionEx)) - ), - std::abs(Position::getDistanceZ(position, positionEx)) - )); + lua_pushnumber(L, std::max(std::max(std::abs(Position::getDistanceX(position, positionEx)), + std::abs(Position::getDistanceY(position, positionEx))), + std::abs(Position::getDistanceZ(position, positionEx)))); return 1; } @@ -6271,6 +6661,18 @@ int LuaScriptInterface::luaItemGetWeight(lua_State* L) return 1; } +int LuaScriptInterface::luaItemGetWorth(lua_State* L) +{ + // item:getWorth() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getWorth()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemGetSubType(lua_State* L) { // item:getSubType() @@ -6464,7 +6866,8 @@ int LuaScriptInterface::luaItemRemoveAttribute(lua_State* L) return 1; } -int LuaScriptInterface::luaItemGetCustomAttribute(lua_State* L) { +int LuaScriptInterface::luaItemGetCustomAttribute(lua_State* L) +{ // item:getCustomAttribute(key) Item* item = getUserdata(L, 1); if (!item) { @@ -6490,7 +6893,8 @@ int LuaScriptInterface::luaItemGetCustomAttribute(lua_State* L) { return 1; } -int LuaScriptInterface::luaItemSetCustomAttribute(lua_State* L) { +int LuaScriptInterface::luaItemSetCustomAttribute(lua_State* L) +{ // item:setCustomAttribute(key, value) Item* item = getUserdata(L, 1); if (!item) { @@ -6530,7 +6934,8 @@ int LuaScriptInterface::luaItemSetCustomAttribute(lua_State* L) { return 1; } -int LuaScriptInterface::luaItemRemoveCustomAttribute(lua_State* L) { +int LuaScriptInterface::luaItemRemoveCustomAttribute(lua_State* L) +{ // item:removeCustomAttribute(key) Item* item = getUserdata(L, 1); if (!item) { @@ -6594,13 +6999,15 @@ int LuaScriptInterface::luaItemMoveTo(lua_State* L) return 1; } - uint32_t flags = getNumber(L, 3, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); + uint32_t flags = getNumber( + L, 3, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); if (item->getParent() == VirtualCylinder::virtualCylinder) { pushBoolean(L, g_game.internalAddItem(toCylinder, item, INDEX_WHEREEVER, flags) == RETURNVALUE_NOERROR); } else { Item* moveItem = nullptr; - ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, flags); + ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, + item->getItemCount(), &moveItem, flags); if (moveItem) { *itemPtr = moveItem; } @@ -6680,19 +7087,6 @@ int LuaScriptInterface::luaItemDecay(lua_State* L) return 1; } -int LuaScriptInterface::luaItemGetDescription(lua_State* L) -{ - // item:getDescription(distance) - Item* item = getUserdata(L, 1); - if (item) { - int32_t distance = getNumber(L, 2); - pushString(L, item->getDescription(distance)); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemGetSpecialDescription(lua_State* L) { // item:getSpecialDescription() @@ -6755,40 +7149,92 @@ int LuaScriptInterface::luaItemIsStoreItem(lua_State* L) return 1; } -// Container -int LuaScriptInterface::luaContainerCreate(lua_State* L) +int LuaScriptInterface::luaItemSetReflect(lua_State* L) { - // Container(uid) - uint32_t id = getNumber(L, 2); + // item:setReflect(combatType, reflect) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } - Container* container = getScriptEnv()->getContainerByUID(id); - if (container) { - pushUserdata(L, container); - setMetatable(L, -1, "Container"); + item->setReflect(getNumber(L, 2), getReflect(L, 3)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaItemGetReflect(lua_State* L) +{ + // item:getReflect(combatType[, total = true]) + Item* item = getUserdata(L, 1); + if (item) { + pushReflect(L, item->getReflect(getNumber(L, 2), getBoolean(L, 3, true))); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaContainerGetSize(lua_State* L) +int LuaScriptInterface::luaItemSetBoostPercent(lua_State* L) { - // container:getSize() - Container* container = getUserdata(L, 1); - if (container) { - lua_pushnumber(L, container->size()); - } else { + // item:setBoostPercent(combatType, percent) + Item* item = getUserdata(L, 1); + if (!item) { lua_pushnil(L); + return 1; } + + item->setBoostPercent(getNumber(L, 2), getNumber(L, 3)); + pushBoolean(L, true); return 1; } -int LuaScriptInterface::luaContainerGetCapacity(lua_State* L) +int LuaScriptInterface::luaItemGetBoostPercent(lua_State* L) { - // container:getCapacity() - Container* container = getUserdata(L, 1); - if (container) { - lua_pushnumber(L, container->capacity()); + // item:getBoostPercent(combatType[, total = true]) + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getBoostPercent(getNumber(L, 2), getBoolean(L, 3, true))); + } else { + lua_pushnil(L); + } + return 1; +} + +// Container +int LuaScriptInterface::luaContainerCreate(lua_State* L) +{ + // Container(uid) + uint32_t id = getNumber(L, 2); + + Container* container = getScriptEnv()->getContainerByUID(id); + if (container) { + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetSize(lua_State* L) +{ + // container:getSize() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetCapacity(lua_State* L) +{ + // container:getCapacity() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->capacity()); } else { lua_pushnil(L); } @@ -6976,18 +7422,6 @@ int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) return 1; } -int LuaScriptInterface::luaContainerGetContentDescription(lua_State* L) -{ - // container:getContentDescription() - Container* container = getUserdata(L, 1); - if (container) { - pushString(L, container->getContentDescription()); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaContainerGetItems(lua_State* L) { // container:getItems([recursive = false]) @@ -7052,6 +7486,104 @@ int LuaScriptInterface::luaTeleportSetDestination(lua_State* L) return 1; } +// Podium +int LuaScriptInterface::luaPodiumCreate(lua_State* L) +{ + // Podium(uid) + uint32_t id = getNumber(L, 2); + + Item* item = getScriptEnv()->getItemByUID(id); + if (item && item->getPodium()) { + pushUserdata(L, item); + setMetatable(L, -1, "Podium"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPodiumGetOutfit(lua_State* L) +{ + // podium:getOutfit() + const Podium* podium = getUserdata(L, 1); + if (podium) { + pushOutfit(L, podium->getOutfit()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPodiumSetOutfit(lua_State* L) +{ + // podium:setOutfit(outfit) + Podium* podium = getUserdata(L, 1); + if (podium) { + podium->setOutfit(getOutfit(L, 2)); + g_game.updatePodium(podium); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPodiumHasFlag(lua_State* L) +{ + // podium:hasFlag(flag) + Podium* podium = getUserdata(L, 1); + if (podium) { + PodiumFlags flag = getNumber(L, 2); + pushBoolean(L, podium->hasFlag(flag)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPodiumSetFlag(lua_State* L) +{ + // podium:setFlag(flag, value) + uint8_t value = getBoolean(L, 3); + PodiumFlags flag = getNumber(L, 2); + Podium* podium = getUserdata(L, 1); + + if (podium) { + podium->setFlagValue(flag, value); + g_game.updatePodium(podium); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPodiumGetDirection(lua_State* L) +{ + // podium:getDirection() + const Podium* podium = getUserdata(L, 1); + if (podium) { + lua_pushnumber(L, podium->getDirection()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPodiumSetDirection(lua_State* L) +{ + // podium:setDirection(direction) + Podium* podium = getUserdata(L, 1); + if (podium) { + podium->setDirection(getNumber(L, 2)); + g_game.updatePodium(podium); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + // Creature int LuaScriptInterface::luaCreatureCreate(lua_State* L) { @@ -7202,6 +7734,12 @@ int LuaScriptInterface::luaCreatureCanSeeCreature(lua_State* L) const Creature* creature = getUserdata(L, 1); if (creature) { const Creature* otherCreature = getCreature(L, 2); + if (!otherCreature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + pushBoolean(L, creature->canSeeCreature(otherCreature)); } else { lua_pushnil(L); @@ -7209,6 +7747,37 @@ int LuaScriptInterface::luaCreatureCanSeeCreature(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureCanSeeGhostMode(lua_State* L) +{ + // creature:canSeeGhostMode(creature) + const Creature* creature = getUserdata(L, 1); + if (creature) { + const Creature* otherCreature = getCreature(L, 2); + if (!otherCreature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + pushBoolean(L, creature->canSeeGhostMode(otherCreature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureCanSeeInvisibility(lua_State* L) +{ + // creature:canSeeInvisibility() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->canSeeInvisibility()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaCreatureGetParent(lua_State* L) { // creature:getParent() @@ -7276,8 +7845,7 @@ int LuaScriptInterface::luaCreatureSetTarget(lua_State* L) // creature:setTarget(target) Creature* creature = getUserdata(L, 1); if (creature) { - Creature* target = getCreature(L, 2); - pushBoolean(L, creature->setAttackedCreature(target)); + pushBoolean(L, creature->setAttackedCreature(getCreature(L, 2))); } else { lua_pushnil(L); } @@ -7308,8 +7876,7 @@ int LuaScriptInterface::luaCreatureSetFollowCreature(lua_State* L) // creature:setFollowCreature(followedCreature) Creature* creature = getUserdata(L, 1); if (creature) { - Creature* followCreature = getCreature(L, 2); - pushBoolean(L, creature->setFollowCreature(followCreature)); + pushBoolean(L, creature->setFollowCreature(getCreature(L, 2))); } else { lua_pushnil(L); } @@ -7346,7 +7913,14 @@ int LuaScriptInterface::luaCreatureSetMaster(lua_State* L) } pushBoolean(L, creature->setMaster(getCreature(L, 2))); - g_game.updateCreatureType(creature); + + // update summon icon + SpectatorVec spectators; + g_game.map.getSpectators(spectators, creature->getPosition(), true, true); + + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendUpdateTileCreature(creature); + } return 1; } @@ -7707,18 +8281,29 @@ int LuaScriptInterface::luaCreatureAddCondition(lua_State* L) int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) { // creature:removeCondition(conditionType[, conditionId = CONDITIONID_COMBAT[, subId = 0[, force = false]]]) + // creature:removeCondition(condition[, force = false]) Creature* creature = getUserdata(L, 1); if (!creature) { lua_pushnil(L); return 1; } - ConditionType_t conditionType = getNumber(L, 2); - ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); - uint32_t subId = getNumber(L, 4, 0); - Condition* condition = creature->getCondition(conditionType, conditionId, subId); + Condition* condition = nullptr; + + bool force = false; + + if (isUserdata(L, 2)) { + condition = getUserdata(L, 2); + force = getBoolean(L, 3, false); + } else { + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + uint32_t subId = getNumber(L, 4, 0); + condition = creature->getCondition(conditionType, conditionId, subId); + force = getBoolean(L, 5, false); + } + if (condition) { - bool force = getBoolean(L, 5, false); creature->removeCondition(condition, force); pushBoolean(L, true); } else { @@ -7858,10 +8443,13 @@ int LuaScriptInterface::luaCreatureSay(lua_State* L) spectators.emplace_back(target); } + // Prevent infinity echo on event onHear + bool echo = getScriptEnv()->getScriptId() == g_events->getScriptId(EventInfoId::CREATURE_ONHEAR); + if (position.x != 0) { - pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators, &position)); + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators, &position, echo)); } else { - pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators)); + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators, nullptr, echo)); } return 1; } @@ -7920,7 +8508,8 @@ int LuaScriptInterface::luaCreatureGetDescription(lua_State* L) int LuaScriptInterface::luaCreatureGetPathTo(lua_State* L) { - // creature:getPathTo(pos[, minTargetDist = 0[, maxTargetDist = 1[, fullPathSearch = true[, clearSight = true[, maxSearchDist = 0]]]]]) + // creature:getPathTo(pos[, minTargetDist = 0[, maxTargetDist = 1[, fullPathSearch = true[, clearSight = true[, + // maxSearchDist = 0]]]]]) Creature* creature = getUserdata(L, 1); if (!creature) { lua_pushnil(L); @@ -7998,7 +8587,7 @@ int LuaScriptInterface::luaPlayerCreate(lua_State* L) Player* player; if (isNumber(L, 2)) { uint32_t id = getNumber(L, 2); - if (id >= 0x10000000 && id <= Player::playerAutoID) { + if (id >= CREATURE_ID_MIN && id <= Player::playerIDLimit) { player = g_game.getPlayerByID(id); } else { player = g_game.getPlayerByGUID(id); @@ -8173,7 +8762,6 @@ int LuaScriptInterface::luaPlayerGetDepotChest(lua_State* L) bool autoCreate = getBoolean(L, 3, false); DepotChest* depotChest = player->getDepotChest(depotId, autoCreate); if (depotChest) { - player->setLastDepotId(depotId); // FIXME: workaround for #2251 pushUserdata(L, depotChest); setItemMetatable(L, -1, depotChest); } else { @@ -8255,7 +8843,7 @@ int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) // player:addExperience(experience[, sendText = false]) Player* player = getUserdata(L, 1); if (player) { - int64_t experience = getNumber(L, 2); + uint64_t experience = getNumber(L, 2); bool sendText = getBoolean(L, 3, false); player->addExperience(nullptr, experience, sendText); pushBoolean(L, true); @@ -8270,7 +8858,7 @@ int LuaScriptInterface::luaPlayerRemoveExperience(lua_State* L) // player:removeExperience(experience[, sendText = false]) Player* player = getUserdata(L, 1); if (player) { - int64_t experience = getNumber(L, 2); + uint64_t experience = getNumber(L, 2); bool sendText = getBoolean(L, 3, false); player->removeExperience(experience, sendText); pushBoolean(L, true); @@ -8403,6 +8991,19 @@ int LuaScriptInterface::luaPlayerAddManaSpent(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerRemoveManaSpent(lua_State* L) +{ + // player:removeManaSpent(amount[, notify = true]) + Player* player = getUserdata(L, 1); + if (player) { + player->removeManaSpent(getNumber(L, 2), getBoolean(L, 3, true)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerGetBaseMaxHealth(lua_State* L) { // player:getBaseMaxHealth() @@ -8494,6 +9095,21 @@ int LuaScriptInterface::luaPlayerAddSkillTries(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerRemoveSkillTries(lua_State* L) +{ + // player:removeSkillTries(skillType, tries[, notify = true]) + Player* player = getUserdata(L, 1); + if (player) { + skills_t skillType = getNumber(L, 2); + uint64_t tries = getNumber(L, 3); + player->removeSkillTries(skillType, tries, getBoolean(L, 4, true)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerGetSpecialSkill(lua_State* L) { // player:getSpecialSkill(specialSkillType) @@ -8601,7 +9217,7 @@ int LuaScriptInterface::luaPlayerSetOfflineTrainingSkill(lua_State* L) // player:setOfflineTrainingSkill(skillId) Player* player = getUserdata(L, 1); if (player) { - uint32_t skillId = getNumber(L, 2); + int32_t skillId = getNumber(L, 2); player->setOfflineTrainingSkill(skillId); pushBoolean(L, true); } else { @@ -9058,7 +9674,7 @@ int LuaScriptInterface::luaPlayerAddItem(lua_State* L) int32_t itemCount = 1; int parameters = lua_gettop(L); - if (parameters >= 4) { + if (parameters >= 5) { itemCount = std::max(1, count); } else if (it.hasSubType()) { if (it.stackable) { @@ -9119,8 +9735,8 @@ int LuaScriptInterface::luaPlayerAddItem(lua_State* L) int LuaScriptInterface::luaPlayerAddItemEx(lua_State* L) { - // player:addItemEx(item[, canDropOnMap = false[, index = INDEX_WHEREEVER[, flags = 0]]]) - // player:addItemEx(item[, canDropOnMap = true[, slot = CONST_SLOT_WHEREEVER]]) + // player:addItemEx(item[, canDropOnMap = false[, index = INDEX_WHEREEVER[, flags = 0]]]) player:addItemEx(item[, + // canDropOnMap = true[, slot = CONST_SLOT_WHEREEVER]]) Item* item = getUserdata(L, 2); if (!item) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); @@ -9185,6 +9801,28 @@ int LuaScriptInterface::luaPlayerRemoveItem(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerSendSupplyUsed(lua_State* L) +{ + // player:sendSupplyUsed(item) + Player* player = getUserdata(L, 1); + if (!player) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Item* item = getUserdata(L, 2); + if (!item) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + player->sendSupplyUsed(item->getClientID()); + pushBoolean(L, true); + return 1; +} + int LuaScriptInterface::luaPlayerGetMoney(lua_State* L) { // player:getMoney() @@ -9274,7 +9912,9 @@ int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) } item->setParent(player); - player->setWriteItem(item, length); + player->windowTextId++; + player->writeItem = item; + player->maxWriteLen = length; player->sendTextWindow(item, length, canWrite); pushBoolean(L, true); return 1; @@ -9282,8 +9922,8 @@ int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) int LuaScriptInterface::luaPlayerSendTextMessage(lua_State* L) { - // player:sendTextMessage(type, text[, position, primaryValue = 0, primaryColor = TEXTCOLOR_NONE[, secondaryValue = 0, secondaryColor = TEXTCOLOR_NONE]]) - // player:sendTextMessage(type, text, channelId) + // player:sendTextMessage(type, text[, position, primaryValue = 0, primaryColor = TEXTCOLOR_NONE[, secondaryValue = + // 0, secondaryColor = TEXTCOLOR_NONE]]) player:sendTextMessage(type, text, channelId) Player* player = getUserdata(L, 1); if (!player) { @@ -9529,7 +10169,22 @@ int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerAddMount(lua_State* L) { +int LuaScriptInterface::luaPlayerSendEditPodium(lua_State* L) +{ + // player:sendEditPodium(item) + Player* player = getUserdata(L, 1); + Item* item = getUserdata(L, 2); + if (player && item) { + player->sendPodiumWindow(item); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddMount(lua_State* L) +{ // player:addMount(mountId or mountName) Player* player = getUserdata(L, 1); if (!player) { @@ -9552,7 +10207,8 @@ int LuaScriptInterface::luaPlayerAddMount(lua_State* L) { return 1; } -int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) { +int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) +{ // player:removeMount(mountId or mountName) Player* player = getUserdata(L, 1); if (!player) { @@ -9575,7 +10231,8 @@ int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) { return 1; } -int LuaScriptInterface::luaPlayerHasMount(lua_State* L) { +int LuaScriptInterface::luaPlayerHasMount(lua_State* L) +{ // player:hasMount(mountId or mountName) const Player* player = getUserdata(L, 1); if (!player) { @@ -9655,7 +10312,7 @@ int LuaScriptInterface::luaPlayerAddBlessing(lua_State* L) return 1; } - player->addBlessing(1 << blessing); + player->addBlessing(blessing); pushBoolean(L, true); return 1; } @@ -9675,7 +10332,7 @@ int LuaScriptInterface::luaPlayerRemoveBlessing(lua_State* L) return 1; } - player->removeBlessing(1 << blessing); + player->removeBlessing(blessing); pushBoolean(L, true); return 1; } @@ -9902,7 +10559,7 @@ int LuaScriptInterface::luaPlayerSetEditHouse(lua_State* L) int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) { - // player:setGhostMode(enabled[, showEffect=true]) + // player:setGhostMode(enabled[, magicEffect = CONST_ME_TELEPORT]) Player* player = getUserdata(L, 1); if (!player) { lua_pushnil(L); @@ -9915,12 +10572,13 @@ int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) return 1; } - bool showEffect = getBoolean(L, 3, true); + MagicEffectClasses magicEffect = getNumber(L, 3, CONST_ME_TELEPORT); player->switchGhostMode(); Tile* tile = player->getTile(); const Position& position = player->getPosition(); + const bool isInvisible = player->isInvisible(); SpectatorVec spectators; g_game.map.getSpectators(spectators, position, true, true); @@ -9930,9 +10588,13 @@ int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) if (enabled) { tmpPlayer->sendRemoveTileCreature(player, position, tile->getClientIndexOfCreature(tmpPlayer, player)); } else { - tmpPlayer->sendCreatureAppear(player, position, showEffect); + tmpPlayer->sendCreatureAppear(player, position, magicEffect); } } else { + if (isInvisible) { + continue; + } + tmpPlayer->sendCreatureChangeVisible(player, !enabled); } } @@ -10100,6 +10762,32 @@ int LuaScriptInterface::luaPlayerGetStoreInbox(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerIsNearDepotBox(lua_State* L) +{ + // player:isNearDepotBox() + const Player* const player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + pushBoolean(L, player->isNearDepotBox()); + return 1; +} + +int LuaScriptInterface::luaPlayerGetIdleTime(lua_State* L) +{ + // player:getIdleTime() + const Player* const player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, player->getIdleTime()); + return 1; +} + // Monster int LuaScriptInterface::luaMonsterCreate(lua_State* L) { @@ -10146,6 +10834,24 @@ int LuaScriptInterface::luaMonsterGetType(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterRename(lua_State* L) +{ + // monster:rename(name[, nameDescription]) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + monster->setName(getString(L, 2)); + if (lua_gettop(L) >= 3) { + monster->setNameDescription(getString(L, 3)); + } + + pushBoolean(L, true); + return 1; +} + int LuaScriptInterface::luaMonsterGetSpawnPosition(lua_State* L) { // monster:getSpawnPosition() @@ -10202,6 +10908,12 @@ int LuaScriptInterface::luaMonsterIsTarget(lua_State* L) Monster* monster = getUserdata(L, 1); if (monster) { const Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + pushBoolean(L, monster->isTarget(creature)); } else { lua_pushnil(L); @@ -10215,6 +10927,12 @@ int LuaScriptInterface::luaMonsterIsOpponent(lua_State* L) Monster* monster = getUserdata(L, 1); if (monster) { const Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + pushBoolean(L, monster->isOpponent(creature)); } else { lua_pushnil(L); @@ -10228,6 +10946,12 @@ int LuaScriptInterface::luaMonsterIsFriend(lua_State* L) Monster* monster = getUserdata(L, 1); if (monster) { const Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + pushBoolean(L, monster->isFriend(creature)); } else { lua_pushnil(L); @@ -10241,6 +10965,12 @@ int LuaScriptInterface::luaMonsterAddFriend(lua_State* L) Monster* monster = getUserdata(L, 1); if (monster) { Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + monster->addFriend(creature); pushBoolean(L, true); } else { @@ -10255,6 +10985,12 @@ int LuaScriptInterface::luaMonsterRemoveFriend(lua_State* L) Monster* monster = getUserdata(L, 1); if (monster) { Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + monster->removeFriend(creature); pushBoolean(L, true); } else { @@ -10306,6 +11042,12 @@ int LuaScriptInterface::luaMonsterAddTarget(lua_State* L) } Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + bool pushFront = getBoolean(L, 3, false); monster->addTarget(creature, pushFront); pushBoolean(L, true); @@ -10321,7 +11063,14 @@ int LuaScriptInterface::luaMonsterRemoveTarget(lua_State* L) return 1; } - monster->removeTarget(getCreature(L, 2)); + Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + monster->removeTarget(creature); pushBoolean(L, true); return 1; } @@ -10365,6 +11114,12 @@ int LuaScriptInterface::luaMonsterSelectTarget(lua_State* L) Monster* monster = getUserdata(L, 1); if (monster) { Creature* creature = getCreature(L, 2); + if (!creature) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + pushBoolean(L, monster->selectTarget(creature)); } else { lua_pushnil(L); @@ -10385,6 +11140,30 @@ int LuaScriptInterface::luaMonsterSearchTarget(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterIsWalkingToSpawn(lua_State* L) +{ + // monster:isWalkingToSpawn() + Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->isWalkingToSpawn()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterWalkToSpawn(lua_State* L) +{ + // monster:walkToSpawn() + Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->walkToSpawn()); + } else { + lua_pushnil(L); + } + return 1; +} + // Npc int LuaScriptInterface::luaNpcCreate(lua_State* L) { @@ -10456,10 +11235,24 @@ int LuaScriptInterface::luaNpcSetSpeechBubble(lua_State* L) { // npc:setSpeechBubble(speechBubble) Npc* npc = getUserdata(L, 1); - if (npc) { - npc->setSpeechBubble(getNumber(L, 2)); + if (!npc) { + lua_pushnil(L); + return 1; } - return 0; + + if (!isNumber(L, 2)) { + lua_pushnil(L); + return 1; + } + + uint8_t speechBubble = getNumber(L, 2); + if (speechBubble > SPEECHBUBBLE_LAST) { + lua_pushnil(L); + } else { + npc->setSpeechBubble(speechBubble); + pushBoolean(L, true); + } + return 1; } // Guild @@ -10988,6 +11781,18 @@ int LuaScriptInterface::luaVocationGetPromotion(lua_State* L) return 1; } +int LuaScriptInterface::luaVocationAllowsPvp(lua_State* L) +{ + // vocation:allowsPvp() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + pushBoolean(L, vocation->allowsPvp()); + } else { + lua_pushnil(L); + } + return 1; +} + // Town int LuaScriptInterface::luaTownCreate(lua_State* L) { @@ -11127,6 +11932,84 @@ int LuaScriptInterface::luaHouseGetRent(lua_State* L) return 1; } +int LuaScriptInterface::luaHouseSetRent(lua_State* L) +{ + // house:setRent(rent) + uint32_t rent = getNumber(L, 2); + House* house = getUserdata(L, 1); + if (house) { + house->setRent(rent); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetPaidUntil(lua_State* L) +{ + // house:getPaidUntil() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getPaidUntil()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseSetPaidUntil(lua_State* L) +{ + // house:setPaidUntil(timestamp) + time_t timestamp = getNumber(L, 2); + House* house = getUserdata(L, 1); + if (house) { + house->setPaidUntil(timestamp); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetPayRentWarnings(lua_State* L) +{ + // house:getPayRentWarnings() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getPayRentWarnings()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseSetPayRentWarnings(lua_State* L) +{ + // house:setPayRentWarnings(warnings) + uint32_t warnings = getNumber(L, 2); + House* house = getUserdata(L, 1); + if (house) { + house->setPayRentWarnings(warnings); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetOwnerName(lua_State* L) +{ + // house:getOwnerName() + House* house = getUserdata(L, 1); + if (house) { + pushString(L, house->getOwnerName()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaHouseGetOwnerGuid(lua_State* L) { // house:getOwnerGuid() @@ -11321,8 +12204,8 @@ int LuaScriptInterface::luaHouseGetItems(lua_State* L) int index = 0; for (Tile* tile : tiles) { TileItemVector* itemVector = tile->getItemList(); - if(itemVector) { - for(Item* item : *itemVector) { + if (itemVector) { + for (Item* item : *itemVector) { pushUserdata(L, item); setItemMetatable(L, -1, item); lua_rawseti(L, -2, ++index); @@ -11408,6 +12291,19 @@ int LuaScriptInterface::luaHouseKickPlayer(lua_State* L) return 1; } +int LuaScriptInterface::luaHouseSave(lua_State* L) +{ + // house:save() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + pushBoolean(L, IOMapSerialize::saveHouse(house)); + return 1; +} + // ItemType int LuaScriptInterface::luaItemTypeCreate(lua_State* L) { @@ -11692,7 +12588,7 @@ int LuaScriptInterface::luaItemTypeGetDescription(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State *L) +int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State* L) { // itemType:getSlotPosition() const ItemType* itemType = getUserdata(L, 1); @@ -11756,6 +12652,19 @@ int LuaScriptInterface::luaItemTypeGetWeight(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetWorth(lua_State* L) +{ + // itemType:getWorth() + const ItemType* itemType = getUserdata(L, 1); + if (!itemType) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, itemType->worth); + return 1; +} + int LuaScriptInterface::luaItemTypeGetHitChance(lua_State* L) { // itemType:getHitChance() @@ -11792,6 +12701,18 @@ int LuaScriptInterface::luaItemTypeGetAttack(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetAttackSpeed(lua_State* L) +{ + // itemType:getAttackSpeed() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->attackSpeed); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetDefense(lua_State* L) { // itemType:getDefense() @@ -11864,6 +12785,18 @@ int LuaScriptInterface::luaItemTypeGetCorpseType(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetClassification(lua_State* L) +{ + // itemType:getClassification() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->classification); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) { // itemType:getAbilities() @@ -11910,31 +12843,63 @@ int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) lua_pushnumber(L, abilities.skills[i]); lua_rawseti(L, -2, i + 1); } - lua_setfield(L, -2, "skills"); + lua_setfield(L, -2, "skills"); + + // Special skills + lua_createtable(L, 0, SPECIALSKILL_LAST + 1); + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; i++) { + lua_pushnumber(L, abilities.specialSkills[i]); + lua_rawseti(L, -2, i + 1); + } + lua_setfield(L, -2, "specialSkills"); + + // Field absorb percent + lua_createtable(L, 0, COMBAT_COUNT); + for (int32_t i = 0; i < COMBAT_COUNT; i++) { + lua_pushnumber(L, abilities.fieldAbsorbPercent[i]); + lua_rawseti(L, -2, i + 1); + } + lua_setfield(L, -2, "fieldAbsorbPercent"); + + // Absorb percent + lua_createtable(L, 0, COMBAT_COUNT); + for (int32_t i = 0; i < COMBAT_COUNT; i++) { + lua_pushnumber(L, abilities.absorbPercent[i]); + lua_rawseti(L, -2, i + 1); + } + lua_setfield(L, -2, "absorbPercent"); + + // special magic level + lua_createtable(L, 0, COMBAT_COUNT); + for (int32_t i = 0; i < COMBAT_COUNT; i++) { + lua_pushnumber(L, abilities.specialMagicLevelSkill[i]); + lua_rawseti(L, -2, i + 1); + } + lua_setfield(L, -2, "specialMagicLevel"); - // Special skills - lua_createtable(L, 0, SPECIALSKILL_LAST + 1); - for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; i++) { - lua_pushnumber(L, abilities.specialSkills[i]); + // Damage boost percent + lua_createtable(L, 0, COMBAT_COUNT); + for (int32_t i = 0; i < COMBAT_COUNT; i++) { + lua_pushnumber(L, abilities.boostPercent[i]); lua_rawseti(L, -2, i + 1); } - lua_setfield(L, -2, "specialSkills"); + lua_setfield(L, -2, "boostPercent"); - // Field absorb percent + // Reflect chance lua_createtable(L, 0, COMBAT_COUNT); for (int32_t i = 0; i < COMBAT_COUNT; i++) { - lua_pushnumber(L, abilities.fieldAbsorbPercent[i]); + lua_pushnumber(L, abilities.reflect[i].chance); lua_rawseti(L, -2, i + 1); } - lua_setfield(L, -2, "fieldAbsorbPercent"); + lua_setfield(L, -2, "reflectChance"); - // Absorb percent + // Reflect percent lua_createtable(L, 0, COMBAT_COUNT); for (int32_t i = 0; i < COMBAT_COUNT; i++) { - lua_pushnumber(L, abilities.absorbPercent[i]); + lua_pushnumber(L, abilities.reflect[i].percent); lua_rawseti(L, -2, i + 1); } - lua_setfield(L, -2, "absorbPercent"); + lua_setfield(L, -2, "reflectPercent"); } return 1; } @@ -12035,6 +13000,18 @@ int LuaScriptInterface::luaItemTypeGetLevelDoor(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetRuneSpellName(lua_State* L) +{ + // itemType:getRuneSpellName() + const ItemType* itemType = getUserdata(L, 1); + if (itemType && itemType->isRune()) { + pushString(L, itemType->runeSpellName); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetVocationString(lua_State* L) { // itemType:getVocationString() @@ -12071,6 +13048,48 @@ int LuaScriptInterface::luaItemTypeGetMinReqMagicLevel(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetMarketBuyStatistics(lua_State* L) +{ + // itemType:getMarketBuyStatistics() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemType->id); + if (statistics) { + lua_createtable(L, 4, 0); + setField(L, "numTransactions", statistics->numTransactions); + setField(L, "totalPrice", statistics->totalPrice); + setField(L, "highestPrice", statistics->highestPrice); + setField(L, "lowestPrice", statistics->lowestPrice); + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetMarketSellStatistics(lua_State* L) +{ + // itemType:getMarketSellStatistics() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + MarketStatistics* statistics = IOMarket::getInstance().getSaleStatistics(itemType->id); + if (statistics) { + lua_createtable(L, 4, 0); + setField(L, "numTransactions", statistics->numTransactions); + setField(L, "totalPrice", statistics->totalPrice); + setField(L, "highestPrice", statistics->highestPrice); + setField(L, "lowestPrice", statistics->lowestPrice); + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetElementType(lua_State* L) { // itemType:getElementType() @@ -12195,16 +13214,26 @@ int LuaScriptInterface::luaItemTypeIsStoreItem(lua_State* L) int LuaScriptInterface::luaCombatCreate(lua_State* L) { // Combat() - pushUserdata(L, g_luaEnvironment.createCombatObject(getScriptEnv()->getScriptInterface())); + pushSharedPtr(L, g_luaEnvironment.createCombatObject(getScriptEnv()->getScriptInterface())); setMetatable(L, -1, "Combat"); return 1; } +int LuaScriptInterface::luaCombatDelete(lua_State* L) +{ + Combat_ptr& combat = getSharedPtr(L, 1); + if (combat) { + combat.reset(); + } + return 0; +} + int LuaScriptInterface::luaCombatSetParameter(lua_State* L) { // combat:setParameter(key, value) - Combat* combat = getUserdata(L, 1); + const Combat_ptr& combat = getSharedPtr(L, 1); if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); lua_pushnil(L); return 1; } @@ -12221,11 +13250,32 @@ int LuaScriptInterface::luaCombatSetParameter(lua_State* L) return 1; } +int LuaScriptInterface::luaCombatGetParameter(lua_State* L) +{ + // combat:getParameter(key) + const Combat_ptr& combat = getSharedPtr(L, 1); + if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + int32_t value = combat->getParam(getNumber(L, 2)); + if (value == std::numeric_limits().max()) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, value); + return 1; +} + int LuaScriptInterface::luaCombatSetFormula(lua_State* L) { // combat:setFormula(type, mina, minb, maxa, maxb) - Combat* combat = getUserdata(L, 1); + const Combat_ptr& combat = getSharedPtr(L, 1); if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); lua_pushnil(L); return 1; } @@ -12256,22 +13306,30 @@ int LuaScriptInterface::luaCombatSetArea(lua_State* L) return 1; } - Combat* combat = getUserdata(L, 1); - if (combat) { - combat->setArea(new AreaCombat(*area)); - pushBoolean(L, true); - } else { + const Combat_ptr& combat = getSharedPtr(L, 1); + if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); lua_pushnil(L); + return 1; } + + combat->setArea(new AreaCombat(*area)); + pushBoolean(L, true); return 1; } int LuaScriptInterface::luaCombatAddCondition(lua_State* L) { // combat:addCondition(condition) + const Combat_ptr& combat = getSharedPtr(L, 1); + if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + Condition* condition = getUserdata(L, 2); - Combat* combat = getUserdata(L, 1); - if (combat && condition) { + if (condition) { combat->addCondition(condition->clone()); pushBoolean(L, true); } else { @@ -12283,21 +13341,24 @@ int LuaScriptInterface::luaCombatAddCondition(lua_State* L) int LuaScriptInterface::luaCombatClearConditions(lua_State* L) { // combat:clearConditions() - Combat* combat = getUserdata(L, 1); - if (combat) { - combat->clearConditions(); - pushBoolean(L, true); - } else { + const Combat_ptr& combat = getSharedPtr(L, 1); + if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); lua_pushnil(L); + return 1; } + + combat->clearConditions(); + pushBoolean(L, true); return 1; } int LuaScriptInterface::luaCombatSetCallback(lua_State* L) { // combat:setCallback(key, function) - Combat* combat = getUserdata(L, 1); + const Combat_ptr& combat = getSharedPtr(L, 1); if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); lua_pushnil(L); return 1; } @@ -12322,22 +13383,25 @@ int LuaScriptInterface::luaCombatSetCallback(lua_State* L) int LuaScriptInterface::luaCombatSetOrigin(lua_State* L) { // combat:setOrigin(origin) - Combat* combat = getUserdata(L, 1); - if (combat) { - combat->setOrigin(getNumber(L, 2)); - pushBoolean(L, true); - } else { + const Combat_ptr& combat = getSharedPtr(L, 1); + if (!combat) { + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); lua_pushnil(L); + return 1; } + + combat->setOrigin(getNumber(L, 2)); + pushBoolean(L, true); return 1; } int LuaScriptInterface::luaCombatExecute(lua_State* L) { // combat:execute(creature, variant) - Combat* combat = getUserdata(L, 1); + const Combat_ptr& combat = getSharedPtr(L, 1); if (!combat) { - pushBoolean(L, false); + reportErrorFunc(L, getErrorDesc(LUA_ERROR_COMBAT_NOT_FOUND)); + lua_pushnil(L); return 1; } @@ -12352,9 +13416,9 @@ int LuaScriptInterface::luaCombatExecute(lua_State* L) Creature* creature = getCreature(L, 2); const LuaVariant& variant = getVariant(L, 3); - switch (variant.type) { + switch (variant.type()) { case VARIANT_NUMBER: { - Creature* target = g_game.getCreatureByID(variant.number); + Creature* target = g_game.getCreatureByID(variant.getNumber()); if (!target) { pushBoolean(L, false); return 1; @@ -12369,22 +13433,22 @@ int LuaScriptInterface::luaCombatExecute(lua_State* L) } case VARIANT_POSITION: { - combat->doCombat(creature, variant.pos); + combat->doCombat(creature, variant.getPosition()); break; } case VARIANT_TARGETPOSITION: { if (combat->hasArea()) { - combat->doCombat(creature, variant.pos); + combat->doCombat(creature, variant.getTargetPosition()); } else { - combat->postCombatEffects(creature, variant.pos); - g_game.addMagicEffect(variant.pos, CONST_ME_POFF); + combat->postCombatEffects(creature, variant.getTargetPosition()); + g_game.addMagicEffect(variant.getTargetPosition(), CONST_ME_POFF); } break; } case VARIANT_STRING: { - Player* target = g_game.getPlayerByName(variant.text); + Player* target = g_game.getPlayerByName(variant.getString()); if (!target) { pushBoolean(L, false); return 1; @@ -12557,6 +13621,25 @@ int LuaScriptInterface::luaConditionSetParameter(lua_State* L) return 1; } +int LuaScriptInterface::luaConditionGetParameter(lua_State* L) +{ + // condition:getParameter(key) + Condition* condition = getUserdata(L, 1); + if (!condition) { + lua_pushnil(L); + return 1; + } + + int32_t value = condition->getParam(getNumber(L, 2)); + if (value == std::numeric_limits().max()) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, value); + return 1; +} + int LuaScriptInterface::luaConditionSetFormula(lua_State* L) { // condition:setFormula(mina, minb, maxa, maxb) @@ -12670,6 +13753,23 @@ int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeIsChallengeable(lua_State* L) +{ + // get: monsterType:isChallengeable() set: monsterType:isChallengeable(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isChallengeable); + } else { + monsterType->info.isChallengeable = getBoolean(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) { // get: monsterType:isConvinceable() set: monsterType:isConvinceable(bool) @@ -12704,6 +13804,23 @@ int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeIsIgnoringSpawnBlock(lua_State* L) +{ + // get: monsterType:isIgnoringSpawnBlock() set: monsterType:isIgnoringSpawnBlock(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isIgnoringSpawnBlock); + } else { + monsterType->info.isIgnoringSpawnBlock = getBoolean(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) { // get: monsterType:isIllusionable() set: monsterType:isIllusionable(bool) @@ -12823,6 +13940,57 @@ int LuaScriptInterface::luaMonsterTypeCanPushCreatures(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeCanWalkOnEnergy(lua_State* L) +{ + // get: monsterType:canWalkOnEnergy() set: monsterType:canWalkOnEnergy(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canWalkOnEnergy); + } else { + monsterType->info.canWalkOnEnergy = getBoolean(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeCanWalkOnFire(lua_State* L) +{ + // get: monsterType:canWalkOnFire() set: monsterType:canWalkOnFire(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canWalkOnFire); + } else { + monsterType->info.canWalkOnFire = getBoolean(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeCanWalkOnPoison(lua_State* L) +{ + // get: monsterType:canWalkOnPoison() set: monsterType:canWalkOnPoison(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canWalkOnPoison); + } else { + monsterType->info.canWalkOnPoison = getBoolean(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + int32_t LuaScriptInterface::luaMonsterTypeName(lua_State* L) { // get: monsterType:name() set: monsterType:name(name) @@ -12986,7 +14154,8 @@ int LuaScriptInterface::luaMonsterTypeCombatImmunities(lua_State* L) monsterType->info.damageImmunities |= COMBAT_MANADRAIN; pushBoolean(L, true); } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity << " for monster: " << monsterType->name << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity + << " for monster: " << monsterType->name << std::endl; lua_pushnil(L); } } @@ -13045,7 +14214,8 @@ int LuaScriptInterface::luaMonsterTypeConditionImmunities(lua_State* L) monsterType->info.conditionImmunities |= CONDITION_BLEEDING; pushBoolean(L, true); } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity << " for monster: " << monsterType->name << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity + << " for monster: " << monsterType->name << std::endl; lua_pushnil(L); } } @@ -13348,13 +14518,14 @@ int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) int LuaScriptInterface::luaMonsterTypeAddSummon(lua_State* L) { - // monsterType:addSummon(name, interval, chance) + // monsterType:addSummon(name, interval, chance[, max = -1]) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { summonBlock_t summon; summon.name = getString(L, 2); - summon.chance = getNumber(L, 3); - summon.speed = getNumber(L, 4); + summon.speed = getNumber(L, 3); + summon.chance = getNumber(L, 4); + summon.max = getNumber(L, 5, -1); monsterType->info.summons.push_back(summon); pushBoolean(L, true); } else { @@ -13670,7 +14841,7 @@ int LuaScriptInterface::luaLootSetId(lua_State* L) loot->lootBlock.id = getNumber(L, 2); } else { auto name = getString(L, 2); - auto ids = Item::items.nameToItems.equal_range(asLowerCaseString(name)); + auto ids = Item::items.nameToItems.equal_range(boost::algorithm::to_lower_copy(name)); if (ids.first == Item::items.nameToItems.cend()) { std::cout << "[Warning - Loot:setId] Unknown loot item \"" << name << "\". " << std::endl; @@ -13914,6 +15085,19 @@ int LuaScriptInterface::luaMonsterSpellSetNeedTarget(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterSpellSetNeedDirection(lua_State* L) +{ + // monsterSpell:setNeedDirection(bool) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->needDirection = getBoolean(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterSpellSetCombatLength(lua_State* L) { // monsterSpell:setCombatLength(length) @@ -13953,6 +15137,19 @@ int LuaScriptInterface::luaMonsterSpellSetCombatRadius(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterSpellSetCombatRing(lua_State* L) +{ + // monsterSpell:setCombatRing(ring) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->ring = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterSpellSetConditionType(lua_State* L) { // monsterSpell:setConditionType(type) @@ -14008,6 +15205,19 @@ int LuaScriptInterface::luaMonsterSpellSetConditionDuration(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterSpellSetConditionDrunkenness(lua_State* L) +{ + // monsterSpell:setConditionDrunkenness(drunkenness) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->drunkenness = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterSpellSetConditionTickInterval(lua_State* L) { // monsterSpell:setConditionTickInterval(interval) @@ -14047,6 +15257,28 @@ int LuaScriptInterface::luaMonsterSpellSetCombatEffect(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterSpellSetOutfit(lua_State* L) +{ + // monsterSpell:setOutfit(outfit) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + if (isTable(L, 2)) { + spell->outfit = getOutfit(L, 2); + } else if (isNumber(L, 2)) { + spell->outfit.lookTypeEx = getNumber(L, 2); + } else if (isString(L, 2)) { + MonsterType* mType = g_monsters.getMonsterType(getString(L, 2)); + if (mType) { + spell->outfit = mType->info.outfit; + } + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + // Party int32_t LuaScriptInterface::luaPartyCreate(lua_State* L) { @@ -14327,7 +15559,7 @@ int LuaScriptInterface::luaSpellCreate(lua_State* L) return 1; } - std::string tmp = asLowerCaseString(arg); + std::string tmp = boost::algorithm::to_lower_copy(arg); if (tmp == "instant") { spellType = SPELL_INSTANT; } else if (tmp == "rune") { @@ -14398,7 +15630,7 @@ int LuaScriptInterface::luaSpellRegister(lua_State* L) } else if (spell->spellType == SPELL_RUNE) { RuneSpell* rune = dynamic_cast(getUserdata(L, 1)); if (rune->getMagicLevel() != 0 || rune->getLevel() != 0) { - //Change information in the ItemType to get accurate description + // Change information in the ItemType to get accurate description ItemType& iType = Item::items.getItemType(rune->getRuneItemId()); iType.name = rune->getName(); iType.runeMagLevel = rune->getMagicLevel(); @@ -14506,7 +15738,8 @@ int LuaScriptInterface::luaSpellGroup(lua_State* L) } pushBoolean(L, true); } else { - std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) << " or secondaryGroup: " << getString(L, 3) << std::endl; + std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) + << " or secondaryGroup: " << getString(L, 3) << std::endl; pushBoolean(L, false); return 1; } @@ -14798,6 +16031,23 @@ int LuaScriptInterface::luaSpellAggressive(lua_State* L) return 1; } +int LuaScriptInterface::luaSpellPzLock(lua_State* L) +{ + // spell:isPzLock(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getPzLock()); + } else { + spell->setPzLock(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaSpellVocation(lua_State* L) { // spell:vocation(vocation) @@ -14815,12 +16065,12 @@ int LuaScriptInterface::luaSpellVocation(lua_State* L) pushString(L, name); lua_rawseti(L, -2, ++i); } - setMetatable(L, -1, "Spell"); } else { int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc for (int i = 0; i < parameters; ++i) { std::vector vocList = explodeString(getString(L, 2 + i), ";"); - spell->addVocMap(g_vocations.getVocationId(vocList[0]), vocList.size() > 1 ? booleanString(vocList[1]) : false); + spell->addVocMap(g_vocations.getVocationId(vocList[0]), + vocList.size() > 1 ? booleanString(vocList[1]) : false); } pushBoolean(L, true); } @@ -15195,9 +16445,9 @@ int LuaScriptInterface::luaActionRegister(lua_State* L) return 1; } pushBoolean(L, g_actions->registerLuaEvent(action)); - action->getActionIdRange().clear(); - action->getItemIdRange().clear(); - action->getUniqueIdRange().clear(); + action->clearActionIdRange(); + action->clearItemIdRange(); + action->clearUniqueIdRange(); } else { lua_pushnil(L); } @@ -15424,7 +16674,7 @@ int LuaScriptInterface::luaCreatureEventType(lua_State* L) CreatureEvent* creature = getUserdata(L, 1); if (creature) { std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); + std::string tmpStr = boost::algorithm::to_lower_copy(typeName); if (tmpStr == "login") { creature->setEventType(CREATURE_EVENT_LOGIN); } else if (tmpStr == "logout") { @@ -15450,7 +16700,8 @@ int LuaScriptInterface::luaCreatureEventType(lua_State* L) } else if (tmpStr == "extendedopcode") { creature->setEventType(CREATURE_EVENT_EXTENDED_OPCODE); } else { - std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for creature event: " << typeName << std::endl; + std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for creature event: " << typeName + << std::endl; pushBoolean(L, false); } creature->setLoaded(true); @@ -15519,7 +16770,7 @@ int LuaScriptInterface::luaMoveEventType(lua_State* L) MoveEvent* moveevent = getUserdata(L, 1); if (moveevent) { std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); + std::string tmpStr = boost::algorithm::to_lower_copy(typeName); if (tmpStr == "stepin") { moveevent->setEventType(MOVE_EVENT_STEP_IN); moveevent->stepFunction = moveevent->StepInField; @@ -15533,10 +16784,10 @@ int LuaScriptInterface::luaMoveEventType(lua_State* L) moveevent->setEventType(MOVE_EVENT_DEEQUIP); moveevent->equipFunction = moveevent->DeEquipItem; } else if (tmpStr == "additem") { - moveevent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + moveevent->setEventType(MOVE_EVENT_ADD_ITEM); moveevent->moveFunction = moveevent->AddItemField; } else if (tmpStr == "removeitem") { - moveevent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + moveevent->setEventType(MOVE_EVENT_REMOVE_ITEM); moveevent->moveFunction = moveevent->RemoveItemField; } else { std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << typeName << std::endl; @@ -15554,7 +16805,8 @@ int LuaScriptInterface::luaMoveEventRegister(lua_State* L) // moveevent:register() MoveEvent* moveevent = getUserdata(L, 1); if (moveevent) { - if ((moveevent->getEventType() == MOVE_EVENT_EQUIP || moveevent->getEventType() == MOVE_EVENT_DEEQUIP) && moveevent->getSlot() == SLOTP_WHEREEVER) { + if ((moveevent->getEventType() == MOVE_EVENT_EQUIP || moveevent->getEventType() == MOVE_EVENT_DEEQUIP) && + moveevent->getSlot() == SLOTP_WHEREEVER) { uint32_t id = moveevent->getItemIdRange().at(0); ItemType& it = Item::items.getItemType(id); moveevent->setSlot(it.slotPosition); @@ -15564,10 +16816,10 @@ int LuaScriptInterface::luaMoveEventRegister(lua_State* L) return 1; } pushBoolean(L, g_moveEvents->registerLuaEvent(moveevent)); - moveevent->getItemIdRange().clear(); - moveevent->getActionIdRange().clear(); - moveevent->getUniqueIdRange().clear(); - moveevent->getPosList().clear(); + moveevent->clearItemIdRange(); + moveevent->clearActionIdRange(); + moveevent->clearUniqueIdRange(); + moveevent->clearPosList(); } else { lua_pushnil(L); } @@ -15600,7 +16852,7 @@ int LuaScriptInterface::luaMoveEventSlot(lua_State* L) } if (moveevent->getEventType() == MOVE_EVENT_EQUIP || moveevent->getEventType() == MOVE_EVENT_DEEQUIP) { - std::string slotName = asLowerCaseString(getString(L, 2)); + std::string slotName = boost::algorithm::to_lower_copy(getString(L, 2)); if (slotName == "head") { moveevent->setSlot(SLOTP_HEAD); } else if (slotName == "necklace") { @@ -15694,7 +16946,7 @@ int LuaScriptInterface::luaMoveEventVocation(lua_State* L) } if (showInDescription) { if (moveevent->getVocationString().empty()) { - tmp = asLowerCaseString(getString(L, 2)); + tmp = boost::algorithm::to_lower_copy(getString(L, 2)); tmp += "s"; moveevent->setVocationString(tmp); } else { @@ -15704,7 +16956,7 @@ int LuaScriptInterface::luaMoveEventVocation(lua_State* L) } else { tmp += ", "; } - tmp += asLowerCaseString(getString(L, 2)); + tmp += boost::algorithm::to_lower_copy(getString(L, 2)); tmp += "s"; moveevent->setVocationString(tmp); } @@ -15716,6 +16968,19 @@ int LuaScriptInterface::luaMoveEventVocation(lua_State* L) return 1; } +int LuaScriptInterface::luaMoveEventTileItem(lua_State* L) +{ + // moveevent:tileItem(bool) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + moveevent->setTileItem(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMoveEventItemId(lua_State* L) { // moveevent:id(ids) @@ -15824,7 +17089,7 @@ int LuaScriptInterface::luaGlobalEventType(lua_State* L) GlobalEvent* global = getUserdata(L, 1); if (global) { std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); + std::string tmpStr = boost::algorithm::to_lower_copy(typeName); if (tmpStr == "startup") { global->setEventType(GLOBALEVENT_STARTUP); } else if (tmpStr == "shutdown") { @@ -15832,7 +17097,8 @@ int LuaScriptInterface::luaGlobalEventType(lua_State* L) } else if (tmpStr == "record") { global->setEventType(GLOBALEVENT_RECORD); } else { - std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for global event: " << typeName << std::endl; + std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for global event: " << typeName + << std::endl; pushBoolean(L, false); } pushBoolean(L, true); @@ -15851,6 +17117,14 @@ int LuaScriptInterface::luaGlobalEventRegister(lua_State* L) pushBoolean(L, false); return 1; } + + if (globalevent->getEventType() == GLOBALEVENT_NONE && globalevent->getInterval() == 0) { + std::cout << "[Error - LuaScriptInterface::luaGlobalEventRegister] No interval for globalevent with name " + << globalevent->getName() << std::endl; + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_globalEvents->registerLuaEvent(globalevent)); } else { lua_pushnil(L); @@ -15884,7 +17158,8 @@ int LuaScriptInterface::luaGlobalEventTime(lua_State* L) int32_t hour = params.front(); if (hour < 0 || hour > 23) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << timer + << "\" for globalevent with name: " << globalevent->getName() << std::endl; pushBoolean(L, false); return 1; } @@ -15896,7 +17171,8 @@ int LuaScriptInterface::luaGlobalEventTime(lua_State* L) if (params.size() > 1) { min = params[1]; if (min < 0 || min > 59) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << timer + << "\" for globalevent with name: " << globalevent->getName() << std::endl; pushBoolean(L, false); return 1; } @@ -15904,7 +17180,8 @@ int LuaScriptInterface::luaGlobalEventTime(lua_State* L) if (params.size() > 2) { sec = params[2]; if (sec < 0 || sec > 59) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; + std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << timer + << "\" for globalevent with name: " << globalevent->getName() << std::endl; pushBoolean(L, false); return 1; } @@ -16010,7 +17287,7 @@ int LuaScriptInterface::luaWeaponAction(lua_State* L) Weapon* weapon = getUserdata(L, 1); if (weapon) { std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); + std::string tmpStr = boost::algorithm::to_lower_copy(typeName); if (tmpStr == "removecount") { weapon->action = WEAPONACTION_REMOVECOUNT; } else if (tmpStr == "removecharge") { @@ -16031,8 +17308,13 @@ int LuaScriptInterface::luaWeaponAction(lua_State* L) int LuaScriptInterface::luaWeaponRegister(lua_State* L) { // weapon:register() - Weapon* weapon = getUserdata(L, 1); - if (weapon) { + Weapon** weaponPtr = getRawUserdata(L, 1); + if (!weaponPtr) { + lua_pushnil(L); + return 1; + } + + if (auto* weapon = *weaponPtr) { if (weapon->weaponType == WEAPON_DISTANCE || weapon->weaponType == WEAPON_AMMO) { weapon = getUserdata(L, 1); } else if (weapon->weaponType == WEAPON_WAND) { @@ -16054,6 +17336,7 @@ int LuaScriptInterface::luaWeaponRegister(lua_State* L) weapon->configureWeapon(it); pushBoolean(L, g_weapons->registerLuaEvent(weapon)); + *weaponPtr = nullptr; // Remove luascript reference } else { lua_pushnil(L); } @@ -16078,7 +17361,7 @@ int LuaScriptInterface::luaWeaponOnUseWeapon(lua_State* L) int LuaScriptInterface::luaWeaponUnproperly(lua_State* L) { - // weapon:wieldedUnproperly(bool) + // weapon:wieldUnproperly(bool) Weapon* weapon = getUserdata(L, 1); if (weapon) { weapon->setWieldUnproperly(getBoolean(L, 2)); @@ -16220,7 +17503,7 @@ int LuaScriptInterface::luaWeaponElement(lua_State* L) if (weapon) { if (!getNumber(L, 2)) { std::string element = getString(L, 2); - std::string tmpStrValue = asLowerCaseString(element); + std::string tmpStrValue = boost::algorithm::to_lower_copy(element); if (tmpStrValue == "earth") { weapon->params.combatType = COMBAT_EARTHDAMAGE; } else if (tmpStrValue == "ice") { @@ -16273,7 +17556,7 @@ int LuaScriptInterface::luaWeaponVocation(lua_State* L) if (showInDescription) { if (weapon->getVocationString().empty()) { - tmp = asLowerCaseString(getString(L, 2)); + tmp = boost::algorithm::to_lower_copy(getString(L, 2)); tmp += "s"; weapon->setVocationString(tmp); } else { @@ -16283,7 +17566,7 @@ int LuaScriptInterface::luaWeaponVocation(lua_State* L) } else { tmp += ", "; } - tmp += asLowerCaseString(getString(L, 2)); + tmp += boost::algorithm::to_lower_copy(getString(L, 2)); tmp += "s"; weapon->setVocationString(tmp); } @@ -16394,7 +17677,7 @@ int LuaScriptInterface::luaWeaponDuration(lua_State* L) int LuaScriptInterface::luaWeaponDecayTo(lua_State* L) { - // weapon:decayTo([itemid = 0] + // weapon:decayTo([itemid = 0]) Weapon* weapon = getUserdata(L, 1); if (weapon) { uint16_t itemid = getNumber(L, 2, 0); @@ -16486,7 +17769,7 @@ int LuaScriptInterface::luaWeaponAmmoType(lua_State* L) if (type == "arrow") { it.ammoType = AMMO_ARROW; - } else if (type == "bolt"){ + } else if (type == "bolt") { it.ammoType = AMMO_BOLT; } else { std::cout << "[Warning - weapon:ammoType] Type \"" << type << "\" does not exist." << std::endl; @@ -16541,7 +17824,7 @@ int LuaScriptInterface::luaWeaponExtraElement(lua_State* L) if (!getNumber(L, 3)) { std::string element = getString(L, 3); - std::string tmpStrValue = asLowerCaseString(element); + std::string tmpStrValue = boost::algorithm::to_lower_copy(element); if (tmpStrValue == "earth") { it.abilities.get()->elementType = COMBAT_EARTHDAMAGE; } else if (tmpStrValue == "ice") { @@ -16638,7 +17921,7 @@ LuaScriptInterface* LuaEnvironment::getTestInterface() return testInterface; } -Combat* LuaEnvironment::getCombatObject(uint32_t id) const +Combat_ptr LuaEnvironment::getCombatObject(uint32_t id) const { auto it = combatMap.find(id); if (it == combatMap.end()) { @@ -16647,9 +17930,9 @@ Combat* LuaEnvironment::getCombatObject(uint32_t id) const return it->second; } -Combat* LuaEnvironment::createCombatObject(LuaScriptInterface* interface) +Combat_ptr LuaEnvironment::createCombatObject(LuaScriptInterface* interface) { - Combat* combat = new Combat; + Combat_ptr combat = std::make_shared(); combatMap[++lastCombatId] = combat; combatIdMap[interface].push_back(lastCombatId); return combat; @@ -16665,7 +17948,6 @@ void LuaEnvironment::clearCombatObjects(LuaScriptInterface* interface) for (uint32_t id : it->second) { auto itt = combatMap.find(id); if (itt != combatMap.end()) { - delete itt->second; combatMap.erase(itt); } } @@ -16715,15 +17997,15 @@ void LuaEnvironment::executeTimerEvent(uint32_t eventIndex) LuaTimerEventDesc timerEventDesc = std::move(it->second); timerEvents.erase(it); - //push function + // push function lua_rawgeti(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); - //push parameters + // push parameters for (auto parameter : boost::adaptors::reverse(timerEventDesc.parameters)) { lua_rawgeti(luaState, LUA_REGISTRYINDEX, parameter); } - //call the function + // call the function if (reserveScriptEnv()) { ScriptEnvironment* env = getScriptEnv(); env->setTimerEvent(); @@ -16733,7 +18015,7 @@ void LuaEnvironment::executeTimerEvent(uint32_t eventIndex) std::cout << "[Error - LuaScriptInterface::executeTimerEvent] Call stack overflow" << std::endl; } - //free resources + // free resources luaL_unref(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); for (auto parameter : timerEventDesc.parameters) { luaL_unref(luaState, LUA_REGISTRYINDEX, parameter); diff --git a/src/luascript.h b/src/luascript.h index d3fbc80884..3773674202 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -1,30 +1,12 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 -#define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 - -#if __has_include("luajit/lua.hpp") -#include -#else -#include -#endif +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_LUASCRIPT_H +#define FS_LUASCRIPT_H + +#include "database.h" +#include "enums.h" +#include "position.h" #if LUA_VERSION_NUM >= 502 #ifndef LUA_COMPAT_ALL @@ -36,57 +18,46 @@ #endif #endif -#include "database.h" -#include "enums.h" -#include "position.h" -#include "outfit.h" - -class Thing; -class Creature; -class Player; -class Item; -class Container; class AreaCombat; class Combat; -class Condition; -class Npc; -class Monster; +class Container; +class Creature; +class Cylinder; class InstantSpell; +class Item; +class LuaScriptInterface; +class LuaVariant; +class Npc; +class Player; +class Thing; +struct LootBlock; +struct Mount; +struct Outfit; + +using Combat_ptr = std::shared_ptr; -enum { +enum +{ EVENT_ID_LOADING = 1, EVENT_ID_USER = 1000, }; -enum LuaVariantType_t { - VARIANT_NONE, - - VARIANT_NUMBER, - VARIANT_POSITION, - VARIANT_TARGETPOSITION, - VARIANT_STRING, -}; - -enum LuaDataType { +enum LuaDataType +{ LuaData_Unknown, LuaData_Item, LuaData_Container, LuaData_Teleport, + LuaData_Podium, LuaData_Player, LuaData_Monster, LuaData_Npc, LuaData_Tile, }; -struct LuaVariant { - LuaVariantType_t type = VARIANT_NONE; - std::string text; - Position pos; - uint32_t number = 0; -}; - -struct LuaTimerEventDesc { +struct LuaTimerEventDesc +{ int32_t scriptId = -1; int32_t function = -1; std::vector parameters; @@ -96,94 +67,81 @@ struct LuaTimerEventDesc { LuaTimerEventDesc(LuaTimerEventDesc&& other) = default; }; -class LuaScriptInterface; -class Cylinder; -class Game; - -struct LootBlock; - class ScriptEnvironment { - public: - ScriptEnvironment(); - ~ScriptEnvironment(); +public: + ScriptEnvironment(); + ~ScriptEnvironment(); - // non-copyable - ScriptEnvironment(const ScriptEnvironment&) = delete; - ScriptEnvironment& operator=(const ScriptEnvironment&) = delete; + // non-copyable + ScriptEnvironment(const ScriptEnvironment&) = delete; + ScriptEnvironment& operator=(const ScriptEnvironment&) = delete; - void resetEnv(); + void resetEnv(); - void setScriptId(int32_t scriptId, LuaScriptInterface* scriptInterface) { - this->scriptId = scriptId; - interface = scriptInterface; - } - bool setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface); + void setScriptId(int32_t scriptId, LuaScriptInterface* scriptInterface) + { + this->scriptId = scriptId; + interface = scriptInterface; + } + bool setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface); - int32_t getScriptId() const { - return scriptId; - } - LuaScriptInterface* getScriptInterface() { - return interface; - } + int32_t getScriptId() const { return scriptId; } + LuaScriptInterface* getScriptInterface() { return interface; } - void setTimerEvent() { - timerEvent = true; - } + void setTimerEvent() { timerEvent = true; } - void getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const; + void getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, + bool& timerEvent) const; - void addTempItem(Item* item); - static void removeTempItem(Item* item); - uint32_t addThing(Thing* thing); - void insertItem(uint32_t uid, Item* item); + void addTempItem(Item* item); + static void removeTempItem(Item* item); + uint32_t addThing(Thing* thing); + void insertItem(uint32_t uid, Item* item); - static DBResult_ptr getResultByID(uint32_t id); - static uint32_t addResult(DBResult_ptr res); - static bool removeResult(uint32_t id); + static DBResult_ptr getResultByID(uint32_t id); + static uint32_t addResult(DBResult_ptr res); + static bool removeResult(uint32_t id); - void setNpc(Npc* npc) { - curNpc = npc; - } - Npc* getNpc() const { - return curNpc; - } + void setNpc(Npc* npc) { curNpc = npc; } + Npc* getNpc() const { return curNpc; } - Thing* getThingByUID(uint32_t uid); - Item* getItemByUID(uint32_t uid); - Container* getContainerByUID(uint32_t uid); - void removeItemByUID(uint32_t uid); + Thing* getThingByUID(uint32_t uid); + Item* getItemByUID(uint32_t uid); + Container* getContainerByUID(uint32_t uid); + void removeItemByUID(uint32_t uid); - private: - using VariantVector = std::vector; - using StorageMap = std::map; - using DBResultMap = std::map; +private: + using VariantVector = std::vector; + using StorageMap = std::map; + using DBResultMap = std::map; - LuaScriptInterface* interface; + LuaScriptInterface* interface; - //for npc scripts - Npc* curNpc = nullptr; + // for npc scripts + Npc* curNpc = nullptr; - //temporary item list - static std::multimap tempItems; + // temporary item list + static std::multimap tempItems; - //local item map - std::unordered_map localMap; - uint32_t lastUID = std::numeric_limits::max(); + // local item map + std::unordered_map localMap; + uint32_t lastUID = std::numeric_limits::max(); - //script file id - int32_t scriptId; - int32_t callbackId; - bool timerEvent; + // script file id + int32_t scriptId; + int32_t callbackId; + bool timerEvent; - //result map - static uint32_t lastResultId; - static DBResultMap tempResults; + // result map + static uint32_t lastResultId; + static DBResultMap tempResults; }; -#define reportErrorFunc(L, a) LuaScriptInterface::reportError(__FUNCTION__, a, L, true) +#define reportErrorFunc(L, a) LuaScriptInterface::reportError(__FUNCTION__, a, L, true) -enum ErrorCode_t { +enum ErrorCode_t +{ LUA_ERROR_PLAYER_NOT_FOUND, LUA_ERROR_CREATURE_NOT_FOUND, LUA_ERROR_ITEM_NOT_FOUND, @@ -201,1364 +159,1454 @@ enum ErrorCode_t { class LuaScriptInterface { - public: - explicit LuaScriptInterface(std::string interfaceName); - virtual ~LuaScriptInterface(); - - // non-copyable - LuaScriptInterface(const LuaScriptInterface&) = delete; - LuaScriptInterface& operator=(const LuaScriptInterface&) = delete; - - virtual bool initState(); - bool reInitState(); - - int32_t loadFile(const std::string& file, Npc* npc = nullptr); - - const std::string& getFileById(int32_t scriptId); - int32_t getEvent(const std::string& eventName); - int32_t getEvent(); - int32_t getMetaEvent(const std::string& globalName, const std::string& eventName); - - static ScriptEnvironment* getScriptEnv() { - assert(scriptEnvIndex >= 0 && scriptEnvIndex < 16); - return scriptEnv + scriptEnvIndex; - } - - static bool reserveScriptEnv() { - return ++scriptEnvIndex < 16; +public: + explicit LuaScriptInterface(std::string interfaceName); + virtual ~LuaScriptInterface(); + + // non-copyable + LuaScriptInterface(const LuaScriptInterface&) = delete; + LuaScriptInterface& operator=(const LuaScriptInterface&) = delete; + + virtual bool initState(); + bool reInitState(); + + int32_t loadFile(const std::string& file, Npc* npc = nullptr); + + const std::string& getFileById(int32_t scriptId); + int32_t getEvent(const std::string& eventName); + int32_t getEvent(); + int32_t getMetaEvent(const std::string& globalName, const std::string& eventName); + + static ScriptEnvironment* getScriptEnv() + { + assert(scriptEnvIndex >= 0 && scriptEnvIndex < 16); + return scriptEnv + scriptEnvIndex; + } + + static bool reserveScriptEnv() { return ++scriptEnvIndex < 16; } + + static void resetScriptEnv() + { + assert(scriptEnvIndex >= 0); + scriptEnv[scriptEnvIndex--].resetEnv(); + } + + static void reportError(const char* function, const std::string& error_desc, lua_State* L = nullptr, + bool stack_trace = false); + + const std::string& getInterfaceName() const { return interfaceName; } + const std::string& getLastLuaError() const { return lastLuaError; } + + lua_State* getLuaState() const { return luaState; } + + bool pushFunction(int32_t functionId); + + static int luaErrorHandler(lua_State* L); + bool callFunction(int params); + void callVoidFunction(int params); + + // push/pop common structures + static void pushThing(lua_State* L, Thing* thing); + static void pushVariant(lua_State* L, const LuaVariant& var); + static void pushString(lua_State* L, const std::string& value); + static void pushCallback(lua_State* L, int32_t callback); + static void pushCylinder(lua_State* L, Cylinder* cylinder); + + static std::string popString(lua_State* L); + static int32_t popCallback(lua_State* L); + + // Userdata + template + static void pushUserdata(lua_State* L, T* value) + { + T** userdata = static_cast(lua_newuserdata(L, sizeof(T*))); + *userdata = value; + } + + // Shared Ptr + template + static void pushSharedPtr(lua_State* L, T value) + { + new (lua_newuserdata(L, sizeof(T))) T(std::move(value)); + } + + // Metatables + static void setMetatable(lua_State* L, int32_t index, const std::string& name); + static void setWeakMetatable(lua_State* L, int32_t index, const std::string& name); + + static void setItemMetatable(lua_State* L, int32_t index, const Item* item); + static void setCreatureMetatable(lua_State* L, int32_t index, const Creature* creature); + + // Get + template + static typename std::enable_if::value, T>::type getNumber(lua_State* L, int32_t arg) + { + return static_cast(static_cast(lua_tonumber(L, arg))); + } + + template + static typename std::enable_if::value && std::is_unsigned::value, T>::type getNumber( + lua_State* L, int32_t arg) + { + double num = lua_tonumber(L, arg); + if (num < static_cast(std::numeric_limits::lowest()) || + num > static_cast(std::numeric_limits::max())) { + reportErrorFunc(L, + fmt::format("Argument {} has out-of-range value for {}: {}", arg, typeid(T).name(), num)); } - static void resetScriptEnv() { - assert(scriptEnvIndex >= 0); - scriptEnv[scriptEnvIndex--].resetEnv(); + return static_cast(num); + } + + template + static typename std::enable_if< + (std::is_integral::value && std::is_signed::value) || std::is_floating_point::value, T>::type + getNumber(lua_State* L, int32_t arg) + { + double num = lua_tonumber(L, arg); + if (num < static_cast(std::numeric_limits::lowest()) || + num > static_cast(std::numeric_limits::max())) { + reportErrorFunc(L, + fmt::format("Argument {} has out-of-range value for {}: {}", arg, typeid(T).name(), num)); } - static void reportError(const char* function, const std::string& error_desc, lua_State* L = nullptr, bool stack_trace = false); + return static_cast(num); + } - const std::string& getInterfaceName() const { - return interfaceName; + template + static T getNumber(lua_State* L, int32_t arg, T defaultValue) + { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; } - const std::string& getLastLuaError() const { - return lastLuaError; + return getNumber(L, arg); + } + template + static T* getUserdata(lua_State* L, int32_t arg) + { + T** userdata = getRawUserdata(L, arg); + if (!userdata) { + return nullptr; } - - lua_State* getLuaState() const { - return luaState; + return *userdata; + } + template + static T** getRawUserdata(lua_State* L, int32_t arg) + { + return static_cast(lua_touserdata(L, arg)); + } + template + static std::shared_ptr& getSharedPtr(lua_State* L, int32_t arg) + { + return *static_cast*>(lua_touserdata(L, arg)); + } + + static bool getBoolean(lua_State* L, int32_t arg) { return lua_toboolean(L, arg) != 0; } + static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) + { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; } + return lua_toboolean(L, arg) != 0; + } + + static std::string getString(lua_State* L, int32_t arg); + static Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); + static Position getPosition(lua_State* L, int32_t arg); + static Outfit_t getOutfit(lua_State* L, int32_t arg); + static Outfit getOutfitClass(lua_State* L, int32_t arg); + static InstantSpell* getInstantSpell(lua_State* L, int32_t arg); + static Reflect getReflect(lua_State* L, int32_t arg); + + static Thing* getThing(lua_State* L, int32_t arg); + static Creature* getCreature(lua_State* L, int32_t arg); + static Player* getPlayer(lua_State* L, int32_t arg); + + template + static T getField(lua_State* L, int32_t arg, const std::string& key) + { + lua_getfield(L, arg, key.c_str()); + return getNumber(L, -1); + } + + static std::string getFieldString(lua_State* L, int32_t arg, const std::string& key); + + static LuaDataType getUserdataType(lua_State* L, int32_t arg); + + // Is + static bool isNumber(lua_State* L, int32_t arg) { return lua_type(L, arg) == LUA_TNUMBER; } + static bool isString(lua_State* L, int32_t arg) { return lua_isstring(L, arg) != 0; } + static bool isBoolean(lua_State* L, int32_t arg) { return lua_isboolean(L, arg); } + static bool isTable(lua_State* L, int32_t arg) { return lua_istable(L, arg); } + static bool isFunction(lua_State* L, int32_t arg) { return lua_isfunction(L, arg); } + static bool isUserdata(lua_State* L, int32_t arg) { return lua_isuserdata(L, arg) != 0; } + + // Push + static void pushBoolean(lua_State* L, bool value); + static void pushCombatDamage(lua_State* L, const CombatDamage& damage); + static void pushInstantSpell(lua_State* L, const InstantSpell& spell); + static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); + static void pushOutfit(lua_State* L, const Outfit_t& outfit); + static void pushOutfit(lua_State* L, const Outfit* outfit); + static void pushMount(lua_State* L, const Mount* mount); + static void pushLoot(lua_State* L, const std::vector& lootList); + static void pushReflect(lua_State* L, const Reflect& reflect); + + // + static void setField(lua_State* L, const char* index, lua_Number value) + { + lua_pushnumber(L, value); + lua_setfield(L, -2, index); + } + + static void setField(lua_State* L, const char* index, const std::string& value) + { + pushString(L, value); + lua_setfield(L, -2, index); + } + + static std::string escapeString(std::string string); - bool pushFunction(int32_t functionId); - - static int luaErrorHandler(lua_State* L); - bool callFunction(int params); - void callVoidFunction(int params); - - //push/pop common structures - static void pushThing(lua_State* L, Thing* thing); - static void pushVariant(lua_State* L, const LuaVariant& var); - static void pushString(lua_State* L, const std::string& value); - static void pushCallback(lua_State* L, int32_t callback); - static void pushCylinder(lua_State* L, Cylinder* cylinder); - - static std::string popString(lua_State* L); - static int32_t popCallback(lua_State* L); - - // Userdata - template - static void pushUserdata(lua_State* L, T* value) - { - T** userdata = static_cast(lua_newuserdata(L, sizeof(T*))); - *userdata = value; - } - - // Metatables - static void setMetatable(lua_State* L, int32_t index, const std::string& name); - static void setWeakMetatable(lua_State* L, int32_t index, const std::string& name); - - static void setItemMetatable(lua_State* L, int32_t index, const Item* item); - static void setCreatureMetatable(lua_State* L, int32_t index, const Creature* creature); - - // Get - template - static typename std::enable_if::value, T>::type - getNumber(lua_State* L, int32_t arg) - { - return static_cast(static_cast(lua_tonumber(L, arg))); - } - template - static typename std::enable_if::value || std::is_floating_point::value, T>::type - getNumber(lua_State* L, int32_t arg) - { - return static_cast(lua_tonumber(L, arg)); - } - template - static T getNumber(lua_State *L, int32_t arg, T defaultValue) - { - const auto parameters = lua_gettop(L); - if (parameters == 0 || arg > parameters) { - return defaultValue; - } - return getNumber(L, arg); - } - template - static T* getUserdata(lua_State* L, int32_t arg) - { - T** userdata = getRawUserdata(L, arg); - if (!userdata) { - return nullptr; - } - return *userdata; - } - template - static T** getRawUserdata(lua_State* L, int32_t arg) - { - return static_cast(lua_touserdata(L, arg)); - } - - static bool getBoolean(lua_State* L, int32_t arg) - { - return lua_toboolean(L, arg) != 0; - } - static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) - { - const auto parameters = lua_gettop(L); - if (parameters == 0 || arg > parameters) { - return defaultValue; - } - return lua_toboolean(L, arg) != 0; - } - - static std::string getString(lua_State* L, int32_t arg); - static Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); - static Position getPosition(lua_State* L, int32_t arg); - static Outfit_t getOutfit(lua_State* L, int32_t arg); - static Outfit getOutfitClass(lua_State* L, int32_t arg); - static LuaVariant getVariant(lua_State* L, int32_t arg); - static InstantSpell* getInstantSpell(lua_State* L, int32_t arg); - - static Thing* getThing(lua_State* L, int32_t arg); - static Creature* getCreature(lua_State* L, int32_t arg); - static Player* getPlayer(lua_State* L, int32_t arg); - - template - static T getField(lua_State* L, int32_t arg, const std::string& key) - { - lua_getfield(L, arg, key.c_str()); - return getNumber(L, -1); - } - - static std::string getFieldString(lua_State* L, int32_t arg, const std::string& key); - - static LuaDataType getUserdataType(lua_State* L, int32_t arg); +#ifndef LUAJIT_VERSION + static const luaL_Reg luaBitReg[7]; +#endif + static const luaL_Reg luaConfigManagerTable[4]; + static const luaL_Reg luaDatabaseTable[9]; + static const luaL_Reg luaResultTable[6]; - // Is - static bool isNumber(lua_State* L, int32_t arg) - { - return lua_type(L, arg) == LUA_TNUMBER; - } - static bool isString(lua_State* L, int32_t arg) - { - return lua_isstring(L, arg) != 0; - } - static bool isBoolean(lua_State* L, int32_t arg) - { - return lua_isboolean(L, arg); - } - static bool isTable(lua_State* L, int32_t arg) - { - return lua_istable(L, arg); - } - static bool isFunction(lua_State* L, int32_t arg) - { - return lua_isfunction(L, arg); - } - static bool isUserdata(lua_State* L, int32_t arg) - { - return lua_isuserdata(L, arg) != 0; - } + static int protectedCall(lua_State* L, int nargs, int nresults); - // Push - static void pushBoolean(lua_State* L, bool value); - static void pushCombatDamage(lua_State* L, const CombatDamage& damage); - static void pushInstantSpell(lua_State* L, const InstantSpell& spell); - static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); - static void pushOutfit(lua_State* L, const Outfit_t& outfit); - static void pushOutfit(lua_State* L, const Outfit* outfit); - static void pushLoot(lua_State* L, const std::vector& lootList); - - // - static void setField(lua_State* L, const char* index, lua_Number value) - { - lua_pushnumber(L, value); - lua_setfield(L, -2, index); - } +protected: + virtual bool closeState(); - static void setField(lua_State* L, const char* index, const std::string& value) - { - pushString(L, value); - lua_setfield(L, -2, index); - } + void registerFunctions(); - static std::string escapeString(const std::string& string); + void registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func); -#ifndef LUAJIT_VERSION - static const luaL_Reg luaBitReg[7]; -#endif - static const luaL_Reg luaConfigManagerTable[4]; - static const luaL_Reg luaDatabaseTable[9]; - static const luaL_Reg luaResultTable[6]; + static std::string getErrorDesc(ErrorCode_t code); - static int protectedCall(lua_State* L, int nargs, int nresults); + lua_State* luaState = nullptr; - protected: - virtual bool closeState(); + int32_t eventTableRef = -1; + int32_t runningEventId = EVENT_ID_USER; - void registerFunctions(); + // script file cache + std::map cacheFiles; - void registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func); +private: + void registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction = nullptr); + void registerTable(const std::string& tableName); + void registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func); + void registerGlobalMethod(const std::string& functionName, lua_CFunction func); + void registerVariable(const std::string& tableName, const std::string& name, lua_Number value); + void registerGlobalVariable(const std::string& name, lua_Number value); + void registerGlobalBoolean(const std::string& name, bool value); - static std::string getErrorDesc(ErrorCode_t code); + static std::string getStackTrace(lua_State* L, const std::string& error_desc); - lua_State* luaState = nullptr; + static bool getArea(lua_State* L, std::vector& vec, uint32_t& rows); - int32_t eventTableRef = -1; - int32_t runningEventId = EVENT_ID_USER; + // lua functions + static int luaDoPlayerAddItem(lua_State* L); - //script file cache - std::map cacheFiles; + // get item info + static int luaGetDepotId(lua_State* L); - private: - void registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction = nullptr); - void registerTable(const std::string& tableName); - void registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func); - void registerGlobalMethod(const std::string& functionName, lua_CFunction func); - void registerVariable(const std::string& tableName, const std::string& name, lua_Number value); - void registerGlobalVariable(const std::string& name, lua_Number value); - void registerGlobalBoolean(const std::string& name, bool value); + // get world info + static int luaGetWorldTime(lua_State* L); + static int luaGetWorldUpTime(lua_State* L); + static int luaGetWorldLight(lua_State* L); + static int luaSetWorldLight(lua_State* L); - static std::string getStackTrace(lua_State* L, const std::string& error_desc); + // get subtype name + static int luaGetSubTypeName(lua_State* L); - static bool getArea(lua_State* L, std::vector& vec, uint32_t& rows); + // type validation + static int luaIsDepot(lua_State* L); + static int luaIsMoveable(lua_State* L); + static int luaIsValidUID(lua_State* L); - //lua functions - static int luaDoPlayerAddItem(lua_State* L); + // container + static int luaDoAddContainerItem(lua_State* L); - //get item info - static int luaGetDepotId(lua_State* L); + // + static int luaCreateCombatArea(lua_State* L); - //get world info - static int luaGetWorldTime(lua_State* L); - static int luaGetWorldUpTime(lua_State* L); - static int luaGetWorldLight(lua_State* L); - static int luaSetWorldLight(lua_State* L); + static int luaDoAreaCombat(lua_State* L); + static int luaDoTargetCombat(lua_State* L); - //get subtype name - static int luaGetSubTypeName(lua_State* L); + static int luaDoChallengeCreature(lua_State* L); - //type validation - static int luaIsDepot(lua_State* L); - static int luaIsMoveable(lua_State* L); - static int luaIsValidUID(lua_State* L); + static int luaDebugPrint(lua_State* L); + static int luaAddEvent(lua_State* L); + static int luaStopEvent(lua_State* L); - //container - static int luaDoAddContainerItem(lua_State* L); + static int luaSaveServer(lua_State* L); + static int luaCleanMap(lua_State* L); - // - static int luaCreateCombatArea(lua_State* L); + static int luaIsInWar(lua_State* L); - static int luaDoAreaCombat(lua_State* L); - static int luaDoTargetCombat(lua_State* L); + static int luaGetWaypointPositionByName(lua_State* L); - static int luaDoChallengeCreature(lua_State* L); + static int luaSendChannelMessage(lua_State* L); + static int luaSendGuildChannelMessage(lua_State* L); - static int luaDebugPrint(lua_State* L); - static int luaAddEvent(lua_State* L); - static int luaStopEvent(lua_State* L); + static int luaIsScriptsInterface(lua_State* L); - static int luaSaveServer(lua_State* L); - static int luaCleanMap(lua_State* L); +#ifndef LUAJIT_VERSION + static int luaBitNot(lua_State* L); + static int luaBitAnd(lua_State* L); + static int luaBitOr(lua_State* L); + static int luaBitXor(lua_State* L); + static int luaBitLeftShift(lua_State* L); + static int luaBitRightShift(lua_State* L); +#endif - static int luaIsInWar(lua_State* L); + static int luaConfigManagerGetString(lua_State* L); + static int luaConfigManagerGetNumber(lua_State* L); + static int luaConfigManagerGetBoolean(lua_State* L); - static int luaGetWaypointPositionByName(lua_State* L); + static int luaDatabaseExecute(lua_State* L); + static int luaDatabaseAsyncExecute(lua_State* L); + static int luaDatabaseStoreQuery(lua_State* L); + static int luaDatabaseAsyncStoreQuery(lua_State* L); + static int luaDatabaseEscapeString(lua_State* L); + static int luaDatabaseEscapeBlob(lua_State* L); + static int luaDatabaseLastInsertId(lua_State* L); + static int luaDatabaseTableExists(lua_State* L); - static int luaSendChannelMessage(lua_State* L); - static int luaSendGuildChannelMessage(lua_State* L); + static int luaResultGetNumber(lua_State* L); + static int luaResultGetString(lua_State* L); + static int luaResultGetStream(lua_State* L); + static int luaResultNext(lua_State* L); + static int luaResultFree(lua_State* L); + + // Userdata + static int luaUserdataCompare(lua_State* L); + + // _G + static int luaIsType(lua_State* L); + static int luaRawGetMetatable(lua_State* L); + + // os + static int luaSystemTime(lua_State* L); + + // table + static int luaTableCreate(lua_State* L); + static int luaTablePack(lua_State* L); + + // Game + static int luaGameGetSpectators(lua_State* L); + static int luaGameGetPlayers(lua_State* L); + static int luaGameLoadMap(lua_State* L); - static int luaIsScriptsInterface(lua_State* L); + static int luaGameGetExperienceStage(lua_State* L); + static int luaGameGetExperienceForLevel(lua_State* L); + static int luaGameGetMonsterCount(lua_State* L); + static int luaGameGetPlayerCount(lua_State* L); + static int luaGameGetNpcCount(lua_State* L); + static int luaGameGetMonsterTypes(lua_State* L); + static int luaGameGetCurrencyItems(lua_State* L); + static int luaGameGetItemTypeByClientId(lua_State* L); + static int luaGameGetMountIdByLookType(lua_State* L); -#ifndef LUAJIT_VERSION - static int luaBitNot(lua_State* L); - static int luaBitAnd(lua_State* L); - static int luaBitOr(lua_State* L); - static int luaBitXor(lua_State* L); - static int luaBitLeftShift(lua_State* L); - static int luaBitRightShift(lua_State* L); -#endif + static int luaGameGetTowns(lua_State* L); + static int luaGameGetHouses(lua_State* L); + static int luaGameGetOutfits(lua_State* L); + static int luaGameGetMounts(lua_State* L); - static int luaConfigManagerGetString(lua_State* L); - static int luaConfigManagerGetNumber(lua_State* L); - static int luaConfigManagerGetBoolean(lua_State* L); + static int luaGameGetGameState(lua_State* L); + static int luaGameSetGameState(lua_State* L); + + static int luaGameGetWorldType(lua_State* L); + static int luaGameSetWorldType(lua_State* L); + + static int luaGameGetItemAttributeByName(lua_State* L); + static int luaGameGetReturnMessage(lua_State* L); + + static int luaGameCreateItem(lua_State* L); + static int luaGameCreateContainer(lua_State* L); + static int luaGameCreateMonster(lua_State* L); + static int luaGameCreateNpc(lua_State* L); + static int luaGameCreateTile(lua_State* L); + static int luaGameCreateMonsterType(lua_State* L); + + static int luaGameStartRaid(lua_State* L); + + static int luaGameGetClientVersion(lua_State* L); + + static int luaGameReload(lua_State* L); + + static int luaGameGetAccountStorageValue(lua_State* L); + static int luaGameSetAccountStorageValue(lua_State* L); + static int luaGameSaveAccountStorageValues(lua_State* L); + + // Variant + static int luaVariantCreate(lua_State* L); + + static int luaVariantGetNumber(lua_State* L); + static int luaVariantGetString(lua_State* L); + static int luaVariantGetPosition(lua_State* L); + + // Position + static int luaPositionCreate(lua_State* L); + static int luaPositionAdd(lua_State* L); + static int luaPositionSub(lua_State* L); + static int luaPositionCompare(lua_State* L); + + static int luaPositionGetDistance(lua_State* L); + static int luaPositionIsSightClear(lua_State* L); + + static int luaPositionSendMagicEffect(lua_State* L); + static int luaPositionSendDistanceEffect(lua_State* L); - static int luaDatabaseExecute(lua_State* L); - static int luaDatabaseAsyncExecute(lua_State* L); - static int luaDatabaseStoreQuery(lua_State* L); - static int luaDatabaseAsyncStoreQuery(lua_State* L); - static int luaDatabaseEscapeString(lua_State* L); - static int luaDatabaseEscapeBlob(lua_State* L); - static int luaDatabaseLastInsertId(lua_State* L); - static int luaDatabaseTableExists(lua_State* L); + // Tile + static int luaTileCreate(lua_State* L); + + static int luaTileRemove(lua_State* L); - static int luaResultGetNumber(lua_State* L); - static int luaResultGetString(lua_State* L); - static int luaResultGetStream(lua_State* L); - static int luaResultNext(lua_State* L); - static int luaResultFree(lua_State* L); + static int luaTileGetPosition(lua_State* L); + static int luaTileGetGround(lua_State* L); + static int luaTileGetThing(lua_State* L); + static int luaTileGetThingCount(lua_State* L); + static int luaTileGetTopVisibleThing(lua_State* L); - // Userdata - static int luaUserdataCompare(lua_State* L); + static int luaTileGetTopTopItem(lua_State* L); + static int luaTileGetTopDownItem(lua_State* L); + static int luaTileGetFieldItem(lua_State* L); - // _G - static int luaIsType(lua_State* L); - static int luaRawGetMetatable(lua_State* L); + static int luaTileGetItemById(lua_State* L); + static int luaTileGetItemByType(lua_State* L); + static int luaTileGetItemByTopOrder(lua_State* L); + static int luaTileGetItemCountById(lua_State* L); - // os - static int luaSystemTime(lua_State* L); + static int luaTileGetBottomCreature(lua_State* L); + static int luaTileGetTopCreature(lua_State* L); + static int luaTileGetBottomVisibleCreature(lua_State* L); + static int luaTileGetTopVisibleCreature(lua_State* L); - // table - static int luaTableCreate(lua_State* L); - static int luaTablePack(lua_State* L); + static int luaTileGetItems(lua_State* L); + static int luaTileGetItemCount(lua_State* L); + static int luaTileGetDownItemCount(lua_State* L); + static int luaTileGetTopItemCount(lua_State* L); - // Game - static int luaGameGetSpectators(lua_State* L); - static int luaGameGetPlayers(lua_State* L); - static int luaGameLoadMap(lua_State* L); + static int luaTileGetCreatures(lua_State* L); + static int luaTileGetCreatureCount(lua_State* L); - static int luaGameGetExperienceStage(lua_State* L); - static int luaGameGetMonsterCount(lua_State* L); - static int luaGameGetPlayerCount(lua_State* L); - static int luaGameGetNpcCount(lua_State* L); - static int luaGameGetMonsterTypes(lua_State* L); + static int luaTileHasProperty(lua_State* L); + static int luaTileHasFlag(lua_State* L); - static int luaGameGetTowns(lua_State* L); - static int luaGameGetHouses(lua_State* L); + static int luaTileGetThingIndex(lua_State* L); - static int luaGameGetGameState(lua_State* L); - static int luaGameSetGameState(lua_State* L); + static int luaTileQueryAdd(lua_State* L); + static int luaTileAddItem(lua_State* L); + static int luaTileAddItemEx(lua_State* L); - static int luaGameGetWorldType(lua_State* L); - static int luaGameSetWorldType(lua_State* L); + static int luaTileGetHouse(lua_State* L); - static int luaGameGetReturnMessage(lua_State* L); - - static int luaGameCreateItem(lua_State* L); - static int luaGameCreateContainer(lua_State* L); - static int luaGameCreateMonster(lua_State* L); - static int luaGameCreateNpc(lua_State* L); - static int luaGameCreateTile(lua_State* L); - static int luaGameCreateMonsterType(lua_State* L); - - static int luaGameStartRaid(lua_State* L); - - static int luaGameGetClientVersion(lua_State* L); - - static int luaGameReload(lua_State* L); - - // Variant - static int luaVariantCreate(lua_State* L); - - static int luaVariantGetNumber(lua_State* L); - static int luaVariantGetString(lua_State* L); - static int luaVariantGetPosition(lua_State* L); - - // Position - static int luaPositionCreate(lua_State* L); - static int luaPositionAdd(lua_State* L); - static int luaPositionSub(lua_State* L); - static int luaPositionCompare(lua_State* L); + // NetworkMessage + static int luaNetworkMessageCreate(lua_State* L); + static int luaNetworkMessageDelete(lua_State* L); - static int luaPositionGetDistance(lua_State* L); - static int luaPositionIsSightClear(lua_State* L); + static int luaNetworkMessageGetByte(lua_State* L); + static int luaNetworkMessageGetU16(lua_State* L); + static int luaNetworkMessageGetU32(lua_State* L); + static int luaNetworkMessageGetU64(lua_State* L); + static int luaNetworkMessageGetString(lua_State* L); + static int luaNetworkMessageGetPosition(lua_State* L); - static int luaPositionSendMagicEffect(lua_State* L); - static int luaPositionSendDistanceEffect(lua_State* L); + static int luaNetworkMessageAddByte(lua_State* L); + static int luaNetworkMessageAddU16(lua_State* L); + static int luaNetworkMessageAddU32(lua_State* L); + static int luaNetworkMessageAddU64(lua_State* L); + static int luaNetworkMessageAddString(lua_State* L); + static int luaNetworkMessageAddPosition(lua_State* L); + static int luaNetworkMessageAddDouble(lua_State* L); + static int luaNetworkMessageAddItem(lua_State* L); + static int luaNetworkMessageAddItemId(lua_State* L); - // Tile - static int luaTileCreate(lua_State* L); + static int luaNetworkMessageReset(lua_State* L); + static int luaNetworkMessageSeek(lua_State* L); + static int luaNetworkMessageTell(lua_State* L); + static int luaNetworkMessageLength(lua_State* L); + static int luaNetworkMessageSkipBytes(lua_State* L); + static int luaNetworkMessageSendToPlayer(lua_State* L); - static int luaTileRemove(lua_State* L); + // ModalWindow + static int luaModalWindowCreate(lua_State* L); + static int luaModalWindowDelete(lua_State* L); - static int luaTileGetPosition(lua_State* L); - static int luaTileGetGround(lua_State* L); - static int luaTileGetThing(lua_State* L); - static int luaTileGetThingCount(lua_State* L); - static int luaTileGetTopVisibleThing(lua_State* L); + static int luaModalWindowGetId(lua_State* L); + static int luaModalWindowGetTitle(lua_State* L); + static int luaModalWindowGetMessage(lua_State* L); - static int luaTileGetTopTopItem(lua_State* L); - static int luaTileGetTopDownItem(lua_State* L); - static int luaTileGetFieldItem(lua_State* L); + static int luaModalWindowSetTitle(lua_State* L); + static int luaModalWindowSetMessage(lua_State* L); - static int luaTileGetItemById(lua_State* L); - static int luaTileGetItemByType(lua_State* L); - static int luaTileGetItemByTopOrder(lua_State* L); - static int luaTileGetItemCountById(lua_State* L); + static int luaModalWindowGetButtonCount(lua_State* L); + static int luaModalWindowGetChoiceCount(lua_State* L); - static int luaTileGetBottomCreature(lua_State* L); - static int luaTileGetTopCreature(lua_State* L); - static int luaTileGetBottomVisibleCreature(lua_State* L); - static int luaTileGetTopVisibleCreature(lua_State* L); + static int luaModalWindowAddButton(lua_State* L); + static int luaModalWindowAddChoice(lua_State* L); - static int luaTileGetItems(lua_State* L); - static int luaTileGetItemCount(lua_State* L); - static int luaTileGetDownItemCount(lua_State* L); - static int luaTileGetTopItemCount(lua_State* L); + static int luaModalWindowGetDefaultEnterButton(lua_State* L); + static int luaModalWindowSetDefaultEnterButton(lua_State* L); - static int luaTileGetCreatures(lua_State* L); - static int luaTileGetCreatureCount(lua_State* L); + static int luaModalWindowGetDefaultEscapeButton(lua_State* L); + static int luaModalWindowSetDefaultEscapeButton(lua_State* L); - static int luaTileHasProperty(lua_State* L); - static int luaTileHasFlag(lua_State* L); + static int luaModalWindowHasPriority(lua_State* L); + static int luaModalWindowSetPriority(lua_State* L); - static int luaTileGetThingIndex(lua_State* L); + static int luaModalWindowSendToPlayer(lua_State* L); - static int luaTileQueryAdd(lua_State* L); - static int luaTileAddItem(lua_State* L); - static int luaTileAddItemEx(lua_State* L); + // Item + static int luaItemCreate(lua_State* L); - static int luaTileGetHouse(lua_State* L); + static int luaItemIsItem(lua_State* L); - // NetworkMessage - static int luaNetworkMessageCreate(lua_State* L); - static int luaNetworkMessageDelete(lua_State* L); + static int luaItemGetParent(lua_State* L); + static int luaItemGetTopParent(lua_State* L); - static int luaNetworkMessageGetByte(lua_State* L); - static int luaNetworkMessageGetU16(lua_State* L); - static int luaNetworkMessageGetU32(lua_State* L); - static int luaNetworkMessageGetU64(lua_State* L); - static int luaNetworkMessageGetString(lua_State* L); - static int luaNetworkMessageGetPosition(lua_State* L); + static int luaItemGetId(lua_State* L); - static int luaNetworkMessageAddByte(lua_State* L); - static int luaNetworkMessageAddU16(lua_State* L); - static int luaNetworkMessageAddU32(lua_State* L); - static int luaNetworkMessageAddU64(lua_State* L); - static int luaNetworkMessageAddString(lua_State* L); - static int luaNetworkMessageAddPosition(lua_State* L); - static int luaNetworkMessageAddDouble(lua_State* L); - static int luaNetworkMessageAddItem(lua_State* L); - static int luaNetworkMessageAddItemId(lua_State* L); + static int luaItemClone(lua_State* L); + static int luaItemSplit(lua_State* L); + static int luaItemRemove(lua_State* L); - static int luaNetworkMessageReset(lua_State* L); - static int luaNetworkMessageSeek(lua_State* L); - static int luaNetworkMessageTell(lua_State* L); - static int luaNetworkMessageLength(lua_State* L); - static int luaNetworkMessageSkipBytes(lua_State* L); - static int luaNetworkMessageSendToPlayer(lua_State* L); + static int luaItemGetUniqueId(lua_State* L); + static int luaItemGetActionId(lua_State* L); + static int luaItemSetActionId(lua_State* L); - // ModalWindow - static int luaModalWindowCreate(lua_State* L); - static int luaModalWindowDelete(lua_State* L); + static int luaItemGetCount(lua_State* L); + static int luaItemGetCharges(lua_State* L); + static int luaItemGetFluidType(lua_State* L); + static int luaItemGetWeight(lua_State* L); + static int luaItemGetWorth(lua_State* L); - static int luaModalWindowGetId(lua_State* L); - static int luaModalWindowGetTitle(lua_State* L); - static int luaModalWindowGetMessage(lua_State* L); + static int luaItemGetSubType(lua_State* L); - static int luaModalWindowSetTitle(lua_State* L); - static int luaModalWindowSetMessage(lua_State* L); + static int luaItemGetName(lua_State* L); + static int luaItemGetPluralName(lua_State* L); + static int luaItemGetArticle(lua_State* L); - static int luaModalWindowGetButtonCount(lua_State* L); - static int luaModalWindowGetChoiceCount(lua_State* L); + static int luaItemGetPosition(lua_State* L); + static int luaItemGetTile(lua_State* L); - static int luaModalWindowAddButton(lua_State* L); - static int luaModalWindowAddChoice(lua_State* L); + static int luaItemHasAttribute(lua_State* L); + static int luaItemGetAttribute(lua_State* L); + static int luaItemSetAttribute(lua_State* L); + static int luaItemRemoveAttribute(lua_State* L); + static int luaItemGetCustomAttribute(lua_State* L); + static int luaItemSetCustomAttribute(lua_State* L); + static int luaItemRemoveCustomAttribute(lua_State* L); - static int luaModalWindowGetDefaultEnterButton(lua_State* L); - static int luaModalWindowSetDefaultEnterButton(lua_State* L); + static int luaItemMoveTo(lua_State* L); + static int luaItemTransform(lua_State* L); + static int luaItemDecay(lua_State* L); - static int luaModalWindowGetDefaultEscapeButton(lua_State* L); - static int luaModalWindowSetDefaultEscapeButton(lua_State* L); + static int luaItemGetSpecialDescription(lua_State* L); - static int luaModalWindowHasPriority(lua_State* L); - static int luaModalWindowSetPriority(lua_State* L); + static int luaItemHasProperty(lua_State* L); + static int luaItemIsLoadedFromMap(lua_State* L); - static int luaModalWindowSendToPlayer(lua_State* L); + static int luaItemSetStoreItem(lua_State* L); + static int luaItemIsStoreItem(lua_State* L); - // Item - static int luaItemCreate(lua_State* L); + static int luaItemSetReflect(lua_State* L); + static int luaItemGetReflect(lua_State* L); - static int luaItemIsItem(lua_State* L); + static int luaItemSetBoostPercent(lua_State* L); + static int luaItemGetBoostPercent(lua_State* L); - static int luaItemGetParent(lua_State* L); - static int luaItemGetTopParent(lua_State* L); + // Container + static int luaContainerCreate(lua_State* L); - static int luaItemGetId(lua_State* L); + static int luaContainerGetSize(lua_State* L); + static int luaContainerGetCapacity(lua_State* L); + static int luaContainerGetEmptySlots(lua_State* L); + static int luaContainerGetItems(lua_State* L); + static int luaContainerGetItemHoldingCount(lua_State* L); + static int luaContainerGetItemCountById(lua_State* L); - static int luaItemClone(lua_State* L); - static int luaItemSplit(lua_State* L); - static int luaItemRemove(lua_State* L); + static int luaContainerGetItem(lua_State* L); + static int luaContainerHasItem(lua_State* L); + static int luaContainerAddItem(lua_State* L); + static int luaContainerAddItemEx(lua_State* L); + static int luaContainerGetCorpseOwner(lua_State* L); - static int luaItemGetUniqueId(lua_State* L); - static int luaItemGetActionId(lua_State* L); - static int luaItemSetActionId(lua_State* L); + // Teleport + static int luaTeleportCreate(lua_State* L); - static int luaItemGetCount(lua_State* L); - static int luaItemGetCharges(lua_State* L); - static int luaItemGetFluidType(lua_State* L); - static int luaItemGetWeight(lua_State* L); + static int luaTeleportGetDestination(lua_State* L); + static int luaTeleportSetDestination(lua_State* L); - static int luaItemGetSubType(lua_State* L); + // Podium + static int luaPodiumCreate(lua_State* L); - static int luaItemGetName(lua_State* L); - static int luaItemGetPluralName(lua_State* L); - static int luaItemGetArticle(lua_State* L); + static int luaPodiumGetOutfit(lua_State* L); + static int luaPodiumSetOutfit(lua_State* L); + static int luaPodiumHasFlag(lua_State* L); + static int luaPodiumSetFlag(lua_State* L); + static int luaPodiumGetDirection(lua_State* L); + static int luaPodiumSetDirection(lua_State* L); - static int luaItemGetPosition(lua_State* L); - static int luaItemGetTile(lua_State* L); + // Creature + static int luaCreatureCreate(lua_State* L); - static int luaItemHasAttribute(lua_State* L); - static int luaItemGetAttribute(lua_State* L); - static int luaItemSetAttribute(lua_State* L); - static int luaItemRemoveAttribute(lua_State* L); - static int luaItemGetCustomAttribute(lua_State* L); - static int luaItemSetCustomAttribute(lua_State* L); - static int luaItemRemoveCustomAttribute(lua_State* L); + static int luaCreatureGetEvents(lua_State* L); + static int luaCreatureRegisterEvent(lua_State* L); + static int luaCreatureUnregisterEvent(lua_State* L); - static int luaItemMoveTo(lua_State* L); - static int luaItemTransform(lua_State* L); - static int luaItemDecay(lua_State* L); + static int luaCreatureIsRemoved(lua_State* L); + static int luaCreatureIsCreature(lua_State* L); + static int luaCreatureIsInGhostMode(lua_State* L); + static int luaCreatureIsHealthHidden(lua_State* L); + static int luaCreatureIsMovementBlocked(lua_State* L); + static int luaCreatureIsImmune(lua_State* L); - static int luaItemGetDescription(lua_State* L); - static int luaItemGetSpecialDescription(lua_State* L); + static int luaCreatureCanSee(lua_State* L); + static int luaCreatureCanSeeCreature(lua_State* L); + static int luaCreatureCanSeeGhostMode(lua_State* L); + static int luaCreatureCanSeeInvisibility(lua_State* L); - static int luaItemHasProperty(lua_State* L); - static int luaItemIsLoadedFromMap(lua_State* L); + static int luaCreatureGetParent(lua_State* L); - static int luaItemSetStoreItem(lua_State* L); - static int luaItemIsStoreItem(lua_State* L); + static int luaCreatureGetId(lua_State* L); + static int luaCreatureGetName(lua_State* L); - // Container - static int luaContainerCreate(lua_State* L); + static int luaCreatureGetTarget(lua_State* L); + static int luaCreatureSetTarget(lua_State* L); - static int luaContainerGetSize(lua_State* L); - static int luaContainerGetCapacity(lua_State* L); - static int luaContainerGetEmptySlots(lua_State* L); - static int luaContainerGetContentDescription(lua_State* L); - static int luaContainerGetItems(lua_State* L); - static int luaContainerGetItemHoldingCount(lua_State* L); - static int luaContainerGetItemCountById(lua_State* L); + static int luaCreatureGetFollowCreature(lua_State* L); + static int luaCreatureSetFollowCreature(lua_State* L); - static int luaContainerGetItem(lua_State* L); - static int luaContainerHasItem(lua_State* L); - static int luaContainerAddItem(lua_State* L); - static int luaContainerAddItemEx(lua_State* L); - static int luaContainerGetCorpseOwner(lua_State* L); + static int luaCreatureGetMaster(lua_State* L); + static int luaCreatureSetMaster(lua_State* L); - // Teleport - static int luaTeleportCreate(lua_State* L); + static int luaCreatureGetLight(lua_State* L); + static int luaCreatureSetLight(lua_State* L); - static int luaTeleportGetDestination(lua_State* L); - static int luaTeleportSetDestination(lua_State* L); + static int luaCreatureGetSpeed(lua_State* L); + static int luaCreatureGetBaseSpeed(lua_State* L); + static int luaCreatureChangeSpeed(lua_State* L); - // Creature - static int luaCreatureCreate(lua_State* L); + static int luaCreatureSetDropLoot(lua_State* L); + static int luaCreatureSetSkillLoss(lua_State* L); - static int luaCreatureGetEvents(lua_State* L); - static int luaCreatureRegisterEvent(lua_State* L); - static int luaCreatureUnregisterEvent(lua_State* L); + static int luaCreatureGetPosition(lua_State* L); + static int luaCreatureGetTile(lua_State* L); + static int luaCreatureGetDirection(lua_State* L); + static int luaCreatureSetDirection(lua_State* L); - static int luaCreatureIsRemoved(lua_State* L); - static int luaCreatureIsCreature(lua_State* L); - static int luaCreatureIsInGhostMode(lua_State* L); - static int luaCreatureIsHealthHidden(lua_State* L); - static int luaCreatureIsMovementBlocked(lua_State* L); - static int luaCreatureIsImmune(lua_State* L); + static int luaCreatureGetHealth(lua_State* L); + static int luaCreatureSetHealth(lua_State* L); + static int luaCreatureAddHealth(lua_State* L); + static int luaCreatureGetMaxHealth(lua_State* L); + static int luaCreatureSetMaxHealth(lua_State* L); + static int luaCreatureSetHiddenHealth(lua_State* L); + static int luaCreatureSetMovementBlocked(lua_State* L); - static int luaCreatureCanSee(lua_State* L); - static int luaCreatureCanSeeCreature(lua_State* L); + static int luaCreatureGetSkull(lua_State* L); + static int luaCreatureSetSkull(lua_State* L); - static int luaCreatureGetParent(lua_State* L); + static int luaCreatureGetOutfit(lua_State* L); + static int luaCreatureSetOutfit(lua_State* L); - static int luaCreatureGetId(lua_State* L); - static int luaCreatureGetName(lua_State* L); + static int luaCreatureGetCondition(lua_State* L); + static int luaCreatureAddCondition(lua_State* L); + static int luaCreatureRemoveCondition(lua_State* L); + static int luaCreatureHasCondition(lua_State* L); - static int luaCreatureGetTarget(lua_State* L); - static int luaCreatureSetTarget(lua_State* L); + static int luaCreatureRemove(lua_State* L); + static int luaCreatureTeleportTo(lua_State* L); + static int luaCreatureSay(lua_State* L); - static int luaCreatureGetFollowCreature(lua_State* L); - static int luaCreatureSetFollowCreature(lua_State* L); + static int luaCreatureGetDamageMap(lua_State* L); - static int luaCreatureGetMaster(lua_State* L); - static int luaCreatureSetMaster(lua_State* L); + static int luaCreatureGetSummons(lua_State* L); - static int luaCreatureGetLight(lua_State* L); - static int luaCreatureSetLight(lua_State* L); + static int luaCreatureGetDescription(lua_State* L); - static int luaCreatureGetSpeed(lua_State* L); - static int luaCreatureGetBaseSpeed(lua_State* L); - static int luaCreatureChangeSpeed(lua_State* L); + static int luaCreatureGetPathTo(lua_State* L); + static int luaCreatureMove(lua_State* L); - static int luaCreatureSetDropLoot(lua_State* L); - static int luaCreatureSetSkillLoss(lua_State* L); + static int luaCreatureGetZone(lua_State* L); - static int luaCreatureGetPosition(lua_State* L); - static int luaCreatureGetTile(lua_State* L); - static int luaCreatureGetDirection(lua_State* L); - static int luaCreatureSetDirection(lua_State* L); + // Player + static int luaPlayerCreate(lua_State* L); - static int luaCreatureGetHealth(lua_State* L); - static int luaCreatureSetHealth(lua_State* L); - static int luaCreatureAddHealth(lua_State* L); - static int luaCreatureGetMaxHealth(lua_State* L); - static int luaCreatureSetMaxHealth(lua_State* L); - static int luaCreatureSetHiddenHealth(lua_State* L); - static int luaCreatureSetMovementBlocked(lua_State* L); + static int luaPlayerIsPlayer(lua_State* L); - static int luaCreatureGetSkull(lua_State* L); - static int luaCreatureSetSkull(lua_State* L); + static int luaPlayerGetGuid(lua_State* L); + static int luaPlayerGetIp(lua_State* L); + static int luaPlayerGetAccountId(lua_State* L); + static int luaPlayerGetLastLoginSaved(lua_State* L); + static int luaPlayerGetLastLogout(lua_State* L); - static int luaCreatureGetOutfit(lua_State* L); - static int luaCreatureSetOutfit(lua_State* L); + static int luaPlayerGetAccountType(lua_State* L); + static int luaPlayerSetAccountType(lua_State* L); - static int luaCreatureGetCondition(lua_State* L); - static int luaCreatureAddCondition(lua_State* L); - static int luaCreatureRemoveCondition(lua_State* L); - static int luaCreatureHasCondition(lua_State* L); + static int luaPlayerGetCapacity(lua_State* L); + static int luaPlayerSetCapacity(lua_State* L); - static int luaCreatureRemove(lua_State* L); - static int luaCreatureTeleportTo(lua_State* L); - static int luaCreatureSay(lua_State* L); + static int luaPlayerGetFreeCapacity(lua_State* L); - static int luaCreatureGetDamageMap(lua_State* L); + static int luaPlayerGetDepotChest(lua_State* L); + static int luaPlayerGetInbox(lua_State* L); - static int luaCreatureGetSummons(lua_State* L); + static int luaPlayerGetSkullTime(lua_State* L); + static int luaPlayerSetSkullTime(lua_State* L); + static int luaPlayerGetDeathPenalty(lua_State* L); - static int luaCreatureGetDescription(lua_State* L); + static int luaPlayerGetExperience(lua_State* L); + static int luaPlayerAddExperience(lua_State* L); + static int luaPlayerRemoveExperience(lua_State* L); + static int luaPlayerGetLevel(lua_State* L); - static int luaCreatureGetPathTo(lua_State* L); - static int luaCreatureMove(lua_State* L); + static int luaPlayerGetMagicLevel(lua_State* L); + static int luaPlayerGetBaseMagicLevel(lua_State* L); + static int luaPlayerGetMana(lua_State* L); + static int luaPlayerAddMana(lua_State* L); + static int luaPlayerGetMaxMana(lua_State* L); + static int luaPlayerSetMaxMana(lua_State* L); + static int luaPlayerGetManaSpent(lua_State* L); + static int luaPlayerAddManaSpent(lua_State* L); + static int luaPlayerRemoveManaSpent(lua_State* L); - static int luaCreatureGetZone(lua_State* L); + static int luaPlayerGetBaseMaxHealth(lua_State* L); + static int luaPlayerGetBaseMaxMana(lua_State* L); - // Player - static int luaPlayerCreate(lua_State* L); + static int luaPlayerGetSkillLevel(lua_State* L); + static int luaPlayerGetEffectiveSkillLevel(lua_State* L); + static int luaPlayerGetSkillPercent(lua_State* L); + static int luaPlayerGetSkillTries(lua_State* L); + static int luaPlayerAddSkillTries(lua_State* L); + static int luaPlayerRemoveSkillTries(lua_State* L); + static int luaPlayerGetSpecialSkill(lua_State* L); + static int luaPlayerAddSpecialSkill(lua_State* L); - static int luaPlayerIsPlayer(lua_State* L); + static int luaPlayerAddOfflineTrainingTime(lua_State* L); + static int luaPlayerGetOfflineTrainingTime(lua_State* L); + static int luaPlayerRemoveOfflineTrainingTime(lua_State* L); - static int luaPlayerGetGuid(lua_State* L); - static int luaPlayerGetIp(lua_State* L); - static int luaPlayerGetAccountId(lua_State* L); - static int luaPlayerGetLastLoginSaved(lua_State* L); - static int luaPlayerGetLastLogout(lua_State* L); + static int luaPlayerAddOfflineTrainingTries(lua_State* L); - static int luaPlayerGetAccountType(lua_State* L); - static int luaPlayerSetAccountType(lua_State* L); + static int luaPlayerGetOfflineTrainingSkill(lua_State* L); + static int luaPlayerSetOfflineTrainingSkill(lua_State* L); - static int luaPlayerGetCapacity(lua_State* L); - static int luaPlayerSetCapacity(lua_State* L); + static int luaPlayerGetItemCount(lua_State* L); + static int luaPlayerGetItemById(lua_State* L); - static int luaPlayerGetFreeCapacity(lua_State* L); + static int luaPlayerGetVocation(lua_State* L); + static int luaPlayerSetVocation(lua_State* L); - static int luaPlayerGetDepotChest(lua_State* L); - static int luaPlayerGetInbox(lua_State* L); + static int luaPlayerGetSex(lua_State* L); + static int luaPlayerSetSex(lua_State* L); - static int luaPlayerGetSkullTime(lua_State* L); - static int luaPlayerSetSkullTime(lua_State* L); - static int luaPlayerGetDeathPenalty(lua_State* L); + static int luaPlayerGetTown(lua_State* L); + static int luaPlayerSetTown(lua_State* L); - static int luaPlayerGetExperience(lua_State* L); - static int luaPlayerAddExperience(lua_State* L); - static int luaPlayerRemoveExperience(lua_State* L); - static int luaPlayerGetLevel(lua_State* L); + static int luaPlayerGetGuild(lua_State* L); + static int luaPlayerSetGuild(lua_State* L); - static int luaPlayerGetMagicLevel(lua_State* L); - static int luaPlayerGetBaseMagicLevel(lua_State* L); - static int luaPlayerGetMana(lua_State* L); - static int luaPlayerAddMana(lua_State* L); - static int luaPlayerGetMaxMana(lua_State* L); - static int luaPlayerSetMaxMana(lua_State* L); - static int luaPlayerGetManaSpent(lua_State* L); - static int luaPlayerAddManaSpent(lua_State* L); + static int luaPlayerGetGuildLevel(lua_State* L); + static int luaPlayerSetGuildLevel(lua_State* L); - static int luaPlayerGetBaseMaxHealth(lua_State* L); - static int luaPlayerGetBaseMaxMana(lua_State* L); + static int luaPlayerGetGuildNick(lua_State* L); + static int luaPlayerSetGuildNick(lua_State* L); - static int luaPlayerGetSkillLevel(lua_State* L); - static int luaPlayerGetEffectiveSkillLevel(lua_State* L); - static int luaPlayerGetSkillPercent(lua_State* L); - static int luaPlayerGetSkillTries(lua_State* L); - static int luaPlayerAddSkillTries(lua_State* L); - static int luaPlayerGetSpecialSkill(lua_State* L); - static int luaPlayerAddSpecialSkill(lua_State* L); + static int luaPlayerGetGroup(lua_State* L); + static int luaPlayerSetGroup(lua_State* L); - static int luaPlayerAddOfflineTrainingTime(lua_State* L); - static int luaPlayerGetOfflineTrainingTime(lua_State* L); - static int luaPlayerRemoveOfflineTrainingTime(lua_State* L); + static int luaPlayerGetStamina(lua_State* L); + static int luaPlayerSetStamina(lua_State* L); - static int luaPlayerAddOfflineTrainingTries(lua_State* L); + static int luaPlayerGetSoul(lua_State* L); + static int luaPlayerAddSoul(lua_State* L); + static int luaPlayerGetMaxSoul(lua_State* L); - static int luaPlayerGetOfflineTrainingSkill(lua_State* L); - static int luaPlayerSetOfflineTrainingSkill(lua_State* L); + static int luaPlayerGetBankBalance(lua_State* L); + static int luaPlayerSetBankBalance(lua_State* L); - static int luaPlayerGetItemCount(lua_State* L); - static int luaPlayerGetItemById(lua_State* L); + static int luaPlayerGetStorageValue(lua_State* L); + static int luaPlayerSetStorageValue(lua_State* L); - static int luaPlayerGetVocation(lua_State* L); - static int luaPlayerSetVocation(lua_State* L); + static int luaPlayerAddItem(lua_State* L); + static int luaPlayerAddItemEx(lua_State* L); + static int luaPlayerRemoveItem(lua_State* L); + static int luaPlayerSendSupplyUsed(lua_State* L); - static int luaPlayerGetSex(lua_State* L); - static int luaPlayerSetSex(lua_State* L); + static int luaPlayerGetMoney(lua_State* L); + static int luaPlayerAddMoney(lua_State* L); + static int luaPlayerRemoveMoney(lua_State* L); - static int luaPlayerGetTown(lua_State* L); - static int luaPlayerSetTown(lua_State* L); + static int luaPlayerShowTextDialog(lua_State* L); - static int luaPlayerGetGuild(lua_State* L); - static int luaPlayerSetGuild(lua_State* L); + static int luaPlayerSendTextMessage(lua_State* L); + static int luaPlayerSendChannelMessage(lua_State* L); + static int luaPlayerSendPrivateMessage(lua_State* L); - static int luaPlayerGetGuildLevel(lua_State* L); - static int luaPlayerSetGuildLevel(lua_State* L); + static int luaPlayerChannelSay(lua_State* L); + static int luaPlayerOpenChannel(lua_State* L); - static int luaPlayerGetGuildNick(lua_State* L); - static int luaPlayerSetGuildNick(lua_State* L); + static int luaPlayerGetSlotItem(lua_State* L); - static int luaPlayerGetGroup(lua_State* L); - static int luaPlayerSetGroup(lua_State* L); + static int luaPlayerGetParty(lua_State* L); - static int luaPlayerGetStamina(lua_State* L); - static int luaPlayerSetStamina(lua_State* L); + static int luaPlayerAddOutfit(lua_State* L); + static int luaPlayerAddOutfitAddon(lua_State* L); + static int luaPlayerRemoveOutfit(lua_State* L); + static int luaPlayerRemoveOutfitAddon(lua_State* L); + static int luaPlayerHasOutfit(lua_State* L); + static int luaPlayerCanWearOutfit(lua_State* L); + static int luaPlayerSendOutfitWindow(lua_State* L); - static int luaPlayerGetSoul(lua_State* L); - static int luaPlayerAddSoul(lua_State* L); - static int luaPlayerGetMaxSoul(lua_State* L); + static int luaPlayerSendEditPodium(lua_State* L); - static int luaPlayerGetBankBalance(lua_State* L); - static int luaPlayerSetBankBalance(lua_State* L); + static int luaPlayerAddMount(lua_State* L); + static int luaPlayerRemoveMount(lua_State* L); + static int luaPlayerHasMount(lua_State* L); - static int luaPlayerGetStorageValue(lua_State* L); - static int luaPlayerSetStorageValue(lua_State* L); + static int luaPlayerGetPremiumEndsAt(lua_State* L); + static int luaPlayerSetPremiumEndsAt(lua_State* L); - static int luaPlayerAddItem(lua_State* L); - static int luaPlayerAddItemEx(lua_State* L); - static int luaPlayerRemoveItem(lua_State* L); + static int luaPlayerHasBlessing(lua_State* L); + static int luaPlayerAddBlessing(lua_State* L); + static int luaPlayerRemoveBlessing(lua_State* L); - static int luaPlayerGetMoney(lua_State* L); - static int luaPlayerAddMoney(lua_State* L); - static int luaPlayerRemoveMoney(lua_State* L); + static int luaPlayerCanLearnSpell(lua_State* L); + static int luaPlayerLearnSpell(lua_State* L); + static int luaPlayerForgetSpell(lua_State* L); + static int luaPlayerHasLearnedSpell(lua_State* L); - static int luaPlayerShowTextDialog(lua_State* L); + static int luaPlayerSendTutorial(lua_State* L); + static int luaPlayerAddMapMark(lua_State* L); - static int luaPlayerSendTextMessage(lua_State* L); - static int luaPlayerSendChannelMessage(lua_State* L); - static int luaPlayerSendPrivateMessage(lua_State* L); + static int luaPlayerSave(lua_State* L); + static int luaPlayerPopupFYI(lua_State* L); - static int luaPlayerChannelSay(lua_State* L); - static int luaPlayerOpenChannel(lua_State* L); + static int luaPlayerIsPzLocked(lua_State* L); - static int luaPlayerGetSlotItem(lua_State* L); + static int luaPlayerGetClient(lua_State* L); - static int luaPlayerGetParty(lua_State* L); + static int luaPlayerGetHouse(lua_State* L); + static int luaPlayerSendHouseWindow(lua_State* L); + static int luaPlayerSetEditHouse(lua_State* L); - static int luaPlayerAddOutfit(lua_State* L); - static int luaPlayerAddOutfitAddon(lua_State* L); - static int luaPlayerRemoveOutfit(lua_State* L); - static int luaPlayerRemoveOutfitAddon(lua_State* L); - static int luaPlayerHasOutfit(lua_State* L); - static int luaPlayerCanWearOutfit(lua_State* L); - static int luaPlayerSendOutfitWindow(lua_State* L); + static int luaPlayerSetGhostMode(lua_State* L); - static int luaPlayerAddMount(lua_State* L); - static int luaPlayerRemoveMount(lua_State* L); - static int luaPlayerHasMount(lua_State* L); + static int luaPlayerGetContainerId(lua_State* L); + static int luaPlayerGetContainerById(lua_State* L); + static int luaPlayerGetContainerIndex(lua_State* L); - static int luaPlayerGetPremiumEndsAt(lua_State* L); - static int luaPlayerSetPremiumEndsAt(lua_State* L); + static int luaPlayerGetInstantSpells(lua_State* L); + static int luaPlayerCanCast(lua_State* L); - static int luaPlayerHasBlessing(lua_State* L); - static int luaPlayerAddBlessing(lua_State* L); - static int luaPlayerRemoveBlessing(lua_State* L); + static int luaPlayerHasChaseMode(lua_State* L); + static int luaPlayerHasSecureMode(lua_State* L); + static int luaPlayerGetFightMode(lua_State* L); - static int luaPlayerCanLearnSpell(lua_State* L); - static int luaPlayerLearnSpell(lua_State* L); - static int luaPlayerForgetSpell(lua_State* L); - static int luaPlayerHasLearnedSpell(lua_State* L); + static int luaPlayerGetStoreInbox(lua_State* L); - static int luaPlayerSendTutorial(lua_State* L); - static int luaPlayerAddMapMark(lua_State* L); + static int luaPlayerIsNearDepotBox(lua_State* L); - static int luaPlayerSave(lua_State* L); - static int luaPlayerPopupFYI(lua_State* L); + static int luaPlayerGetIdleTime(lua_State* L); - static int luaPlayerIsPzLocked(lua_State* L); + // Monster + static int luaMonsterCreate(lua_State* L); - static int luaPlayerGetClient(lua_State* L); + static int luaMonsterIsMonster(lua_State* L); - static int luaPlayerGetHouse(lua_State* L); - static int luaPlayerSendHouseWindow(lua_State* L); - static int luaPlayerSetEditHouse(lua_State* L); + static int luaMonsterGetType(lua_State* L); - static int luaPlayerSetGhostMode(lua_State* L); + static int luaMonsterRename(lua_State* L); - static int luaPlayerGetContainerId(lua_State* L); - static int luaPlayerGetContainerById(lua_State* L); - static int luaPlayerGetContainerIndex(lua_State* L); + static int luaMonsterGetSpawnPosition(lua_State* L); + static int luaMonsterIsInSpawnRange(lua_State* L); - static int luaPlayerGetInstantSpells(lua_State* L); - static int luaPlayerCanCast(lua_State* L); + static int luaMonsterIsIdle(lua_State* L); + static int luaMonsterSetIdle(lua_State* L); - static int luaPlayerHasChaseMode(lua_State* L); - static int luaPlayerHasSecureMode(lua_State* L); - static int luaPlayerGetFightMode(lua_State* L); + static int luaMonsterIsTarget(lua_State* L); + static int luaMonsterIsOpponent(lua_State* L); + static int luaMonsterIsFriend(lua_State* L); - static int luaPlayerGetStoreInbox(lua_State* L); + static int luaMonsterAddFriend(lua_State* L); + static int luaMonsterRemoveFriend(lua_State* L); + static int luaMonsterGetFriendList(lua_State* L); + static int luaMonsterGetFriendCount(lua_State* L); - // Monster - static int luaMonsterCreate(lua_State* L); + static int luaMonsterAddTarget(lua_State* L); + static int luaMonsterRemoveTarget(lua_State* L); + static int luaMonsterGetTargetList(lua_State* L); + static int luaMonsterGetTargetCount(lua_State* L); - static int luaMonsterIsMonster(lua_State* L); + static int luaMonsterSelectTarget(lua_State* L); + static int luaMonsterSearchTarget(lua_State* L); - static int luaMonsterGetType(lua_State* L); + static int luaMonsterIsWalkingToSpawn(lua_State* L); + static int luaMonsterWalkToSpawn(lua_State* L); - static int luaMonsterGetSpawnPosition(lua_State* L); - static int luaMonsterIsInSpawnRange(lua_State* L); + // Npc + static int luaNpcCreate(lua_State* L); - static int luaMonsterIsIdle(lua_State* L); - static int luaMonsterSetIdle(lua_State* L); + static int luaNpcIsNpc(lua_State* L); - static int luaMonsterIsTarget(lua_State* L); - static int luaMonsterIsOpponent(lua_State* L); - static int luaMonsterIsFriend(lua_State* L); + static int luaNpcSetMasterPos(lua_State* L); - static int luaMonsterAddFriend(lua_State* L); - static int luaMonsterRemoveFriend(lua_State* L); - static int luaMonsterGetFriendList(lua_State* L); - static int luaMonsterGetFriendCount(lua_State* L); + static int luaNpcGetSpeechBubble(lua_State* L); + static int luaNpcSetSpeechBubble(lua_State* L); - static int luaMonsterAddTarget(lua_State* L); - static int luaMonsterRemoveTarget(lua_State* L); - static int luaMonsterGetTargetList(lua_State* L); - static int luaMonsterGetTargetCount(lua_State* L); + // Guild + static int luaGuildCreate(lua_State* L); - static int luaMonsterSelectTarget(lua_State* L); - static int luaMonsterSearchTarget(lua_State* L); + static int luaGuildGetId(lua_State* L); + static int luaGuildGetName(lua_State* L); + static int luaGuildGetMembersOnline(lua_State* L); - // Npc - static int luaNpcCreate(lua_State* L); + static int luaGuildAddRank(lua_State* L); + static int luaGuildGetRankById(lua_State* L); + static int luaGuildGetRankByLevel(lua_State* L); - static int luaNpcIsNpc(lua_State* L); + static int luaGuildGetMotd(lua_State* L); + static int luaGuildSetMotd(lua_State* L); - static int luaNpcSetMasterPos(lua_State* L); + // Group + static int luaGroupCreate(lua_State* L); - static int luaNpcGetSpeechBubble(lua_State* L); - static int luaNpcSetSpeechBubble(lua_State* L); + static int luaGroupGetId(lua_State* L); + static int luaGroupGetName(lua_State* L); + static int luaGroupGetFlags(lua_State* L); + static int luaGroupGetAccess(lua_State* L); + static int luaGroupGetMaxDepotItems(lua_State* L); + static int luaGroupGetMaxVipEntries(lua_State* L); + static int luaGroupHasFlag(lua_State* L); - // Guild - static int luaGuildCreate(lua_State* L); + // Vocation + static int luaVocationCreate(lua_State* L); - static int luaGuildGetId(lua_State* L); - static int luaGuildGetName(lua_State* L); - static int luaGuildGetMembersOnline(lua_State* L); + static int luaVocationGetId(lua_State* L); + static int luaVocationGetClientId(lua_State* L); + static int luaVocationGetName(lua_State* L); + static int luaVocationGetDescription(lua_State* L); - static int luaGuildAddRank(lua_State* L); - static int luaGuildGetRankById(lua_State* L); - static int luaGuildGetRankByLevel(lua_State* L); + static int luaVocationGetRequiredSkillTries(lua_State* L); + static int luaVocationGetRequiredManaSpent(lua_State* L); - static int luaGuildGetMotd(lua_State* L); - static int luaGuildSetMotd(lua_State* L); + static int luaVocationGetCapacityGain(lua_State* L); - // Group - static int luaGroupCreate(lua_State* L); + static int luaVocationGetHealthGain(lua_State* L); + static int luaVocationGetHealthGainTicks(lua_State* L); + static int luaVocationGetHealthGainAmount(lua_State* L); - static int luaGroupGetId(lua_State* L); - static int luaGroupGetName(lua_State* L); - static int luaGroupGetFlags(lua_State* L); - static int luaGroupGetAccess(lua_State* L); - static int luaGroupGetMaxDepotItems(lua_State* L); - static int luaGroupGetMaxVipEntries(lua_State* L); - static int luaGroupHasFlag(lua_State* L); + static int luaVocationGetManaGain(lua_State* L); + static int luaVocationGetManaGainTicks(lua_State* L); + static int luaVocationGetManaGainAmount(lua_State* L); - // Vocation - static int luaVocationCreate(lua_State* L); + static int luaVocationGetMaxSoul(lua_State* L); + static int luaVocationGetSoulGainTicks(lua_State* L); - static int luaVocationGetId(lua_State* L); - static int luaVocationGetClientId(lua_State* L); - static int luaVocationGetName(lua_State* L); - static int luaVocationGetDescription(lua_State* L); + static int luaVocationGetAttackSpeed(lua_State* L); + static int luaVocationGetBaseSpeed(lua_State* L); - static int luaVocationGetRequiredSkillTries(lua_State* L); - static int luaVocationGetRequiredManaSpent(lua_State* L); + static int luaVocationGetDemotion(lua_State* L); + static int luaVocationGetPromotion(lua_State* L); - static int luaVocationGetCapacityGain(lua_State* L); + static int luaVocationAllowsPvp(lua_State* L); - static int luaVocationGetHealthGain(lua_State* L); - static int luaVocationGetHealthGainTicks(lua_State* L); - static int luaVocationGetHealthGainAmount(lua_State* L); + // Town + static int luaTownCreate(lua_State* L); - static int luaVocationGetManaGain(lua_State* L); - static int luaVocationGetManaGainTicks(lua_State* L); - static int luaVocationGetManaGainAmount(lua_State* L); - - static int luaVocationGetMaxSoul(lua_State* L); - static int luaVocationGetSoulGainTicks(lua_State* L); - - static int luaVocationGetAttackSpeed(lua_State* L); - static int luaVocationGetBaseSpeed(lua_State* L); - - static int luaVocationGetDemotion(lua_State* L); - static int luaVocationGetPromotion(lua_State* L); - - // Town - static int luaTownCreate(lua_State* L); - - static int luaTownGetId(lua_State* L); - static int luaTownGetName(lua_State* L); - static int luaTownGetTemplePosition(lua_State* L); - - // House - static int luaHouseCreate(lua_State* L); - - static int luaHouseGetId(lua_State* L); - static int luaHouseGetName(lua_State* L); - static int luaHouseGetTown(lua_State* L); - static int luaHouseGetExitPosition(lua_State* L); - static int luaHouseGetRent(lua_State* L); - - static int luaHouseGetOwnerGuid(lua_State* L); - static int luaHouseSetOwnerGuid(lua_State* L); - static int luaHouseStartTrade(lua_State* L); - - static int luaHouseGetBeds(lua_State* L); - static int luaHouseGetBedCount(lua_State* L); - - static int luaHouseGetDoors(lua_State* L); - static int luaHouseGetDoorCount(lua_State* L); - static int luaHouseGetDoorIdByPosition(lua_State* L); - - static int luaHouseGetTiles(lua_State* L); - static int luaHouseGetItems(lua_State* L); - static int luaHouseGetTileCount(lua_State* L); - - static int luaHouseCanEditAccessList(lua_State* L); - static int luaHouseGetAccessList(lua_State* L); - static int luaHouseSetAccessList(lua_State* L); - - static int luaHouseKickPlayer(lua_State* L); - - // ItemType - static int luaItemTypeCreate(lua_State* L); - - static int luaItemTypeIsCorpse(lua_State* L); - static int luaItemTypeIsDoor(lua_State* L); - static int luaItemTypeIsContainer(lua_State* L); - static int luaItemTypeIsFluidContainer(lua_State* L); - static int luaItemTypeIsMovable(lua_State* L); - static int luaItemTypeIsRune(lua_State* L); - static int luaItemTypeIsStackable(lua_State* L); - static int luaItemTypeIsReadable(lua_State* L); - static int luaItemTypeIsWritable(lua_State* L); - static int luaItemTypeIsBlocking(lua_State* L); - static int luaItemTypeIsGroundTile(lua_State* L); - static int luaItemTypeIsMagicField(lua_State* L); - static int luaItemTypeIsUseable(lua_State* L); - static int luaItemTypeIsPickupable(lua_State* L); - - static int luaItemTypeGetType(lua_State* L); - static int luaItemTypeGetGroup(lua_State* L); - static int luaItemTypeGetId(lua_State* L); - static int luaItemTypeGetClientId(lua_State* L); - static int luaItemTypeGetName(lua_State* L); - static int luaItemTypeGetPluralName(lua_State* L); - static int luaItemTypeGetArticle(lua_State* L); - static int luaItemTypeGetDescription(lua_State* L); - static int luaItemTypeGetSlotPosition(lua_State *L); - - static int luaItemTypeGetCharges(lua_State* L); - static int luaItemTypeGetFluidSource(lua_State* L); - static int luaItemTypeGetCapacity(lua_State* L); - static int luaItemTypeGetWeight(lua_State* L); - - static int luaItemTypeGetHitChance(lua_State* L); - static int luaItemTypeGetShootRange(lua_State* L); - static int luaItemTypeGetAttack(lua_State* L); - static int luaItemTypeGetDefense(lua_State* L); - static int luaItemTypeGetExtraDefense(lua_State* L); - static int luaItemTypeGetArmor(lua_State* L); - static int luaItemTypeGetWeaponType(lua_State* L); - - static int luaItemTypeGetElementType(lua_State* L); - static int luaItemTypeGetElementDamage(lua_State* L); - - static int luaItemTypeGetTransformEquipId(lua_State* L); - static int luaItemTypeGetTransformDeEquipId(lua_State* L); - static int luaItemTypeGetDestroyId(lua_State* L); - static int luaItemTypeGetDecayId(lua_State* L); - static int luaItemTypeGetRequiredLevel(lua_State* L); - static int luaItemTypeGetAmmoType(lua_State* L); - static int luaItemTypeGetCorpseType(lua_State* L); - static int luaItemTypeHasShowCount(lua_State* L); - static int luaItemTypeGetAbilities(lua_State* L); - static int luaItemTypeHasShowAttributes(lua_State* L); - static int luaItemTypeHasShowCharges(lua_State* L); - static int luaItemTypeHasShowDuration(lua_State* L); - static int luaItemTypeHasAllowDistRead(lua_State* L); - static int luaItemTypeGetWieldInfo(lua_State* L); - static int luaItemTypeGetDuration(lua_State* L); - static int luaItemTypeGetLevelDoor(lua_State* L); - static int luaItemTypeGetVocationString(lua_State* L); - static int luaItemTypeGetMinReqLevel(lua_State* L); - static int luaItemTypeGetMinReqMagicLevel(lua_State* L); - - static int luaItemTypeHasSubType(lua_State* L); - - static int luaItemTypeIsStoreItem(lua_State* L); - - // Combat - static int luaCombatCreate(lua_State* L); - - static int luaCombatSetParameter(lua_State* L); - static int luaCombatSetFormula(lua_State* L); - - static int luaCombatSetArea(lua_State* L); - static int luaCombatAddCondition(lua_State* L); - static int luaCombatClearConditions(lua_State* L); - static int luaCombatSetCallback(lua_State* L); - static int luaCombatSetOrigin(lua_State* L); - - static int luaCombatExecute(lua_State* L); - - // Condition - static int luaConditionCreate(lua_State* L); - static int luaConditionDelete(lua_State* L); - - static int luaConditionGetId(lua_State* L); - static int luaConditionGetSubId(lua_State* L); - static int luaConditionGetType(lua_State* L); - static int luaConditionGetIcons(lua_State* L); - static int luaConditionGetEndTime(lua_State* L); - - static int luaConditionClone(lua_State* L); - - static int luaConditionGetTicks(lua_State* L); - static int luaConditionSetTicks(lua_State* L); - - static int luaConditionSetParameter(lua_State* L); - static int luaConditionSetFormula(lua_State* L); - static int luaConditionSetOutfit(lua_State* L); - - static int luaConditionAddDamage(lua_State* L); - - // Outfit - static int luaOutfitCreate(lua_State* L); - static int luaOutfitCompare(lua_State* L); - - // MonsterType - static int luaMonsterTypeCreate(lua_State* L); - - static int luaMonsterTypeIsAttackable(lua_State* L); - static int luaMonsterTypeIsConvinceable(lua_State* L); - static int luaMonsterTypeIsSummonable(lua_State* L); - static int luaMonsterTypeIsIllusionable(lua_State* L); - static int luaMonsterTypeIsHostile(lua_State* L); - static int luaMonsterTypeIsPushable(lua_State* L); - static int luaMonsterTypeIsHealthHidden(lua_State* L); - static int luaMonsterTypeIsBoss(lua_State* L); - - static int luaMonsterTypeCanPushItems(lua_State* L); - static int luaMonsterTypeCanPushCreatures(lua_State* L); - - static int luaMonsterTypeName(lua_State* L); - static int luaMonsterTypeNameDescription(lua_State* L); - - static int luaMonsterTypeHealth(lua_State* L); - static int luaMonsterTypeMaxHealth(lua_State* L); - static int luaMonsterTypeRunHealth(lua_State* L); - static int luaMonsterTypeExperience(lua_State* L); - static int luaMonsterTypeSkull(lua_State* L); - - static int luaMonsterTypeCombatImmunities(lua_State* L); - static int luaMonsterTypeConditionImmunities(lua_State* L); - - static int luaMonsterTypeGetAttackList(lua_State* L); - static int luaMonsterTypeAddAttack(lua_State* L); - - static int luaMonsterTypeGetDefenseList(lua_State* L); - static int luaMonsterTypeAddDefense(lua_State* L); - - static int luaMonsterTypeGetElementList(lua_State* L); - static int luaMonsterTypeAddElement(lua_State* L); - - static int luaMonsterTypeGetVoices(lua_State* L); - static int luaMonsterTypeAddVoice(lua_State* L); - - static int luaMonsterTypeGetLoot(lua_State* L); - static int luaMonsterTypeAddLoot(lua_State* L); - - static int luaMonsterTypeGetCreatureEvents(lua_State* L); - static int luaMonsterTypeRegisterEvent(lua_State* L); - - static int luaMonsterTypeEventOnCallback(lua_State* L); - static int luaMonsterTypeEventType(lua_State* L); - - static int luaMonsterTypeGetSummonList(lua_State* L); - static int luaMonsterTypeAddSummon(lua_State* L); - - static int luaMonsterTypeMaxSummons(lua_State* L); - - static int luaMonsterTypeArmor(lua_State* L); - static int luaMonsterTypeDefense(lua_State* L); - static int luaMonsterTypeOutfit(lua_State* L); - static int luaMonsterTypeRace(lua_State* L); - static int luaMonsterTypeCorpseId(lua_State* L); - static int luaMonsterTypeManaCost(lua_State* L); - static int luaMonsterTypeBaseSpeed(lua_State* L); - static int luaMonsterTypeLight(lua_State* L); - - static int luaMonsterTypeStaticAttackChance(lua_State* L); - static int luaMonsterTypeTargetDistance(lua_State* L); - static int luaMonsterTypeYellChance(lua_State* L); - static int luaMonsterTypeYellSpeedTicks(lua_State* L); - static int luaMonsterTypeChangeTargetChance(lua_State* L); - static int luaMonsterTypeChangeTargetSpeed(lua_State* L); - - // Loot - static int luaCreateLoot(lua_State* L); - static int luaDeleteLoot(lua_State* L); - static int luaLootSetId(lua_State* L); - static int luaLootSetMaxCount(lua_State* L); - static int luaLootSetSubType(lua_State* L); - static int luaLootSetChance(lua_State* L); - static int luaLootSetActionId(lua_State* L); - static int luaLootSetDescription(lua_State* L); - static int luaLootAddChildLoot(lua_State* L); - - // MonsterSpell - static int luaCreateMonsterSpell(lua_State* L); - static int luaDeleteMonsterSpell(lua_State* L); - static int luaMonsterSpellSetType(lua_State* L); - static int luaMonsterSpellSetScriptName(lua_State* L); - static int luaMonsterSpellSetChance(lua_State* L); - static int luaMonsterSpellSetInterval(lua_State* L); - static int luaMonsterSpellSetRange(lua_State* L); - static int luaMonsterSpellSetCombatValue(lua_State* L); - static int luaMonsterSpellSetCombatType(lua_State* L); - static int luaMonsterSpellSetAttackValue(lua_State* L); - static int luaMonsterSpellSetNeedTarget(lua_State* L); - static int luaMonsterSpellSetCombatLength(lua_State* L); - static int luaMonsterSpellSetCombatSpread(lua_State* L); - static int luaMonsterSpellSetCombatRadius(lua_State* L); - static int luaMonsterSpellSetConditionType(lua_State* L); - static int luaMonsterSpellSetConditionDamage(lua_State* L); - static int luaMonsterSpellSetConditionSpeedChange(lua_State* L); - static int luaMonsterSpellSetConditionDuration(lua_State* L); - static int luaMonsterSpellSetConditionTickInterval(lua_State* L); - static int luaMonsterSpellSetCombatShootEffect(lua_State* L); - static int luaMonsterSpellSetCombatEffect(lua_State* L); - - // Party - static int luaPartyCreate(lua_State* L); - static int luaPartyDisband(lua_State* L); - - static int luaPartyGetLeader(lua_State* L); - static int luaPartySetLeader(lua_State* L); - - static int luaPartyGetMembers(lua_State* L); - static int luaPartyGetMemberCount(lua_State* L); - - static int luaPartyGetInvitees(lua_State* L); - static int luaPartyGetInviteeCount(lua_State* L); - - static int luaPartyAddInvite(lua_State* L); - static int luaPartyRemoveInvite(lua_State* L); - - static int luaPartyAddMember(lua_State* L); - static int luaPartyRemoveMember(lua_State* L); - - static int luaPartyIsSharedExperienceActive(lua_State* L); - static int luaPartyIsSharedExperienceEnabled(lua_State* L); - static int luaPartyShareExperience(lua_State* L); - static int luaPartySetSharedExperience(lua_State* L); - - // Spells - static int luaSpellCreate(lua_State* L); - - static int luaSpellOnCastSpell(lua_State* L); - static int luaSpellRegister(lua_State* L); - static int luaSpellName(lua_State* L); - static int luaSpellId(lua_State* L); - static int luaSpellGroup(lua_State* L); - static int luaSpellCooldown(lua_State* L); - static int luaSpellGroupCooldown(lua_State* L); - static int luaSpellLevel(lua_State* L); - static int luaSpellMagicLevel(lua_State* L); - static int luaSpellMana(lua_State* L); - static int luaSpellManaPercent(lua_State* L); - static int luaSpellSoul(lua_State* L); - static int luaSpellRange(lua_State* L); - static int luaSpellPremium(lua_State* L); - static int luaSpellEnabled(lua_State* L); - static int luaSpellNeedTarget(lua_State* L); - static int luaSpellNeedWeapon(lua_State* L); - static int luaSpellNeedLearn(lua_State* L); - static int luaSpellSelfTarget(lua_State* L); - static int luaSpellBlocking(lua_State* L); - static int luaSpellAggressive(lua_State* L); - static int luaSpellVocation(lua_State* L); - - // only for InstantSpells - static int luaSpellWords(lua_State* L); - static int luaSpellNeedDirection(lua_State* L); - static int luaSpellHasParams(lua_State* L); - static int luaSpellHasPlayerNameParam(lua_State* L); - static int luaSpellNeedCasterTargetOrDirection(lua_State* L); - static int luaSpellIsBlockingWalls(lua_State* L); - - // only for RuneSpells - static int luaSpellRuneLevel(lua_State* L); - static int luaSpellRuneMagicLevel(lua_State* L); - static int luaSpellRuneId(lua_State* L); - static int luaSpellCharges(lua_State* L); - static int luaSpellAllowFarUse(lua_State* L); - static int luaSpellBlockWalls(lua_State* L); - static int luaSpellCheckFloor(lua_State* L); - - // Actions - static int luaCreateAction(lua_State* L); - static int luaActionOnUse(lua_State* L); - static int luaActionRegister(lua_State* L); - static int luaActionItemId(lua_State* L); - static int luaActionActionId(lua_State* L); - static int luaActionUniqueId(lua_State* L); - static int luaActionAllowFarUse(lua_State* L); - static int luaActionBlockWalls(lua_State* L); - static int luaActionCheckFloor(lua_State* L); - - // Talkactions - static int luaCreateTalkaction(lua_State* L); - static int luaTalkactionOnSay(lua_State* L); - static int luaTalkactionRegister(lua_State* L); - static int luaTalkactionSeparator(lua_State* L); - static int luaTalkactionAccess(lua_State* L); - static int luaTalkactionAccountType(lua_State* L); - - // CreatureEvents - static int luaCreateCreatureEvent(lua_State* L); - static int luaCreatureEventType(lua_State* L); - static int luaCreatureEventRegister(lua_State* L); - static int luaCreatureEventOnCallback(lua_State* L); - - // MoveEvents - static int luaCreateMoveEvent(lua_State* L); - static int luaMoveEventType(lua_State* L); - static int luaMoveEventRegister(lua_State* L); - static int luaMoveEventOnCallback(lua_State* L); - static int luaMoveEventLevel(lua_State* L); - static int luaMoveEventSlot(lua_State* L); - static int luaMoveEventMagLevel(lua_State* L); - static int luaMoveEventPremium(lua_State* L); - static int luaMoveEventVocation(lua_State* L); - static int luaMoveEventItemId(lua_State* L); - static int luaMoveEventActionId(lua_State* L); - static int luaMoveEventUniqueId(lua_State* L); - static int luaMoveEventPosition(lua_State* L); - - // GlobalEvents - static int luaCreateGlobalEvent(lua_State* L); - static int luaGlobalEventType(lua_State* L); - static int luaGlobalEventRegister(lua_State* L); - static int luaGlobalEventOnCallback(lua_State* L); - static int luaGlobalEventTime(lua_State* L); - static int luaGlobalEventInterval(lua_State* L); - - // Weapon - static int luaCreateWeapon(lua_State* L); - static int luaWeaponId(lua_State* L); - static int luaWeaponLevel(lua_State* L); - static int luaWeaponMagicLevel(lua_State* L); - static int luaWeaponMana(lua_State* L); - static int luaWeaponManaPercent(lua_State* L); - static int luaWeaponHealth(lua_State* L); - static int luaWeaponHealthPercent(lua_State* L); - static int luaWeaponSoul(lua_State* L); - static int luaWeaponPremium(lua_State* L); - static int luaWeaponBreakChance(lua_State* L); - static int luaWeaponAction(lua_State* L); - static int luaWeaponUnproperly(lua_State* L); - static int luaWeaponVocation(lua_State* L); - static int luaWeaponOnUseWeapon(lua_State* L); - static int luaWeaponRegister(lua_State* L); - static int luaWeaponElement(lua_State* L); - static int luaWeaponAttack(lua_State* L); - static int luaWeaponDefense(lua_State* L); - static int luaWeaponRange(lua_State* L); - static int luaWeaponCharges(lua_State* L); - static int luaWeaponDuration(lua_State* L); - static int luaWeaponDecayTo(lua_State* L); - static int luaWeaponTransformEquipTo(lua_State* L); - static int luaWeaponTransformDeEquipTo(lua_State* L); - static int luaWeaponSlotType(lua_State* L); - static int luaWeaponHitChance(lua_State* L); - static int luaWeaponExtraElement(lua_State* L); - - // exclusively for distance weapons - static int luaWeaponMaxHitChance(lua_State* L); - static int luaWeaponAmmoType(lua_State* L); - - // exclusively for wands - static int luaWeaponWandDamage(lua_State* L); - - // exclusively for wands & distance weapons - static int luaWeaponShootType(lua_State* L); - - // - std::string lastLuaError; - - std::string interfaceName; - - static ScriptEnvironment scriptEnv[16]; - static int32_t scriptEnvIndex; - - std::string loadingFile; + static int luaTownGetId(lua_State* L); + static int luaTownGetName(lua_State* L); + static int luaTownGetTemplePosition(lua_State* L); + + // House + static int luaHouseCreate(lua_State* L); + + static int luaHouseGetId(lua_State* L); + static int luaHouseGetName(lua_State* L); + static int luaHouseGetTown(lua_State* L); + static int luaHouseGetExitPosition(lua_State* L); + + static int luaHouseGetRent(lua_State* L); + static int luaHouseSetRent(lua_State* L); + + static int luaHouseGetPaidUntil(lua_State* L); + static int luaHouseSetPaidUntil(lua_State* L); + + static int luaHouseGetPayRentWarnings(lua_State* L); + static int luaHouseSetPayRentWarnings(lua_State* L); + + static int luaHouseGetOwnerName(lua_State* L); + static int luaHouseGetOwnerGuid(lua_State* L); + static int luaHouseSetOwnerGuid(lua_State* L); + static int luaHouseStartTrade(lua_State* L); + + static int luaHouseGetBeds(lua_State* L); + static int luaHouseGetBedCount(lua_State* L); + + static int luaHouseGetDoors(lua_State* L); + static int luaHouseGetDoorCount(lua_State* L); + static int luaHouseGetDoorIdByPosition(lua_State* L); + + static int luaHouseGetTiles(lua_State* L); + static int luaHouseGetItems(lua_State* L); + static int luaHouseGetTileCount(lua_State* L); + + static int luaHouseCanEditAccessList(lua_State* L); + static int luaHouseGetAccessList(lua_State* L); + static int luaHouseSetAccessList(lua_State* L); + + static int luaHouseKickPlayer(lua_State* L); + + static int luaHouseSave(lua_State* L); + + // ItemType + static int luaItemTypeCreate(lua_State* L); + + static int luaItemTypeIsCorpse(lua_State* L); + static int luaItemTypeIsDoor(lua_State* L); + static int luaItemTypeIsContainer(lua_State* L); + static int luaItemTypeIsFluidContainer(lua_State* L); + static int luaItemTypeIsMovable(lua_State* L); + static int luaItemTypeIsRune(lua_State* L); + static int luaItemTypeIsStackable(lua_State* L); + static int luaItemTypeIsReadable(lua_State* L); + static int luaItemTypeIsWritable(lua_State* L); + static int luaItemTypeIsBlocking(lua_State* L); + static int luaItemTypeIsGroundTile(lua_State* L); + static int luaItemTypeIsMagicField(lua_State* L); + static int luaItemTypeIsUseable(lua_State* L); + static int luaItemTypeIsPickupable(lua_State* L); + + static int luaItemTypeGetType(lua_State* L); + static int luaItemTypeGetGroup(lua_State* L); + static int luaItemTypeGetId(lua_State* L); + static int luaItemTypeGetClientId(lua_State* L); + static int luaItemTypeGetName(lua_State* L); + static int luaItemTypeGetPluralName(lua_State* L); + static int luaItemTypeGetArticle(lua_State* L); + static int luaItemTypeGetDescription(lua_State* L); + static int luaItemTypeGetSlotPosition(lua_State* L); + + static int luaItemTypeGetCharges(lua_State* L); + static int luaItemTypeGetFluidSource(lua_State* L); + static int luaItemTypeGetCapacity(lua_State* L); + static int luaItemTypeGetWeight(lua_State* L); + static int luaItemTypeGetWorth(lua_State* L); + + static int luaItemTypeGetHitChance(lua_State* L); + static int luaItemTypeGetShootRange(lua_State* L); + static int luaItemTypeGetAttack(lua_State* L); + static int luaItemTypeGetAttackSpeed(lua_State* L); + static int luaItemTypeGetDefense(lua_State* L); + static int luaItemTypeGetExtraDefense(lua_State* L); + static int luaItemTypeGetArmor(lua_State* L); + static int luaItemTypeGetWeaponType(lua_State* L); + + static int luaItemTypeGetElementType(lua_State* L); + static int luaItemTypeGetElementDamage(lua_State* L); + + static int luaItemTypeGetTransformEquipId(lua_State* L); + static int luaItemTypeGetTransformDeEquipId(lua_State* L); + static int luaItemTypeGetDestroyId(lua_State* L); + static int luaItemTypeGetDecayId(lua_State* L); + static int luaItemTypeGetRequiredLevel(lua_State* L); + static int luaItemTypeGetAmmoType(lua_State* L); + static int luaItemTypeGetCorpseType(lua_State* L); + static int luaItemTypeGetClassification(lua_State* L); + static int luaItemTypeHasShowCount(lua_State* L); + static int luaItemTypeGetAbilities(lua_State* L); + static int luaItemTypeHasShowAttributes(lua_State* L); + static int luaItemTypeHasShowCharges(lua_State* L); + static int luaItemTypeHasShowDuration(lua_State* L); + static int luaItemTypeHasAllowDistRead(lua_State* L); + static int luaItemTypeGetWieldInfo(lua_State* L); + static int luaItemTypeGetDuration(lua_State* L); + static int luaItemTypeGetLevelDoor(lua_State* L); + static int luaItemTypeGetRuneSpellName(lua_State* L); + static int luaItemTypeGetVocationString(lua_State* L); + static int luaItemTypeGetMinReqLevel(lua_State* L); + static int luaItemTypeGetMinReqMagicLevel(lua_State* L); + + static int luaItemTypeGetMarketBuyStatistics(lua_State* L); + static int luaItemTypeGetMarketSellStatistics(lua_State* L); + + static int luaItemTypeHasSubType(lua_State* L); + + static int luaItemTypeIsStoreItem(lua_State* L); + + // Combat + static int luaCombatCreate(lua_State* L); + static int luaCombatDelete(lua_State* L); + + static int luaCombatSetParameter(lua_State* L); + static int luaCombatGetParameter(lua_State* L); + + static int luaCombatSetFormula(lua_State* L); + + static int luaCombatSetArea(lua_State* L); + static int luaCombatAddCondition(lua_State* L); + static int luaCombatClearConditions(lua_State* L); + static int luaCombatSetCallback(lua_State* L); + static int luaCombatSetOrigin(lua_State* L); + + static int luaCombatExecute(lua_State* L); + + // Condition + static int luaConditionCreate(lua_State* L); + static int luaConditionDelete(lua_State* L); + + static int luaConditionGetId(lua_State* L); + static int luaConditionGetSubId(lua_State* L); + static int luaConditionGetType(lua_State* L); + static int luaConditionGetIcons(lua_State* L); + static int luaConditionGetEndTime(lua_State* L); + + static int luaConditionClone(lua_State* L); + + static int luaConditionGetTicks(lua_State* L); + static int luaConditionSetTicks(lua_State* L); + + static int luaConditionSetParameter(lua_State* L); + static int luaConditionGetParameter(lua_State* L); + + static int luaConditionSetFormula(lua_State* L); + static int luaConditionSetOutfit(lua_State* L); + + static int luaConditionAddDamage(lua_State* L); + + // Outfit + static int luaOutfitCreate(lua_State* L); + static int luaOutfitCompare(lua_State* L); + + // MonsterType + static int luaMonsterTypeCreate(lua_State* L); + + static int luaMonsterTypeIsAttackable(lua_State* L); + static int luaMonsterTypeIsChallengeable(lua_State* L); + static int luaMonsterTypeIsConvinceable(lua_State* L); + static int luaMonsterTypeIsSummonable(lua_State* L); + static int luaMonsterTypeIsIgnoringSpawnBlock(lua_State* L); + static int luaMonsterTypeIsIllusionable(lua_State* L); + static int luaMonsterTypeIsHostile(lua_State* L); + static int luaMonsterTypeIsPushable(lua_State* L); + static int luaMonsterTypeIsHealthHidden(lua_State* L); + static int luaMonsterTypeIsBoss(lua_State* L); + + static int luaMonsterTypeCanPushItems(lua_State* L); + static int luaMonsterTypeCanPushCreatures(lua_State* L); + + static int luaMonsterTypeCanWalkOnEnergy(lua_State* L); + static int luaMonsterTypeCanWalkOnFire(lua_State* L); + static int luaMonsterTypeCanWalkOnPoison(lua_State* L); + + static int luaMonsterTypeName(lua_State* L); + static int luaMonsterTypeNameDescription(lua_State* L); + + static int luaMonsterTypeHealth(lua_State* L); + static int luaMonsterTypeMaxHealth(lua_State* L); + static int luaMonsterTypeRunHealth(lua_State* L); + static int luaMonsterTypeExperience(lua_State* L); + static int luaMonsterTypeSkull(lua_State* L); + + static int luaMonsterTypeCombatImmunities(lua_State* L); + static int luaMonsterTypeConditionImmunities(lua_State* L); + + static int luaMonsterTypeGetAttackList(lua_State* L); + static int luaMonsterTypeAddAttack(lua_State* L); + + static int luaMonsterTypeGetDefenseList(lua_State* L); + static int luaMonsterTypeAddDefense(lua_State* L); + + static int luaMonsterTypeGetElementList(lua_State* L); + static int luaMonsterTypeAddElement(lua_State* L); + + static int luaMonsterTypeGetVoices(lua_State* L); + static int luaMonsterTypeAddVoice(lua_State* L); + + static int luaMonsterTypeGetLoot(lua_State* L); + static int luaMonsterTypeAddLoot(lua_State* L); + + static int luaMonsterTypeGetCreatureEvents(lua_State* L); + static int luaMonsterTypeRegisterEvent(lua_State* L); + + static int luaMonsterTypeEventOnCallback(lua_State* L); + static int luaMonsterTypeEventType(lua_State* L); + + static int luaMonsterTypeGetSummonList(lua_State* L); + static int luaMonsterTypeAddSummon(lua_State* L); + + static int luaMonsterTypeMaxSummons(lua_State* L); + + static int luaMonsterTypeArmor(lua_State* L); + static int luaMonsterTypeDefense(lua_State* L); + static int luaMonsterTypeOutfit(lua_State* L); + static int luaMonsterTypeRace(lua_State* L); + static int luaMonsterTypeCorpseId(lua_State* L); + static int luaMonsterTypeManaCost(lua_State* L); + static int luaMonsterTypeBaseSpeed(lua_State* L); + static int luaMonsterTypeLight(lua_State* L); + + static int luaMonsterTypeStaticAttackChance(lua_State* L); + static int luaMonsterTypeTargetDistance(lua_State* L); + static int luaMonsterTypeYellChance(lua_State* L); + static int luaMonsterTypeYellSpeedTicks(lua_State* L); + static int luaMonsterTypeChangeTargetChance(lua_State* L); + static int luaMonsterTypeChangeTargetSpeed(lua_State* L); + + // Loot + static int luaCreateLoot(lua_State* L); + static int luaDeleteLoot(lua_State* L); + static int luaLootSetId(lua_State* L); + static int luaLootSetMaxCount(lua_State* L); + static int luaLootSetSubType(lua_State* L); + static int luaLootSetChance(lua_State* L); + static int luaLootSetActionId(lua_State* L); + static int luaLootSetDescription(lua_State* L); + static int luaLootAddChildLoot(lua_State* L); + + // MonsterSpell + static int luaCreateMonsterSpell(lua_State* L); + static int luaDeleteMonsterSpell(lua_State* L); + static int luaMonsterSpellSetType(lua_State* L); + static int luaMonsterSpellSetScriptName(lua_State* L); + static int luaMonsterSpellSetChance(lua_State* L); + static int luaMonsterSpellSetInterval(lua_State* L); + static int luaMonsterSpellSetRange(lua_State* L); + static int luaMonsterSpellSetCombatValue(lua_State* L); + static int luaMonsterSpellSetCombatType(lua_State* L); + static int luaMonsterSpellSetAttackValue(lua_State* L); + static int luaMonsterSpellSetNeedTarget(lua_State* L); + static int luaMonsterSpellSetNeedDirection(lua_State* L); + static int luaMonsterSpellSetCombatLength(lua_State* L); + static int luaMonsterSpellSetCombatSpread(lua_State* L); + static int luaMonsterSpellSetCombatRadius(lua_State* L); + static int luaMonsterSpellSetCombatRing(lua_State* L); + static int luaMonsterSpellSetConditionType(lua_State* L); + static int luaMonsterSpellSetConditionDamage(lua_State* L); + static int luaMonsterSpellSetConditionSpeedChange(lua_State* L); + static int luaMonsterSpellSetConditionDuration(lua_State* L); + static int luaMonsterSpellSetConditionDrunkenness(lua_State* L); + static int luaMonsterSpellSetConditionTickInterval(lua_State* L); + static int luaMonsterSpellSetCombatShootEffect(lua_State* L); + static int luaMonsterSpellSetCombatEffect(lua_State* L); + static int luaMonsterSpellSetOutfit(lua_State* L); + + // Party + static int luaPartyCreate(lua_State* L); + static int luaPartyDisband(lua_State* L); + + static int luaPartyGetLeader(lua_State* L); + static int luaPartySetLeader(lua_State* L); + + static int luaPartyGetMembers(lua_State* L); + static int luaPartyGetMemberCount(lua_State* L); + + static int luaPartyGetInvitees(lua_State* L); + static int luaPartyGetInviteeCount(lua_State* L); + + static int luaPartyAddInvite(lua_State* L); + static int luaPartyRemoveInvite(lua_State* L); + + static int luaPartyAddMember(lua_State* L); + static int luaPartyRemoveMember(lua_State* L); + + static int luaPartyIsSharedExperienceActive(lua_State* L); + static int luaPartyIsSharedExperienceEnabled(lua_State* L); + static int luaPartyShareExperience(lua_State* L); + static int luaPartySetSharedExperience(lua_State* L); + + // Spells + static int luaSpellCreate(lua_State* L); + + static int luaSpellOnCastSpell(lua_State* L); + static int luaSpellRegister(lua_State* L); + static int luaSpellName(lua_State* L); + static int luaSpellId(lua_State* L); + static int luaSpellGroup(lua_State* L); + static int luaSpellCooldown(lua_State* L); + static int luaSpellGroupCooldown(lua_State* L); + static int luaSpellLevel(lua_State* L); + static int luaSpellMagicLevel(lua_State* L); + static int luaSpellMana(lua_State* L); + static int luaSpellManaPercent(lua_State* L); + static int luaSpellSoul(lua_State* L); + static int luaSpellRange(lua_State* L); + static int luaSpellPremium(lua_State* L); + static int luaSpellEnabled(lua_State* L); + static int luaSpellNeedTarget(lua_State* L); + static int luaSpellNeedWeapon(lua_State* L); + static int luaSpellNeedLearn(lua_State* L); + static int luaSpellSelfTarget(lua_State* L); + static int luaSpellBlocking(lua_State* L); + static int luaSpellAggressive(lua_State* L); + static int luaSpellPzLock(lua_State* L); + static int luaSpellVocation(lua_State* L); + + // only for InstantSpells + static int luaSpellWords(lua_State* L); + static int luaSpellNeedDirection(lua_State* L); + static int luaSpellHasParams(lua_State* L); + static int luaSpellHasPlayerNameParam(lua_State* L); + static int luaSpellNeedCasterTargetOrDirection(lua_State* L); + static int luaSpellIsBlockingWalls(lua_State* L); + + // only for RuneSpells + static int luaSpellRuneLevel(lua_State* L); + static int luaSpellRuneMagicLevel(lua_State* L); + static int luaSpellRuneId(lua_State* L); + static int luaSpellCharges(lua_State* L); + static int luaSpellAllowFarUse(lua_State* L); + static int luaSpellBlockWalls(lua_State* L); + static int luaSpellCheckFloor(lua_State* L); + + // Actions + static int luaCreateAction(lua_State* L); + static int luaActionOnUse(lua_State* L); + static int luaActionRegister(lua_State* L); + static int luaActionItemId(lua_State* L); + static int luaActionActionId(lua_State* L); + static int luaActionUniqueId(lua_State* L); + static int luaActionAllowFarUse(lua_State* L); + static int luaActionBlockWalls(lua_State* L); + static int luaActionCheckFloor(lua_State* L); + + // Talkactions + static int luaCreateTalkaction(lua_State* L); + static int luaTalkactionOnSay(lua_State* L); + static int luaTalkactionRegister(lua_State* L); + static int luaTalkactionSeparator(lua_State* L); + static int luaTalkactionAccess(lua_State* L); + static int luaTalkactionAccountType(lua_State* L); + + // CreatureEvents + static int luaCreateCreatureEvent(lua_State* L); + static int luaCreatureEventType(lua_State* L); + static int luaCreatureEventRegister(lua_State* L); + static int luaCreatureEventOnCallback(lua_State* L); + + // MoveEvents + static int luaCreateMoveEvent(lua_State* L); + static int luaMoveEventType(lua_State* L); + static int luaMoveEventRegister(lua_State* L); + static int luaMoveEventOnCallback(lua_State* L); + static int luaMoveEventLevel(lua_State* L); + static int luaMoveEventSlot(lua_State* L); + static int luaMoveEventMagLevel(lua_State* L); + static int luaMoveEventPremium(lua_State* L); + static int luaMoveEventVocation(lua_State* L); + static int luaMoveEventTileItem(lua_State* L); + static int luaMoveEventItemId(lua_State* L); + static int luaMoveEventActionId(lua_State* L); + static int luaMoveEventUniqueId(lua_State* L); + static int luaMoveEventPosition(lua_State* L); + + // GlobalEvents + static int luaCreateGlobalEvent(lua_State* L); + static int luaGlobalEventType(lua_State* L); + static int luaGlobalEventRegister(lua_State* L); + static int luaGlobalEventOnCallback(lua_State* L); + static int luaGlobalEventTime(lua_State* L); + static int luaGlobalEventInterval(lua_State* L); + + // Weapon + static int luaCreateWeapon(lua_State* L); + static int luaWeaponId(lua_State* L); + static int luaWeaponLevel(lua_State* L); + static int luaWeaponMagicLevel(lua_State* L); + static int luaWeaponMana(lua_State* L); + static int luaWeaponManaPercent(lua_State* L); + static int luaWeaponHealth(lua_State* L); + static int luaWeaponHealthPercent(lua_State* L); + static int luaWeaponSoul(lua_State* L); + static int luaWeaponPremium(lua_State* L); + static int luaWeaponBreakChance(lua_State* L); + static int luaWeaponAction(lua_State* L); + static int luaWeaponUnproperly(lua_State* L); + static int luaWeaponVocation(lua_State* L); + static int luaWeaponOnUseWeapon(lua_State* L); + static int luaWeaponRegister(lua_State* L); + static int luaWeaponElement(lua_State* L); + static int luaWeaponAttack(lua_State* L); + static int luaWeaponDefense(lua_State* L); + static int luaWeaponRange(lua_State* L); + static int luaWeaponCharges(lua_State* L); + static int luaWeaponDuration(lua_State* L); + static int luaWeaponDecayTo(lua_State* L); + static int luaWeaponTransformEquipTo(lua_State* L); + static int luaWeaponTransformDeEquipTo(lua_State* L); + static int luaWeaponSlotType(lua_State* L); + static int luaWeaponHitChance(lua_State* L); + static int luaWeaponExtraElement(lua_State* L); + + // exclusively for distance weapons + static int luaWeaponMaxHitChance(lua_State* L); + static int luaWeaponAmmoType(lua_State* L); + + // exclusively for wands + static int luaWeaponWandDamage(lua_State* L); + + // exclusively for wands & distance weapons + static int luaWeaponShootType(lua_State* L); + + // + std::string lastLuaError; + + std::string interfaceName; + + static ScriptEnvironment scriptEnv[16]; + static int32_t scriptEnvIndex; + + std::string loadingFile; }; class LuaEnvironment : public LuaScriptInterface { - public: - LuaEnvironment(); - ~LuaEnvironment(); +public: + LuaEnvironment(); + ~LuaEnvironment(); - // non-copyable - LuaEnvironment(const LuaEnvironment&) = delete; - LuaEnvironment& operator=(const LuaEnvironment&) = delete; + // non-copyable + LuaEnvironment(const LuaEnvironment&) = delete; + LuaEnvironment& operator=(const LuaEnvironment&) = delete; - bool initState() override; - bool reInitState(); - bool closeState() override; + bool initState() override; + bool reInitState(); + bool closeState() override; - LuaScriptInterface* getTestInterface(); + LuaScriptInterface* getTestInterface(); - Combat* getCombatObject(uint32_t id) const; - Combat* createCombatObject(LuaScriptInterface* interface); - void clearCombatObjects(LuaScriptInterface* interface); + Combat_ptr getCombatObject(uint32_t id) const; + Combat_ptr createCombatObject(LuaScriptInterface* interface); + void clearCombatObjects(LuaScriptInterface* interface); - AreaCombat* getAreaObject(uint32_t id) const; - uint32_t createAreaObject(LuaScriptInterface* interface); - void clearAreaObjects(LuaScriptInterface* interface); + AreaCombat* getAreaObject(uint32_t id) const; + uint32_t createAreaObject(LuaScriptInterface* interface); + void clearAreaObjects(LuaScriptInterface* interface); - private: - void executeTimerEvent(uint32_t eventIndex); +private: + void executeTimerEvent(uint32_t eventIndex); - std::unordered_map timerEvents; - std::unordered_map combatMap; - std::unordered_map areaMap; + std::unordered_map timerEvents; + std::unordered_map combatMap; + std::unordered_map areaMap; - std::unordered_map> combatIdMap; - std::unordered_map> areaIdMap; + std::unordered_map> combatIdMap; + std::unordered_map> areaIdMap; - LuaScriptInterface* testInterface = nullptr; + LuaScriptInterface* testInterface = nullptr; - uint32_t lastEventTimerId = 1; - uint32_t lastCombatId = 0; - uint32_t lastAreaId = 0; + uint32_t lastEventTimerId = 1; + uint32_t lastCombatId = 0; + uint32_t lastAreaId = 0; - friend class LuaScriptInterface; - friend class CombatSpell; + friend class LuaScriptInterface; + friend class CombatSpell; }; -#endif +#endif // FS_LUASCRIPT_H diff --git a/src/luavariant.h b/src/luavariant.h new file mode 100644 index 0000000000..9fe630f300 --- /dev/null +++ b/src/luavariant.h @@ -0,0 +1,38 @@ +#ifndef FS_LUAVARIANT_H +#define FS_LUAVARIANT_H + +enum LuaVariantType_t +{ + VARIANT_NUMBER = 0, + VARIANT_POSITION = 1, + VARIANT_TARGETPOSITION = 2, + VARIANT_STRING = 3, + + VARIANT_NONE = std::variant_npos, +}; + +class LuaVariant +{ +public: + uint32_t getNumber() const { return std::get(variant); } + const Position& getPosition() const { return std::get(variant); } + const Position& getTargetPosition() const { return std::get(variant); } + const std::string& getString() const { return std::get(variant); } + + bool isNumber() const { return variant.index() == VARIANT_NUMBER; } + bool isPosition() const { return variant.index() == VARIANT_POSITION; } + bool isTargetPosition() const { return variant.index() == VARIANT_TARGETPOSITION; } + bool isString() const { return variant.index() == VARIANT_STRING; } + + void setNumber(uint32_t value) { variant.emplace(value); } + void setPosition(const Position& value) { variant.emplace(value); } + void setTargetPosition(const Position& value) { variant.emplace(value); } + void setString(const std::string& value) { variant.emplace(value); } + + auto type() const { return static_cast(variant.index()); } + +private: + std::variant variant; +}; + +#endif // FS_LUAVARIANT_H diff --git a/src/mailbox.cpp b/src/mailbox.cpp index 3053f0d841..d3460dabf6 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -1,26 +1,12 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "mailbox.h" + #include "game.h" +#include "inbox.h" #include "iologindata.h" extern Game g_game; @@ -45,15 +31,9 @@ ReturnValue Mailbox::queryRemove(const Thing&, uint32_t, uint32_t, Creature* /*= return RETURNVALUE_NOTPOSSIBLE; } -Cylinder* Mailbox::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) -{ - return this; -} +Cylinder* Mailbox::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) { return this; } -void Mailbox::addThing(Thing* thing) -{ - return addThing(0, thing); -} +void Mailbox::addThing(Thing* thing) { return addThing(0, thing); } void Mailbox::addThing(int32_t, Thing* thing) { @@ -102,8 +82,8 @@ bool Mailbox::sendItem(Item* item) const Player* player = g_game.getPlayerByName(receiver); if (player) { - if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, - item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { + if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), + nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { g_game.transformItem(item, item->getID() + 1); player->onReceiveMail(); return true; @@ -114,8 +94,8 @@ bool Mailbox::sendItem(Item* item) const return false; } - if (g_game.internalMoveItem(item->getParent(), tmpPlayer.getInbox(), INDEX_WHEREEVER, - item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { + if (g_game.internalMoveItem(item->getParent(), tmpPlayer.getInbox(), INDEX_WHEREEVER, item, + item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { g_game.transformItem(item, item->getID() + 1); IOLoginData::savePlayer(&tmpPlayer); return true; @@ -142,11 +122,8 @@ bool Mailbox::getReceiver(Item* item, std::string& name) const } name = getFirstLine(text); - trimString(name); + boost::algorithm::trim(name); return true; } -bool Mailbox::canSend(const Item* item) -{ - return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER; -} +bool Mailbox::canSend(const Item* item) { return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER; } diff --git a/src/mailbox.h b/src/mailbox.h index d0cc27cfc8..b310ec22f7 100644 --- a/src/mailbox.h +++ b/src/mailbox.h @@ -1,66 +1,47 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB -#define FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB +#ifndef FS_MAILBOX_H +#define FS_MAILBOX_H -#include "item.h" #include "cylinder.h" -#include "const.h" +#include "item.h" class Mailbox final : public Item, public Cylinder { - public: - explicit Mailbox(uint16_t itemId) : Item(itemId) {} +public: + explicit Mailbox(uint16_t itemId) : Item(itemId) {} - Mailbox* getMailbox() override { - return this; - } - const Mailbox* getMailbox() const override { - return this; - } + Mailbox* getMailbox() override { return this; } + const Mailbox* getMailbox() const override { return this; } - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, - uint32_t& maxQueryCount, uint32_t flags) const override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; - Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) override; + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - void addThing(Thing* thing) override; - void addThing(int32_t index, Thing* thing) override; + void addThing(Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void removeThing(Thing* thing, uint32_t count) override; + void removeThing(Thing* thing, uint32_t count) override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; - private: - bool getReceiver(Item* item, std::string& name) const; - bool sendItem(Item* item) const; +private: + bool getReceiver(Item* item, std::string& name) const; + bool sendItem(Item* item) const; - static bool canSend(const Item* item); + static bool canSend(const Item* item); }; -#endif +#endif // FS_MAILBOX_H diff --git a/src/map.cpp b/src/map.cpp index 9e39fc0468..9f35235307 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1,30 +1,17 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "iomap.h" -#include "iomapserialize.h" +#include "map.h" + #include "combat.h" #include "creature.h" #include "game.h" +#include "iomap.h" +#include "iomapserialize.h" #include "monster.h" +#include "spectators.h" extern Game g_game; @@ -104,25 +91,25 @@ void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile) QTreeLeafNode* leaf = root.createLeaf(x, y, 15); if (QTreeLeafNode::newLeaf) { - //update north + // update north QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE); if (northLeaf) { northLeaf->leafS = leaf; } - //update west leaf + // update west leaf QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y); if (westLeaf) { westLeaf->leafE = leaf; } - //update south + // update south QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE); if (southLeaf) { leaf->leafS = southLeaf; } - //update east + // update east QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y); if (eastLeaf) { leaf->leafE = eastLeaf; @@ -196,7 +183,8 @@ void Map::removeTile(uint16_t x, uint16_t y, uint8_t z) } } -bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos/* = false*/, bool forceLogin/* = false*/) +bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos /* = false*/, + bool forceLogin /* = false*/) { bool foundTile; bool placeInPZ; @@ -212,19 +200,11 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte } if (!foundTile) { - static std::vector> extendedRelList { - {0, -2}, - {-1, -1}, {0, -1}, {1, -1}, - {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, - {-1, 1}, {0, 1}, {1, 1}, - {0, 2} - }; - - static std::vector> normalRelList { - {-1, -1}, {0, -1}, {1, -1}, - {-1, 0}, {1, 0}, - {-1, 1}, {0, 1}, {1, 1} - }; + static std::vector> extendedRelList{ + {0, -2}, {-1, -1}, {0, -1}, {1, -1}, {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, {-1, 1}, {0, 1}, {1, 1}, {0, 2}}; + + static std::vector> normalRelList{{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, + {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; std::vector>& relList = (extendedPos ? extendedRelList : normalRelList); @@ -268,7 +248,7 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte return true; } -void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = false*/) +void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport /* = false*/) { Tile& oldTile = *creature.getTile(); @@ -293,7 +273,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = } } - //remove the creature + // remove the creature oldTile.removeThing(&creature, 0); QTreeLeafNode* leaf = getQTNode(oldPos.x, oldPos.y); @@ -305,7 +285,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = new_leaf->addCreature(&creature); } - //add the creature + // add the creature newTile.addThing(&creature); if (!teleport) { @@ -322,19 +302,20 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = } } - //send to client + // send to client size_t i = 0; for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { - //Use the correct stackpos + // Use the correct stackpos int32_t stackpos = oldStackPosVector[i++]; if (stackpos != -1) { - tmpPlayer->sendCreatureMove(&creature, newPos, newTile.getClientIndexOfCreature(tmpPlayer, &creature), oldPos, stackpos, teleport); + tmpPlayer->sendCreatureMove(&creature, newPos, newTile.getClientIndexOfCreature(tmpPlayer, &creature), + oldPos, stackpos, teleport); } } } - //event method + // event method for (Creature* spectator : spectators) { spectator->onCreatureMove(&creature, &newTile, newPos, &oldTile, oldPos, teleport); } @@ -343,12 +324,14 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = newTile.postAddNotification(&creature, &oldTile, 0); } -void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const +void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX, + int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, + int32_t maxRangeZ, bool onlyPlayers) const { - int_fast16_t min_y = centerPos.y + minRangeY; - int_fast16_t min_x = centerPos.x + minRangeX; - int_fast16_t max_y = centerPos.y + maxRangeY; - int_fast16_t max_x = centerPos.x + maxRangeX; + auto min_y = centerPos.y + minRangeY; + auto min_x = centerPos.x + minRangeX; + auto max_y = centerPos.y + maxRangeY; + auto max_x = centerPos.x + maxRangeX; int32_t minoffset = centerPos.getZ() - maxRangeZ; uint16_t x1 = std::min(0xFFFF, std::max(0, (min_x + minoffset))); @@ -363,7 +346,8 @@ void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& center int32_t endx2 = x2 - (x2 % FLOOR_SIZE); int32_t endy2 = y2 - (y2 % FLOOR_SIZE); - const QTreeLeafNode* startLeaf = QTreeNode::getLeafStatic(&root, startx1, starty1); + const QTreeLeafNode* startLeaf = + QTreeNode::getLeafStatic(&root, startx1, starty1); const QTreeLeafNode* leafS = startLeaf; const QTreeLeafNode* leafE; @@ -379,7 +363,8 @@ void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& center } int_fast16_t offsetZ = Position::getOffsetZ(centerPos, cpos); - if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || (max_x + offsetZ) < cpos.x) { + if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || + (max_x + offsetZ) < cpos.x) { continue; } @@ -399,7 +384,9 @@ void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& center } } -void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, bool multifloor /*= false*/, bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) +void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, bool multifloor /*= false*/, + bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, + int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) { if (centerPos.z >= MAP_MAX_LAYERS) { return; @@ -413,7 +400,8 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo minRangeY = (minRangeY == 0 ? -maxViewportY : -minRangeY); maxRangeY = (maxRangeY == 0 ? maxViewportY : maxRangeY); - if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && maxRangeY == maxViewportY && multifloor) { + if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && + maxRangeY == maxViewportY && multifloor) { if (onlyPlayers) { auto it = playersSpectatorCache.find(centerPos); if (it != playersSpectatorCache.end()) { @@ -459,7 +447,7 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo if (multifloor) { if (centerPos.z > 7) { - //underground (8->15) + // underground (8->15) minRangeZ = std::max(centerPos.getZ() - 2, 0); maxRangeZ = std::min(centerPos.getZ() + 2, MAP_MAX_LAYERS - 1); } else if (centerPos.z == 6) { @@ -477,7 +465,8 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo maxRangeZ = centerPos.z; } - getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); + getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, + onlyPlayers); if (cacheResult) { if (onlyPlayers) { @@ -489,102 +478,150 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo } } -void Map::clearSpectatorCache() -{ - spectatorCache.clear(); -} +void Map::clearSpectatorCache() { spectatorCache.clear(); } -void Map::clearPlayersSpectatorCache() -{ - playersSpectatorCache.clear(); -} +void Map::clearPlayersSpectatorCache() { playersSpectatorCache.clear(); } bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, - int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const + bool sameFloor /*= false*/, int32_t rangex /*= Map::maxClientViewportX*/, + int32_t rangey /*= Map::maxClientViewportY*/) const { - //z checks - //underground 8->15 - //ground level and above 7->0 - if ((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 && fromPos.z < 8)) { + if (Position::getDistanceX(fromPos, toPos) > rangex || Position::getDistanceY(fromPos, toPos) > rangey) { return false; } - int32_t deltaz = Position::getDistanceZ(fromPos, toPos); - if (deltaz > 2) { - return false; + return !checkLineOfSight || isSightClear(fromPos, toPos, sameFloor); +} + +bool Map::isTileClear(uint16_t x, uint16_t y, uint8_t z, bool blockFloor /*= false*/) const +{ + const Tile* tile = getTile(x, y, z); + if (!tile) { + return true; } - if ((Position::getDistanceX(fromPos, toPos) - deltaz) > rangex) { + if (blockFloor && tile->getGround()) { return false; } - //distance checks - if ((Position::getDistanceY(fromPos, toPos) - deltaz) > rangey) { - return false; + return !tile->hasProperty(CONST_PROP_BLOCKPROJECTILE); +} + +namespace { + +bool checkSteepLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z) +{ + float dx = x1 - x0; + float slope = (dx == 0) ? 1 : (y1 - y0) / dx; + float yi = y0 + slope; + + for (uint16_t x = x0 + 1; x < x1; ++x) { + // 0.1 is necessary to avoid loss of precision during calculation + if (!g_game.map.isTileClear(std::floor(yi + 0.1), x, z)) { + return false; + } + yi += slope; } - if (!checkLineOfSight) { - return true; + return true; +} + +bool checkSlightLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z) +{ + float dx = x1 - x0; + float slope = (dx == 0) ? 1 : (y1 - y0) / dx; + float yi = y0 + slope; + + for (uint16_t x = x0 + 1; x < x1; ++x) { + // 0.1 is necessary to avoid loss of precision during calculation + if (!g_game.map.isTileClear(x, std::floor(yi + 0.1), z)) { + return false; + } + yi += slope; } - return isSightClear(fromPos, toPos, false); + + return true; } -bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const +} // namespace + +bool Map::checkSightLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z) const { - if (fromPos == toPos) { + if (x0 == x1 && y0 == y1) { return true; } - Position start(fromPos.z > toPos.z ? toPos : fromPos); - Position destination(fromPos.z > toPos.z ? fromPos : toPos); - - const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0 : -1; - const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0 : -1; + if (std::abs(y1 - y0) > std::abs(x1 - x0)) { + if (y1 > y0) { + return checkSteepLine(y0, x0, y1, x1, z); + } + return checkSteepLine(y1, x1, y0, x0, z); + } - int32_t A = Position::getOffsetY(destination, start); - int32_t B = Position::getOffsetX(start, destination); - int32_t C = -(A * destination.x + B * destination.y); + if (x0 > x1) { + return checkSlightLine(x1, y1, x0, y0, z); + } - while (start.x != destination.x || start.y != destination.y) { - int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C); - int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C); - int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C); + return checkSlightLine(x0, y0, x1, y1, z); +} - if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) { - start.y += my; +bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor /*= false*/) const +{ + // target is on the same floor + if (fromPos.z == toPos.z) { + // skip checks if toPos is next to us + if (Position::getDistanceX(fromPos, toPos) < 2 && Position::getDistanceY(fromPos, toPos) < 2) { + return true; } - if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) { - start.x += mx; + // sight is clear or sameFloor is enabled + bool sightClear = checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, fromPos.z); + if (sightClear || sameFloor) { + return sightClear; } - const Tile* tile = getTile(start.x, start.y, start.z); - if (tile && tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) { - return false; + // no obstacles above floor 0 so we can throw above the obstacle + if (fromPos.z == 0) { + return true; } + + // check if tiles above us and the target are clear and check for a clear sight between them + uint8_t newZ = fromPos.z - 1; + return isTileClear(fromPos.x, fromPos.y, newZ, true) && isTileClear(toPos.x, toPos.y, newZ, true) && + checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, newZ); + } + + // target is on a different floor + if (sameFloor) { + return false; } - // now we need to perform a jump between floors to see if everything is clear (literally) - while (start.z != destination.z) { - const Tile* tile = getTile(start.x, start.y, start.z); - if (tile && tile->getThingCount() > 0) { + // skip checks for sight line in case fromPos and toPos cross the ground floor + if ((fromPos.z < 8 && toPos.z > 7) || (fromPos.z > 7 && toPos.z < 8)) { + return false; + } + + // target is above us + if (fromPos.z > toPos.z) { + if (Position::getDistanceZ(fromPos, toPos) > 1) { return false; } - start.z++; + // check a tile above us and the path to the target + uint8_t newZ = fromPos.z - 1; + return isTileClear(fromPos.x, fromPos.y, newZ, true) && + checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, newZ); } - return true; -} - -bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const -{ - if (floorCheck && fromPos.z != toPos.z) { - return false; + // target is below us check if tiles above the target are clear + for (uint8_t z = fromPos.z; z < toPos.z; ++z) { + if (!isTileClear(toPos.x, toPos.y, z, true)) { + return false; + } } - // Cast two converging rays and see if either yields a result. - return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos); + // check if we can throw to the tile above the target + return checkSightLine(fromPos.x, fromPos.y, toPos.x, toPos.y, fromPos.z); } const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const @@ -596,7 +633,7 @@ const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const return getTile(pos.x, pos.y, pos.z); } - //used for non-cached tiles + // used for non-cached tiles Tile* tile = getTile(pos.x, pos.y, pos.z); if (creature.getTile() != tile) { if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) != RETURNVALUE_NOERROR) { @@ -606,7 +643,8 @@ const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const return tile; } -bool Map::getPathMatching(const Creature& creature, std::vector& dirList, const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const +bool Map::getPathMatching(const Creature& creature, std::vector& dirList, + const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const { Position pos = creature.getPosition(); Position endPos; @@ -616,18 +654,11 @@ bool Map::getPathMatching(const Creature& creature, std::vector& dirL int32_t bestMatch = 0; static int_fast32_t dirNeighbors[8][5][2] = { - {{-1, 0}, {0, 1}, {1, 0}, {1, 1}, {-1, 1}}, - {{-1, 0}, {0, 1}, {0, -1}, {-1, -1}, {-1, 1}}, - {{-1, 0}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}}, - {{0, 1}, {1, 0}, {0, -1}, {1, -1}, {1, 1}}, - {{1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}}, - {{-1, 0}, {0, -1}, {-1, -1}, {1, -1}, {-1, 1}}, - {{0, 1}, {1, 0}, {1, -1}, {1, 1}, {-1, 1}}, - {{-1, 0}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}} - }; - static int_fast32_t allNeighbors[8][2] = { - {-1, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1} - }; + {{-1, 0}, {0, 1}, {1, 0}, {1, 1}, {-1, 1}}, {{-1, 0}, {0, 1}, {0, -1}, {-1, -1}, {-1, 1}}, + {{-1, 0}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}}, {{0, 1}, {1, 0}, {0, -1}, {1, -1}, {1, 1}}, + {{1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}}, {{-1, 0}, {0, -1}, {-1, -1}, {1, -1}, {-1, 1}}, + {{0, 1}, {1, 0}, {1, -1}, {1, 1}, {-1, 1}}, {{-1, 0}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}}}; + static int_fast32_t allNeighbors[8][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}}; const Position startPos = pos; @@ -692,7 +723,8 @@ bool Map::getPathMatching(const Creature& creature, std::vector& dirL pos.x = x + *neighbors++; pos.y = y + *neighbors++; - if (fpp.maxSearchDist != 0 && (Position::getDistanceX(startPos, pos) > fpp.maxSearchDist || Position::getDistanceY(startPos, pos) > fpp.maxSearchDist)) { + if (fpp.maxSearchDist != 0 && (Position::getDistanceX(startPos, pos) > fpp.maxSearchDist || + Position::getDistanceY(startPos, pos) > fpp.maxSearchDist)) { continue; } @@ -711,14 +743,14 @@ bool Map::getPathMatching(const Creature& creature, std::vector& dirL } } - //The cost (g) for this neighbor + // The cost (g) for this neighbor const int_fast32_t cost = AStarNodes::getMapWalkCost(n, pos); const int_fast32_t extraCost = AStarNodes::getTileWalkCost(creature, tile); const int_fast32_t newf = f + cost + extraCost; if (neighborNode) { if (neighborNode->f <= newf) { - //The node on the closed/open list is cheaper than this one + // The node on the closed/open list is cheaper than this one continue; } @@ -726,7 +758,7 @@ bool Map::getPathMatching(const Creature& creature, std::vector& dirL neighborNode->parent = n; nodes.openNode(neighborNode); } else { - //Does not exist in the open/closed list, create a new node + // Does not exist in the open/closed list, create a new node neighborNode = nodes.createOpenNode(n, pos.x, pos.y, newf); if (!neighborNode) { if (found) { @@ -783,8 +815,7 @@ bool Map::getPathMatching(const Creature& creature, std::vector& dirL // AStarNodes -AStarNodes::AStarNodes(uint32_t x, uint32_t y) - : nodes(), openNodes() +AStarNodes::AStarNodes(uint32_t x, uint32_t y) : nodes(), openNodes() { curNode = 1; closedNodes = 0; @@ -855,10 +886,7 @@ void AStarNodes::openNode(AStarNode* node) } } -int_fast32_t AStarNodes::getClosedNodes() const -{ - return closedNodes; -} +int_fast32_t AStarNodes::getClosedNodes() const { return closedNodes; } AStarNode* AStarNodes::getNodeByPosition(uint32_t x, uint32_t y) { @@ -872,7 +900,7 @@ AStarNode* AStarNodes::getNodeByPosition(uint32_t x, uint32_t y) int_fast32_t AStarNodes::getMapWalkCost(AStarNode* node, const Position& neighborPos) { if (std::abs(node->x - neighborPos.x) == std::abs(node->y - neighborPos.y)) { - //diagonal movement extra cost + // diagonal movement extra cost return MAP_DIAGONALWALKCOST; } return MAP_NORMALWALKCOST; @@ -881,15 +909,16 @@ int_fast32_t AStarNodes::getMapWalkCost(AStarNode* node, const Position& neighbo int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* tile) { int_fast32_t cost = 0; - if (tile->getTopVisibleCreature(&creature) != nullptr) { - //destroy creature cost + if (tile->getTopVisibleCreature(&creature)) { + // destroy creature cost cost += MAP_NORMALWALKCOST * 3; } if (const MagicField* field = tile->getFieldItem()) { CombatType_t combatType = field->getCombatType(); const Monster* monster = creature.getMonster(); - if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType)) && (monster && !monster->canWalkOnFieldType(combatType))) { + if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType)) && + (monster && !monster->canWalkOnFieldType(combatType))) { cost += MAP_NORMALWALKCOST * 18; } } @@ -1023,8 +1052,7 @@ uint32_t Map::clean() const g_game.setGameState(GAME_STATE_NORMAL); } - std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "") - << " from " << tiles << " tile" << (tiles != 1 ? "s" : "") << " in " - << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "") << " from " << tiles << " tile" + << (tiles != 1 ? "s" : "") << " in " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; return count; } diff --git a/src/map.h b/src/map.h index 40c5d9f05b..5369133c75 100644 --- a/src/map.h +++ b/src/map.h @@ -1,45 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_MAP_H_E3953D57C058461F856F5221D359DAFA -#define FS_MAP_H_E3953D57C058461F856F5221D359DAFA +#ifndef FS_MAP_H +#define FS_MAP_H -#include "position.h" -#include "item.h" -#include "fileloader.h" - -#include "tools.h" -#include "tile.h" -#include "town.h" #include "house.h" +#include "position.h" #include "spawn.h" +#include "town.h" class Creature; -class Player; -class Game; -class Tile; -class Map; static constexpr int32_t MAP_MAX_LAYERS = 16; struct FindPathParams; -struct AStarNode { +struct AStarNode +{ AStarNode* parent; int_fast32_t f; uint16_t x, y; @@ -52,25 +28,25 @@ static constexpr int32_t MAP_DIAGONALWALKCOST = 25; class AStarNodes { - public: - AStarNodes(uint32_t x, uint32_t y); - - AStarNode* createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f); - AStarNode* getBestNode(); - void closeNode(AStarNode* node); - void openNode(AStarNode* node); - int_fast32_t getClosedNodes() const; - AStarNode* getNodeByPosition(uint32_t x, uint32_t y); - - static int_fast32_t getMapWalkCost(AStarNode* node, const Position& neighborPos); - static int_fast32_t getTileWalkCost(const Creature& creature, const Tile* tile); - - private: - AStarNode nodes[MAX_NODES]; - bool openNodes[MAX_NODES]; - std::unordered_map nodeTable; - size_t curNode; - int_fast32_t closedNodes; +public: + AStarNodes(uint32_t x, uint32_t y); + + AStarNode* createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f); + AStarNode* getBestNode(); + void closeNode(AStarNode* node); + void openNode(AStarNode* node); + int_fast32_t getClosedNodes() const; + AStarNode* getNodeByPosition(uint32_t x, uint32_t y); + + static int_fast32_t getMapWalkCost(AStarNode* node, const Position& neighborPos); + static int_fast32_t getTileWalkCost(const Creature& creature, const Tile* tile); + +private: + AStarNode nodes[MAX_NODES]; + bool openNodes[MAX_NODES]; + std::unordered_map nodeTable; + size_t curNode; + int_fast32_t closedNodes; }; using SpectatorCache = std::map; @@ -79,7 +55,8 @@ static constexpr int32_t FLOOR_BITS = 3; static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS); static constexpr int32_t FLOOR_MASK = (FLOOR_SIZE - 1); -struct Floor { +struct Floor +{ constexpr Floor() = default; ~Floor(); @@ -95,204 +72,208 @@ class QTreeLeafNode; class QTreeNode { - public: - constexpr QTreeNode() = default; - virtual ~QTreeNode(); +public: + constexpr QTreeNode() = default; + virtual ~QTreeNode(); - // non-copyable - QTreeNode(const QTreeNode&) = delete; - QTreeNode& operator=(const QTreeNode&) = delete; + // non-copyable + QTreeNode(const QTreeNode&) = delete; + QTreeNode& operator=(const QTreeNode&) = delete; - bool isLeaf() const { - return leaf; - } + bool isLeaf() const { return leaf; } - QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); + QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); - template - static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) - { - do { - node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; - if (!node) { - return nullptr; - } + template + static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) + { + do { + node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; + if (!node) { + return nullptr; + } - x <<= 1; - y <<= 1; - } while (!node->leaf); - return static_cast(node); - } + x <<= 1; + y <<= 1; + } while (!node->leaf); + return static_cast(node); + } - QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); + QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); - protected: - bool leaf = false; +protected: + bool leaf = false; - private: - QTreeNode* child[4] = {}; +private: + QTreeNode* child[4] = {}; - friend class Map; + friend class Map; }; class QTreeLeafNode final : public QTreeNode { - public: - QTreeLeafNode() { leaf = true; newLeaf = true; } - ~QTreeLeafNode(); - - // non-copyable - QTreeLeafNode(const QTreeLeafNode&) = delete; - QTreeLeafNode& operator=(const QTreeLeafNode&) = delete; - - Floor* createFloor(uint32_t z); - Floor* getFloor(uint8_t z) const { - return array[z]; - } - - void addCreature(Creature* c); - void removeCreature(Creature* c); - - private: - static bool newLeaf; - QTreeLeafNode* leafS = nullptr; - QTreeLeafNode* leafE = nullptr; - Floor* array[MAP_MAX_LAYERS] = {}; - CreatureVector creature_list; - CreatureVector player_list; - - friend class Map; - friend class QTreeNode; +public: + QTreeLeafNode() + { + leaf = true; + newLeaf = true; + } + ~QTreeLeafNode(); + + // non-copyable + QTreeLeafNode(const QTreeLeafNode&) = delete; + QTreeLeafNode& operator=(const QTreeLeafNode&) = delete; + + Floor* createFloor(uint32_t z); + Floor* getFloor(uint8_t z) const { return array[z]; } + + void addCreature(Creature* c); + void removeCreature(Creature* c); + +private: + static bool newLeaf; + QTreeLeafNode* leafS = nullptr; + QTreeLeafNode* leafE = nullptr; + Floor* array[MAP_MAX_LAYERS] = {}; + CreatureVector creature_list; + CreatureVector player_list; + + friend class Map; + friend class QTreeNode; }; /** - * Map class. - * Holds all the actual map-data - */ + * Map class. + * Holds all the actual map-data + */ class Map { - public: - static constexpr int32_t maxViewportX = 11; //min value: maxClientViewportX + 1 - static constexpr int32_t maxViewportY = 11; //min value: maxClientViewportY + 1 - static constexpr int32_t maxClientViewportX = 8; - static constexpr int32_t maxClientViewportY = 6; - - uint32_t clean() const; - - /** - * Load a map. - * \returns true if the map was loaded successfully - */ - bool loadMap(const std::string& identifier, bool loadHouses); - - /** - * Save a map. - * \returns true if the map was saved successfully - */ - static bool save(); - - /** - * Get a single tile. - * \returns A pointer to that tile. - */ - Tile* getTile(uint16_t x, uint16_t y, uint8_t z) const; - Tile* getTile(const Position& pos) const { - return getTile(pos.x, pos.y, pos.z); - } - - /** - * Set a single tile. - */ - void setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile); - void setTile(const Position& pos, Tile* newTile) { - setTile(pos.x, pos.y, pos.z, newTile); - } - - /** - * Removes a single tile. - */ - void removeTile(uint16_t x, uint16_t y, uint8_t z); - void removeTile(const Position& pos) { - removeTile(pos.x, pos.y, pos.z); - } - - /** - * Place a creature on the map - * \param centerPos The position to place the creature - * \param creature Creature to place on the map - * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away - * \param forceLogin If true, placing the creature will not fail because of obstacles (creatures/chests) - */ - bool placeCreature(const Position& centerPos, Creature* creature, bool extendedPos = false, bool forceLogin = false); - - void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false); - - void getSpectators(SpectatorVec& spectators, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false, - int32_t minRangeX = 0, int32_t maxRangeX = 0, - int32_t minRangeY = 0, int32_t maxRangeY = 0); - - void clearSpectatorCache(); - void clearPlayersSpectatorCache(); - - /** - * Checks if you can throw an object to that position - * \param fromPos from Source point - * \param toPos Destination point - * \param rangex maximum allowed range horizontally - * \param rangey maximum allowed range vertically - * \param checkLineOfSight checks if there is any blocking objects in the way - * \returns The result if you can throw there or not - */ - bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, - int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const; - - /** - * Checks if path is clear from fromPos to toPos - * Notice: This only checks a straight line if the path is clear, for path finding use getPathTo. - * \param fromPos from Source point - * \param toPos Destination point - * \param floorCheck if true then view is not clear if fromPos.z is not the same as toPos.z - * \returns The result if there is no obstacles - */ - bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const; - bool checkSightLine(const Position& fromPos, const Position& toPos) const; - - const Tile* canWalkTo(const Creature& creature, const Position& pos) const; - - bool getPathMatching(const Creature& creature, std::vector& dirList, - const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const; - - std::map waypoints; - - QTreeLeafNode* getQTNode(uint16_t x, uint16_t y) { - return QTreeNode::getLeafStatic(&root, x, y); - } - - Spawns spawns; - Towns towns; - Houses houses; - - private: - SpectatorCache spectatorCache; - SpectatorCache playersSpectatorCache; - - QTreeNode root; - - std::string spawnfile; - std::string housefile; - - uint32_t width = 0; - uint32_t height = 0; - - // Actually scans the map for spectators - void getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, - int32_t minRangeX, int32_t maxRangeX, - int32_t minRangeY, int32_t maxRangeY, - int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const; - - friend class Game; - friend class IOMap; +public: + static constexpr int32_t maxViewportX = 11; // min value: maxClientViewportX + 1 + static constexpr int32_t maxViewportY = 11; // min value: maxClientViewportY + 1 + static constexpr int32_t maxClientViewportX = 8; + static constexpr int32_t maxClientViewportY = 6; + + uint32_t clean() const; + + /** + * Load a map. + * \returns true if the map was loaded successfully + */ + bool loadMap(const std::string& identifier, bool loadHouses); + + /** + * Save a map. + * \returns true if the map was saved successfully + */ + static bool save(); + + /** + * Get a single tile. + * \returns A pointer to that tile. + */ + Tile* getTile(uint16_t x, uint16_t y, uint8_t z) const; + Tile* getTile(const Position& pos) const { return getTile(pos.x, pos.y, pos.z); } + + /** + * Set a single tile. + */ + void setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile); + void setTile(const Position& pos, Tile* newTile) { setTile(pos.x, pos.y, pos.z, newTile); } + + /** + * Removes a single tile. + */ + void removeTile(uint16_t x, uint16_t y, uint8_t z); + void removeTile(const Position& pos) { removeTile(pos.x, pos.y, pos.z); } + + /** + * Place a creature on the map + * \param centerPos The position to place the creature + * \param creature Creature to place on the map + * \param extendedPos If true, the creature will in first-hand be placed 2 + * tiles away \param forceLogin If true, placing the creature will not fail + * because of obstacles (creatures/chests) + */ + bool placeCreature(const Position& centerPos, Creature* creature, bool extendedPos = false, + bool forceLogin = false); + + void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false); + + void getSpectators(SpectatorVec& spectators, const Position& centerPos, bool multifloor = false, + bool onlyPlayers = false, int32_t minRangeX = 0, int32_t maxRangeX = 0, int32_t minRangeY = 0, + int32_t maxRangeY = 0); + + void clearSpectatorCache(); + void clearPlayersSpectatorCache(); + + /** + * Checks if you can throw an object to that position + * \param fromPos from Source point + * \param toPos Destination point + * \param rangex maximum allowed range horizontally + * \param rangey maximum allowed range vertically + * \param checkLineOfSight checks if there is any blocking objects in the + *way \param sameFloor checks if the destination is on same floor \returns + *The result if you can throw there or not + */ + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + bool sameFloor = false, int32_t rangex = Map::maxClientViewportX, + int32_t rangey = Map::maxClientViewportY) const; + + /** + * Checks if there are no obstacles on that position + * \param blockFloor counts the ground tile as an obstacle + * \returns The result if there is an obstacle or not + */ + bool isTileClear(uint16_t x, uint16_t y, uint8_t z, bool blockFloor = false) const; + + /** + * Checks if path is clear from fromPos to toPos + * Notice: This only checks a straight line if the path is clear, for path + *finding use getPathTo. \param fromPos from Source point \param toPos + *Destination point \param sameFloor checks if the destination is on same + *floor \returns The result if there is no obstacles + */ + bool isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor = false) const; + bool checkSightLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t z) const; + + const Tile* canWalkTo(const Creature& creature, const Position& pos) const; + + bool getPathMatching(const Creature& creature, std::vector& dirList, + const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const; + + std::map waypoints; + + QTreeLeafNode* getQTNode(uint16_t x, uint16_t y) + { + return QTreeNode::getLeafStatic(&root, x, y); + } + + Spawns spawns; + Towns towns; + Houses houses; + +private: + SpectatorCache spectatorCache; + SpectatorCache playersSpectatorCache; + + QTreeNode root; + + std::string spawnfile; + std::string housefile; + + uint32_t width = 0; + uint32_t height = 0; + + // Actually scans the map for spectators + void getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX, + int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, + int32_t maxRangeZ, bool onlyPlayers) const; + + friend class Game; + friend class IOMap; }; -#endif +#endif // FS_MAP_H diff --git a/src/monster.cpp b/src/monster.cpp index cd9ae44598..b0d7ad1071 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1,29 +1,16 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "monster.h" + +#include "condition.h" +#include "configmanager.h" +#include "events.h" #include "game.h" +#include "spectators.h" #include "spells.h" -#include "events.h" -#include "configmanager.h" extern Game g_game; extern Monsters g_monsters; @@ -33,7 +20,7 @@ extern ConfigManager g_config; int32_t Monster::despawnRange; int32_t Monster::despawnRadius; -uint32_t Monster::monsterAutoID = 0x40000000; +uint32_t Monster::monsterAutoID = 0x21000000; Monster* Monster::createMonster(const std::string& name) { @@ -44,10 +31,7 @@ Monster* Monster::createMonster(const std::string& name) return new Monster(mType); } -Monster::Monster(MonsterType* mType) : - Creature(), - strDescription(mType->nameDescription), - mType(mType) +Monster::Monster(MonsterType* mType) : Creature(), nameDescription(mType->nameDescription), mType(mType) { defaultOutfit = mType->info.outfit; currentOutfit = mType->info.outfit; @@ -72,19 +56,48 @@ Monster::~Monster() clearFriendList(); } -void Monster::addList() +void Monster::addList() { g_game.addMonster(this); } + +void Monster::removeList() { g_game.removeMonster(this); } + +const std::string& Monster::getName() const { - g_game.addMonster(this); + if (name.empty()) { + return mType->name; + } + return name; } -void Monster::removeList() +void Monster::setName(const std::string& name) { - g_game.removeMonster(this); + if (getName() == name) { + return; + } + + this->name = name; + + // NOTE: Due to how client caches known creatures, it is not feasible to send creature update to everyone that has + // ever met it + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true, true); + for (Creature* spectator : spectators) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendUpdateTileCreature(this); + } + } +} + +const std::string& Monster::getNameDescription() const +{ + if (nameDescription.empty()) { + return mType->nameDescription; + } + return nameDescription; } bool Monster::canSee(const Position& pos) const { - return Creature::canSee(getPosition(), pos, 9, 9); + return Creature::canSee(getPosition(), pos, Map::maxClientViewportX + 1, Map::maxClientViewportX + 1); } bool Monster::canWalkOnFieldType(CombatType_t combatType) const @@ -101,10 +114,7 @@ bool Monster::canWalkOnFieldType(CombatType_t combatType) const } } -void Monster::onAttackedCreatureDisappear(bool) -{ - attackTicks = 0; -} +void Monster::onAttackedCreatureDisappear(bool) { attackTicks = 0; } void Monster::onCreatureAppear(Creature* creature, bool isLogin) { @@ -136,7 +146,7 @@ void Monster::onCreatureAppear(Creature* creature, bool isLogin) } if (creature == this) { - //We just spawned lets look around to see who is there. + // We just spawned lets look around to see who is there. if (isSummon()) { isMasterInRange = canSee(getMaster()->getPosition()); } @@ -188,8 +198,8 @@ void Monster::onRemoveCreature(Creature* creature, bool isLogout) } } -void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) +void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); @@ -239,7 +249,7 @@ void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Posi } if (canSeeNewPos && isSummon() && getMaster() == creature) { - isMasterInRange = true; //Follow master again + isMasterInRange = true; // Follow master again } updateIdleStatus(); @@ -264,7 +274,7 @@ void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Posi } } } else if (isOpponent(creature)) { - //we have no target lets try pick this one + // we have no target lets try pick this one selectTarget(creature); } } @@ -320,7 +330,7 @@ void Monster::removeFriend(Creature* creature) } } -void Monster::addTarget(Creature* creature, bool pushFront/* = false*/) +void Monster::addTarget(Creature* creature, bool pushFront /* = false*/) { assert(creature != this); if (std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { @@ -390,7 +400,7 @@ void Monster::clearFriendList() friendList.clear(); } -void Monster::onCreatureFound(Creature* creature, bool pushFront/* = false*/) +void Monster::onCreatureFound(Creature* creature, bool pushFront /* = false*/) { if (!creature) { return; @@ -416,7 +426,7 @@ void Monster::onCreatureEnter(Creature* creature) // std::cout << "onCreatureEnter - " << creature->getName() << std::endl; if (getMaster() == creature) { - //Follow master again + // Follow master again isMasterInRange = true; } @@ -457,7 +467,7 @@ bool Monster::isOpponent(const Creature* creature) const } } else { if ((creature->getPlayer() && !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) || - (creature->getMaster() && creature->getMaster()->getPlayer())) { + (creature->getMaster() && creature->getMaster()->getPlayer())) { return true; } } @@ -470,20 +480,26 @@ void Monster::onCreatureLeave(Creature* creature) // std::cout << "onCreatureLeave - " << creature->getName() << std::endl; if (getMaster() == creature) { - //Take random steps and only use defense abilities (e.g. heal) until its master comes back + // Take random steps and only use defense abilities (e.g. heal) until its master comes back isMasterInRange = false; } - //update friendList + // update friendList if (isFriend(creature)) { removeFriend(creature); } - //update targetList + // update targetList if (isOpponent(creature)) { removeTarget(creature); - if (targetList.empty()) { - updateIdleStatus(); + updateIdleStatus(); + + if (!isSummon() && targetList.empty()) { + int32_t walkToSpawnRadius = g_config.getNumber(ConfigManager::DEFAULT_WALKTOSPAWNRADIUS); + if (walkToSpawnRadius > 0 && + !Position::areInRange(position, masterPos, walkToSpawnRadius, walkToSpawnRadius)) { + walkToSpawn(); + } } } } @@ -510,7 +526,8 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL if (++it != resultList.end()) { const Position& targetPosition = target->getPosition(); - int32_t minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + int32_t minRange = + Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); do { const Position& pos = (*it)->getPosition(); @@ -561,7 +578,7 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL } } - //lets just pick the first target in the list + // lets just pick the first target in the list for (Creature* target : targetList) { if (followCreature != target && selectTarget(target)) { return true; @@ -590,7 +607,8 @@ void Monster::onFollowCreatureComplete(const Creature* creature) } BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool /* field = false */, bool /* ignoreResistances = false */) + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool /* field = false */, + bool /* ignoreResistances = false */) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); @@ -615,8 +633,8 @@ BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32 bool Monster::isTarget(const Creature* creature) const { - if (creature->isRemoved() || !creature->isAttackable() || - creature->getZone() == ZONE_PROTECTION || !canSeeCreature(creature)) { + if (creature->isRemoved() || !creature->isAttackable() || creature->getZone() == ZONE_PROTECTION || + !canSeeCreature(creature)) { return false; } @@ -634,13 +652,13 @@ bool Monster::selectTarget(Creature* creature) auto it = std::find(targetList.begin(), targetList.end(), creature); if (it == targetList.end()) { - //Target not found in our target list. + // Target not found in our target list. return false; } if (isHostile() || isSummon()) { if (setAttackedCreature(creature) && !isSummon()) { - g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + g_dispatcher.addTask(createTask([id = getID()]() { g_game.checkCreatureAttack(id); })); } } return setFollowCreature(creature); @@ -669,9 +687,8 @@ void Monster::updateIdleStatus() bool idle = false; if (!isSummon() && targetList.empty()) { // check if there are aggressive conditions - idle = std::find_if(conditions.begin(), conditions.end(), [](Condition* condition) { - return condition->isAggressive(); - }) == conditions.end(); + idle = std::find_if(conditions.begin(), conditions.end(), + [](Condition* condition) { return condition->isAggressive(); }) == conditions.end(); } setIdle(idle); @@ -725,14 +742,13 @@ void Monster::onThink(uint32_t interval) } if (!isInSpawnRange(position)) { + g_game.addMagicEffect(this->getPosition(), CONST_ME_POFF); if (g_config.getBoolean(ConfigManager::REMOVE_ON_DESPAWN)) { g_game.removeCreature(this, false); } else { g_game.internalTeleport(this, masterPos); setIdle(true); } - - g_game.addMagicEffect(this->getPosition(), CONST_ME_POFF); } else { updateIdleStatus(); @@ -742,16 +758,16 @@ void Monster::onThink(uint32_t interval) if (isSummon()) { if (!attackedCreature) { if (getMaster() && getMaster()->getAttackedCreature()) { - //This happens if the monster is summoned during combat + // This happens if the monster is summoned during combat selectTarget(getMaster()->getAttackedCreature()); } else if (getMaster() != followCreature) { - //Our master has not ordered us to attack anything, lets follow him around instead. + // Our master has not ordered us to attack anything, lets follow him around instead. setFollowCreature(getMaster()); } } else if (attackedCreature == this) { setFollowCreature(nullptr); } else if (followCreature != attackedCreature) { - //This happens just after a master orders an attack, so lets follow it as well. + // This happens just after a master orders an attack, so lets follow it as well. setFollowCreature(attackedCreature); } } else if (!targetList.empty()) { @@ -787,7 +803,7 @@ void Monster::doAttacking(uint32_t interval) for (const spellBlock_t& spellBlock : mType->info.attackSpells) { bool inRange = false; - if (attackedCreature == nullptr) { + if (!attackedCreature) { break; } @@ -809,7 +825,7 @@ void Monster::doAttacking(uint32_t interval) } if (!inRange && spellBlock.isMelee) { - //melee swing out of reach + // melee swing out of reach lastMeleeAttack = 0; } } @@ -827,7 +843,8 @@ bool Monster::canUseAttack(const Position& pos, const Creature* target) const { if (isHostile()) { const Position& targetPos = target->getPosition(); - uint32_t distance = std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)); + uint32_t distance = + std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)); for (const spellBlock_t& spellBlock : mType->info.attackSpells) { if (spellBlock.range != 0 && distance <= spellBlock.range) { return g_game.isSightClear(pos, targetPos, true); @@ -838,8 +855,8 @@ bool Monster::canUseAttack(const Position& pos, const Creature* target) const return true; } -bool Monster::canUseSpell(const Position& pos, const Position& targetPos, - const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks) +bool Monster::canUseSpell(const Position& pos, const Position& targetPos, const spellBlock_t& sb, uint32_t interval, + bool& inRange, bool& resetTicks) { inRange = true; @@ -854,12 +871,13 @@ bool Monster::canUseSpell(const Position& pos, const Position& targetPos, } if (attackTicks % sb.speed >= interval) { - //already used this spell for this round + // already used this spell for this round return false; } } - if (sb.range != 0 && std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)) > sb.range) { + if (sb.range != 0 && + std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)) > sb.range) { inRange = false; return false; } @@ -927,7 +945,7 @@ void Monster::onThinkDefense(uint32_t interval) } if (defenseTicks % spellBlock.speed >= interval) { - //already used this spell for this round + // already used this spell for this round continue; } @@ -950,7 +968,7 @@ void Monster::onThinkDefense(uint32_t interval) } if (defenseTicks % summonBlock.speed >= interval) { - //already used this spell for this round + // already used this spell for this round continue; } @@ -976,7 +994,6 @@ void Monster::onThinkDefense(uint32_t interval) summon->setSkillLoss(false); summon->setMaster(this); g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); - g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); } else { delete summon; } @@ -999,7 +1016,8 @@ void Monster::onThinkYell(uint32_t interval) if (yellTicks >= mType->info.yellSpeedTicks) { yellTicks = 0; - if (!mType->info.voiceVector.empty() && (mType->info.yellChance >= static_cast(uniform_random(1, 100)))) { + if (!mType->info.voiceVector.empty() && + (mType->info.yellChance >= static_cast(uniform_random(1, 100)))) { uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1); const voiceBlock_t& vb = mType->info.voiceVector[index]; @@ -1012,28 +1030,54 @@ void Monster::onThinkYell(uint32_t interval) } } -void Monster::onWalk() +bool Monster::walkToSpawn() { - Creature::onWalk(); + if (walkingToSpawn || !spawn || !targetList.empty()) { + return false; + } + + int32_t distance = + std::max(Position::getDistanceX(position, masterPos), Position::getDistanceY(position, masterPos)); + if (distance == 0) { + return false; + } + + listWalkDir.clear(); + if (!getPathTo(masterPos, listWalkDir, 0, std::max(0, distance - 5), true, true, distance)) { + return false; + } + + walkingToSpawn = true; + startAutoWalk(); + return true; +} + +void Monster::onWalk() { Creature::onWalk(); } + +void Monster::onWalkComplete() +{ + // Continue walking to spawn + if (walkingToSpawn) { + walkingToSpawn = false; + walkToSpawn(); + } } bool Monster::pushItem(Item* item) { const Position& centerPos = item->getPosition(); - static std::vector> relList { - {-1, -1}, {0, -1}, {1, -1}, - {-1, 0}, {1, 0}, - {-1, 1}, {0, 1}, {1, 1} - }; + static std::vector> relList{{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, + {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; std::shuffle(relList.begin(), relList.end(), getRandomGenerator()); for (const auto& it : relList) { Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z); Tile* tile = g_game.map.getTile(tryPos); - if (tile && g_game.canThrowObjectTo(centerPos, tryPos)) { - if (g_game.internalMoveItem(item->getParent(), tile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr) == RETURNVALUE_NOERROR) { + if (tile && g_game.canThrowObjectTo(centerPos, tryPos, true, true)) { + if (g_game.internalMoveItem(item->getParent(), tile, INDEX_WHEREEVER, item, item->getItemCount(), + nullptr) == RETURNVALUE_NOERROR) { return true; } } @@ -1043,9 +1087,8 @@ bool Monster::pushItem(Item* item) void Monster::pushItems(Tile* tile) { - //We can not use iterators here since we can push the item to another tile - //which will invalidate the iterator. - //start from the end to minimize the amount of traffic + // We can not use iterators here since we can push the item to another tile which will invalidate the iterator. + // start from the end to minimize the amount of traffic if (TileItemVector* items = tile->getItemList()) { uint32_t moveCount = 0; uint32_t removeCount = 0; @@ -1053,8 +1096,8 @@ void Monster::pushItems(Tile* tile) int32_t downItemSize = tile->getDownItemCount(); for (int32_t i = downItemSize; --i >= 0;) { Item* item = items->at(i); - if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) - || item->hasProperty(CONST_PROP_BLOCKSOLID))) { + if (item && item->hasProperty(CONST_PROP_MOVEABLE) && + (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID))) { if (moveCount < 20 && Monster::pushItem(item)) { ++moveCount; } else if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { @@ -1071,11 +1114,7 @@ void Monster::pushItems(Tile* tile) bool Monster::pushCreature(Creature* creature) { - static std::vector dirList { - DIRECTION_NORTH, - DIRECTION_WEST, DIRECTION_EAST, - DIRECTION_SOUTH - }; + static std::vector dirList{DIRECTION_NORTH, DIRECTION_WEST, DIRECTION_EAST, DIRECTION_SOUTH}; std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); for (Direction dir : dirList) { @@ -1092,8 +1131,7 @@ bool Monster::pushCreature(Creature* creature) void Monster::pushCreatures(Tile* tile) { - //We can not use iterators here since we can push a creature to another tile - //which will invalidate the iterator. + // We can not use iterators here since we can push a creature to another tile which will invalidate the iterator. if (CreatureVector* creatures = tile->getCreatures()) { uint32_t removeCount = 0; Monster* lastPushedMonster = nullptr; @@ -1107,7 +1145,6 @@ void Monster::pushCreatures(Tile* tile) } monster->changeHealth(-monster->getHealth()); - monster->setDropLoot(false); removeCount++; } @@ -1122,20 +1159,20 @@ void Monster::pushCreatures(Tile* tile) bool Monster::getNextStep(Direction& direction, uint32_t& flags) { - if (isIdle || getHealth() <= 0) { - //we don't have anyone watching, might as well stop walking + if (!walkingToSpawn && (isIdle || getHealth() <= 0)) { + // we don't have anyone watching, might as well stop walking eventWalk = 0; return false; } bool result = false; - if ((!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { + if (!walkingToSpawn && (!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { if (getTimeSinceLastMove() >= 1000) { randomStepping = true; - //choose a random direction + // choose a random direction result = getRandomStep(getPosition(), direction); } - } else if ((isSummon() && isMasterInRange) || followCreature) { + } else if ((isSummon() && isMasterInRange) || followCreature || walkingToSpawn) { randomStepping = false; result = Creature::getNextStep(direction, flags); if (result) { @@ -1145,7 +1182,7 @@ bool Monster::getNextStep(Direction& direction, uint32_t& flags) ignoreFieldDamage = false; updateMapCache(); } - //target dancing + // target dancing if (attackedCreature && attackedCreature == followCreature) { if (isFleeing()) { result = getDanceStep(getPosition(), direction, false, false); @@ -1175,11 +1212,7 @@ bool Monster::getNextStep(Direction& direction, uint32_t& flags) bool Monster::getRandomStep(const Position& creaturePos, Direction& direction) const { - static std::vector dirList{ - DIRECTION_NORTH, - DIRECTION_WEST, DIRECTION_EAST, - DIRECTION_SOUTH - }; + static std::vector dirList{DIRECTION_NORTH, DIRECTION_WEST, DIRECTION_EAST, DIRECTION_SOUTH}; std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); for (Direction dir : dirList) { @@ -1191,12 +1224,12 @@ bool Monster::getRandomStep(const Position& creaturePos, Direction& direction) c return false; } -bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, - bool keepAttack /*= true*/, bool keepDistance /*= true*/) +bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, bool keepAttack /*= true*/, + bool keepDistance /*= true*/) { bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature); - assert(attackedCreature != nullptr); + assert(attackedCreature); const Position& centerPos = attackedCreature->getPosition(); int_fast32_t offset_x = Position::getOffsetX(creaturePos, centerPos); @@ -1208,6 +1241,7 @@ bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, uint32_t centerToDist = std::max(distance_x, distance_y); std::vector dirList; + dirList.reserve(4); if (!keepDistance || offset_y >= 0) { uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() - 1) - centerPos.getY())); @@ -1215,7 +1249,8 @@ bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, bool result = true; if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); + result = (!canDoAttackNow || + canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); } if (result) { @@ -1230,7 +1265,8 @@ bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, bool result = true; if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); + result = (!canDoAttackNow || + canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); } if (result) { @@ -1245,7 +1281,8 @@ bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, bool result = true; if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); + result = (!canDoAttackNow || + canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); } if (result) { @@ -1260,7 +1297,8 @@ bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, bool result = true; if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); + result = (!canDoAttackNow || + canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); } if (result) { @@ -1289,14 +1327,15 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b if (!flee && (distance > mType->info.targetDistance || !g_game.isSightClear(creaturePos, targetPos, true))) { return false; // let the A* calculate it } else if (!flee && distance == mType->info.targetDistance) { - return true; // we don't really care here, since it's what we wanted to reach (a dance-step will take of dancing in that position) + return true; // we don't really care here, since it's what we wanted to reach (a dance-step will take of dancing + // in that position) } int_fast32_t offsetx = Position::getOffsetX(creaturePos, targetPos); int_fast32_t offsety = Position::getOffsetY(creaturePos, targetPos); if (dx <= 1 && dy <= 1) { - //seems like a target is near, it this case we need to slow down our movements (as a monster) + // seems like a target is near, it this case we need to slow down our movements (as a monster) if (stepDuration < 2) { stepDuration++; } @@ -1305,14 +1344,16 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b } if (offsetx == 0 && offsety == 0) { - return getRandomStep(creaturePos, direction); // player is "on" the monster so let's get some random step and rest will be taken care later. + return getRandomStep( + creaturePos, + direction); // player is "on" the monster so let's get some random step and rest will be taken care later. } if (dx == dy) { - //player is diagonal to the monster + // player is diagonal to the monster if (offsetx >= 1 && offsety >= 1) { // player is NW - //escape to SE, S or E [and some extra] + // escape to SE, S or E [and some extra] bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); bool e = canWalkTo(creaturePos, DIRECTION_EAST); @@ -1357,8 +1398,8 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b return true; } else if (offsetx <= -1 && offsety <= -1) { - //player is SE - //escape to NW , W or N [and some extra] + // player is SE + // escape to NW , W or N [and some extra] bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool n = canWalkTo(creaturePos, DIRECTION_NORTH); @@ -1405,8 +1446,8 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b return true; } else if (offsetx >= 1 && offsety <= -1) { - //player is SW - //escape to NE, N, E [and some extra] + // player is SW + // escape to NE, N, E [and some extra] bool n = canWalkTo(creaturePos, DIRECTION_NORTH); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (n && e) { @@ -1453,7 +1494,7 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b return true; } else if (offsetx <= -1 && offsety >= 1) { // player is NE - //escape to SW, S, W [and some extra] + // escape to SW, S, W [and some extra] bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); if (w && s) { @@ -1499,13 +1540,13 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b } } - //Now let's decide where the player is located to the monster (what direction) so we can decide where to escape. + // Now let's decide where the player is located to the monster (what direction) so we can decide where to escape. if (dy > dx) { Direction playerDir = offsety < 0 ? DIRECTION_SOUTH : DIRECTION_NORTH; switch (playerDir) { case DIRECTION_NORTH: { - // Player is to the NORTH, so obviously we need to check if we can go SOUTH, if not then let's choose WEST or EAST - // and again if we can't we need to decide about some diagonal movements. + // Player is to the NORTH, so obviously we need to check if we can go SOUTH, if not then let's choose + // WEST or EAST and again if we can't we need to decide about some diagonal movements. if (canWalkTo(creaturePos, DIRECTION_SOUTH)) { direction = DIRECTION_SOUTH; return true; @@ -1782,7 +1823,8 @@ bool Monster::canWalkTo(Position pos, Direction direction) const } Tile* tile = g_game.map.getTile(pos); - if (tile && tile->getTopVisibleCreature(this) == nullptr && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR) { + if (tile && !tile->getTopVisibleCreature(this) && + tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR) { return true; } } @@ -1871,14 +1913,14 @@ void Monster::updateLookDirection() int32_t dx = std::abs(offsetx); int32_t dy = std::abs(offsety); if (dx > dy) { - //look EAST/WEST + // look EAST/WEST if (offsetx < 0) { newDir = DIRECTION_WEST; } else { newDir = DIRECTION_EAST; } } else if (dx < dy) { - //look NORTH/SOUTH + // look NORTH/SOUTH if (offsety < 0) { newDir = DIRECTION_NORTH; } else { @@ -1932,10 +1974,7 @@ void Monster::dropLoot(Container* corpse, Creature*) } } -void Monster::setNormalCreatureLight() -{ - internalLight = mType->info.light; -} +void Monster::setNormalCreatureLight() { internalLight = mType->info.light; } void Monster::drainHealth(Creature* attacker, int32_t damage) { @@ -1951,19 +1990,23 @@ void Monster::drainHealth(Creature* attacker, int32_t damage) } } -void Monster::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +void Monster::changeHealth(int32_t healthChange, bool sendHealthChange /* = true*/) { - //In case a player with ignore flag set attacks the monster + // In case a player with ignore flag set attacks the monster setIdle(false); Creature::changeHealth(healthChange, sendHealthChange); } -bool Monster::challengeCreature(Creature* creature) +bool Monster::challengeCreature(Creature* creature, bool force /* = false*/) { if (isSummon()) { return false; } + if (!mType->info.isChallengeable && !force) { + return false; + } + bool result = selectTarget(creature); if (result) { targetChangeCooldown = 8000; @@ -1990,7 +2033,7 @@ void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) fpp.fullPathSearch = !canUseAttack(getPosition(), creature); } } else if (isFleeing()) { - //Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) + // Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) fpp.maxTargetDist = Map::maxViewportX; fpp.clearSight = false; fpp.keepDistance = true; @@ -2001,3 +2044,13 @@ void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) fpp.fullPathSearch = !canUseAttack(getPosition(), creature); } } + +bool Monster::canPushItems() const +{ + Monster* master = this->master ? this->master->getMonster() : nullptr; + if (master) { + return master->mType->info.canPushItems; + } + + return mType->info.canPushItems; +} diff --git a/src/monster.h b/src/monster.h index f1b8b5b437..5370577511 100644 --- a/src/monster.h +++ b/src/monster.h @@ -1,36 +1,22 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0 -#define FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0 - -#include "tile.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_MONSTER_H +#define FS_MONSTER_H + +#include "creature.h" #include "monsters.h" +#include "position.h" -class Creature; -class Game; +class Item; class Spawn; +class Tile; using CreatureHashSet = std::unordered_set; using CreatureList = std::list; -enum TargetSearchType_t { +enum TargetSearchType_t +{ TARGETSEARCH_DEFAULT, TARGETSEARCH_RANDOM, TARGETSEARCH_ATTACKRANGE, @@ -39,241 +25,194 @@ enum TargetSearchType_t { class Monster final : public Creature { - public: - static Monster* createMonster(const std::string& name); - static int32_t despawnRange; - static int32_t despawnRadius; +public: + static Monster* createMonster(const std::string& name); + static int32_t despawnRange; + static int32_t despawnRadius; - explicit Monster(MonsterType* mType); - ~Monster(); + explicit Monster(MonsterType* mType); + ~Monster(); - // non-copyable - Monster(const Monster&) = delete; - Monster& operator=(const Monster&) = delete; + // non-copyable + Monster(const Monster&) = delete; + Monster& operator=(const Monster&) = delete; - Monster* getMonster() override { - return this; - } - const Monster* getMonster() const override { - return this; - } + Monster* getMonster() override { return this; } + const Monster* getMonster() const override { return this; } - void setID() override { - if (id == 0) { - id = monsterAutoID++; - } + void setID() override + { + if (id == 0) { + id = monsterAutoID++; } + } - void removeList() override; - void addList() override; + void addList() override; + void removeList() override; - const std::string& getName() const override { - return mType->name; - } - const std::string& getNameDescription() const override { - return mType->nameDescription; - } - std::string getDescription(int32_t) const override { - return strDescription + '.'; - } + const std::string& getName() const override; + void setName(const std::string& name); - CreatureType_t getType() const override { - return CREATURETYPE_MONSTER; - } + const std::string& getNameDescription() const override; + void setNameDescription(const std::string& nameDescription) { this->nameDescription = nameDescription; }; - const Position& getMasterPos() const { - return masterPos; - } - void setMasterPos(Position pos) { - masterPos = pos; - } + std::string getDescription(int32_t) const override { return nameDescription + '.'; } - RaceType_t getRace() const override { - return mType->info.race; - } - int32_t getArmor() const override { - return mType->info.armor; - } - int32_t getDefense() const override { - return mType->info.defense; - } - bool isPushable() const override { - return mType->info.pushable && baseSpeed != 0; - } - bool isAttackable() const override { - return mType->info.isAttackable; - } + CreatureType_t getType() const override { return CREATURETYPE_MONSTER; } - bool canPushItems() const { - return mType->info.canPushItems; - } - bool canPushCreatures() const { - return mType->info.canPushCreatures; - } - bool isHostile() const { - return mType->info.isHostile; - } - bool canSee(const Position& pos) const override; - bool canSeeInvisibility() const override { - return isImmune(CONDITION_INVISIBLE); - } - uint32_t getManaCost() const { - return mType->info.manaCost; - } - void setSpawn(Spawn* spawn) { - this->spawn = spawn; - } - bool canWalkOnFieldType(CombatType_t combatType) const; + const Position& getMasterPos() const { return masterPos; } + void setMasterPos(Position pos) { masterPos = pos; } - void onAttackedCreatureDisappear(bool isLogout) override; + RaceType_t getRace() const override { return mType->info.race; } + int32_t getArmor() const override { return mType->info.armor; } + int32_t getDefense() const override { return mType->info.defense; } + bool isPushable() const override { return mType->info.pushable && baseSpeed != 0; } + bool isAttackable() const override { return mType->info.isAttackable; } - void onCreatureAppear(Creature* creature, bool isLogin) override; - void onRemoveCreature(Creature* creature, bool isLogout) override; - void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) override; - void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; + bool canPushItems() const; + bool canPushCreatures() const { return mType->info.canPushCreatures; } + bool isHostile() const { return mType->info.isHostile; } + bool canSee(const Position& pos) const override; + bool canSeeInvisibility() const override { return isImmune(CONDITION_INVISIBLE); } + uint32_t getManaCost() const { return mType->info.manaCost; } + void setSpawn(Spawn* spawn) { this->spawn = spawn; } + bool canWalkOnFieldType(CombatType_t combatType) const; - void drainHealth(Creature* attacker, int32_t damage) override; - void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; - void onWalk() override; - bool getNextStep(Direction& direction, uint32_t& flags) override; - void onFollowCreatureComplete(const Creature* creature) override; + void onAttackedCreatureDisappear(bool isLogout) override; - void onThink(uint32_t interval) override; + void onCreatureAppear(Creature* creature, bool isLogin) override; + void onRemoveCreature(Creature* creature, bool isLogout) override; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) override; + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; - bool challengeCreature(Creature* creature) override; + void drainHealth(Creature* attacker, int32_t damage) override; + void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; - void setNormalCreatureLight() override; - bool getCombatValues(int32_t& min, int32_t& max) override; + bool isWalkingToSpawn() const { return walkingToSpawn; } + bool walkToSpawn(); + void onWalk() override; + void onWalkComplete() override; + bool getNextStep(Direction& direction, uint32_t& flags) override; + void onFollowCreatureComplete(const Creature* creature) override; - void doAttacking(uint32_t interval) override; - bool hasExtraSwing() override { - return lastMeleeAttack == 0; - } + void onThink(uint32_t interval) override; - bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); - bool selectTarget(Creature* creature); - - const CreatureList& getTargetList() const { - return targetList; - } - const CreatureHashSet& getFriendList() const { - return friendList; - } + bool challengeCreature(Creature* creature, bool force = false) override; - bool isTarget(const Creature* creature) const; - bool isFleeing() const { - return !isSummon() && getHealth() <= mType->info.runAwayHealth && challengeFocusDuration <= 0; - } + void setNormalCreatureLight() override; + bool getCombatValues(int32_t& min, int32_t& max) override; - bool getDistanceStep(const Position& targetPos, Direction& direction, bool flee = false); - bool isTargetNearby() const { - return stepDuration >= 1; - } - bool isIgnoringFieldDamage() const { - return ignoreFieldDamage; - } + void doAttacking(uint32_t interval) override; + bool hasExtraSwing() override { return lastMeleeAttack == 0; } - BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false, bool ignoreResistances = false) override; + bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); + bool selectTarget(Creature* creature); - static uint32_t monsterAutoID; + const CreatureList& getTargetList() const { return targetList; } + const CreatureHashSet& getFriendList() const { return friendList; } - private: - CreatureHashSet friendList; - CreatureList targetList; + bool isTarget(const Creature* creature) const; + bool isFleeing() const + { + return !isSummon() && getHealth() <= mType->info.runAwayHealth && challengeFocusDuration <= 0; + } - std::string strDescription; + bool getDistanceStep(const Position& targetPos, Direction& direction, bool flee = false); + bool isTargetNearby() const { return stepDuration >= 1; } + bool isIgnoringFieldDamage() const { return ignoreFieldDamage; } - MonsterType* mType; - Spawn* spawn = nullptr; + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense = false, + bool checkArmor = false, bool field = false, bool ignoreResistances = false) override; - int64_t lastMeleeAttack = 0; + static uint32_t monsterAutoID; - uint32_t attackTicks = 0; - uint32_t targetTicks = 0; - uint32_t targetChangeTicks = 0; - uint32_t defenseTicks = 0; - uint32_t yellTicks = 0; - int32_t minCombatValue = 0; - int32_t maxCombatValue = 0; - int32_t targetChangeCooldown = 0; - int32_t challengeFocusDuration = 0; - int32_t stepDuration = 0; +private: + CreatureHashSet friendList; + CreatureList targetList; - Position masterPos; + std::string name; + std::string nameDescription; - bool isIdle = true; - bool isMasterInRange = false; - bool randomStepping = false; - bool ignoreFieldDamage = false; + MonsterType* mType; + Spawn* spawn = nullptr; - void onCreatureEnter(Creature* creature); - void onCreatureLeave(Creature* creature); - void onCreatureFound(Creature* creature, bool pushFront = false); + int64_t lastMeleeAttack = 0; - void updateLookDirection(); + uint32_t attackTicks = 0; + uint32_t targetTicks = 0; + uint32_t targetChangeTicks = 0; + uint32_t defenseTicks = 0; + uint32_t yellTicks = 0; + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t targetChangeCooldown = 0; + int32_t challengeFocusDuration = 0; + int32_t stepDuration = 0; - void addFriend(Creature* creature); - void removeFriend(Creature* creature); - void addTarget(Creature* creature, bool pushFront = false); - void removeTarget(Creature* creature); + Position masterPos; - void updateTargetList(); - void clearTargetList(); - void clearFriendList(); + bool ignoreFieldDamage = false; + bool isIdle = true; + bool isMasterInRange = false; + bool randomStepping = false; + bool walkingToSpawn = false; + + void onCreatureEnter(Creature* creature); + void onCreatureLeave(Creature* creature); + void onCreatureFound(Creature* creature, bool pushFront = false); + + void updateLookDirection(); + + void addFriend(Creature* creature); + void removeFriend(Creature* creature); + void addTarget(Creature* creature, bool pushFront = false); + void removeTarget(Creature* creature); + + void updateTargetList(); + void clearTargetList(); + void clearFriendList(); + + void death(Creature* lastHitCreature) override; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; - void death(Creature* lastHitCreature) override; - Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; + void setIdle(bool idle); + void updateIdleStatus(); + bool getIdleStatus() const { return isIdle; } - void setIdle(bool idle); - void updateIdleStatus(); - bool getIdleStatus() const { - return isIdle; - } + void onAddCondition(ConditionType_t type) override; + void onEndCondition(ConditionType_t type) override; - void onAddCondition(ConditionType_t type) override; - void onEndCondition(ConditionType_t type) override; + bool canUseAttack(const Position& pos, const Creature* target) const; + bool canUseSpell(const Position& pos, const Position& targetPos, const spellBlock_t& sb, uint32_t interval, + bool& inRange, bool& resetTicks); + bool getRandomStep(const Position& creaturePos, Direction& direction) const; + bool getDanceStep(const Position& creaturePos, Direction& direction, bool keepAttack = true, + bool keepDistance = true); + bool isInSpawnRange(const Position& pos) const; + bool canWalkTo(Position pos, Direction direction) const; - bool canUseAttack(const Position& pos, const Creature* target) const; - bool canUseSpell(const Position& pos, const Position& targetPos, - const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks); - bool getRandomStep(const Position& creaturePos, Direction& direction) const; - bool getDanceStep(const Position& creaturePos, Direction& direction, - bool keepAttack = true, bool keepDistance = true); - bool isInSpawnRange(const Position& pos) const; - bool canWalkTo(Position pos, Direction direction) const; - - static bool pushItem(Item* item); - static void pushItems(Tile* tile); - static bool pushCreature(Creature* creature); - static void pushCreatures(Tile* tile); - - void onThinkTarget(uint32_t interval); - void onThinkYell(uint32_t interval); - void onThinkDefense(uint32_t interval); - - bool isFriend(const Creature* creature) const; - bool isOpponent(const Creature* creature) const; - - uint64_t getLostExperience() const override { - return skillLoss ? mType->info.experience : 0; - } - uint16_t getLookCorpse() const override { - return mType->info.lookcorpse; - } - void dropLoot(Container* corpse, Creature* lastHitCreature) override; - uint32_t getDamageImmunities() const override { - return mType->info.damageImmunities; - } - uint32_t getConditionImmunities() const override { - return mType->info.conditionImmunities; - } - void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; - bool useCacheMap() const override { - return !randomStepping; - } + static bool pushItem(Item* item); + static void pushItems(Tile* tile); + static bool pushCreature(Creature* creature); + static void pushCreatures(Tile* tile); - friend class LuaScriptInterface; + void onThinkTarget(uint32_t interval); + void onThinkYell(uint32_t interval); + void onThinkDefense(uint32_t interval); + + bool isFriend(const Creature* creature) const; + bool isOpponent(const Creature* creature) const; + + uint64_t getLostExperience() const override { return skillLoss ? mType->info.experience : 0; } + uint16_t getLookCorpse() const override { return mType->info.lookcorpse; } + void dropLoot(Container* corpse, Creature* lastHitCreature) override; + uint32_t getDamageImmunities() const override { return mType->info.damageImmunities; } + uint32_t getConditionImmunities() const override { return mType->info.conditionImmunities; } + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; + bool useCacheMap() const override { return !randomStepping; } + + friend class LuaScriptInterface; }; -#endif +#endif // FS_MONSTER_H diff --git a/src/monsters.cpp b/src/monsters.cpp index c44d18c5fd..83ee24239e 100644 --- a/src/monsters.cpp +++ b/src/monsters.cpp @@ -1,33 +1,16 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "monsters.h" -#include "monster.h" -#include "spells.h" + #include "combat.h" -#include "weapons.h" #include "configmanager.h" #include "game.h" - #include "pugicast.h" +#include "spells.h" +#include "weapons.h" extern Game g_game; extern Spells* g_spells; @@ -69,20 +52,19 @@ bool Monsters::loadFromXml(bool reloading /*= false*/) loaded = true; for (auto monsterNode : doc.child("monsters").children()) { - std::string name = asLowerCaseString(monsterNode.attribute("name").as_string()); + std::string name = boost::algorithm::to_lower_copy(monsterNode.attribute("name").as_string()); std::string file = "data/monster/" + std::string(monsterNode.attribute("file").as_string()); - auto forceLoad = g_config.getBoolean(ConfigManager::FORCE_MONSTERTYPE_LOAD); - if (forceLoad) { - loadMonster(file, name, true); - continue; - } + unloadedMonsters.emplace(name, file); + } - if (reloading && monsters.find(name) != monsters.end()) { - loadMonster(file, name, true); - } else { - unloadedMonsters.emplace(name, file); + bool forceLoad = g_config.getBoolean(ConfigManager::FORCE_MONSTERTYPE_LOAD); + + for (auto it : unloadedMonsters) { + if ((forceLoad || reloading) && monsters.find(it.first) != monsters.end()) { + loadMonster(it.second, it.first, reloading); } } + return true; } @@ -95,10 +77,11 @@ bool Monsters::reload() return loadFromXml(true); } -ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, - int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval) +ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t maxDamage, int32_t minDamage, + int32_t startDamage, uint32_t tickInterval) { - ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); + ConditionDamage* condition = + static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); condition->setParam(CONDITION_PARAM_TICKINTERVAL, tickInterval); condition->setParam(CONDITION_PARAM_MINVALUE, minDamage); condition->setParam(CONDITION_PARAM_MAXVALUE, maxDamage); @@ -132,11 +115,13 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co uint32_t chance = pugi::cast(attr.value()); if (chance > 100) { chance = 100; - std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Chance value out of bounds for spell: " << name << std::endl; + std::cout << "[Warning - Monsters::deserializeSpell] " << description + << " - Chance value out of bounds for spell: " << name << std::endl; } sb.chance = chance; - } else if (asLowerCaseString(name) != "melee") { - std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Missing chance value on non-melee spell: " << name << std::endl; + } else if (boost::algorithm::to_lower_copy(name) != "melee") { + std::cout << "[Warning - Monsters::deserializeSpell] " << description + << " - Missing chance value on non-melee spell: " << name << std::endl; } if ((attr = node.attribute("range"))) { @@ -154,7 +139,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co if ((attr = node.attribute("max"))) { sb.maxCombatValue = pugi::cast(attr.value()); - //normalize values + // normalize values if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { int32_t value = sb.maxCombatValue; sb.maxCombatValue = sb.minCombatValue; @@ -190,15 +175,16 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } combatSpell = combatSpellPtr.release(); - combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, + 0); } else { - Combat* combat = new Combat; + Combat_ptr combat = std::make_shared(); if ((attr = node.attribute("length"))) { int32_t length = pugi::cast(attr.value()); if (length > 0) { int32_t spread = 3; - //need direction spell + // need direction spell if ((attr = node.attribute("spread"))) { spread = std::max(0, pugi::cast(attr.value())); } @@ -214,7 +200,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co if ((attr = node.attribute("radius"))) { int32_t radius = pugi::cast(attr.value()); - //target spell + // target spell if ((attr = node.attribute("target"))) { needTarget = attr.as_bool(); } @@ -224,7 +210,20 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->setArea(area); } - std::string tmpName = asLowerCaseString(name); + if ((attr = node.attribute("ring"))) { + int32_t ring = pugi::cast(attr.value()); + + // target spell + if ((attr = node.attribute("target"))) { + needTarget = attr.as_bool(); + } + + AreaCombat* area = new AreaCombat(); + area->setupAreaRing(ring); + combat->setArea(area); + } + + std::string tmpName = boost::algorithm::to_lower_copy(name); if (tmpName == "melee") { sb.isMelee = true; @@ -232,7 +231,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co pugi::xml_attribute attackAttribute, skillAttribute; if ((attackAttribute = node.attribute("attack")) && (skillAttribute = node.attribute("skill"))) { sb.minCombatValue = 0; - sb.maxCombatValue = -Weapons::getMaxMeleeDamage(pugi::cast(skillAttribute.value()), pugi::cast(attackAttribute.value())); + sb.maxCombatValue = -Weapons::getMaxMeleeDamage(pugi::cast(skillAttribute.value()), + pugi::cast(attackAttribute.value())); } ConditionType_t conditionType = CONDITION_NONE; @@ -350,21 +350,22 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } if (minSpeedChange == 0) { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing speedchange/minspeedchange value" << std::endl; - delete combat; + std::cout << "[Error - Monsters::deserializeSpell] - " << description + << " - missing speedchange/minspeedchange value" << std::endl; return false; } - if (minSpeedChange < -1000) { - std::cout << "[Warning - Monsters::deserializeSpell] - " << description << " - you cannot reduce a creatures speed below -1000 (100%)" << std::endl; - minSpeedChange = -1000; - } - if (maxSpeedChange == 0) { maxSpeedChange = minSpeedChange; // static speedchange value } } + if (minSpeedChange < -1000) { + std::cout << "[Warning - Monsters::deserializeSpell] - " << description + << " - you cannot reduce a creatures speed below -1000 (100%)" << std::endl; + minSpeedChange = -1000; + } + ConditionType_t conditionType; if (minSpeedChange >= 0) { conditionType = CONDITION_HASTE; @@ -373,7 +374,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co conditionType = CONDITION_PARALYZE; } - ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); + ConditionSpeed* condition = static_cast( + Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); condition->setFormulaVars(minSpeedChange / 1000.0, 0, maxSpeedChange / 1000.0, 0); combat->addCondition(condition); } else if (tmpName == "outfit") { @@ -386,7 +388,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co if ((attr = node.attribute("monster"))) { MonsterType* mType = g_monsters.getMonsterType(attr.as_string()); if (mType) { - ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + ConditionOutfit* condition = static_cast( + Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(mType->info.outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); combat->addCondition(condition); @@ -395,7 +398,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co Outfit_t outfit; outfit.lookTypeEx = pugi::cast(attr.value()); - ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + ConditionOutfit* condition = static_cast( + Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); combat->addCondition(condition); @@ -412,12 +416,18 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->addCondition(condition); } else if (tmpName == "drunk") { int32_t duration = 10000; + uint8_t drunkenness = 25; if ((attr = node.attribute("duration"))) { duration = pugi::cast(attr.value()); } - Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); + if ((attr = node.attribute("drunkenness"))) { + drunkenness = pugi::cast(attr.value()); + } + + Condition* condition = + Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, drunkenness); combat->addCondition(condition); } else if (tmpName == "firefield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); @@ -425,13 +435,11 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP); } else if (tmpName == "energyfield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); - } else if (tmpName == "firecondition" || tmpName == "energycondition" || - tmpName == "earthcondition" || tmpName == "poisoncondition" || - tmpName == "icecondition" || tmpName == "freezecondition" || - tmpName == "deathcondition" || tmpName == "cursecondition" || - tmpName == "holycondition" || tmpName == "dazzlecondition" || - tmpName == "drowncondition" || tmpName == "bleedcondition" || - tmpName == "physicalcondition") { + } else if (tmpName == "firecondition" || tmpName == "energycondition" || tmpName == "earthcondition" || + tmpName == "poisoncondition" || tmpName == "icecondition" || tmpName == "freezecondition" || + tmpName == "deathcondition" || tmpName == "cursecondition" || tmpName == "holycondition" || + tmpName == "dazzlecondition" || tmpName == "drowncondition" || tmpName == "bleedcondition" || + tmpName == "physicalcondition") { ConditionType_t conditionType = CONDITION_NONE; uint32_t tickInterval = 2000; @@ -486,8 +494,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } else if (tmpName == "effect") { // } else { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name << std::endl; - delete combat; + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name + << std::endl; return false; } @@ -497,26 +505,31 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co for (auto attributeNode : node.children()) { if ((attr = attributeNode.attribute("key"))) { const char* value = attr.value(); - if (strcasecmp(value, "shooteffect") == 0) { + if (caseInsensitiveEqual(value, "shooteffect")) { if ((attr = attributeNode.attribute("value"))) { - ShootType_t shoot = getShootType(asLowerCaseString(attr.as_string())); + ShootType_t shoot = + getShootType(boost::algorithm::to_lower_copy(attr.as_string())); if (shoot != CONST_ANI_NONE) { combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot); } else { - std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown shootEffect: " << attr.as_string() << std::endl; + std::cout << "[Warning - Monsters::deserializeSpell] " << description + << " - Unknown shootEffect: " << attr.as_string() << std::endl; } } - } else if (strcasecmp(value, "areaeffect") == 0) { + } else if (caseInsensitiveEqual(value, "areaeffect")) { if ((attr = attributeNode.attribute("value"))) { - MagicEffectClasses effect = getMagicEffect(asLowerCaseString(attr.as_string())); + MagicEffectClasses effect = + getMagicEffect(boost::algorithm::to_lower_copy(attr.as_string())); if (effect != CONST_ME_NONE) { combat->setParam(COMBAT_PARAM_EFFECT, effect); } else { - std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown areaEffect: " << attr.as_string() << std::endl; + std::cout << "[Warning - Monsters::deserializeSpell] " << description + << " - Unknown areaEffect: " << attr.as_string() << std::endl; } } } else { - std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << attr.as_string() << "\" does not exist." << std::endl; + std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << attr.as_string() + << "\" does not exist." << std::endl; } } } @@ -579,9 +592,10 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std } combatSpell = combatSpellPtr.release(); - combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, + 0); } else { - std::unique_ptr combat{ new Combat }; + Combat_ptr combat = std::make_shared(); sb.combatSpell = true; if (spell->length > 0) { @@ -600,29 +614,37 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std combat->setArea(area); } - std::string tmpName = asLowerCaseString(spell->name); + if (spell->ring > 0) { + AreaCombat* area = new AreaCombat(); + area->setupAreaRing(spell->ring); + combat->setArea(area); + } - if (tmpName == "melee") { - sb.isMelee = true; + if (spell->conditionType != CONDITION_NONE) { + ConditionType_t conditionType = spell->conditionType; - if (spell->attack > 0 && spell->skill > 0) { - sb.minCombatValue = 0; - sb.maxCombatValue = -Weapons::getMaxMeleeDamage(spell->skill, spell->attack); + uint32_t tickInterval = 2000; + if (spell->tickInterval != 0) { + tickInterval = spell->tickInterval; } - if (spell->conditionType != CONDITION_NONE) { - ConditionType_t conditionType = spell->conditionType; + int32_t conMinDamage = std::abs(spell->conditionMinDamage); + int32_t conMaxDamage = std::abs(spell->conditionMaxDamage); + int32_t startDamage = std::abs(spell->conditionStartDamage); - int32_t minDamage = spell->conditionMinDamage; - int32_t maxDamage = minDamage; + Condition* condition = + getDamageCondition(conditionType, conMaxDamage, conMinDamage, startDamage, tickInterval); + combat->addCondition(condition); + } - uint32_t tickInterval = 2000; - if (spell->tickInterval != 0) { - tickInterval = spell->tickInterval; - } + std::string tmpName = boost::algorithm::to_lower_copy(spell->name); - Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, spell->conditionStartDamage, tickInterval); - combat->addCondition(condition); + if (tmpName == "melee") { + sb.isMelee = true; + + if (spell->attack > 0 && spell->skill > 0) { + sb.minCombatValue = 0; + sb.maxCombatValue = -Weapons::getMaxMeleeDamage(spell->skill, spell->attack); } sb.range = 1; @@ -632,7 +654,8 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std combat->setOrigin(ORIGIN_MELEE); } else if (tmpName == "combat") { if (spell->combatType == COMBAT_UNDEFINEDDAMAGE) { - std::cout << "[Warning - Monsters::deserializeSpell] - " << description << " - spell has undefined damage" << std::endl; + std::cout << "[Warning - Monsters::deserializeSpell] - " << description + << " - spell has undefined damage" << std::endl; combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); } @@ -655,13 +678,15 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std if (spell->minSpeedChange != 0) { minSpeedChange = spell->minSpeedChange; } else { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing speedchange/minspeedchange value" << std::endl; + std::cout << "[Error - Monsters::deserializeSpell] - " << description + << " - missing speedchange/minspeedchange value" << std::endl; delete spell; return false; } if (minSpeedChange < -1000) { - std::cout << "[Warning - Monsters::deserializeSpell] - " << description << " - you cannot reduce a creatures speed below -1000 (100%)" << std::endl; + std::cout << "[Warning - Monsters::deserializeSpell] - " << description + << " - you cannot reduce a creatures speed below -1000 (100%)" << std::endl; minSpeedChange = -1000; } @@ -679,7 +704,8 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std conditionType = CONDITION_PARALYZE; } - ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); + ConditionSpeed* condition = static_cast( + Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); condition->setFormulaVars(minSpeedChange / 1000.0, 0, maxSpeedChange / 1000.0, 0); combat->addCondition(condition); } else if (tmpName == "outfit") { @@ -689,7 +715,8 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std duration = spell->duration; } - ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + ConditionOutfit* condition = static_cast( + Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(spell->outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); combat->addCondition(condition); @@ -705,12 +732,18 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std combat->addCondition(condition); } else if (tmpName == "drunk") { int32_t duration = 10000; + uint8_t drunkenness = 25; if (spell->duration != 0) { duration = spell->duration; } - Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); + if (spell->drunkenness != 0) { + drunkenness = spell->drunkenness; + } + + Condition* condition = + Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, drunkenness); combat->addCondition(condition); } else if (tmpName == "firefield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); @@ -719,38 +752,17 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std } else if (tmpName == "energyfield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); } else if (tmpName == "condition") { - uint32_t tickInterval = 2000; - if (spell->conditionType == CONDITION_NONE) { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Condition is not set for: " << spell->name << std::endl; + std::cout << "[Error - Monsters::deserializeSpell] - " << description + << " - Condition is not set for: " << spell->name << std::endl; } - - if (spell->tickInterval != 0) { - int32_t value = spell->tickInterval; - if (value > 0) { - tickInterval = value; - } - } - - int32_t minDamage = std::abs(spell->conditionMinDamage); - int32_t maxDamage = std::abs(spell->conditionMaxDamage); - int32_t startDamage = 0; - - if (spell->conditionStartDamage != 0) { - int32_t value = std::abs(spell->conditionStartDamage); - if (value <= minDamage) { - startDamage = value; - } - } - - Condition* condition = getDamageCondition(spell->conditionType, maxDamage, minDamage, startDamage, tickInterval); - combat->addCondition(condition); } else if (tmpName == "strength") { // } else if (tmpName == "effect") { // } else { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << spell->name << std::endl; + std::cout << "[Error - Monsters::deserializeSpell] - " << description + << " - Unknown spell name: " << spell->name << std::endl; } if (spell->needTarget) { @@ -764,7 +776,7 @@ bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std } combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); - combatSpell = new CombatSpell(combat.release(), spell->needTarget, spell->needDirection); + combatSpell = new CombatSpell(combat, spell->needTarget, spell->needDirection); } sb.spell = combatSpell; @@ -798,7 +810,7 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } if (reloading) { - auto it = monsters.find(asLowerCaseString(monsterName)); + auto it = monsters.find(boost::algorithm::to_lower_copy(monsterName)); if (it != monsters.end()) { mType = &it->second; mType->info = {}; @@ -806,7 +818,7 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } if (!mType) { - mType = &monsters[asLowerCaseString(monsterName)]; + mType = &monsters[boost::algorithm::to_lower_copy(monsterName)]; } mType->name = attr.as_string(); @@ -814,11 +826,11 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if ((attr = monsterNode.attribute("nameDescription"))) { mType->nameDescription = attr.as_string(); } else { - mType->nameDescription = "a " + asLowerCaseString(mType->name); + mType->nameDescription = "a " + boost::algorithm::to_lower_copy(mType->name); } if ((attr = monsterNode.attribute("race"))) { - std::string tmpStrValue = asLowerCaseString(attr.as_string()); + std::string tmpStrValue = boost::algorithm::to_lower_copy(attr.as_string()); uint16_t tmpInt = pugi::cast(attr.value()); if (tmpStrValue == "venom" || tmpInt == 1) { mType->info.race = RACE_VENOM; @@ -830,8 +842,11 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.race = RACE_FIRE; } else if (tmpStrValue == "energy" || tmpInt == 5) { mType->info.race = RACE_ENERGY; + } else if (tmpStrValue == "ink" || tmpInt == 6) { + mType->info.race = RACE_INK; } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file + << std::endl; } } @@ -848,7 +863,7 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } if ((attr = monsterNode.attribute("skull"))) { - mType->info.skull = getSkullType(asLowerCaseString(attr.as_string())); + mType->info.skull = getSkullType(boost::algorithm::to_lower_copy(attr.as_string())); } if ((attr = monsterNode.attribute("script"))) { @@ -887,7 +902,8 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if (mType->info.health > mType->info.healthMax) { mType->info.health = mType->info.healthMax; - std::cout << "[Warning - Monsters::loadMonster] Health now is greater than health max." << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Health now is greater than health max." << file + << std::endl; } } @@ -895,66 +911,73 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m for (auto flagNode : node.children()) { attr = flagNode.first_attribute(); const char* attrName = attr.name(); - if (strcasecmp(attrName, "summonable") == 0) { + if (caseInsensitiveEqual(attrName, "summonable")) { mType->info.isSummonable = attr.as_bool(); - } else if (strcasecmp(attrName, "attackable") == 0) { + } else if (caseInsensitiveEqual(attrName, "attackable")) { mType->info.isAttackable = attr.as_bool(); - } else if (strcasecmp(attrName, "hostile") == 0) { + } else if (caseInsensitiveEqual(attrName, "hostile")) { mType->info.isHostile = attr.as_bool(); - } else if (strcasecmp(attrName, "illusionable") == 0) { + } else if (caseInsensitiveEqual(attrName, "ignorespawnblock")) { + mType->info.isIgnoringSpawnBlock = attr.as_bool(); + } else if (caseInsensitiveEqual(attrName, "illusionable")) { mType->info.isIllusionable = attr.as_bool(); - } else if (strcasecmp(attrName, "convinceable") == 0) { + } else if (caseInsensitiveEqual(attrName, "challengeable")) { + mType->info.isChallengeable = attr.as_bool(); + } else if (caseInsensitiveEqual(attrName, "convinceable")) { mType->info.isConvinceable = attr.as_bool(); - } else if (strcasecmp(attrName, "pushable") == 0) { + } else if (caseInsensitiveEqual(attrName, "pushable")) { mType->info.pushable = attr.as_bool(); - } else if (strcasecmp(attrName, "isboss") == 0) { + } else if (caseInsensitiveEqual(attrName, "isboss")) { mType->info.isBoss = attr.as_bool(); - } else if (strcasecmp(attrName, "canpushitems") == 0) { + } else if (caseInsensitiveEqual(attrName, "canpushitems")) { mType->info.canPushItems = attr.as_bool(); - } else if (strcasecmp(attrName, "canpushcreatures") == 0) { + } else if (caseInsensitiveEqual(attrName, "canpushcreatures")) { mType->info.canPushCreatures = attr.as_bool(); - } else if (strcasecmp(attrName, "staticattack") == 0) { + } else if (caseInsensitiveEqual(attrName, "staticattack")) { uint32_t staticAttack = pugi::cast(attr.value()); if (staticAttack > 100) { - std::cout << "[Warning - Monsters::loadMonster] staticattack greater than 100. " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] staticattack greater than 100. " << file + << std::endl; staticAttack = 100; } mType->info.staticAttackChance = staticAttack; - } else if (strcasecmp(attrName, "lightlevel") == 0) { + } else if (caseInsensitiveEqual(attrName, "lightlevel")) { mType->info.light.level = pugi::cast(attr.value()); - } else if (strcasecmp(attrName, "lightcolor") == 0) { + } else if (caseInsensitiveEqual(attrName, "lightcolor")) { mType->info.light.color = pugi::cast(attr.value()); - } else if (strcasecmp(attrName, "targetdistance") == 0) { + } else if (caseInsensitiveEqual(attrName, "targetdistance")) { int32_t targetDistance = pugi::cast(attr.value()); if (targetDistance < 1) { targetDistance = 1; std::cout << "[Warning - Monsters::loadMonster] targetdistance less than 1. " << file << std::endl; } mType->info.targetDistance = targetDistance; - } else if (strcasecmp(attrName, "runonhealth") == 0) { + } else if (caseInsensitiveEqual(attrName, "runonhealth")) { mType->info.runAwayHealth = pugi::cast(attr.value()); - } else if (strcasecmp(attrName, "hidehealth") == 0) { + } else if (caseInsensitiveEqual(attrName, "hidehealth")) { mType->info.hiddenHealth = attr.as_bool(); - } else if (strcasecmp(attrName, "canwalkonenergy") == 0) { + } else if (caseInsensitiveEqual(attrName, "canwalkonenergy")) { mType->info.canWalkOnEnergy = attr.as_bool(); - } else if (strcasecmp(attrName, "canwalkonfire") == 0) { + } else if (caseInsensitiveEqual(attrName, "canwalkonfire")) { mType->info.canWalkOnFire = attr.as_bool(); - } else if (strcasecmp(attrName, "canwalkonpoison") == 0) { + } else if (caseInsensitiveEqual(attrName, "canwalkonpoison")) { mType->info.canWalkOnPoison = attr.as_bool(); } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file + << std::endl; } } - // if a monster can push creatures, - // it should not be pushable. + // if a monster can push creatures, it should not be pushable. if (mType->info.canPushCreatures) { mType->info.pushable = false; } } if (mType->info.manaCost == 0 && (mType->info.isSummonable || mType->info.isConvinceable)) { - std::cout << "[Warning - Monsters::loadMonster] manaCost missing or zero on monster with summonable and/or convinceable flags: " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] manaCost missing or zero on monster with summonable and/or convinceable flags: " + << file << std::endl; } if ((node = monsterNode.child("targetchange"))) { @@ -968,7 +991,8 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m int32_t chance = pugi::cast(attr.value()); if (chance > 100) { chance = 100; - std::cout << "[Warning - Monsters::loadMonster] targetchange chance value out of bounds. " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] targetchange chance value out of bounds. " << file + << std::endl; } mType->info.changeTargetChance = chance; } else { @@ -1047,7 +1071,7 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if ((node = monsterNode.child("immunities"))) { for (auto immunityNode : node.children()) { if ((attr = immunityNode.attribute("name"))) { - std::string tmpStrValue = asLowerCaseString(attr.as_string()); + std::string tmpStrValue = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStrValue == "physical") { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; mType->info.conditionImmunities |= CONDITION_BLEEDING; @@ -1057,8 +1081,7 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } else if (tmpStrValue == "fire") { mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; - } else if (tmpStrValue == "poison" || - tmpStrValue == "earth") { + } else if (tmpStrValue == "poison" || tmpStrValue == "earth") { mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; mType->info.conditionImmunities |= CONDITION_POISON; } else if (tmpStrValue == "drown") { @@ -1088,7 +1111,8 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } else if (tmpStrValue == "bleed") { mType->info.conditionImmunities |= CONDITION_BLEEDING; } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " + << file << std::endl; } } else if ((attr = immunityNode.attribute("physical"))) { if (attr.as_bool()) { @@ -1154,7 +1178,8 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_DRUNK; } - } else if ((attr = immunityNode.attribute("invisible")) || (attr = immunityNode.attribute("invisibility"))) { + } else if ((attr = immunityNode.attribute("invisible")) || + (attr = immunityNode.attribute("invisibility"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_INVISIBLE; } @@ -1215,52 +1240,72 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if ((attr = elementNode.attribute("physicalPercent"))) { mType->info.elementMap[COMBAT_PHYSICALDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_PHYSICALDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"physical\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"physical\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("icePercent"))) { mType->info.elementMap[COMBAT_ICEDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_ICEDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"ice\" on immunity and element tags. " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Same element \"ice\" on immunity and element tags. " + << file << std::endl; } - } else if ((attr = elementNode.attribute("poisonPercent")) || (attr = elementNode.attribute("earthPercent"))) { + } else if ((attr = elementNode.attribute("poisonPercent")) || + (attr = elementNode.attribute("earthPercent"))) { mType->info.elementMap[COMBAT_EARTHDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_EARTHDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"earth\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"earth\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("firePercent"))) { mType->info.elementMap[COMBAT_FIREDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_FIREDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"fire\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"fire\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("energyPercent"))) { mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_ENERGYDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"energy\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"energy\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("holyPercent"))) { mType->info.elementMap[COMBAT_HOLYDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_HOLYDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"holy\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"holy\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("deathPercent"))) { mType->info.elementMap[COMBAT_DEATHDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_DEATHDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"death\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"death\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("drownPercent"))) { mType->info.elementMap[COMBAT_DROWNDAMAGE] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_DROWNDAMAGE) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"drown\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"drown\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("lifedrainPercent"))) { mType->info.elementMap[COMBAT_LIFEDRAIN] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_LIFEDRAIN) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"lifedrain\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"lifedrain\" on immunity and element tags. " + << file << std::endl; } } else if ((attr = elementNode.attribute("manadrainPercent"))) { mType->info.elementMap[COMBAT_MANADRAIN] = pugi::cast(attr.value()); if (mType->info.damageImmunities & COMBAT_MANADRAIN) { - std::cout << "[Warning - Monsters::loadMonster] Same element \"manadrain\" on immunity and element tags. " << file << std::endl; + std::cout + << "[Warning - Monsters::loadMonster] Same element \"manadrain\" on immunity and element tags. " + << file << std::endl; } } else { std::cout << "[Warning - Monsters::loadMonster] Unknown element percent. " << file << std::endl; @@ -1289,7 +1334,8 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m chance = pugi::cast(attr.value()); if (chance > 100) { chance = 100; - std::cout << "[Warning - Monsters::loadMonster] Summon chance value out of bounds. " << file << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Summon chance value out of bounds. " << file + << std::endl; } } @@ -1373,7 +1419,7 @@ bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) } else if ((attr = node.attribute("name"))) { auto name = attr.as_string(); - auto ids = Item::items.nameToItems.equal_range(asLowerCaseString(name)); + auto ids = Item::items.nameToItems.equal_range(boost::algorithm::to_lower_copy(name)); if (ids.first == Item::items.nameToItems.cend()) { std::cout << "[Warning - Monsters::loadMonster] Unknown loot item \"" << name << "\". " << std::endl; @@ -1396,10 +1442,6 @@ bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) if ((attr = node.attribute("countmax"))) { int32_t lootCountMax = pugi::cast(attr.value()); - if (lootCountMax > 100) { - std::cout << "[Warning - Monsters::loadMonster] Invalid \"countmax\" "<< lootCountMax <<" used for loot, the max allowed value is 100. " << std::endl; - return false; - } lootBlock.countmax = std::max(1, lootCountMax); } else { lootBlock.countmax = 1; @@ -1408,7 +1450,8 @@ bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) if ((attr = node.attribute("chance")) || (attr = node.attribute("chance1"))) { int32_t lootChance = pugi::cast(attr.value()); if (lootChance > static_cast(MAX_LOOTCHANCE)) { - std::cout << "[Warning - Monsters::loadMonster] Invalid \"chance\" "<< lootChance <<" used for loot, the max is " << MAX_LOOTCHANCE << ". " << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Invalid \"chance\" " << lootChance + << " used for loot, the max is " << MAX_LOOTCHANCE << ". " << std::endl; } lootBlock.chance = std::min(MAX_LOOTCHANCE, lootChance); } else { @@ -1419,7 +1462,7 @@ bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) loadLootContainer(node, lootBlock); } - //optional + // optional if ((attr = node.attribute("subtype"))) { lootBlock.subType = pugi::cast(attr.value()); } else { @@ -1441,8 +1484,8 @@ bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) { - // NOTE: attribute was left for backwards compatibility with pre 1.x TFS versions. - // Please don't use it, if you don't have to. + // NOTE: attribute was left for backwards compatibility with pre 1.x TFS versions. Please don't use it, if + // you don't have to. for (auto subNode : node.child("inside") ? node.child("inside").children() : node.children()) { LootBlock lootBlock; if (loadLootItem(subNode, lootBlock)) { @@ -1453,16 +1496,16 @@ void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) MonsterType* Monsters::getMonsterType(const std::string& name, bool loadFromFile /*= true */) { - std::string lowerCaseName = asLowerCaseString(name); + std::string lowerCaseName = boost::algorithm::to_lower_copy(name); auto it = monsters.find(lowerCaseName); if (it == monsters.end()) { - auto it2 = unloadedMonsters.find(lowerCaseName); - if (it2 == unloadedMonsters.end()) { + if (!loadFromFile) { return nullptr; } - if (!loadFromFile) { + auto it2 = unloadedMonsters.find(lowerCaseName); + if (it2 == unloadedMonsters.end()) { return nullptr; } diff --git a/src/monsters.h b/src/monsters.h index bc86a3b185..e1e6c3f520 100644 --- a/src/monsters.h +++ b/src/monsters.h @@ -1,41 +1,31 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D -#define FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D - -#include "creature.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_MONSTERS_H +#define FS_MONSTERS_H + +#include "const.h" +#include "enums.h" + +class ConditionDamage; +class LuaScriptInterface; const uint32_t MAX_LOOTCHANCE = 100000; -struct LootBlock { +struct LootBlock +{ uint16_t id; uint32_t countmax; uint32_t chance; - //optional + // optional int32_t subType; int32_t actionId; std::string text; std::vector childLoot; - LootBlock() { + LootBlock() + { id = 0; countmax = 1; chance = 0; @@ -45,18 +35,20 @@ struct LootBlock { } }; -class Loot { - public: - Loot() = default; +class Loot +{ +public: + Loot() = default; - // non-copyable - Loot(const Loot&) = delete; - Loot& operator=(const Loot&) = delete; + // non-copyable + Loot(const Loot&) = delete; + Loot& operator=(const Loot&) = delete; - LootBlock lootBlock; + LootBlock lootBlock; }; -struct summonBlock_t { +struct summonBlock_t +{ std::string name; uint32_t chance; uint32_t speed; @@ -65,20 +57,22 @@ struct summonBlock_t { }; class BaseSpell; -struct spellBlock_t { +struct spellBlock_t +{ constexpr spellBlock_t() = default; ~spellBlock_t(); spellBlock_t(const spellBlock_t& other) = delete; spellBlock_t& operator=(const spellBlock_t& other) = delete; spellBlock_t(spellBlock_t&& other) : - spell(other.spell), - chance(other.chance), - speed(other.speed), - range(other.range), - minCombatValue(other.minCombatValue), - maxCombatValue(other.maxCombatValue), - combatSpell(other.combatSpell), - isMelee(other.isMelee) { + spell(other.spell), + chance(other.chance), + speed(other.speed), + range(other.range), + minCombatValue(other.minCombatValue), + maxCombatValue(other.maxCombatValue), + combatSpell(other.combatSpell), + isMelee(other.isMelee) + { other.spell = nullptr; } @@ -92,14 +86,16 @@ struct spellBlock_t { bool isMelee = false; }; -struct voiceBlock_t { +struct voiceBlock_t +{ std::string text; bool yellText; }; class MonsterType { - struct MonsterInfo { + struct MonsterInfo + { LuaScriptInterface* scriptInterface; std::map elementMap; @@ -147,13 +143,15 @@ class MonsterType bool canPushItems = false; bool canPushCreatures = false; bool pushable = true; - bool isSummonable = false; - bool isIllusionable = false; - bool isConvinceable = false; bool isAttackable = true; + bool isBoss = false; + bool isChallengeable = true; + bool isConvinceable = false; bool isHostile = true; + bool isIgnoringSpawnBlock = false; + bool isIllusionable = false; + bool isSummonable = false; bool hiddenHealth = false; - bool isBoss = false; bool canWalkOnEnergy = true; bool canWalkOnFire = true; bool canWalkOnPoison = true; @@ -161,100 +159,100 @@ class MonsterType MonstersEvent_t eventType = MONSTERS_EVENT_NONE; }; - public: - MonsterType() = default; +public: + MonsterType() = default; - // non-copyable - MonsterType(const MonsterType&) = delete; - MonsterType& operator=(const MonsterType&) = delete; + // non-copyable + MonsterType(const MonsterType&) = delete; + MonsterType& operator=(const MonsterType&) = delete; - bool loadCallback(LuaScriptInterface* scriptInterface); + bool loadCallback(LuaScriptInterface* scriptInterface); - std::string name; - std::string nameDescription; + std::string name; + std::string nameDescription; - MonsterInfo info; + MonsterInfo info; - void loadLoot(MonsterType* monsterType, LootBlock lootBlock); + void loadLoot(MonsterType* monsterType, LootBlock lootBlock); }; class MonsterSpell { - public: - MonsterSpell() = default; - - MonsterSpell(const MonsterSpell&) = delete; - MonsterSpell& operator=(const MonsterSpell&) = delete; - - std::string name = ""; - std::string scriptName = ""; - - uint8_t chance = 100; - uint8_t range = 0; - - uint16_t interval = 2000; - - int32_t minCombatValue = 0; - int32_t maxCombatValue = 0; - int32_t attack = 0; - int32_t skill = 0; - int32_t length = 0; - int32_t spread = 0; - int32_t radius = 0; - int32_t conditionMinDamage = 0; - int32_t conditionMaxDamage = 0; - int32_t conditionStartDamage = 0; - int32_t tickInterval = 0; - int32_t minSpeedChange = 0; - int32_t maxSpeedChange = 0; - int32_t duration = 0; - - bool isScripted = false; - bool needTarget = false; - bool needDirection = false; - bool combatSpell = false; - bool isMelee = false; +public: + MonsterSpell() = default; - Outfit_t outfit = {}; - ShootType_t shoot = CONST_ANI_NONE; - MagicEffectClasses effect = CONST_ME_NONE; - ConditionType_t conditionType = CONDITION_NONE; - CombatType_t combatType = COMBAT_UNDEFINEDDAMAGE; + MonsterSpell(const MonsterSpell&) = delete; + MonsterSpell& operator=(const MonsterSpell&) = delete; + + std::string name = ""; + std::string scriptName = ""; + + uint8_t chance = 100; + uint8_t range = 0; + uint8_t drunkenness = 0; + + uint16_t interval = 2000; + + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t attack = 0; + int32_t skill = 0; + int32_t length = 0; + int32_t spread = 0; + int32_t radius = 0; + int32_t ring = 0; + int32_t conditionMinDamage = 0; + int32_t conditionMaxDamage = 0; + int32_t conditionStartDamage = 0; + int32_t tickInterval = 0; + int32_t minSpeedChange = 0; + int32_t maxSpeedChange = 0; + int32_t duration = 0; + + bool isScripted = false; + bool needTarget = false; + bool needDirection = false; + bool combatSpell = false; + bool isMelee = false; + + Outfit_t outfit = {}; + ShootType_t shoot = CONST_ANI_NONE; + MagicEffectClasses effect = CONST_ME_NONE; + ConditionType_t conditionType = CONDITION_NONE; + CombatType_t combatType = COMBAT_UNDEFINEDDAMAGE; }; class Monsters { - public: - Monsters() = default; - // non-copyable - Monsters(const Monsters&) = delete; - Monsters& operator=(const Monsters&) = delete; +public: + Monsters() = default; + // non-copyable + Monsters(const Monsters&) = delete; + Monsters& operator=(const Monsters&) = delete; - bool loadFromXml(bool reloading = false); - bool isLoaded() const { - return loaded; - } - bool reload(); + bool loadFromXml(bool reloading = false); + bool isLoaded() const { return loaded; } + bool reload(); - MonsterType* getMonsterType(const std::string& name, bool loadFromFile = true); - bool deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std::string& description = ""); + MonsterType* getMonsterType(const std::string& name, bool loadFromFile = true); + bool deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std::string& description = ""); - std::unique_ptr scriptInterface; - std::map monsters; + std::unique_ptr scriptInterface; + std::map monsters; - private: - ConditionDamage* getDamageCondition(ConditionType_t conditionType, - int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval); - bool deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description = ""); +private: + ConditionDamage* getDamageCondition(ConditionType_t conditionType, int32_t maxDamage, int32_t minDamage, + int32_t startDamage, uint32_t tickInterval); + bool deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description = ""); - MonsterType* loadMonster(const std::string& file, const std::string& monsterName, bool reloading = false); + MonsterType* loadMonster(const std::string& file, const std::string& monsterName, bool reloading = false); - void loadLootContainer(const pugi::xml_node& node, LootBlock&); - bool loadLootItem(const pugi::xml_node& node, LootBlock&); + void loadLootContainer(const pugi::xml_node& node, LootBlock&); + bool loadLootItem(const pugi::xml_node& node, LootBlock&); - std::map unloadedMonsters; + std::map unloadedMonsters; - bool loaded = false; + bool loaded = false; }; -#endif +#endif // FS_MONSTERS_H diff --git a/src/mounts.cpp b/src/mounts.cpp index ce10d9710b..0c7c9e884c 100644 --- a/src/mounts.cpp +++ b/src/mounts.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -40,13 +24,22 @@ bool Mounts::loadFromXml() } for (auto mountNode : doc.child("mounts").children()) { - mounts.emplace_back( - static_cast(pugi::cast(mountNode.attribute("id").value())), - pugi::cast(mountNode.attribute("clientid").value()), - mountNode.attribute("name").as_string(), - pugi::cast(mountNode.attribute("speed").value()), - mountNode.attribute("premium").as_bool() - ); + uint16_t nodeId = pugi::cast(mountNode.attribute("id").value()); + if (nodeId == 0 || nodeId > std::numeric_limits::max()) { + std::cout << "[Notice - Mounts::loadFromXml] Mount id \"" << nodeId << "\" is not within 1 and 255 range" + << std::endl; + continue; + } + + if (getMountByID(nodeId)) { + std::cout << "[Notice - Mounts::loadFromXml] Duplicate mount with id: " << nodeId << std::endl; + continue; + } + + mounts.emplace_back(static_cast(nodeId), pugi::cast(mountNode.attribute("clientid").value()), + mountNode.attribute("name").as_string(), + pugi::cast(mountNode.attribute("speed").value()), + mountNode.attribute("premium").as_bool()); } mounts.shrink_to_fit(); return true; @@ -54,17 +47,16 @@ bool Mounts::loadFromXml() Mount* Mounts::getMountByID(uint8_t id) { - auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) { - return mount.id == id; - }); + auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) { return mount.id == id; }); return it != mounts.end() ? &*it : nullptr; } -Mount* Mounts::getMountByName(const std::string& name) { +Mount* Mounts::getMountByName(const std::string& name) +{ auto mountName = name.c_str(); for (auto& it : mounts) { - if (strcasecmp(mountName, it.name.c_str()) == 0) { + if (caseInsensitiveEqual(mountName, it.name)) { return ⁢ } } @@ -74,9 +66,8 @@ Mount* Mounts::getMountByName(const std::string& name) { Mount* Mounts::getMountByClientID(uint16_t clientId) { - auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const Mount& mount) { - return mount.clientId == clientId; - }); + auto it = std::find_if(mounts.begin(), mounts.end(), + [clientId](const Mount& mount) { return mount.clientId == clientId; }); return it != mounts.end() ? &*it : nullptr; } diff --git a/src/mounts.h b/src/mounts.h index c63d42503a..42fe270077 100644 --- a/src/mounts.h +++ b/src/mounts.h @@ -1,29 +1,14 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6 -#define FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6 +#ifndef FS_MOUNTS_H +#define FS_MOUNTS_H struct Mount { Mount(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) : - name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {} + name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) + {} std::string name; int32_t speed; @@ -34,19 +19,17 @@ struct Mount class Mounts { - public: - bool reload(); - bool loadFromXml(); - Mount* getMountByID(uint8_t id); - Mount* getMountByName(const std::string& name); - Mount* getMountByClientID(uint16_t clientId); +public: + bool reload(); + bool loadFromXml(); + Mount* getMountByID(uint8_t id); + Mount* getMountByName(const std::string& name); + Mount* getMountByClientID(uint16_t clientId); - const std::vector& getMounts() const { - return mounts; - } + const std::vector& getMounts() const { return mounts; } - private: - std::vector mounts; +private: + std::vector mounts; }; -#endif +#endif // FS_MOUNTS_H diff --git a/src/movement.cpp b/src/movement.cpp index b25b489cef..4b34fa5f43 100644 --- a/src/movement.cpp +++ b/src/movement.cpp @@ -1,50 +1,27 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "game.h" +#include "movement.h" +#include "combat.h" +#include "game.h" #include "pugicast.h" -#include "movement.h" - extern Game g_game; extern Vocations g_vocations; -MoveEvents::MoveEvents() : - scriptInterface("MoveEvents Interface") -{ - scriptInterface.initState(); -} +MoveEvents::MoveEvents() : scriptInterface("MoveEvents Interface") { scriptInterface.initState(); } -MoveEvents::~MoveEvents() -{ - clear(false); -} +MoveEvents::~MoveEvents() { clear(false); } void MoveEvents::clearMap(MoveListMap& map, bool fromLua) { for (auto it = map.begin(); it != map.end(); ++it) { for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) { auto& moveEvents = it->second.moveEvent[eventType]; - for (auto find = moveEvents.begin(); find != moveEvents.end(); ) { + for (auto find = moveEvents.begin(); find != moveEvents.end();) { if (fromLua == find->fromLua) { find = moveEvents.erase(find); } else { @@ -60,7 +37,7 @@ void MoveEvents::clearPosMap(MovePosListMap& map, bool fromLua) for (auto it = map.begin(); it != map.end(); ++it) { for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) { auto& moveEvents = it->second.moveEvent[eventType]; - for (auto find = moveEvents.begin(); find != moveEvents.end(); ) { + for (auto find = moveEvents.begin(); find != moveEvents.end();) { if (fromLua == find->fromLua) { find = moveEvents.erase(find); } else { @@ -81,19 +58,13 @@ void MoveEvents::clear(bool fromLua) reInitState(fromLua); } -LuaScriptInterface& MoveEvents::getScriptInterface() -{ - return scriptInterface; -} +LuaScriptInterface& MoveEvents::getScriptInterface() { return scriptInterface; } -std::string MoveEvents::getScriptBaseName() const -{ - return "movements"; -} +std::string MoveEvents::getScriptBaseName() const { return "movements"; } Event_ptr MoveEvents::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "movevent") != 0) { + if (!caseInsensitiveEqual(nodeName, "movevent")) { return nullptr; } return Event_ptr(new MoveEvent(&scriptInterface)); @@ -101,7 +72,7 @@ Event_ptr MoveEvents::getEvent(const std::string& nodeName) bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) { - MoveEvent_ptr moveEvent{static_cast(event.release())}; //event is guaranteed to be a MoveEvent + MoveEvent_ptr moveEvent{static_cast(event.release())}; // event is guaranteed to be a MoveEvent const MoveEvent_t eventType = moveEvent->getEventType(); if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { @@ -203,7 +174,24 @@ bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) bool MoveEvents::registerLuaFunction(MoveEvent* event) { - MoveEvent_ptr moveEvent{ event }; + MoveEvent_ptr moveEvent{event}; + + const MoveEvent_t eventType = moveEvent->getEventType(); + if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { + if (moveEvent->getTileItem()) { + switch (eventType) { + case MOVE_EVENT_ADD_ITEM: + moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + break; + case MOVE_EVENT_REMOVE_ITEM: + moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + break; + default: + break; + } + } + } + if (moveEvent->getItemIdRange().size() > 0) { if (moveEvent->getItemIdRange().size() == 1) { uint32_t id = moveEvent->getItemIdRange().at(0); @@ -236,7 +224,24 @@ bool MoveEvents::registerLuaFunction(MoveEvent* event) bool MoveEvents::registerLuaEvent(MoveEvent* event) { - MoveEvent_ptr moveEvent{ event }; + MoveEvent_ptr moveEvent{event}; + + const MoveEvent_t eventType = moveEvent->getEventType(); + if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { + if (moveEvent->getTileItem()) { + switch (eventType) { + case MOVE_EVENT_ADD_ITEM: + moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + break; + case MOVE_EVENT_REMOVE_ITEM: + moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + break; + default: + break; + } + } + } + if (moveEvent->getItemIdRange().size() > 0) { if (moveEvent->getItemIdRange().size() == 1) { uint32_t id = moveEvent->getItemIdRange().at(0); @@ -320,17 +325,39 @@ MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot) { uint32_t slotp; switch (slot) { - case CONST_SLOT_HEAD: slotp = SLOTP_HEAD; break; - case CONST_SLOT_NECKLACE: slotp = SLOTP_NECKLACE; break; - case CONST_SLOT_BACKPACK: slotp = SLOTP_BACKPACK; break; - case CONST_SLOT_ARMOR: slotp = SLOTP_ARMOR; break; - case CONST_SLOT_RIGHT: slotp = SLOTP_RIGHT; break; - case CONST_SLOT_LEFT: slotp = SLOTP_LEFT; break; - case CONST_SLOT_LEGS: slotp = SLOTP_LEGS; break; - case CONST_SLOT_FEET: slotp = SLOTP_FEET; break; - case CONST_SLOT_AMMO: slotp = SLOTP_AMMO; break; - case CONST_SLOT_RING: slotp = SLOTP_RING; break; - default: slotp = 0; break; + case CONST_SLOT_HEAD: + slotp = SLOTP_HEAD; + break; + case CONST_SLOT_NECKLACE: + slotp = SLOTP_NECKLACE; + break; + case CONST_SLOT_BACKPACK: + slotp = SLOTP_BACKPACK; + break; + case CONST_SLOT_ARMOR: + slotp = SLOTP_ARMOR; + break; + case CONST_SLOT_RIGHT: + slotp = SLOTP_RIGHT; + break; + case CONST_SLOT_LEFT: + slotp = SLOTP_LEFT; + break; + case CONST_SLOT_LEGS: + slotp = SLOTP_LEGS; + break; + case CONST_SLOT_FEET: + slotp = SLOTP_FEET; + break; + case CONST_SLOT_AMMO: + slotp = SLOTP_AMMO; + break; + case CONST_SLOT_RING: + slotp = SLOTP_RING; + break; + default: + slotp = 0; + break; } auto it = itemIdMap.find(item->getID()); @@ -502,12 +529,18 @@ MoveEvent::MoveEvent(LuaScriptInterface* interface) : Event(interface) {} std::string MoveEvent::getScriptEventName() const { switch (eventType) { - case MOVE_EVENT_STEP_IN: return "onStepIn"; - case MOVE_EVENT_STEP_OUT: return "onStepOut"; - case MOVE_EVENT_EQUIP: return "onEquip"; - case MOVE_EVENT_DEEQUIP: return "onDeEquip"; - case MOVE_EVENT_ADD_ITEM: return "onAddItem"; - case MOVE_EVENT_REMOVE_ITEM: return "onRemoveItem"; + case MOVE_EVENT_STEP_IN: + return "onStepIn"; + case MOVE_EVENT_STEP_OUT: + return "onStepOut"; + case MOVE_EVENT_EQUIP: + return "onEquip"; + case MOVE_EVENT_DEEQUIP: + return "onDeEquip"; + case MOVE_EVENT_ADD_ITEM: + return "onAddItem"; + case MOVE_EVENT_REMOVE_ITEM: + return "onRemoveItem"; default: std::cout << "[Error - MoveEvent::getScriptEventName] Invalid event type" << std::endl; return std::string(); @@ -522,7 +555,7 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) return false; } - std::string tmpStr = asLowerCaseString(eventAttr.as_string()); + std::string tmpStr = boost::algorithm::to_lower_copy(eventAttr.as_string()); if (tmpStr == "stepin") { eventType = MOVE_EVENT_STEP_IN; } else if (tmpStr == "stepout") { @@ -536,14 +569,15 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) } else if (tmpStr == "removeitem") { eventType = MOVE_EVENT_REMOVE_ITEM; } else { - std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() << std::endl; + std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() + << std::endl; return false; } if (eventType == MOVE_EVENT_EQUIP || eventType == MOVE_EVENT_DEEQUIP) { pugi::xml_attribute slotAttribute = node.attribute("slot"); if (slotAttribute) { - tmpStr = asLowerCaseString(slotAttribute.as_string()); + tmpStr = boost::algorithm::to_lower_copy(slotAttribute.as_string()); if (tmpStr == "head") { slot = SLOTP_HEAD; } else if (tmpStr == "necklace") { @@ -567,7 +601,8 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) } else if (tmpStr == "ammo") { slot = SLOTP_AMMO; } else { - std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << slotAttribute.as_string() << std::endl; + std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " + << slotAttribute.as_string() << std::endl; } } @@ -597,7 +632,7 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) } } - //Gather vocation information + // Gather vocation information std::list vocStringList; for (auto vocationNode : node.children()) { pugi::xml_attribute vocationNameAttribute = vocationNode.attribute("name"); @@ -609,7 +644,8 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) if (vocationId != -1) { vocEquipMap[vocationId] = true; if (vocationNode.attribute("showInDescription").as_bool(true)) { - vocStringList.push_back(asLowerCaseString(vocationNameAttribute.as_string())); + vocStringList.push_back( + boost::algorithm::to_lower_copy(vocationNameAttribute.as_string())); } } } @@ -646,10 +682,7 @@ uint32_t MoveEvent::StepInField(Creature* creature, Item* item, const Position&) return LUA_ERROR_ITEM_NOT_FOUND; } -uint32_t MoveEvent::StepOutField(Creature*, Item*, const Position&) -{ - return 1; -} +uint32_t MoveEvent::StepOutField(Creature*, Item*, const Position&) { return 1; } uint32_t MoveEvent::AddItemField(Item* item, Item*, const Position&) { @@ -665,10 +698,7 @@ uint32_t MoveEvent::AddItemField(Item* item, Item*, const Position&) return LUA_ERROR_ITEM_NOT_FOUND; } -uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&) -{ - return 1; -} +uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&) { return 1; } ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck) { @@ -717,7 +747,8 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite } if (it.abilities->manaShield) { - Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_MANASHIELD, -1, 0); + Condition* condition = + Condition::createCondition(static_cast(slot), CONDITION_MANASHIELD, -1, 0); player->addCondition(condition); } @@ -731,7 +762,8 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite } if (it.abilities->regeneration) { - Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_REGENERATION, -1, 0); + Condition* condition = + Condition::createCondition(static_cast(slot), CONDITION_REGENERATION, -1, 0); if (it.abilities->healthGain != 0) { condition->setParam(CONDITION_PARAM_HEALTHGAIN, it.abilities->healthGain); @@ -752,7 +784,7 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite player->addCondition(condition); } - //skill modifiers + // skill modifiers bool needUpdateSkills = false; for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { @@ -762,6 +794,12 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite } } + for (int32_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->specialMagicLevelSkill[i]) { + player->setSpecialMagicLevelSkill(indexToCombatType(i), it.abilities->specialMagicLevelSkill[i]); + } + } + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { if (it.abilities->specialSkills[i]) { needUpdateSkills = true; @@ -773,7 +811,7 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite player->sendSkills(); } - //stat modifiers + // stat modifiers bool needUpdateStats = false; for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { @@ -784,12 +822,15 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite if (it.abilities->statsPercent[s]) { needUpdateStats = true; - player->setVarStats(static_cast(s), static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + player->setVarStats(static_cast(s), + static_cast(player->getDefaultStats(static_cast(s)) * + ((it.abilities->statsPercent[s] - 100) / 100.f))); } } if (needUpdateStats) { player->sendStats(); + player->sendSkills(); } return RETURNVALUE_NOERROR; @@ -834,7 +875,7 @@ ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots player->removeCondition(CONDITION_REGENERATION, static_cast(slot)); } - //skill modifiers + // skill modifiers bool needUpdateSkills = false; for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { @@ -844,6 +885,12 @@ ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots } } + for (int32_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->specialMagicLevelSkill[i] != 0) { + player->setSpecialMagicLevelSkill(indexToCombatType(i), -it.abilities->specialMagicLevelSkill[i]); + } + } + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { if (it.abilities->specialSkills[i] != 0) { needUpdateSkills = true; @@ -855,7 +902,7 @@ ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots player->sendSkills(); } - //stat modifiers + // stat modifiers bool needUpdateStats = false; for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { @@ -866,12 +913,15 @@ ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots if (it.abilities->statsPercent[s]) { needUpdateStats = true; - player->setVarStats(static_cast(s), -static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + player->setVarStats(static_cast(s), + -static_cast(player->getDefaultStats(static_cast(s)) * + ((it.abilities->statsPercent[s] - 100) / 100.f))); } } if (needUpdateStats) { player->sendStats(); + player->sendSkills(); } return RETURNVALUE_NOERROR; @@ -880,21 +930,22 @@ ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots bool MoveEvent::loadFunction(const pugi::xml_attribute& attr, bool isScripted) { const char* functionName = attr.as_string(); - if (strcasecmp(functionName, "onstepinfield") == 0) { + if (caseInsensitiveEqual(functionName, "onstepinfield")) { stepFunction = StepInField; - } else if (strcasecmp(functionName, "onstepoutfield") == 0) { + } else if (caseInsensitiveEqual(functionName, "onstepoutfield")) { stepFunction = StepOutField; - } else if (strcasecmp(functionName, "onaddfield") == 0) { + } else if (caseInsensitiveEqual(functionName, "onaddfield")) { moveFunction = AddItemField; - } else if (strcasecmp(functionName, "onremovefield") == 0) { + } else if (caseInsensitiveEqual(functionName, "onremovefield")) { moveFunction = RemoveItemField; - } else if (strcasecmp(functionName, "onequipitem") == 0) { + } else if (caseInsensitiveEqual(functionName, "onequipitem")) { equipFunction = EquipItem; - } else if (strcasecmp(functionName, "ondeequipitem") == 0) { + } else if (caseInsensitiveEqual(functionName, "ondeequipitem")) { equipFunction = DeEquipItem; } else { if (!isScripted) { - std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." + << std::endl; return false; } } @@ -905,29 +956,22 @@ bool MoveEvent::loadFunction(const pugi::xml_attribute& attr, bool isScripted) return true; } -MoveEvent_t MoveEvent::getEventType() const -{ - return eventType; -} +MoveEvent_t MoveEvent::getEventType() const { return eventType; } -void MoveEvent::setEventType(MoveEvent_t type) -{ - eventType = type; -} +void MoveEvent::setEventType(MoveEvent_t type) { eventType = type; } uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos) { if (scripted) { return executeStep(creature, item, pos); - } else { - return stepFunction(creature, item, pos); } + return stepFunction(creature, item, pos); } bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos) { - //onStepIn(creature, item, pos, fromPosition) - //onStepOut(creature, item, pos, fromPosition) + // onStepIn(creature, item, pos, fromPosition) + // onStepOut(creature, item, pos, fromPosition) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - MoveEvent::executeStep] Call stack overflow" << std::endl; return false; @@ -958,15 +1002,14 @@ ReturnValue MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool return RETURNVALUE_CANNOTBEDRESSED; } return equipFunction(this, player, item, slot, isCheck); - } else { - return equipFunction(this, player, item, slot, isCheck); } + return equipFunction(this, player, item, slot, isCheck); } bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot, bool isCheck) { - //onEquip(player, item, slot, isCheck) - //onDeEquip(player, item, slot, isCheck) + // onEquip(player, item, slot, isCheck) + // onDeEquip(player, item, slot, isCheck) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl; return false; @@ -991,15 +1034,14 @@ uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& p { if (scripted) { return executeAddRemItem(item, tileItem, pos); - } else { - return moveFunction(item, tileItem, pos); } + return moveFunction(item, tileItem, pos); } bool MoveEvent::executeAddRemItem(Item* item, Item* tileItem, const Position& pos) { - //onaddItem(moveitem, tileitem, pos) - //onRemoveItem(moveitem, tileitem, pos) + // onaddItem(moveitem, tileitem, pos) + // onRemoveItem(moveitem, tileitem, pos) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - MoveEvent::executeAddRemItem] Call stack overflow" << std::endl; return false; diff --git a/src/movement.h b/src/movement.h index 7b7e375006..ea013b923c 100644 --- a/src/movement.h +++ b/src/movement.h @@ -1,33 +1,20 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 -#define FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_MOVEMENT_H +#define FS_MOVEMENT_H #include "baseevents.h" -#include "item.h" +#include "creature.h" #include "luascript.h" #include "vocation.h" +class MoveEvent; + extern Vocations g_vocations; -enum MoveEvent_t { +enum MoveEvent_t +{ MOVE_EVENT_STEP_IN, MOVE_EVENT_STEP_OUT, MOVE_EVENT_EQUIP, @@ -41,10 +28,10 @@ enum MoveEvent_t { MOVE_EVENT_NONE }; -class MoveEvent; using MoveEvent_ptr = std::unique_ptr; -struct MoveEventList { +struct MoveEventList +{ std::list moveEvent[MOVE_EVENT_LAST]; }; @@ -52,198 +39,150 @@ using VocEquipMap = std::map; class MoveEvents final : public BaseEvents { - public: - MoveEvents(); - ~MoveEvents(); +public: + MoveEvents(); + ~MoveEvents(); - // non-copyable - MoveEvents(const MoveEvents&) = delete; - MoveEvents& operator=(const MoveEvents&) = delete; + // non-copyable + MoveEvents(const MoveEvents&) = delete; + MoveEvents& operator=(const MoveEvents&) = delete; - uint32_t onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType); - ReturnValue onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck); - ReturnValue onPlayerDeEquip(Player* player, Item* item, slots_t slot); - uint32_t onItemMove(Item* item, Tile* tile, bool isAdd); + uint32_t onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType); + ReturnValue onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck); + ReturnValue onPlayerDeEquip(Player* player, Item* item, slots_t slot); + uint32_t onItemMove(Item* item, Tile* tile, bool isAdd); - MoveEvent* getEvent(Item* item, MoveEvent_t eventType); + MoveEvent* getEvent(Item* item, MoveEvent_t eventType); - bool registerLuaEvent(MoveEvent* event); - bool registerLuaFunction(MoveEvent* event); - void clear(bool fromLua) override final; + bool registerLuaEvent(MoveEvent* event); + bool registerLuaFunction(MoveEvent* event); + void clear(bool fromLua) override final; - private: - using MoveListMap = std::map; - using MovePosListMap = std::map; - void clearMap(MoveListMap& map, bool fromLua); - void clearPosMap(MovePosListMap& map, bool fromLua); +private: + using MoveListMap = std::map; + using MovePosListMap = std::map; + void clearMap(MoveListMap& map, bool fromLua); + void clearPosMap(MovePosListMap& map, bool fromLua); - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - void addEvent(MoveEvent moveEvent, int32_t id, MoveListMap& map); + void addEvent(MoveEvent moveEvent, int32_t id, MoveListMap& map); - void addEvent(MoveEvent moveEvent, const Position& pos, MovePosListMap& map); - MoveEvent* getEvent(const Tile* tile, MoveEvent_t eventType); + void addEvent(MoveEvent moveEvent, const Position& pos, MovePosListMap& map); + MoveEvent* getEvent(const Tile* tile, MoveEvent_t eventType); - MoveEvent* getEvent(Item* item, MoveEvent_t eventType, slots_t slot); + MoveEvent* getEvent(Item* item, MoveEvent_t eventType, slots_t slot); - MoveListMap uniqueIdMap; - MoveListMap actionIdMap; - MoveListMap itemIdMap; - MovePosListMap positionMap; + MoveListMap uniqueIdMap; + MoveListMap actionIdMap; + MoveListMap itemIdMap; + MovePosListMap positionMap; - LuaScriptInterface scriptInterface; + LuaScriptInterface scriptInterface; }; using StepFunction = std::function; using MoveFunction = std::function; -using EquipFunction = std::function; +using EquipFunction = + std::function; class MoveEvent final : public Event { - public: - explicit MoveEvent(LuaScriptInterface* interface); - - MoveEvent_t getEventType() const; - void setEventType(MoveEvent_t type); - - bool configureEvent(const pugi::xml_node& node) override; - bool loadFunction(const pugi::xml_attribute& attr, bool isScripted) override; - - uint32_t fireStepEvent(Creature* creature, Item* item, const Position& pos); - uint32_t fireAddRemItem(Item* item, Item* tileItem, const Position& pos); - ReturnValue fireEquip(Player* player, Item* item, slots_t slot, bool isCheck); - - uint32_t getSlot() const { - return slot; - } - - //scripting - bool executeStep(Creature* creature, Item* item, const Position& pos); - bool executeEquip(Player* player, Item* item, slots_t slot, bool isCheck); - bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos); - // - - //onEquip information - uint32_t getReqLevel() const { - return reqLevel; - } - uint32_t getReqMagLv() const { - return reqMagLevel; - } - bool isPremium() const { - return premium; - } - const std::string& getVocationString() const { - return vocationString; - } - void setVocationString(const std::string& str) { - vocationString = str; - } - uint32_t getWieldInfo() const { - return wieldInfo; - } - const VocEquipMap& getVocEquipMap() const { - return vocEquipMap; - } - void addVocEquipMap(std::string vocName) { - int32_t vocationId = g_vocations.getVocationId(vocName); - if (vocationId != -1) { - vocEquipMap[vocationId] = true; - } - } - bool getTileItem() const { - return tileItem; - } - void setTileItem(bool b) { - tileItem = b; - } - std::vector getItemIdRange() { - return itemIdRange; - } - void addItemId(uint32_t id) { - itemIdRange.emplace_back(id); - } - std::vector getActionIdRange() { - return actionIdRange; - } - void addActionId(uint32_t id) { - actionIdRange.emplace_back(id); - } - std::vector getUniqueIdRange() { - return uniqueIdRange; - } - void addUniqueId(uint32_t id) { - uniqueIdRange.emplace_back(id); - } - std::vector getPosList() { - return posList; - } - void addPosList(Position pos) { - posList.emplace_back(pos); - } - void setSlot(uint32_t s) { - slot = s; - } - uint32_t getRequiredLevel() { - return reqLevel; - } - void setRequiredLevel(uint32_t level) { - reqLevel = level; - } - uint32_t getRequiredMagLevel() { - return reqMagLevel; - } - void setRequiredMagLevel(uint32_t level) { - reqMagLevel = level; - } - bool needPremium() { - return premium; - } - void setNeedPremium(bool b) { - premium = b; - } - uint32_t getWieldInfo() { - return wieldInfo; - } - void setWieldInfo(WieldInfo_t info) { - wieldInfo |= info; - } - - static uint32_t StepInField(Creature* creature, Item* item, const Position& pos); - static uint32_t StepOutField(Creature* creature, Item* item, const Position& pos); - - static uint32_t AddItemField(Item* item, Item* tileItem, const Position& pos); - static uint32_t RemoveItemField(Item* item, Item* tileItem, const Position& pos); - - static ReturnValue EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck); - static ReturnValue DeEquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool); - - MoveEvent_t eventType = MOVE_EVENT_NONE; - StepFunction stepFunction; - MoveFunction moveFunction; - EquipFunction equipFunction; - - private: - std::string getScriptEventName() const override; - - uint32_t slot = SLOTP_WHEREEVER; - - //onEquip information - uint32_t reqLevel = 0; - uint32_t reqMagLevel = 0; - bool premium = false; - std::string vocationString; - uint32_t wieldInfo = 0; - VocEquipMap vocEquipMap; - bool tileItem = false; - - std::vector itemIdRange; - std::vector actionIdRange; - std::vector uniqueIdRange; - std::vector posList; +public: + explicit MoveEvent(LuaScriptInterface* interface); + + MoveEvent_t getEventType() const; + void setEventType(MoveEvent_t type); + + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute& attr, bool isScripted) override; + + uint32_t fireStepEvent(Creature* creature, Item* item, const Position& pos); + uint32_t fireAddRemItem(Item* item, Item* tileItem, const Position& pos); + ReturnValue fireEquip(Player* player, Item* item, slots_t slot, bool isCheck); + + uint32_t getSlot() const { return slot; } + + // scripting + bool executeStep(Creature* creature, Item* item, const Position& pos); + bool executeEquip(Player* player, Item* item, slots_t slot, bool isCheck); + bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos); + // + + // onEquip information + uint32_t getReqLevel() const { return reqLevel; } + uint32_t getReqMagLv() const { return reqMagLevel; } + bool isPremium() const { return premium; } + const std::string& getVocationString() const { return vocationString; } + void setVocationString(const std::string& str) { vocationString = str; } + uint32_t getWieldInfo() const { return wieldInfo; } + const VocEquipMap& getVocEquipMap() const { return vocEquipMap; } + void addVocEquipMap(std::string vocName) + { + int32_t vocationId = g_vocations.getVocationId(vocName); + if (vocationId != -1) { + vocEquipMap[vocationId] = true; + } + } + bool getTileItem() const { return tileItem; } + void setTileItem(bool b) { tileItem = b; } + void clearItemIdRange() { return itemIdRange.clear(); } + const std::vector& getItemIdRange() const { return itemIdRange; } + void addItemId(uint32_t id) { itemIdRange.emplace_back(id); } + void clearActionIdRange() { return actionIdRange.clear(); } + const std::vector& getActionIdRange() const { return actionIdRange; } + void addActionId(uint32_t id) { actionIdRange.emplace_back(id); } + void clearUniqueIdRange() { return uniqueIdRange.clear(); } + const std::vector& getUniqueIdRange() const { return uniqueIdRange; } + void addUniqueId(uint32_t id) { uniqueIdRange.emplace_back(id); } + void clearPosList() { return posList.clear(); } + const std::vector& getPosList() const { return posList; } + void addPosList(Position pos) { posList.emplace_back(pos); } + void setSlot(uint32_t s) { slot = s; } + uint32_t getRequiredLevel() { return reqLevel; } + void setRequiredLevel(uint32_t level) { reqLevel = level; } + uint32_t getRequiredMagLevel() { return reqMagLevel; } + void setRequiredMagLevel(uint32_t level) { reqMagLevel = level; } + bool needPremium() { return premium; } + void setNeedPremium(bool b) { premium = b; } + uint32_t getWieldInfo() { return wieldInfo; } + void setWieldInfo(WieldInfo_t info) { wieldInfo |= info; } + + static uint32_t StepInField(Creature* creature, Item* item, const Position& pos); + static uint32_t StepOutField(Creature* creature, Item* item, const Position& pos); + + static uint32_t AddItemField(Item* item, Item* tileItem, const Position& pos); + static uint32_t RemoveItemField(Item* item, Item* tileItem, const Position& pos); + + static ReturnValue EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck); + static ReturnValue DeEquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool); + + MoveEvent_t eventType = MOVE_EVENT_NONE; + StepFunction stepFunction; + MoveFunction moveFunction; + EquipFunction equipFunction; + +private: + std::string getScriptEventName() const override; + + uint32_t slot = SLOTP_WHEREEVER; + + // onEquip information + uint32_t reqLevel = 0; + uint32_t reqMagLevel = 0; + bool premium = false; + std::string vocationString; + uint32_t wieldInfo = 0; + VocEquipMap vocEquipMap; + bool tileItem = false; + + std::vector itemIdRange; + std::vector actionIdRange; + std::vector uniqueIdRange; + std::vector posList; }; -#endif +#endif // FS_MOVEMENT_H diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp index e3f7f4a7b3..b9c7e7aa7b 100644 --- a/src/networkmessage.cpp +++ b/src/networkmessage.cpp @@ -1,30 +1,13 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "networkmessage.h" -#include "container.h" -#include "creature.h" +#include "podium.h" -std::string NetworkMessage::getString(uint16_t stringLen/* = 0*/) +std::string NetworkMessage::getString(uint16_t stringLen /* = 0*/) { if (stringLen == 0) { stringLen = get(); @@ -34,7 +17,7 @@ std::string NetworkMessage::getString(uint16_t stringLen/* = 0*/) return std::string(); } - char* v = reinterpret_cast(buffer) + info.position; //does not break strict aliasing + char* v = reinterpret_cast(buffer) + info.position; // does not break strict aliasing info.position += stringLen; return std::string(v, stringLen); } @@ -61,10 +44,11 @@ void NetworkMessage::addString(const std::string& value) info.length += stringLen; } -void NetworkMessage::addDouble(double value, uint8_t precision/* = 2*/) +void NetworkMessage::addDouble(double value, uint8_t precision /* = 2*/) { addByte(precision); - add(static_cast((value * std::pow(static_cast(10), precision)) + std::numeric_limits::max())); + add(static_cast((value * std::pow(static_cast(10), precision)) + + std::numeric_limits::max())); } void NetworkMessage::addBytes(const char* bytes, size_t size) @@ -101,16 +85,22 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count) add(it.clientId); - addByte(0xFF); // MARK_UNMARKED - if (it.stackable) { addByte(count); } else if (it.isSplash() || it.isFluidContainer()) { addByte(fluidMap[count & 7]); + } else if (it.isContainer()) { + addByte(0x00); // assigned loot container icon + addByte(0x00); // quiver ammo count + } else if (it.classification > 0) { + addByte(0x00); // item tier (0-10) } - if (it.isAnimation) { - addByte(0xFE); // random phase (0xFF for async) + if (it.isPodium()) { + add(0); // looktype + add(0); // lookmount + addByte(2); // direction + addByte(0x01); // is visible (bool) } } @@ -119,20 +109,56 @@ void NetworkMessage::addItem(const Item* item) const ItemType& it = Item::items[item->getID()]; add(it.clientId); - addByte(0xFF); // MARK_UNMARKED if (it.stackable) { addByte(std::min(0xFF, item->getItemCount())); } else if (it.isSplash() || it.isFluidContainer()) { addByte(fluidMap[item->getFluidType() & 7]); + } else if (it.classification > 0) { + addByte(0x00); // item tier (0-10) } - if (it.isAnimation) { - addByte(0xFE); // random phase (0xFF for async) + if (it.isContainer()) { + addByte(0x00); // assigned loot container icon + addByte(0x00); // quiver ammo count } -} -void NetworkMessage::addItemId(uint16_t itemId) -{ - add(Item::items[itemId].clientId); + // display outfit on the podium + if (it.isPodium()) { + const Podium* podium = item->getPodium(); + const Outfit_t& outfit = podium->getOutfit(); + + // add outfit + if (podium->hasFlag(PODIUM_SHOW_OUTFIT)) { + add(outfit.lookType); + if (outfit.lookType != 0) { + addByte(outfit.lookHead); + addByte(outfit.lookBody); + addByte(outfit.lookLegs); + addByte(outfit.lookFeet); + addByte(outfit.lookAddons); + } + } else { + add(0); + } + + // add mount + if (podium->hasFlag(PODIUM_SHOW_MOUNT)) { + add(outfit.lookMount); + if (outfit.lookMount != 0) { + addByte(outfit.lookMountHead); + addByte(outfit.lookMountBody); + addByte(outfit.lookMountLegs); + addByte(outfit.lookMountFeet); + } + } else { + add(0); + } + + addByte(podium->getDirection()); + addByte(podium->hasFlag(PODIUM_SHOW_PLATFORM) ? 0x01 : 0x00); + return; + } } + +void NetworkMessage::addItemId(uint16_t itemId) { add(Item::items[itemId].clientId); } diff --git a/src/networkmessage.h b/src/networkmessage.h index bfd99f1fd3..b0ad544b48 100644 --- a/src/networkmessage.h +++ b/src/networkmessage.h @@ -1,184 +1,166 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03 -#define FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_NETWORKMESSAGE_H +#define FS_NETWORKMESSAGE_H #include "const.h" class Item; -class Creature; -class Player; struct Position; -class RSA; class NetworkMessage { - public: - using MsgSize_t = uint16_t; - // Headers: - // 2 bytes for unencrypted message size - // 4 bytes for checksum - // 2 bytes for encrypted message size - static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 8; - enum { HEADER_LENGTH = 2 }; - enum { CHECKSUM_LENGTH = 4 }; - enum { XTEA_MULTIPLE = 8 }; - enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - CHECKSUM_LENGTH - XTEA_MULTIPLE }; - enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 }; - - NetworkMessage() = default; - - void reset() { - info = {}; - } - - // simply read functions for incoming message - uint8_t getByte() { - if (!canRead(1)) { - return 0; - } - - return buffer[info.position++]; - } - - uint8_t getPreviousByte() { - return buffer[--info.position]; - } - - template - T get() { - if (!canRead(sizeof(T))) { - return 0; - } - - T v; - memcpy(&v, buffer + info.position, sizeof(T)); - info.position += sizeof(T); - return v; - } - - std::string getString(uint16_t stringLen = 0); - Position getPosition(); - - // skips count unknown/unused bytes in an incoming message - void skipBytes(int16_t count) { - info.position += count; - } - - // simply write functions for outgoing message - void addByte(uint8_t value) { - if (!canAdd(1)) { - return; - } - - buffer[info.position++] = value; - info.length++; +public: + using MsgSize_t = uint16_t; + // Headers: + // 2 bytes for unencrypted message size + // 4 bytes for checksum + // 2 bytes for encrypted message size + static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 8; + enum + { + HEADER_LENGTH = 2 + }; + enum + { + CHECKSUM_LENGTH = 4 + }; + enum + { + XTEA_MULTIPLE = 8 + }; + enum + { + MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - CHECKSUM_LENGTH - XTEA_MULTIPLE + }; + enum + { + MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 + }; + + NetworkMessage() = default; + + void reset() { info = {}; } + + // simply read functions for incoming message + uint8_t getByte() + { + if (!canRead(1)) { + return 0; + } + + return buffer[info.position++]; + } + + uint8_t getPreviousByte() { return buffer[--info.position]; } + + template + T get() + { + if (!canRead(sizeof(T))) { + return 0; + } + + T v; + memcpy(&v, buffer + info.position, sizeof(T)); + info.position += sizeof(T); + return v; + } + + std::string getString(uint16_t stringLen = 0); + Position getPosition(); + + // skips count unknown/unused bytes in an incoming message + void skipBytes(int16_t count) { info.position += count; } + + // simply write functions for outgoing message + void addByte(uint8_t value) + { + if (!canAdd(1)) { + return; + } + + buffer[info.position++] = value; + info.length++; + } + + template + void add(T value) + { + if (!canAdd(sizeof(T))) { + return; + } + + memcpy(buffer + info.position, &value, sizeof(T)); + info.position += sizeof(T); + info.length += sizeof(T); + } + + void addBytes(const char* bytes, size_t size); + void addPaddingBytes(size_t n); + + void addString(const std::string& value); + + void addDouble(double value, uint8_t precision = 2); + + // write functions for complex types + void addPosition(const Position& pos); + void addItem(uint16_t id, uint8_t count); + void addItem(const Item* item); + void addItemId(uint16_t itemId); + + MsgSize_t getLength() const { return info.length; } + + void setLength(MsgSize_t newLength) { info.length = newLength; } + + MsgSize_t getBufferPosition() const { return info.position; } + + bool setBufferPosition(MsgSize_t pos) + { + if (pos < NETWORKMESSAGE_MAXSIZE - INITIAL_BUFFER_POSITION) { + info.position = pos + INITIAL_BUFFER_POSITION; + return true; } + return false; + } - template - void add(T value) { - if (!canAdd(sizeof(T))) { - return; - } - - memcpy(buffer + info.position, &value, sizeof(T)); - info.position += sizeof(T); - info.length += sizeof(T); - } + uint16_t getLengthHeader() const { return static_cast(buffer[0] | buffer[1] << 8); } - void addBytes(const char* bytes, size_t size); - void addPaddingBytes(size_t n); + bool isOverrun() const { return info.overrun; } - void addString(const std::string& value); + uint8_t* getBuffer() { return buffer; } - void addDouble(double value, uint8_t precision = 2); + const uint8_t* getBuffer() const { return buffer; } - // write functions for complex types - void addPosition(const Position& pos); - void addItem(uint16_t id, uint8_t count); - void addItem(const Item* item); - void addItemId(uint16_t itemId); + uint8_t* getBodyBuffer() + { + info.position = 2; + return buffer + HEADER_LENGTH; + } - MsgSize_t getLength() const { - return info.length; - } +protected: + struct NetworkMessageInfo + { + MsgSize_t length = 0; + MsgSize_t position = INITIAL_BUFFER_POSITION; + bool overrun = false; + }; - void setLength(MsgSize_t newLength) { - info.length = newLength; - } + NetworkMessageInfo info; + uint8_t buffer[NETWORKMESSAGE_MAXSIZE]; - MsgSize_t getBufferPosition() const { - return info.position; - } +private: + bool canAdd(size_t size) const { return (size + info.position) < MAX_BODY_LENGTH; } - bool setBufferPosition(MsgSize_t pos) { - if (pos < NETWORKMESSAGE_MAXSIZE - INITIAL_BUFFER_POSITION) { - info.position = pos + INITIAL_BUFFER_POSITION; - return true; - } + bool canRead(int32_t size) + { + if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) { + info.overrun = true; return false; } - - uint16_t getLengthHeader() const { - return static_cast(buffer[0] | buffer[1] << 8); - } - - bool isOverrun() const { - return info.overrun; - } - - uint8_t* getBuffer() { - return buffer; - } - - const uint8_t* getBuffer() const { - return buffer; - } - - uint8_t* getBodyBuffer() { - info.position = 2; - return buffer + HEADER_LENGTH; - } - - protected: - struct NetworkMessageInfo { - MsgSize_t length = 0; - MsgSize_t position = INITIAL_BUFFER_POSITION; - bool overrun = false; - }; - - NetworkMessageInfo info; - uint8_t buffer[NETWORKMESSAGE_MAXSIZE]; - - private: - bool canAdd(size_t size) const { - return (size + info.position) < MAX_BODY_LENGTH; - } - - bool canRead(int32_t size) { - if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) { - info.overrun = true; - return false; - } - return true; - } + return true; + } }; -#endif // #ifndef __NETWORK_MESSAGE_H__ +#endif // FS_NETWORKMESSAGE_H diff --git a/src/npc.cpp b/src/npc.cpp index 2d495f9428..67ace07829 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -1,32 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "npc.h" + #include "game.h" #include "pugicast.h" +#include "spectators.h" extern Game g_game; extern LuaEnvironment g_luaEnvironment; -uint32_t Npc::npcAutoID = 0x80000000; +uint32_t Npc::npcAutoID = 0x20000000; NpcScriptInterface* Npc::scriptInterface = nullptr; void Npcs::reload() @@ -54,29 +40,16 @@ Npc* Npc::createNpc(const std::string& name) } Npc::Npc(const std::string& name) : - Creature(), - filename("data/npc/" + name + ".xml"), - npcEventHandler(nullptr), - masterRadius(-1), - loaded(false) + Creature(), filename("data/npc/" + name + ".xml"), npcEventHandler(nullptr), masterRadius(-1), loaded(false) { reset(); } -Npc::~Npc() -{ - reset(); -} +Npc::~Npc() { reset(); } -void Npc::addList() -{ - g_game.addNpc(this); -} +void Npc::addList() { g_game.addNpc(this); } -void Npc::removeList() -{ - g_game.removeNpc(this); -} +void Npc::removeList() { g_game.removeNpc(this); } bool Npc::load() { @@ -112,6 +85,7 @@ void Npc::reset() parameters.clear(); shopPlayerSet.clear(); + spectators.clear(); } void Npc::reload() @@ -119,14 +93,23 @@ void Npc::reload() reset(); load(); - // Simulate that the creature is placed on the map again. - if (npcEventHandler) { - npcEventHandler->onCreatureAppear(this); + SpectatorVec players; + g_game.map.getSpectators(players, getPosition(), true, true); + for (const auto& player : players) { + spectators.insert(player->getPlayer()); } - if (walkTicks > 0) { + const bool hasSpectators = !spectators.empty(); + setIdle(!hasSpectators); + + if (hasSpectators && walkTicks > 0) { addEventWalk(); } + + // Simulate that the creature is placed on the map again. + if (npcEventHandler) { + npcEventHandler->onCreatureAppear(this); + } } bool Npc::loadFromXml() @@ -176,7 +159,7 @@ bool Npc::loadFromXml() } if ((attr = npcNode.attribute("skull"))) { - setSkull(getSkullType(asLowerCaseString(attr.as_string()))); + setSkull(getSkullType(boost::algorithm::to_lower_copy(attr.as_string()))); } pugi::xml_node healthNode = npcNode.child("health"); @@ -195,7 +178,8 @@ bool Npc::loadFromXml() if (health > healthMax) { health = healthMax; - std::cout << "[Warning - Npc::loadFromXml] Health now is greater than health max in " << filename << std::endl; + std::cout << "[Warning - Npc::loadFromXml] Health now is greater than health max in " << filename + << std::endl; } } @@ -255,7 +239,16 @@ void Npc::onCreatureAppear(Creature* creature, bool isLogin) Creature::onCreatureAppear(creature, isLogin); if (creature == this) { - if (walkTicks > 0) { + SpectatorVec players; + g_game.map.getSpectators(players, getPosition(), true, true); + for (const auto& player : players) { + spectators.insert(player->getPlayer()); + } + + const bool hasSpectators = !spectators.empty(); + setIdle(!hasSpectators); + + if (hasSpectators && walkTicks > 0) { addEventWalk(); } @@ -268,7 +261,7 @@ void Npc::onCreatureAppear(Creature* creature, bool isLogin) } spectators.insert(player); - updateIdleStatus(); + setIdle(false); } } @@ -287,12 +280,12 @@ void Npc::onRemoveCreature(Creature* creature, bool isLogout) } spectators.erase(player); - updateIdleStatus(); + setIdle(spectators.empty()); } } -void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) +void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); @@ -311,18 +304,18 @@ void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position spectators.erase(player); } - updateIdleStatus(); + setIdle(spectators.empty()); } } } void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) { - if (creature->getID() == id) { + if (creature == this) { return; } - //only players for script events + // only players for script events Player* player = creature->getPlayer(); if (player) { if (npcEventHandler) { @@ -351,10 +344,7 @@ void Npc::onThink(uint32_t interval) } } -void Npc::doSay(const std::string& text) -{ - g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false); -} +void Npc::doSay(const std::string& text) { g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false); } void Npc::doSayToPlayer(Player* player, const std::string& text) { @@ -364,8 +354,8 @@ void Npc::doSayToPlayer(Player* player, const std::string& text) } } -void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, - uint8_t amount, bool ignore/* = false*/, bool inBackpacks/* = false*/) +void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, + bool ignore /* = false*/, bool inBackpacks /* = false*/) { if (npcEventHandler) { npcEventHandler->onPlayerTrade(player, callback, itemId, count, amount, ignore, inBackpacks); @@ -413,8 +403,12 @@ bool Npc::getNextStep(Direction& dir, uint32_t& flags) return getRandomStep(dir); } -void Npc::setIdle(bool idle) +void Npc::setIdle(const bool idle) { + if (idle == isIdle) { + return; + } + if (isRemoved() || getHealth() <= 0) { return; } @@ -426,14 +420,6 @@ void Npc::setIdle(bool idle) } } -void Npc::updateIdleStatus() -{ - bool status = spectators.empty(); - if (status != isIdle) { - setIdle(status); - } -} - bool Npc::canWalkTo(const Position& fromPos, Direction dir) const { if (masterRadius == 0) { @@ -464,8 +450,9 @@ bool Npc::canWalkTo(const Position& fromPos, Direction dir) const bool Npc::getRandomStep(Direction& dir) const { std::vector dirList; - const Position& creaturePos = getPosition(); + dirList.reserve(4); + const Position& creaturePos = getPosition(); if (canWalkTo(creaturePos, DIRECTION_NORTH)) { dirList.push_back(DIRECTION_NORTH); } @@ -490,12 +477,15 @@ bool Npc::getRandomStep(Direction& dir) const return true; } -void Npc::doMoveTo(const Position& pos) +bool Npc::doMoveTo(const Position& pos, int32_t minTargetDist /* = 1*/, int32_t maxTargetDist /* = 1*/, + bool fullPathSearch /* = true*/, bool clearSight /* = true*/, int32_t maxSearchDist /* = 0*/) { listWalkDir.clear(); - if (getPathTo(pos, listWalkDir, 1, 1, true, true)) { + if (getPathTo(pos, listWalkDir, minTargetDist, maxTargetDist, fullPathSearch, clearSight, maxSearchDist)) { startAutoWalk(); + return true; } + return false; } void Npc::turnToCreature(Creature* creature) @@ -539,15 +529,9 @@ void Npc::setCreatureFocus(Creature* creature) } } -void Npc::addShopPlayer(Player* player) -{ - shopPlayerSet.insert(player); -} +void Npc::addShopPlayer(Player* player) { shopPlayerSet.insert(player); } -void Npc::removeShopPlayer(Player* player) -{ - shopPlayerSet.erase(player); -} +void Npc::removeShopPlayer(Player* player) { shopPlayerSet.erase(player); } void Npc::closeAllShopWindows() { @@ -559,13 +543,9 @@ void Npc::closeAllShopWindows() } } -NpcScriptInterface* Npc::getScriptInterface() -{ - return scriptInterface; -} +NpcScriptInterface* Npc::getScriptInterface() { return scriptInterface; } -NpcScriptInterface::NpcScriptInterface() : - LuaScriptInterface("Npc interface") +NpcScriptInterface::NpcScriptInterface() : LuaScriptInterface("Npc interface") { libLoaded = false; initState(); @@ -610,7 +590,7 @@ bool NpcScriptInterface::loadNpcLib(const std::string& file) void NpcScriptInterface::registerFunctions() { - //npc exclusive functions + // npc exclusive functions lua_register(luaState, "selfSay", NpcScriptInterface::luaActionSay); lua_register(luaState, "selfMove", NpcScriptInterface::luaActionMove); lua_register(luaState, "selfMoveTo", NpcScriptInterface::luaActionMoveTo); @@ -634,7 +614,7 @@ void NpcScriptInterface::registerFunctions() int NpcScriptInterface::luaActionSay(lua_State* L) { - //selfSay(words[, target]) + // selfSay(words[, target]) Npc* npc = getScriptEnv()->getNpc(); if (!npc) { return 0; @@ -655,7 +635,7 @@ int NpcScriptInterface::luaActionSay(lua_State* L) int NpcScriptInterface::luaActionMove(lua_State* L) { - //selfMove(direction) + // selfMove(direction) Npc* npc = getScriptEnv()->getNpc(); if (npc) { g_game.internalMoveCreature(npc, getNumber(L, 1)); @@ -665,23 +645,34 @@ int NpcScriptInterface::luaActionMove(lua_State* L) int NpcScriptInterface::luaActionMoveTo(lua_State* L) { - //selfMoveTo(x,y,z) + // selfMoveTo(x, y, z[, minTargetDist = 1[, maxTargetDist = 1[, fullPathSearch = true[, clearSight = true[, + // maxSearchDist = 0]]]]]) selfMoveTo(position[, minTargetDist = 1[, maxTargetDist = 1[, fullPathSearch = true[, + // clearSight = true[, maxSearchDist = 0]]]]]) Npc* npc = getScriptEnv()->getNpc(); if (!npc) { return 0; } - npc->doMoveTo(Position( - getNumber(L, 1), - getNumber(L, 2), - getNumber(L, 3) - )); - return 0; + Position position; + int32_t argsStart = 2; + if (isTable(L, 1)) { + position = getPosition(L, 1); + } else { + position.x = getNumber(L, 1); + position.y = getNumber(L, 2); + position.z = getNumber(L, 3); + argsStart = 4; + } + + pushBoolean(L, npc->doMoveTo(position, getNumber(L, argsStart, 1), getNumber(L, argsStart + 1, 1), + getBoolean(L, argsStart + 2, true), getBoolean(L, argsStart + 3, true), + getNumber(L, argsStart + 4, 0))); + return 1; } int NpcScriptInterface::luaActionTurn(lua_State* L) { - //selfTurn(direction) + // selfTurn(direction) Npc* npc = getScriptEnv()->getNpc(); if (npc) { g_game.internalCreatureTurn(npc, getNumber(L, 1)); @@ -691,7 +682,7 @@ int NpcScriptInterface::luaActionTurn(lua_State* L) int NpcScriptInterface::luaActionFollow(lua_State* L) { - //selfFollow(player) + // selfFollow(player) Npc* npc = getScriptEnv()->getNpc(); if (!npc) { pushBoolean(L, false); @@ -704,7 +695,7 @@ int NpcScriptInterface::luaActionFollow(lua_State* L) int NpcScriptInterface::luagetDistanceTo(lua_State* L) { - //getDistanceTo(uid) + // getDistanceTo(uid) ScriptEnvironment* env = getScriptEnv(); Npc* npc = env->getNpc(); @@ -728,7 +719,8 @@ int NpcScriptInterface::luagetDistanceTo(lua_State* L) if (npcPos.z != thingPos.z) { lua_pushnumber(L, -1); } else { - int32_t dist = std::max(Position::getDistanceX(npcPos, thingPos), Position::getDistanceY(npcPos, thingPos)); + int32_t dist = + std::max(Position::getDistanceX(npcPos, thingPos), Position::getDistanceY(npcPos, thingPos)); lua_pushnumber(L, dist); } return 1; @@ -736,7 +728,7 @@ int NpcScriptInterface::luagetDistanceTo(lua_State* L) int NpcScriptInterface::luaSetNpcFocus(lua_State* L) { - //doNpcSetCreatureFocus(cid) + // doNpcSetCreatureFocus(cid) Npc* npc = getScriptEnv()->getNpc(); if (npc) { npc->setCreatureFocus(getCreature(L, -1)); @@ -746,7 +738,7 @@ int NpcScriptInterface::luaSetNpcFocus(lua_State* L) int NpcScriptInterface::luaGetNpcCid(lua_State* L) { - //getNpcCid() + // getNpcCid() Npc* npc = getScriptEnv()->getNpc(); if (npc) { lua_pushnumber(L, npc->getID()); @@ -758,7 +750,7 @@ int NpcScriptInterface::luaGetNpcCid(lua_State* L) int NpcScriptInterface::luaGetNpcParameter(lua_State* L) { - //getNpcParameter(paramKey) + // getNpcParameter(paramKey) Npc* npc = getScriptEnv()->getNpc(); if (!npc) { lua_pushnil(L); @@ -778,7 +770,7 @@ int NpcScriptInterface::luaGetNpcParameter(lua_State* L) int NpcScriptInterface::luaOpenShopWindow(lua_State* L) { - //openShopWindow(cid, items, onBuy callback, onSell callback) + // openShopWindow(cid, items, onBuy callback, onSell callback) int32_t sellCallback; if (lua_isfunction(L, -1) == 0) { sellCallback = -1; @@ -814,8 +806,8 @@ int NpcScriptInterface::luaOpenShopWindow(lua_State* L) lua_pop(L, 1); } - item.buyPrice = getField(L, tableIndex, "buy"); - item.sellPrice = getField(L, tableIndex, "sell"); + item.buyPrice = getField(L, tableIndex, "buy"); + item.sellPrice = getField(L, tableIndex, "sell"); item.realName = getFieldString(L, tableIndex, "name"); items.push_back(item); @@ -830,7 +822,7 @@ int NpcScriptInterface::luaOpenShopWindow(lua_State* L) return 1; } - //Close any eventual other shop window currently open. + // Close any eventual other shop window currently open. player->closeShopWindow(false); Npc* npc = getScriptEnv()->getNpc(); @@ -850,7 +842,7 @@ int NpcScriptInterface::luaOpenShopWindow(lua_State* L) int NpcScriptInterface::luaCloseShopWindow(lua_State* L) { - //closeShopWindow(cid) + // closeShopWindow(cid) Npc* npc = getScriptEnv()->getNpc(); if (!npc) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); @@ -870,7 +862,7 @@ int NpcScriptInterface::luaCloseShopWindow(lua_State* L) Npc* merchant = player->getShopOwner(buyCallback, sellCallback); - //Check if we actually have a shop window with this player. + // Check if we actually have a shop window with this player. if (merchant == npc) { player->sendCloseShop(); @@ -892,7 +884,7 @@ int NpcScriptInterface::luaCloseShopWindow(lua_State* L) int NpcScriptInterface::luaDoSellItem(lua_State* L) { - //doSellItem(cid, itemid, amount, subtype, actionid, canDropOnMap) + // doSellItem(cid, itemid, amount, subtype, actionid, canDropOnMap) Player* player = getPlayer(L, 1); if (!player) { reportErrorFunc(L, getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); @@ -1034,8 +1026,8 @@ int NpcScriptInterface::luaNpcOpenShopWindow(lua_State* L) lua_pop(L, 1); } - item.buyPrice = getField(L, tableIndex, "buy"); - item.sellPrice = getField(L, tableIndex, "sell"); + item.buyPrice = getField(L, tableIndex, "buy"); + item.sellPrice = getField(L, tableIndex, "sell"); item.realName = getFieldString(L, tableIndex, "name"); items.push_back(item); @@ -1093,7 +1085,7 @@ int NpcScriptInterface::luaNpcCloseShopWindow(lua_State* L) } NpcEventsHandler::NpcEventsHandler(const std::string& file, Npc* npc) : - npc(npc), scriptInterface(npc->getScriptInterface()) + npc(npc), scriptInterface(npc->getScriptInterface()) { loaded = scriptInterface->loadFile("data/npc/scripts/" + file, npc) == 0; if (!loaded) { @@ -1110,10 +1102,7 @@ NpcEventsHandler::NpcEventsHandler(const std::string& file, Npc* npc) : } } -bool NpcEventsHandler::isLoaded() const -{ - return loaded; -} +bool NpcEventsHandler::isLoaded() const { return loaded; } void NpcEventsHandler::onCreatureAppear(Creature* creature) { @@ -1121,7 +1110,7 @@ void NpcEventsHandler::onCreatureAppear(Creature* creature) return; } - //onCreatureAppear(creature) + // onCreatureAppear(creature) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onCreatureAppear] Call stack overflow" << std::endl; return; @@ -1144,7 +1133,7 @@ void NpcEventsHandler::onCreatureDisappear(Creature* creature) return; } - //onCreatureDisappear(creature) + // onCreatureDisappear(creature) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onCreatureDisappear] Call stack overflow" << std::endl; return; @@ -1167,7 +1156,7 @@ void NpcEventsHandler::onCreatureMove(Creature* creature, const Position& oldPos return; } - //onCreatureMove(creature, oldPos, newPos) + // onCreatureMove(creature, oldPos, newPos) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onCreatureMove] Call stack overflow" << std::endl; return; @@ -1192,7 +1181,7 @@ void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, cons return; } - //onCreatureSay(creature, type, msg) + // onCreatureSay(creature, type, msg) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onCreatureSay] Call stack overflow" << std::endl; return; @@ -1211,14 +1200,14 @@ void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, cons scriptInterface->callFunction(3); } -void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, - uint8_t count, uint8_t amount, bool ignore, bool inBackpacks) +void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, + bool ignore, bool inBackpacks) { if (callback == -1) { return; } - //onBuy(player, itemid, count, amount, ignore, inbackpacks) + // onBuy(player, itemid, count, amount, ignore, inbackpacks) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onPlayerTrade] Call stack overflow" << std::endl; return; @@ -1246,7 +1235,7 @@ void NpcEventsHandler::onPlayerCloseChannel(Player* player) return; } - //onPlayerCloseChannel(player) + // onPlayerCloseChannel(player) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onPlayerCloseChannel] Call stack overflow" << std::endl; return; @@ -1269,7 +1258,7 @@ void NpcEventsHandler::onPlayerEndTrade(Player* player) return; } - //onPlayerEndTrade(player) + // onPlayerEndTrade(player) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onPlayerEndTrade] Call stack overflow" << std::endl; return; @@ -1292,7 +1281,7 @@ void NpcEventsHandler::onThink() return; } - //onThink() + // onThink() if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - NpcScript::onThink] Call stack overflow" << std::endl; return; diff --git a/src/npc.h b/src/npc.h index cd463bb159..b164e5f5a2 100644 --- a/src/npc.h +++ b/src/npc.h @@ -1,255 +1,214 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_NPC_H_B090D0CB549D4435AFA03647195D156F -#define FS_NPC_H_B090D0CB549D4435AFA03647195D156F +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_NPC_H +#define FS_NPC_H #include "creature.h" #include "luascript.h" -#include - class Npc; class Player; class Npcs { - public: - static void reload(); +public: + static void reload(); }; class NpcScriptInterface final : public LuaScriptInterface { - public: - NpcScriptInterface(); - - bool loadNpcLib(const std::string& file); - - private: - void registerFunctions(); - - static int luaActionSay(lua_State* L); - static int luaActionMove(lua_State* L); - static int luaActionMoveTo(lua_State* L); - static int luaActionTurn(lua_State* L); - static int luaActionFollow(lua_State* L); - static int luagetDistanceTo(lua_State* L); - static int luaSetNpcFocus(lua_State* L); - static int luaGetNpcCid(lua_State* L); - static int luaGetNpcParameter(lua_State* L); - static int luaOpenShopWindow(lua_State* L); - static int luaCloseShopWindow(lua_State* L); - static int luaDoSellItem(lua_State* L); - - // metatable - static int luaNpcGetParameter(lua_State* L); - static int luaNpcSetFocus(lua_State* L); - - static int luaNpcOpenShopWindow(lua_State* L); - static int luaNpcCloseShopWindow(lua_State* L); - - private: - bool initState() override; - bool closeState() override; - - bool libLoaded; +public: + NpcScriptInterface(); + + bool loadNpcLib(const std::string& file); + +private: + void registerFunctions(); + + static int luaActionSay(lua_State* L); + static int luaActionMove(lua_State* L); + static int luaActionMoveTo(lua_State* L); + static int luaActionTurn(lua_State* L); + static int luaActionFollow(lua_State* L); + static int luagetDistanceTo(lua_State* L); + static int luaSetNpcFocus(lua_State* L); + static int luaGetNpcCid(lua_State* L); + static int luaGetNpcParameter(lua_State* L); + static int luaOpenShopWindow(lua_State* L); + static int luaCloseShopWindow(lua_State* L); + static int luaDoSellItem(lua_State* L); + + // metatable + static int luaNpcGetParameter(lua_State* L); + static int luaNpcSetFocus(lua_State* L); + + static int luaNpcOpenShopWindow(lua_State* L); + static int luaNpcCloseShopWindow(lua_State* L); + +private: + bool initState() override; + bool closeState() override; + + bool libLoaded; }; class NpcEventsHandler { - public: - NpcEventsHandler(const std::string& file, Npc* npc); - - void onCreatureAppear(Creature* creature); - void onCreatureDisappear(Creature* creature); - void onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos); - void onCreatureSay(Creature* creature, SpeakClasses, const std::string& text); - void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, bool ignore = false, bool inBackpacks = false); - void onPlayerCloseChannel(Player* player); - void onPlayerEndTrade(Player* player); - void onThink(); - - bool isLoaded() const; - - private: - Npc* npc; - NpcScriptInterface* scriptInterface; - - int32_t creatureAppearEvent = -1; - int32_t creatureDisappearEvent = -1; - int32_t creatureMoveEvent = -1; - int32_t creatureSayEvent = -1; - int32_t playerCloseChannelEvent = -1; - int32_t playerEndTradeEvent = -1; - int32_t thinkEvent = -1; - bool loaded = false; +public: + NpcEventsHandler(const std::string& file, Npc* npc); + + void onCreatureAppear(Creature* creature); + void onCreatureDisappear(Creature* creature); + void onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos); + void onCreatureSay(Creature* creature, SpeakClasses, const std::string& text); + void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, + bool ignore = false, bool inBackpacks = false); + void onPlayerCloseChannel(Player* player); + void onPlayerEndTrade(Player* player); + void onThink(); + + bool isLoaded() const; + +private: + Npc* npc; + NpcScriptInterface* scriptInterface; + + int32_t creatureAppearEvent = -1; + int32_t creatureDisappearEvent = -1; + int32_t creatureMoveEvent = -1; + int32_t creatureSayEvent = -1; + int32_t playerCloseChannelEvent = -1; + int32_t playerEndTradeEvent = -1; + int32_t thinkEvent = -1; + bool loaded = false; }; class Npc final : public Creature { - public: - ~Npc(); +public: + ~Npc(); - // non-copyable - Npc(const Npc&) = delete; - Npc& operator=(const Npc&) = delete; + // non-copyable + Npc(const Npc&) = delete; + Npc& operator=(const Npc&) = delete; - Npc* getNpc() override { - return this; - } - const Npc* getNpc() const override { - return this; - } + Npc* getNpc() override { return this; } + const Npc* getNpc() const override { return this; } - bool isPushable() const override { - return pushable && walkTicks != 0; - } + bool isPushable() const override { return pushable && walkTicks != 0; } - void setID() override { - if (id == 0) { - id = npcAutoID++; - } + void setID() override + { + if (id == 0) { + id = ++npcAutoID; } + } - void removeList() override; - void addList() override; + void removeList() override; + void addList() override; - static Npc* createNpc(const std::string& name); + static Npc* createNpc(const std::string& name); - bool canSee(const Position& pos) const override; + bool canSee(const Position& pos) const override; - bool load(); - void reload(); + bool load(); + void reload(); - const std::string& getName() const override { - return name; - } - const std::string& getNameDescription() const override { - return name; - } + const std::string& getName() const override { return name; } + const std::string& getNameDescription() const override { return name; } - CreatureType_t getType() const override { - return CREATURETYPE_NPC; - } + CreatureType_t getType() const override { return CREATURETYPE_NPC; } - uint8_t getSpeechBubble() const override { - return speechBubble; - } - void setSpeechBubble(const uint8_t bubble) { - speechBubble = bubble; - } + uint8_t getSpeechBubble() const override { return speechBubble; } + void setSpeechBubble(const uint8_t bubble) { speechBubble = bubble; } - void doSay(const std::string& text); - void doSayToPlayer(Player* player, const std::string& text); + void doSay(const std::string& text); + void doSayToPlayer(Player* player, const std::string& text); - void doMoveTo(const Position& pos); + bool doMoveTo(const Position& pos, int32_t minTargetDist = 1, int32_t maxTargetDist = 1, bool fullPathSearch = true, + bool clearSight = true, int32_t maxSearchDist = 0); - int32_t getMasterRadius() const { - return masterRadius; - } - const Position& getMasterPos() const { - return masterPos; - } - void setMasterPos(Position pos, int32_t radius = 1) { - masterPos = pos; - if (masterRadius == -1) { - masterRadius = radius; - } + int32_t getMasterRadius() const { return masterRadius; } + const Position& getMasterPos() const { return masterPos; } + void setMasterPos(Position pos, int32_t radius = 1) + { + masterPos = pos; + if (masterRadius == -1) { + masterRadius = radius; } + } - void onPlayerCloseChannel(Player* player); - void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, - uint8_t amount, bool ignore = false, bool inBackpacks = false); - void onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback); + void onPlayerCloseChannel(Player* player); + void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, + bool ignore = false, bool inBackpacks = false); + void onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback); - void turnToCreature(Creature* creature); - void setCreatureFocus(Creature* creature); + void turnToCreature(Creature* creature); + void setCreatureFocus(Creature* creature); - NpcScriptInterface* getScriptInterface(); + NpcScriptInterface* getScriptInterface(); - static uint32_t npcAutoID; + static uint32_t npcAutoID; - private: - explicit Npc(const std::string& name); +private: + explicit Npc(const std::string& name); - void onCreatureAppear(Creature* creature, bool isLogin) override; - void onRemoveCreature(Creature* creature, bool isLogout) override; - void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) override; + void onCreatureAppear(Creature* creature, bool isLogin) override; + void onRemoveCreature(Creature* creature, bool isLogout) override; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) override; - void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; - void onThink(uint32_t interval) override; - std::string getDescription(int32_t lookDistance) const override; + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; + void onThink(uint32_t interval) override; + std::string getDescription(int32_t lookDistance) const override; - bool isImmune(CombatType_t) const override { - return !attackable; - } - bool isImmune(ConditionType_t) const override { - return !attackable; - } - bool isAttackable() const override { - return attackable; - } - bool getNextStep(Direction& dir, uint32_t& flags) override; + bool isImmune(CombatType_t) const override { return !attackable; } + bool isImmune(ConditionType_t) const override { return !attackable; } + bool isAttackable() const override { return attackable; } + bool getNextStep(Direction& dir, uint32_t& flags) override; - void setIdle(bool idle); - void updateIdleStatus(); + void setIdle(const bool idle); - bool canWalkTo(const Position& fromPos, Direction dir) const; - bool getRandomStep(Direction& dir) const; + bool canWalkTo(const Position& fromPos, Direction dir) const; + bool getRandomStep(Direction& dir) const; - void reset(); - bool loadFromXml(); + void reset(); + bool loadFromXml(); - void addShopPlayer(Player* player); - void removeShopPlayer(Player* player); - void closeAllShopWindows(); + void addShopPlayer(Player* player); + void removeShopPlayer(Player* player); + void closeAllShopWindows(); - std::map parameters; + std::map parameters; - std::set shopPlayerSet; - std::set spectators; + std::set shopPlayerSet; + std::set spectators; - std::string name; - std::string filename; + std::string name; + std::string filename; - NpcEventsHandler* npcEventHandler; + NpcEventsHandler* npcEventHandler; - Position masterPos; + Position masterPos; - uint32_t walkTicks; - int32_t focusCreature; - int32_t masterRadius; + uint32_t walkTicks; + int32_t focusCreature; + int32_t masterRadius; - uint8_t speechBubble; + uint8_t speechBubble; - bool floorChange; - bool attackable; - bool ignoreHeight; - bool loaded; - bool isIdle; - bool pushable; + bool floorChange; + bool attackable; + bool ignoreHeight; + bool loaded; + bool isIdle; + bool pushable; - static NpcScriptInterface* scriptInterface; + static NpcScriptInterface* scriptInterface; - friend class Npcs; - friend class NpcScriptInterface; + friend class Npcs; + friend class NpcScriptInterface; }; -#endif +#endif // FS_NPC_H diff --git a/src/otpch.cpp b/src/otpch.cpp index bc54154a6f..56a310d331 100644 --- a/src/otpch.cpp +++ b/src/otpch.cpp @@ -1,20 +1,4 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" diff --git a/src/otpch.h b/src/otpch.h index e15437075e..83fdd7af2c 100644 --- a/src/otpch.h +++ b/src/otpch.h @@ -1,44 +1,56 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#define FS_OTPCH_H_F00C737DA6CA4C8D90F57430C614367F +#ifndef FS_OTPCH_H +#define FS_OTPCH_H // Definitions should be global. #include "definitions.h" +// System headers required in headers should be included here. #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include +#include #include #include +#include +#include +#include +#include #include -#include +#if __has_include("luajit/lua.hpp") +#include +#else +#include +#endif -#include +#endif // FS_OTPCH_H diff --git a/src/otserv.cpp b/src/otserv.cpp index d52ab00fbd..f7a117e9be 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -1,44 +1,28 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "server.h" - +#include "configmanager.h" +#include "databasemanager.h" +#include "databasetasks.h" #include "game.h" - #include "iomarket.h" - -#include "configmanager.h" -#include "scriptmanager.h" -#include "rsa.h" -#include "protocolold.h" +#include "monsters.h" +#include "outfit.h" #include "protocollogin.h" +#include "protocolold.h" #include "protocolstatus.h" -#include "databasemanager.h" +#include "rsa.h" #include "scheduler.h" -#include "databasetasks.h" #include "script.h" +#include "scriptmanager.h" +#include "server.h" + #include -#include + #if __has_include("gitmetadata.h") - #include "gitmetadata.h" +#include "gitmetadata.h" #endif DatabaseTasks g_databaseTasks; @@ -58,7 +42,7 @@ std::unique_lock g_loaderUniqueLock(g_loaderLock); void startupErrorMessage(const std::string& errorStr) { - std::cout << "> ERROR: " << errorStr << std::endl; + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "> ERROR: {:s}\n", errorStr); g_loaderSignal.notify_all(); } @@ -73,10 +57,48 @@ bool argumentsHandler(const StringVector& args); exit(-1); } +namespace anniversary { + +fmt::color paintA = fmt::color(0xFABC01); +fmt::color paintB = fmt::color(0x955A26); +fmt::color paintC = fmt::color(0x0000C0); +fmt::color paintD = fmt::color(0x006500); + +std::string colorA(const std::string text) { return fmt::format(fg(paintA), text); } + +std::string colorB(const std::string text) { return fmt::format(fg(paintB), text); } + +std::string colorC(const std::string text) { return fmt::format(fg(paintC), text); } + +std::string colorD(const std::string text) { return fmt::format(fg(paintD), text); } + +void celebrate() +{ + // clang-format off + std::cout << std::endl; + std::cout << colorA(" ") << " " << colorA(" ") << " " << colorB(" . . ,----.") << std::endl; + std::cout << colorA(" ") << " " << colorA(" ") << " " << colorB(" /|_/| / ',") << std::endl; + std::cout << colorA(" ") << " " << colorA("#` ") << " ''+#. " << colorB(" /") << ", ," << colorB(" )/ ,;'' .'") << std::endl; + std::cout << colorA(" .####. ") << " .#. " << colorA("#| ") << " +#. " << colorB("(y_ ) | ;...;' ") << std::endl; + std::cout << colorA(" .## ##.") << " ######' " << colorA("#| ") << " _+ '##_###. ,+## " << colorB(" ") << "\"" << colorB("| | \\. '.") << std::endl; + std::cout << colorA(" ## ##") << " ##' " << colorA("#| ") << " ##. ###''|#, .#' '#, " << colorB("(") << colorC("#") << colorD("#") << colorB("(/ ) ;") << std::endl; + std::cout << colorA(" ## ##") << " ## " << colorA("#| ") << " # #. ## .#' #' #' " << colorB(" ") << colorD("#") << colorC("#") << colorB(" .. |. .'") << std::endl; + std::cout << colorA(" `## ##`") << " `## ,# " << colorA("#| ") << " #==#. ## .#' '# #' " << colorB(" \\_( ._|--'") << std::endl; + std::cout << colorA(" `####` ") << " `###` " << colorA("##==") << " # #. ## |##. '###' " << colorB(" '--'--'''") << std::endl; + std::cout << std::endl; + std::cout << std::endl; + std::cout << " \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" Happy 15th anniversary! \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"" << std::endl; + std::cout << std::endl; + std::cout << std::endl; + // clang-format on +} + +} // namespace anniversary + int main(int argc, char* argv[]) { StringVector args = StringVector(argv, argv + argc); - if(argc > 1 && !argumentsHandler(args)) { + if (argc > 1 && !argumentsHandler(args)) { return 0; } @@ -88,12 +110,14 @@ int main(int argc, char* argv[]) g_dispatcher.start(); g_scheduler.start(); - g_dispatcher.addTask(createTask(std::bind(mainLoader, argc, argv, &serviceManager))); + g_dispatcher.addTask(createTask([=, services = &serviceManager]() { mainLoader(argc, argv, services); })); g_loaderSignal.wait(g_loaderUniqueLock); if (serviceManager.is_running()) { - std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl << std::endl; + anniversary::celebrate(); + std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl + << std::endl; serviceManager.run(); } else { std::cout << ">> No services running. The server is NOT online." << std::endl; @@ -112,10 +136,10 @@ void printServerVersion() { #if defined(GIT_RETRIEVED_STATE) && GIT_RETRIEVED_STATE std::cout << STATUS_SERVER_NAME << " - Version " << GIT_DESCRIBE << std::endl; - std::cout << "Git SHA1 " << GIT_SHORT_SHA1 << " dated " << GIT_COMMIT_DATE_ISO8601 << std::endl; - #if GIT_IS_DIRTY + std::cout << "Git SHA1 " << GIT_SHORT_SHA1 << " dated " << GIT_COMMIT_DATE_ISO8601 << std::endl; +#if GIT_IS_DIRTY std::cout << "*** DIRTY - NOT OFFICIAL RELEASE ***" << std::endl; - #endif +#endif #else std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; #endif @@ -135,7 +159,7 @@ void printServerVersion() #if defined(LUAJIT_VERSION) std::cout << "Linked with " << LUAJIT_VERSION << " for Lua support" << std::endl; #else - std::cout << "Linked with " << LUA_RELEASE << " for Lua support" << std::endl; + std::cout << "Linked with " << LUA_RELEASE << " for Lua support" << std::endl; #endif std::cout << std::endl; @@ -146,12 +170,19 @@ void printServerVersion() void mainLoader(int, char*[], ServiceManager* services) { - //dispatcher thread + // dispatcher thread g_game.setGameState(GAME_STATE_STARTUP); srand(static_cast(OTSYS_TIME())); #ifdef _WIN32 SetConsoleTitle(STATUS_SERVER_NAME); + + // fixes a problem with escape characters not being processed in Windows consoles + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); #endif printServerVersion(); @@ -181,17 +212,18 @@ void mainLoader(int, char*[], ServiceManager* services) #ifdef _WIN32 const std::string& defaultPriority = g_config.getString(ConfigManager::DEFAULT_PRIORITY); - if (strcasecmp(defaultPriority.c_str(), "high") == 0) { + if (caseInsensitiveEqual(defaultPriority, "high")) { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); - } else if (strcasecmp(defaultPriority.c_str(), "above-normal") == 0) { + } else if (caseInsensitiveEqual(defaultPriority, "above-normal")) { SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); } #endif - //set RSA key + // set RSA key + std::cout << ">> Loading RSA key " << std::endl; try { g_RSA.loadPEM("key.pem"); - } catch(const std::exception& e) { + } catch (const std::exception& e) { startupErrorMessage(e.what()); return; } @@ -209,7 +241,8 @@ void mainLoader(int, char*[], ServiceManager* services) std::cout << ">> Running database manager" << std::endl; if (!DatabaseManager::isDatabaseSetup()) { - startupErrorMessage("The database you have specified in config.lua is empty, please import the schema.sql to your database."); + startupErrorMessage( + "The database you have specified in config.lua is empty, please import the schema.sql to your database."); return; } g_databaseTasks.start(); @@ -220,7 +253,7 @@ void mainLoader(int, char*[], ServiceManager* services) std::cout << "> No tables were optimized." << std::endl; } - //load vocations + // load vocations std::cout << ">> Loading vocations" << std::endl; if (!g_vocations.loadFromXml()) { startupErrorMessage("Unable to load vocations!"); @@ -228,11 +261,14 @@ void mainLoader(int, char*[], ServiceManager* services) } // load item data - std::cout << ">> Loading items" << std::endl; + std::cout << ">> Loading items... "; if (!Item::items.loadFromOtb("data/items/items.otb")) { startupErrorMessage("Unable to load items (OTB)!"); return; } + std::cout << fmt::format("OTB v{:d}.{:d}.{:d}", Item::items.majorVersion, Item::items.minorVersion, + Item::items.buildNumber) + << std::endl; if (!Item::items.loadFromXml()) { startupErrorMessage("Unable to load items (XML)!"); @@ -270,7 +306,7 @@ void mainLoader(int, char*[], ServiceManager* services) } std::cout << ">> Checking world type... " << std::flush; - std::string worldType = asLowerCaseString(g_config.getString(ConfigManager::WORLD_TYPE)); + std::string worldType = boost::algorithm::to_lower_copy(g_config.getString(ConfigManager::WORLD_TYPE)); if (worldType == "pvp") { g_game.setWorldType(WORLD_TYPE_PVP); } else if (worldType == "no-pvp") { @@ -279,10 +315,12 @@ void mainLoader(int, char*[], ServiceManager* services) g_game.setWorldType(WORLD_TYPE_PVP_ENFORCED); } else { std::cout << std::endl; - startupErrorMessage(fmt::format("Unknown world type: {:s}, valid world types are: pvp, no-pvp and pvp-enforced.", g_config.getString(ConfigManager::WORLD_TYPE))); + startupErrorMessage( + fmt::format("Unknown world type: {:s}, valid world types are: pvp, no-pvp and pvp-enforced.", + g_config.getString(ConfigManager::WORLD_TYPE))); return; } - std::cout << asUpperCaseString(worldType) << std::endl; + std::cout << boost::algorithm::to_upper_copy(worldType) << std::endl; std::cout << ">> Loading map" << std::endl; if (!g_game.loadMainMap(g_config.getString(ConfigManager::MAP_NAME))) { @@ -304,7 +342,7 @@ void mainLoader(int, char*[], ServiceManager* services) services->add(static_cast(g_config.getNumber(ConfigManager::LOGIN_PORT))); RentPeriod_t rentPeriod; - std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); + std::string strRentPeriod = boost::algorithm::to_lower_copy(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); if (strRentPeriod == "yearly") { rentPeriod = RENTPERIOD_YEARLY; @@ -327,7 +365,8 @@ void mainLoader(int, char*[], ServiceManager* services) #ifndef _WIN32 if (getuid() == 0 || geteuid() == 0) { - std::cout << "> Warning: " << STATUS_SERVER_NAME << " has been executed as root user, please consider running it as a normal user." << std::endl; + std::cout << "> Warning: " << STATUS_SERVER_NAME + << " has been executed as root user, please consider running it as a normal user." << std::endl; } #endif @@ -341,12 +380,12 @@ bool argumentsHandler(const StringVector& args) for (const auto& arg : args) { if (arg == "--help") { std::clog << "Usage:\n" - "\n" - "\t--config=$1\t\tAlternate configuration file path.\n" - "\t--ip=$1\t\t\tIP address of the server.\n" - "\t\t\t\tShould be equal to the global IP.\n" - "\t--login-port=$1\tPort for login server to listen on.\n" - "\t--game-port=$1\tPort for game server to listen on.\n"; + "\n" + "\t--config=$1\t\tAlternate configuration file path.\n" + "\t--ip=$1\t\t\tIP address of the server.\n" + "\t\t\t\tShould be equal to the global IP.\n" + "\t--login-port=$1\tPort for login server to listen on.\n" + "\t--game-port=$1\tPort for game server to listen on.\n"; return false; } else if (arg == "--version") { printServerVersion(); diff --git a/src/outfit.cpp b/src/outfit.cpp index a3d8d701a0..fc5670b524 100644 --- a/src/outfit.cpp +++ b/src/outfit.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -57,11 +41,8 @@ bool Outfits::loadFromXml() } outfits[type].emplace_back( - outfitNode.attribute("name").as_string(), - pugi::cast(lookTypeAttribute.value()), - outfitNode.attribute("premium").as_bool(), - outfitNode.attribute("unlocked").as_bool(true) - ); + outfitNode.attribute("name").as_string(), pugi::cast(lookTypeAttribute.value()), + outfitNode.attribute("premium").as_bool(), outfitNode.attribute("unlocked").as_bool(true)); } return true; } diff --git a/src/outfit.h b/src/outfit.h index 9e90a9f108..84900ef232 100644 --- a/src/outfit.h +++ b/src/outfit.h @@ -1,34 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_OUTFIT_H_C56E7A707E3F422C8C93D9BE09916AA3 -#define FS_OUTFIT_H_C56E7A707E3F422C8C93D9BE09916AA3 +#ifndef FS_OUTFIT_H +#define FS_OUTFIT_H #include "enums.h" -struct Outfit { +struct Outfit +{ Outfit(std::string name, uint16_t lookType, bool premium, bool unlocked) : - name(std::move(name)), lookType(lookType), premium(premium), unlocked(unlocked) {} + name(std::move(name)), lookType(lookType), premium(premium), unlocked(unlocked) + {} bool operator==(const Outfit& otherOutfit) const { - return name == otherOutfit.name && lookType == otherOutfit.lookType && premium == otherOutfit.premium && unlocked == otherOutfit.unlocked; + return name == otherOutfit.name && lookType == otherOutfit.lookType && premium == otherOutfit.premium && + unlocked == otherOutfit.unlocked; } std::string name; @@ -37,9 +24,11 @@ struct Outfit { bool unlocked; }; -struct ProtocolOutfit { +struct ProtocolOutfit +{ ProtocolOutfit(const std::string& name, uint16_t lookType, uint8_t addons) : - name(name), lookType(lookType), addons(addons) {} + name(name), lookType(lookType), addons(addons) + {} const std::string& name; uint16_t lookType; @@ -48,22 +37,21 @@ struct ProtocolOutfit { class Outfits { - public: - static Outfits& getInstance() { - static Outfits instance; - return instance; - } +public: + static Outfits& getInstance() + { + static Outfits instance; + return instance; + } - bool loadFromXml(); + bool loadFromXml(); - const Outfit* getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const; - const Outfit* getOutfitByLookType(uint16_t lookType) const; - const std::vector& getOutfits(PlayerSex_t sex) const { - return outfits[sex]; - } + const Outfit* getOutfitByLookType(PlayerSex_t sex, uint16_t lookType) const; + const Outfit* getOutfitByLookType(uint16_t lookType) const; + const std::vector& getOutfits(PlayerSex_t sex) const { return outfits[sex]; } - private: - std::vector outfits[PLAYERSEX_LAST + 1]; +private: + std::vector outfits[PLAYERSEX_LAST + 1]; }; -#endif +#endif // FS_OUTFIT_H diff --git a/src/outputmessage.cpp b/src/outputmessage.cpp index 3cb0966a69..9b6ebc3822 100644 --- a/src/outputmessage.cpp +++ b/src/outputmessage.cpp @@ -1,27 +1,12 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "outputmessage.h" -#include "protocol.h" + #include "lockfree.h" +#include "protocol.h" #include "scheduler.h" extern Scheduler g_scheduler; @@ -29,18 +14,19 @@ extern Scheduler g_scheduler; namespace { const uint16_t OUTPUTMESSAGE_FREE_LIST_CAPACITY = 2048; -const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY {10}; +const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY{10}; void sendAll(const std::vector& bufferedProtocols); void scheduleSendAll(const std::vector& bufferedProtocols) { - g_scheduler.addEvent(createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), [&]() { sendAll(bufferedProtocols); })); + g_scheduler.addEvent( + createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), [&]() { sendAll(bufferedProtocols); })); } void sendAll(const std::vector& bufferedProtocols) { - //dispatcher thread + // dispatcher thread for (auto& protocol : bufferedProtocols) { auto& msg = protocol->getCurrentBuffer(); if (msg) { @@ -53,11 +39,11 @@ void sendAll(const std::vector& bufferedProtocols) } } -} +} // namespace void OutputMessagePool::addProtocolToAutosend(Protocol_ptr protocol) { - //dispatcher thread + // dispatcher thread if (bufferedProtocols.empty()) { scheduleSendAll(bufferedProtocols); } @@ -66,7 +52,7 @@ void OutputMessagePool::addProtocolToAutosend(Protocol_ptr protocol) void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) { - //dispatcher thread + // dispatcher thread auto it = std::find(bufferedProtocols.begin(), bufferedProtocols.end(), protocol); if (it != bufferedProtocols.end()) { std::swap(*it, bufferedProtocols.back()); @@ -76,7 +62,7 @@ void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) OutputMessage_ptr OutputMessagePool::getOutputMessage() { - // LockfreePoolingAllocator will leave (void* allocate) ill-formed because - // of sizeof(T), so this guarantees that only one list will be initialized + // LockfreePoolingAllocator will leave (void* allocate) ill-formed because of sizeof(T), so this + // guarantees that only one list will be initialized return std::allocate_shared(LockfreePoolingAllocator()); } diff --git a/src/outputmessage.h b/src/outputmessage.h index 6e749243f3..efa5f14988 100644 --- a/src/outputmessage.h +++ b/src/outputmessage.h @@ -1,104 +1,90 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1 -#define FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_OUTPUTMESSAGE_H +#define FS_OUTPUTMESSAGE_H -#include "networkmessage.h" #include "connection.h" +#include "networkmessage.h" #include "tools.h" -class Protocol; - class OutputMessage : public NetworkMessage { - public: - OutputMessage() = default; - - // non-copyable - OutputMessage(const OutputMessage&) = delete; - OutputMessage& operator=(const OutputMessage&) = delete; - - uint8_t* getOutputBuffer() { - return buffer + outputBufferStart; - } - - void writeMessageLength() { - add_header(info.length); - } - - void addCryptoHeader(bool addChecksum) { - if (addChecksum) { - add_header(adlerChecksum(buffer + outputBufferStart, info.length)); - } +public: + OutputMessage() = default; - writeMessageLength(); - } + // non-copyable + OutputMessage(const OutputMessage&) = delete; + OutputMessage& operator=(const OutputMessage&) = delete; - void append(const NetworkMessage& msg) { - auto msgLen = msg.getLength(); - memcpy(buffer + info.position, msg.getBuffer() + 8, msgLen); - info.length += msgLen; - info.position += msgLen; - } + uint8_t* getOutputBuffer() { return buffer + outputBufferStart; } - void append(const OutputMessage_ptr& msg) { - auto msgLen = msg->getLength(); - memcpy(buffer + info.position, msg->getBuffer() + 8, msgLen); - info.length += msgLen; - info.position += msgLen; - } + void writeMessageLength() { add_header(info.length); } - private: - template - void add_header(T add) { - assert(outputBufferStart >= sizeof(T)); - outputBufferStart -= sizeof(T); - memcpy(buffer + outputBufferStart, &add, sizeof(T)); - //added header size to the message size - info.length += sizeof(T); + void addCryptoHeader(checksumMode_t mode, uint32_t& sequence) + { + if (mode == CHECKSUM_ADLER) { + add_header(adlerChecksum(buffer + outputBufferStart, info.length)); + } else if (mode == CHECKSUM_SEQUENCE) { + add_header(sequence++); } - MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION; + writeMessageLength(); + } + + void append(const NetworkMessage& msg) + { + auto msgLen = msg.getLength(); + memcpy(buffer + info.position, msg.getBuffer() + 8, msgLen); + info.length += msgLen; + info.position += msgLen; + } + + void append(const OutputMessage_ptr& msg) + { + auto msgLen = msg->getLength(); + memcpy(buffer + info.position, msg->getBuffer() + 8, msgLen); + info.length += msgLen; + info.position += msgLen; + } + +private: + template + void add_header(T add) + { + assert(outputBufferStart >= sizeof(T)); + outputBufferStart -= sizeof(T); + memcpy(buffer + outputBufferStart, &add, sizeof(T)); + // added header size to the message size + info.length += sizeof(T); + } + + MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION; }; class OutputMessagePool { - public: - // non-copyable - OutputMessagePool(const OutputMessagePool&) = delete; - OutputMessagePool& operator=(const OutputMessagePool&) = delete; - - static OutputMessagePool& getInstance() { - static OutputMessagePool instance; - return instance; - } - - static OutputMessage_ptr getOutputMessage(); - - void addProtocolToAutosend(Protocol_ptr protocol); - void removeProtocolFromAutosend(const Protocol_ptr& protocol); - private: - OutputMessagePool() = default; - //NOTE: A vector is used here because this container is mostly read - //and relatively rarely modified (only when a client connects/disconnects) - std::vector bufferedProtocols; +public: + // non-copyable + OutputMessagePool(const OutputMessagePool&) = delete; + OutputMessagePool& operator=(const OutputMessagePool&) = delete; + + static OutputMessagePool& getInstance() + { + static OutputMessagePool instance; + return instance; + } + + static OutputMessage_ptr getOutputMessage(); + + void addProtocolToAutosend(Protocol_ptr protocol); + void removeProtocolFromAutosend(const Protocol_ptr& protocol); + +private: + OutputMessagePool() = default; + // NOTE: A vector is used here because this container is mostly read and relatively rarely modified (only when a + // client connects/disconnects) + std::vector bufferedProtocols; }; -#endif +#endif // FS_OUTPUTMESSAGE_H diff --git a/src/party.cpp b/src/party.cpp index 1f1cd50131..ced65bb32e 100644 --- a/src/party.cpp +++ b/src/party.cpp @@ -1,37 +1,19 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "party.h" -#include "game.h" + #include "configmanager.h" #include "events.h" +#include "game.h" extern Game g_game; extern ConfigManager g_config; extern Events* g_events; -Party::Party(Player* leader) : leader(leader) -{ - leader->setParty(this); -} +Party::Party(Player* leader) : leader(leader) { leader->setParty(this); } void Party::disband() { @@ -45,7 +27,6 @@ void Party::disband() currentLeader->setParty(nullptr); currentLeader->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(currentLeader); - g_game.updatePlayerHelpers(*currentLeader); currentLeader->sendCreatureSkull(currentLeader); currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); @@ -70,7 +51,6 @@ void Party::disband() member->sendCreatureSkull(currentLeader); currentLeader->sendCreatureSkull(member); - g_game.updatePlayerHelpers(*member); } memberList.clear(); delete this; @@ -103,7 +83,7 @@ bool Party::leaveParty(Player* player) } } - //since we already passed the leadership, we remove the player from the list + // since we already passed the leadership, we remove the player from the list auto it = std::find(memberList.begin(), memberList.end(), player); if (it != memberList.end()) { memberList.erase(it); @@ -112,27 +92,28 @@ bool Party::leaveParty(Player* player) player->setParty(nullptr); player->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(player); - g_game.updatePlayerHelpers(*player); for (Player* member : memberList) { member->sendCreatureSkull(player); player->sendPlayerPartyIcons(member); - g_game.updatePlayerHelpers(*member); } leader->sendCreatureSkull(player); player->sendCreatureSkull(player); player->sendPlayerPartyIcons(leader); + // remove pending invitation icons from the screen + for (Player* invitee : inviteList) { + player->sendCreatureShield(invitee); + } + player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party."); updateSharedExperience(); clearPlayerPoints(player); - std::ostringstream ss; - ss << player->getName() << " has left the party."; - broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str()); + broadcastPartyMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has left the party.", player->getName())); if (missingLeader || empty()) { disband(); @@ -147,15 +128,14 @@ bool Party::passPartyLeadership(Player* player) return false; } - //Remove it before to broadcast the message correctly + // Remove it before to broadcast the message correctly auto it = std::find(memberList.begin(), memberList.end(), player); if (it != memberList.end()) { memberList.erase(it); } - std::ostringstream ss; - ss << player->getName() << " is now the leader of the party."; - broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str(), true); + broadcastPartyMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} is now the leader of the party.", player->getName()), + true); Player* oldLeader = leader; leader = player; @@ -183,50 +163,57 @@ bool Party::passPartyLeadership(Player* player) bool Party::joinParty(Player& player) { + // check if lua scripts allow the player to join if (!g_events->eventPartyOnJoin(this, &player)) { return false; } - auto it = std::find(inviteList.begin(), inviteList.end(), &player); - if (it == inviteList.end()) { - return false; + // first player accepted the invitation the party gets officially formed the leader can no longer take invitations + // from others + if (memberList.empty()) { + leader->clearPartyInvitations(); } - inviteList.erase(it); - - std::ostringstream ss; - ss << player.getName() << " has joined the party."; - broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str()); - + // add player to the party + memberList.push_back(&player); player.setParty(this); + broadcastPartyMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has joined the party.", player.getName())); + // remove player pending invitations to this and other parties + player.clearPartyInvitations(); + + // update player icon on the screen g_game.updatePlayerShield(&player); + // update player-member party icons for (Player* member : memberList) { member->sendCreatureSkull(&player); player.sendPlayerPartyIcons(member); } - player.sendCreatureSkull(&player); + // update player-leader party icons leader->sendCreatureSkull(&player); player.sendPlayerPartyIcons(leader); + // update player own skull + player.sendCreatureSkull(&player); - memberList.push_back(&player); - - g_game.updatePlayerHelpers(player); + // show the new member who else is invited + for (Player* invitee : inviteList) { + player.sendCreatureShield(invitee); + } - player.removePartyInvitation(this); + // check the party eligibility for shared experience updateSharedExperience(); const std::string& leaderName = leader->getName(); - ss.str(std::string()); - ss << "You have joined " << leaderName << "'" << (leaderName.back() == 's' ? "" : "s") << - " party. Open the party channel to communicate with your companions."; - player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + player.sendTextMessage( + MESSAGE_INFO_DESCR, + fmt::format("You have joined {:s}'{:s} party. Open the party channel to communicate with your companions.", + leaderName, leaderName.back() == 's' ? "" : "s")); return true; } -bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) +bool Party::removeInvite(Player& player, bool removeFromPlayer /* = true*/) { auto it = std::find(inviteList.begin(), inviteList.end(), &player); if (it == inviteList.end()) { @@ -244,12 +231,6 @@ bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) if (empty()) { disband(); - } else { - for (Player* member : memberList) { - g_game.updatePlayerHelpers(*member); - } - - g_game.updatePlayerHelpers(*leader); } return true; @@ -257,14 +238,9 @@ bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) void Party::revokeInvitation(Player& player) { - std::ostringstream ss; - ss << leader->getName() << " has revoked " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " invitation."; - player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); - - ss.str(std::string()); - ss << "Invitation for " << player.getName() << " has been revoked."; - leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); - + player.sendTextMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has revoked {:s} invitation.", leader->getName(), + leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his")); + leader->sendTextMessage(MESSAGE_INFO_DESCR, fmt::format("Invitation for {:s} has been revoked.", player.getName())); removeInvite(player); } @@ -274,32 +250,32 @@ bool Party::invitePlayer(Player& player) return false; } - std::ostringstream ss; - ss << player.getName() << " has been invited."; - if (empty()) { - ss << " Open the party channel to communicate with your members."; + leader->sendTextMessage( + MESSAGE_INFO_DESCR, + fmt::format("{:s} has been invited. Open the party channel to communicate with your members.", + player.getName())); g_game.updatePlayerShield(leader); leader->sendCreatureSkull(leader); + } else { + leader->sendTextMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has been invited.", player.getName())); } - leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); - + // add player to invite lists inviteList.push_back(&player); + player.addPartyInvitation(this); - for (Player* member : memberList) { - g_game.updatePlayerHelpers(*member); - } - g_game.updatePlayerHelpers(*leader); - + // update leader-invitee party status leader->sendCreatureShield(&player); player.sendCreatureShield(leader); - player.addPartyInvitation(this); + // update the invitation status for other members + for (Player* member : memberList) { + member->sendCreatureShield(&player); + } - ss.str(std::string()); - ss << leader->getName() << " has invited you to " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " party."; - player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + player.sendTextMessage(MESSAGE_INFO_DESCR, fmt::format("{:s} has invited you to {:s} party.", leader->getName(), + leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his")); return true; } @@ -339,7 +315,7 @@ void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& ms void Party::updateSharedExperience() { if (sharedExpActive) { - bool result = canEnableSharedExperience(); + bool result = getSharedExperienceStatus() == SHAREDEXP_OK; if (result != sharedExpEnabled) { sharedExpEnabled = result; updateAllPartyIcons(); @@ -347,6 +323,28 @@ void Party::updateSharedExperience() } } +namespace { + +const char* getSharedExpReturnMessage(SharedExpStatus_t value) +{ + switch (value) { + case SHAREDEXP_OK: + return "Shared Experience is now active."; + case SHAREDEXP_TOOFARAWAY: + return "Shared Experience has been activated, but some members of your party are too far away."; + case SHAREDEXP_LEVELDIFFTOOLARGE: + return "Shared Experience has been activated, but the level spread of your party is too wide."; + case SHAREDEXP_MEMBERINACTIVE: + return "Shared Experience has been activated, but some members of your party are inactive."; + case SHAREDEXP_EMPTYPARTY: + return "Shared Experience has been activated, but you are alone in your party."; + default: + return "An error occured. Unable to activate shared experience."; + } +} + +} // namespace + bool Party::setSharedExperience(Player* player, bool sharedExpActive) { if (!player || leader != player) { @@ -360,13 +358,9 @@ bool Party::setSharedExperience(Player* player, bool sharedExpActive) this->sharedExpActive = sharedExpActive; if (sharedExpActive) { - this->sharedExpEnabled = canEnableSharedExperience(); - - if (this->sharedExpEnabled) { - leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience is now active."); - } else { - leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been activated, but some members of your party are inactive."); - } + SharedExpStatus_t sharedExpStatus = getSharedExperienceStatus(); + this->sharedExpEnabled = sharedExpStatus == SHAREDEXP_OK; + leader->sendTextMessage(MESSAGE_INFO_DESCR, getSharedExpReturnMessage(sharedExpStatus)); } else { leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been deactivated."); } @@ -375,7 +369,7 @@ bool Party::setSharedExperience(Player* player, bool sharedExpActive) return true; } -void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/) +void Party::shareExperience(uint64_t experience, Creature* source /* = nullptr*/) { uint64_t shareExperience = experience; g_events->eventPartyOnShareExperience(this, shareExperience); @@ -387,9 +381,14 @@ void Party::shareExperience(uint64_t experience, Creature* source/* = nullptr*/) } bool Party::canUseSharedExperience(const Player* player) const +{ + return getMemberSharedExperienceStatus(player) == SHAREDEXP_OK; +} + +SharedExpStatus_t Party::getMemberSharedExperienceStatus(const Player* player) const { if (memberList.empty()) { - return false; + return SHAREDEXP_EMPTYPARTY; } uint32_t highestLevel = leader->getLevel(); @@ -401,40 +400,43 @@ bool Party::canUseSharedExperience(const Player* player) const uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); if (player->getLevel() < minLevel) { - return false; + return SHAREDEXP_LEVELDIFFTOOLARGE; } - if (!Position::areInRange<30, 30, 1>(leader->getPosition(), player->getPosition())) { - return false; + if (!Position::areInRange( + leader->getPosition(), player->getPosition())) { + return SHAREDEXP_TOOFARAWAY; } if (!player->hasFlag(PlayerFlag_NotGainInFight)) { - //check if the player has healed/attacked anything recently + // check if the player has healed/attacked anything recently auto it = ticksMap.find(player->getID()); if (it == ticksMap.end()) { - return false; + return SHAREDEXP_MEMBERINACTIVE; } uint64_t timeDiff = OTSYS_TIME() - it->second; if (timeDiff > static_cast(g_config.getNumber(ConfigManager::PZ_LOCKED))) { - return false; + return SHAREDEXP_MEMBERINACTIVE; } } - return true; + return SHAREDEXP_OK; } -bool Party::canEnableSharedExperience() +SharedExpStatus_t Party::getSharedExperienceStatus() { - if (!canUseSharedExperience(leader)) { - return false; + SharedExpStatus_t leaderStatus = getMemberSharedExperienceStatus(leader); + if (leaderStatus != SHAREDEXP_OK) { + return leaderStatus; } for (Player* member : memberList) { - if (!canUseSharedExperience(member)) { - return false; + SharedExpStatus_t memberStatus = getMemberSharedExperienceStatus(member); + if (memberStatus != SHAREDEXP_OK) { + return memberStatus; } } - return true; + return SHAREDEXP_OK; } void Party::updatePlayerTicks(Player* player, uint32_t points) diff --git a/src/party.h b/src/party.h index 50c10c09ba..403538ed02 100644 --- a/src/party.h +++ b/src/party.h @@ -1,97 +1,77 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044 -#define FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044 - -#include "player.h" -#include "monsters.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. +#ifndef FS_PARTY_H +#define FS_PARTY_H + +#include "const.h" + +class Creature; class Player; -class Party; using PlayerVector = std::vector; +static constexpr int32_t EXPERIENCE_SHARE_RANGE = 30; +static constexpr int32_t EXPERIENCE_SHARE_FLOORS = 1; + +enum SharedExpStatus_t : uint8_t +{ + SHAREDEXP_OK, + SHAREDEXP_TOOFARAWAY, + SHAREDEXP_LEVELDIFFTOOLARGE, + SHAREDEXP_MEMBERINACTIVE, + SHAREDEXP_EMPTYPARTY +}; + class Party { - public: - explicit Party(Player* leader); - - Player* getLeader() const { - return leader; - } - PlayerVector& getMembers() { - return memberList; - } - const PlayerVector& getInvitees() const { - return inviteList; - } - size_t getMemberCount() const { - return memberList.size(); - } - size_t getInvitationCount() const { - return inviteList.size(); - } - - void disband(); - bool invitePlayer(Player& player); - bool joinParty(Player& player); - void revokeInvitation(Player& player); - bool passPartyLeadership(Player* player); - bool leaveParty(Player* player); - - bool removeInvite(Player& player, bool removeFromPlayer = true); - - bool isPlayerInvited(const Player* player) const; - void updateAllPartyIcons(); - void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false); - bool empty() const { - return memberList.empty() && inviteList.empty(); - } - bool canOpenCorpse(uint32_t ownerId) const; - - void shareExperience(uint64_t experience, Creature* source = nullptr); - bool setSharedExperience(Player* player, bool sharedExpActive); - bool isSharedExperienceActive() const { - return sharedExpActive; - } - bool isSharedExperienceEnabled() const { - return sharedExpEnabled; - } - bool canUseSharedExperience(const Player* player) const; - void updateSharedExperience(); - - void updatePlayerTicks(Player* player, uint32_t points); - void clearPlayerPoints(Player* player); - - private: - bool canEnableSharedExperience(); - - std::map ticksMap; - - PlayerVector memberList; - PlayerVector inviteList; - - Player* leader; - - bool sharedExpActive = false; - bool sharedExpEnabled = false; +public: + explicit Party(Player* leader); + + Player* getLeader() const { return leader; } + PlayerVector& getMembers() { return memberList; } + const PlayerVector& getInvitees() const { return inviteList; } + size_t getMemberCount() const { return memberList.size(); } + size_t getInvitationCount() const { return inviteList.size(); } + + void disband(); + bool invitePlayer(Player& player); + bool joinParty(Player& player); + void revokeInvitation(Player& player); + bool passPartyLeadership(Player* player); + bool leaveParty(Player* player); + + bool removeInvite(Player& player, bool removeFromPlayer = true); + + bool isPlayerInvited(const Player* player) const; + void updateAllPartyIcons(); + void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false); + bool empty() const { return memberList.empty() && inviteList.empty(); } + bool canOpenCorpse(uint32_t ownerId) const; + + void shareExperience(uint64_t experience, Creature* source = nullptr); + bool setSharedExperience(Player* player, bool sharedExpActive); + bool isSharedExperienceActive() const { return sharedExpActive; } + bool isSharedExperienceEnabled() const { return sharedExpEnabled; } + bool canUseSharedExperience(const Player* player) const; + SharedExpStatus_t getMemberSharedExperienceStatus(const Player* player) const; + void updateSharedExperience(); + + void updatePlayerTicks(Player* player, uint32_t points); + void clearPlayerPoints(Player* player); + +private: + SharedExpStatus_t getSharedExperienceStatus(); + + std::map ticksMap; + + PlayerVector memberList; + PlayerVector inviteList; + + Player* leader; + + bool sharedExpActive = false; + bool sharedExpEnabled = false; }; -#endif +#endif // FS_PARTY_H diff --git a/src/player.cpp b/src/player.cpp index 12ec6f3e3c..81726b1c36 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,37 +1,29 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include +#include "player.h" #include "bed.h" #include "chat.h" #include "combat.h" #include "configmanager.h" #include "creatureevent.h" +#include "depotchest.h" #include "events.h" #include "game.h" +#include "inbox.h" #include "iologindata.h" #include "monster.h" #include "movement.h" +#include "npc.h" +#include "outfit.h" +#include "party.h" #include "scheduler.h" +#include "spectators.h" +#include "storeinbox.h" +#include "tools.h" #include "weapons.h" extern ConfigManager g_config; @@ -46,9 +38,15 @@ extern Events* g_events; MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; +uint32_t Player::playerIDLimit = 0x20000000; Player::Player(ProtocolGame_ptr p) : - Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), inbox(new Inbox(ITEM_INBOX)), storeInbox(new StoreInbox(ITEM_STORE_INBOX)), client(std::move(p)) + Creature(), + lastPing(OTSYS_TIME()), + lastPong(lastPing), + client(std::move(p)), + inbox(new Inbox(ITEM_INBOX)), + storeInbox(new StoreInbox(ITEM_STORE_INBOX)) { inbox->incrementReferenceCounter(); @@ -65,9 +63,8 @@ Player::~Player() } } - for (const auto& it : depotLockerMap) { - it.second->removeInbox(inbox); - it.second->decrementReferenceCounter(); + if (depotLocker) { + depotLocker->removeInbox(inbox); } inbox->decrementReferenceCounter(); @@ -79,6 +76,22 @@ Player::~Player() setEditHouse(nullptr); } +void Player::setID() +{ + if (id == 0) { + // allowClones id assignment + if (g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + id = playerAutoID++; + return; + } + + // normal id assignment + if (guid != 0) { + id = playerAutoID + guid; + } + } +} + bool Player::setVocation(uint16_t vocId) { Vocation* voc = g_vocations.getVocation(vocId); @@ -87,13 +100,7 @@ bool Player::setVocation(uint16_t vocId) } vocation = voc; - Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); - if (condition) { - condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount()); - condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); - condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount()); - condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000); - } + updateRegeneration(); return true; } @@ -199,15 +206,9 @@ Item* Player::getInventoryItem(slots_t slot) const return inventory[slot]; } -void Player::addConditionSuppressions(uint32_t conditions) -{ - conditionSuppressions |= conditions; -} +void Player::addConditionSuppressions(uint32_t conditions) { conditionSuppressions |= conditions; } -void Player::removeConditionSuppressions(uint32_t conditions) -{ - conditionSuppressions &= ~conditions; -} +void Player::removeConditionSuppressions(uint32_t conditions) { conditionSuppressions &= ~conditions; } Item* Player::getWeapon(slots_t slot, bool ignoreAmmo) const { @@ -234,7 +235,7 @@ Item* Player::getWeapon(slots_t slot, bool ignoreAmmo) const return item; } -Item* Player::getWeapon(bool ignoreAmmo/* = false*/) const +Item* Player::getWeapon(bool ignoreAmmo /* = false*/) const { Item* item = getWeapon(CONST_SLOT_LEFT, ignoreAmmo); if (item) { @@ -299,7 +300,8 @@ int32_t Player::getArmor() const { int32_t armor = 0; - static const slots_t armorSlots[] = {CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING}; + static const slots_t armorSlots[] = {CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, + CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING}; for (slots_t slot : armorSlots) { Item* inventoryItem = inventory[slot]; if (inventoryItem) { @@ -353,7 +355,7 @@ int32_t Player::getDefense() const } if (shield) { - defenseValue = weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense(); + defenseValue = weapon ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense(); defenseSkill = getSkillLevel(SKILL_SHIELD); } @@ -371,29 +373,47 @@ int32_t Player::getDefense() const return (defenseSkill / 4. + 2.23) * defenseValue * 0.15 * getDefenseFactor() * vocation->defenseMultiplier; } +uint32_t Player::getAttackSpeed() const +{ + const Item* weapon = getWeapon(true); + if (!weapon || weapon->getAttackSpeed() == 0) { + return vocation->getAttackSpeed(); + } + + return weapon->getAttackSpeed(); +} + float Player::getAttackFactor() const { switch (fightMode) { - case FIGHTMODE_ATTACK: return 1.0f; - case FIGHTMODE_BALANCED: return 1.2f; - case FIGHTMODE_DEFENSE: return 2.0f; - default: return 1.0f; + case FIGHTMODE_ATTACK: + return 1.0f; + case FIGHTMODE_BALANCED: + return 1.2f; + case FIGHTMODE_DEFENSE: + return 2.0f; + default: + return 1.0f; } } float Player::getDefenseFactor() const { switch (fightMode) { - case FIGHTMODE_ATTACK: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.5f : 1.0f; - case FIGHTMODE_BALANCED: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.75f : 1.0f; - case FIGHTMODE_DEFENSE: return 1.0f; - default: return 1.0f; + case FIGHTMODE_ATTACK: + return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.5f : 1.0f; + case FIGHTMODE_BALANCED: + return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.75f : 1.0f; + case FIGHTMODE_DEFENSE: + return 1.0f; + default: + return 1.0f; } } -uint16_t Player::getClientIcons() const +uint32_t Player::getClientIcons() const { - uint16_t icons = 0; + uint32_t icons = 0; for (Condition* condition : conditions) { if (!isSuppress(condition->getType())) { icons |= condition->getIcons(); @@ -404,25 +424,14 @@ uint16_t Player::getClientIcons() const icons |= ICON_REDSWORDS; } - if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + if (tile && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { icons |= ICON_PIGEON; // Don't show ICON_SWORDS if player is in protection zone. - if (hasBitSet(ICON_SWORDS, icons)) { - icons &= ~ICON_SWORDS; - } + icons &= ~ICON_SWORDS; } - // Game client debugs with 10 or more icons - // so let's prevent that from happening. - std::bitset<20> icon_bitset(static_cast(icons)); - for (size_t pos = 0, bits_set = icon_bitset.count(); bits_set >= 10; ++pos) { - if (icon_bitset[pos]) { - icon_bitset.reset(pos); - --bits_set; - } - } - return icon_bitset.to_ulong(); + return icons; } void Player::updateInventoryWeight() @@ -438,6 +447,10 @@ void Player::updateInventoryWeight() inventoryWeight += item->getWeight(); } } + + if (StoreInbox* storeInbox = getStoreInbox()) { + inventoryWeight += storeInbox->getWeight(); + } } void Player::addSkillAdvance(skills_t skill, uint64_t count) @@ -445,7 +458,7 @@ void Player::addSkillAdvance(skills_t skill, uint64_t count) uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); if (currReqTries >= nextReqTries) { - //player has reached max skill + // player has reached max skill return; } @@ -461,9 +474,8 @@ void Player::addSkillAdvance(skills_t skill, uint64_t count) skills[skill].tries = 0; skills[skill].percent = 0; - std::ostringstream ss; - ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, + fmt::format("You advanced to {:s} level {:d}.", getSkillName(skill), skills[skill].level)); g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); @@ -495,6 +507,43 @@ void Player::addSkillAdvance(skills_t skill, uint64_t count) } } +void Player::removeSkillTries(skills_t skill, uint64_t count, bool notify /* = false*/) +{ + uint16_t oldLevel = skills[skill].level; + uint8_t oldPercent = skills[skill].percent; + + while (count > skills[skill].tries) { + count -= skills[skill].tries; + + if (skills[skill].level <= MINIMUM_SKILL_LEVEL) { + skills[skill].level = MINIMUM_SKILL_LEVEL; + skills[skill].tries = 0; + count = 0; + break; + } + + skills[skill].tries = vocation->getReqSkillTries(skill, skills[skill].level); + skills[skill].level--; + } + + skills[skill].tries = std::max(0, skills[skill].tries - count); + skills[skill].percent = + Player::getPercentLevel(skills[skill].tries, vocation->getReqSkillTries(skill, skills[skill].level)); + + if (notify) { + bool sendUpdateSkills = false; + if (oldLevel != skills[skill].level) { + sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You were downgraded to {:s} level {:d}.", + getSkillName(skill), skills[skill].level)); + sendUpdateSkills = true; + } + + if (sendUpdateSkills || oldPercent != skills[skill].percent) { + sendSkills(); + } + } +} + void Player::setVarStats(stats_t stat, int32_t modifier) { varStats[stat] += modifier; @@ -525,10 +574,14 @@ void Player::setVarStats(stats_t stat, int32_t modifier) int32_t Player::getDefaultStats(stats_t stat) const { switch (stat) { - case STAT_MAXHITPOINTS: return healthMax; - case STAT_MAXMANAPOINTS: return manaMax; - case STAT_MAGICPOINTS: return getBaseMagicLevel(); - default: return 0; + case STAT_MAXHITPOINTS: + return healthMax; + case STAT_MAXMANAPOINTS: + return manaMax; + case STAT_MAGICPOINTS: + return getBaseMagicLevel(); + default: + return 0; } } @@ -622,19 +675,15 @@ uint16_t Player::getLookCorpse() const { if (sex == PLAYERSEX_FEMALE) { return ITEM_FEMALE_CORPSE; - } else { - return ITEM_MALE_CORPSE; } + return ITEM_MALE_CORPSE; } -void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/) +void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin /* = false*/) { if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { - outfits.emplace_back( - value >> 16, - value & 0xFF - ); + outfits.emplace_back(value >> 16, value & 0xFF); return; } else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) { // do nothing @@ -656,6 +705,14 @@ void Player::addStorageValue(const uint32_t key, const int32_t value, const bool lastQuestlogUpdate = currentFrameTime; sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your questlog has been updated."); } + + for (const TrackedQuest& trackedQuest : trackedQuests) { + if (const Quest* quest = g_game.quests.getQuestByID(trackedQuest.getQuestId())) { + if (quest->isTracking(key, value)) { + sendUpdateQuestTracker(trackedQuest); + } + } + } } } else { storageMap.erase(key); @@ -688,7 +745,7 @@ bool Player::canSeeCreature(const Creature* creature) const return true; } - if (creature->isInGhostMode() && !group->access) { + if (creature->isInGhostMode() && !canSeeGhostMode(creature)) { return false; } @@ -698,6 +755,8 @@ bool Player::canSeeCreature(const Creature* creature) const return true; } +bool Player::canSeeGhostMode(const Creature*) const { return group->access; } + bool Player::canWalkthrough(const Creature* creature) const { if (group->access || creature->isInGhostMode()) { @@ -710,7 +769,9 @@ bool Player::canWalkthrough(const Creature* creature) const } const Tile* playerTile = player->getTile(); - if (!playerTile || (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)))) { + if (!playerTile || + (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && + player->getLevel() > static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)))) { return false; } @@ -746,7 +807,9 @@ bool Player::canWalkthroughEx(const Creature* creature) const } const Tile* playerTile = player->getTile(); - return playerTile && (playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL))); + return playerTile && + (playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || + player->getLevel() <= static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL))); } void Player::onReceiveMail() const @@ -759,8 +822,8 @@ void Player::onReceiveMail() const bool Player::isNearDepotBox() const { const Position& pos = getPosition(); - for (int32_t cx = -1; cx <= 1; ++cx) { - for (int32_t cy = -1; cy <= 1; ++cy) { + for (int32_t cx = -NOTIFY_DEPOT_BOX_RANGE; cx <= NOTIFY_DEPOT_BOX_RANGE; ++cx) { + for (int32_t cy = -NOTIFY_DEPOT_BOX_RANGE; cy <= NOTIFY_DEPOT_BOX_RANGE; ++cy) { Tile* tile = g_game.map.getTile(pos.x + cx, pos.y + cy, pos.z); if (!tile) { continue; @@ -785,35 +848,40 @@ DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate) return nullptr; } - DepotChest* depotChest = new DepotChest(ITEM_DEPOT); - depotChest->incrementReferenceCounter(); - depotChest->setMaxDepotItems(getMaxDepotItems()); - depotChests[depotId] = depotChest; - return depotChest; -} - -DepotLocker* Player::getDepotLocker(uint32_t depotId) -{ - auto it = depotLockerMap.find(depotId); - if (it != depotLockerMap.end()) { - inbox->setParent(it->second); - return it->second; + uint16_t depotItemId = getDepotBoxId(depotId); + if (depotItemId == 0) { + return nullptr; } - DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); - depotLocker->setDepotId(depotId); - depotLocker->internalAddThing(Item::CreateItem(ITEM_MARKET)); - depotLocker->internalAddThing(inbox); - depotLocker->internalAddThing(getDepotChest(depotId, true)); - depotLockerMap[depotId] = depotLocker; - return depotLocker; + it = depotChests.emplace(depotId, new DepotChest(depotItemId)).first; + it->second->setMaxDepotItems(getMaxDepotItems()); + return it->second; } -void Player::sendCancelMessage(ReturnValue message) const +DepotLocker& Player::getDepotLocker() { - sendCancelMessage(getReturnMessage(message)); + if (!depotLocker) { + depotLocker = std::make_shared(ITEM_LOCKER); + depotLocker->internalAddThing(Item::CreateItem(ITEM_MARKET)); + depotLocker->internalAddThing(inbox); + + DepotChest* depotChest = new DepotChest(ITEM_DEPOT, false); + if (depotChest) { + // adding in reverse to align them from first to last + for (int16_t depotId = depotChest->capacity(); depotId >= 0; --depotId) { + if (DepotChest* box = getDepotChest(depotId, true)) { + depotChest->internalAddThing(box); + } + } + + depotLocker->internalAddThing(depotChest); + } + } + return *depotLocker; } +void Player::sendCancelMessage(ReturnValue message) const { sendCancelMessage(getReturnMessage(message)); } + void Player::sendStats() { if (client) { @@ -841,13 +909,24 @@ void Player::sendPing() setAttackedCreature(nullptr); } - if (noPongTime >= 60000 && canLogout()) { - if (g_creatureEvents->playerLogout(this)) { - if (client) { - client->logout(true, true); - } else { - g_game.removeCreature(this, true); - } + int32_t noPongKickTime = vocation->getNoPongKickTime(); + if (pzLocked && noPongKickTime < 60000) { + noPongKickTime = 60000; + } + + if (noPongTime >= noPongKickTime) { + if (isConnecting || getTile()->hasFlag(TILESTATE_NOLOGOUT)) { + return; + } + + if (!g_creatureEvents->playerLogout(this)) { + return; + } + + if (client) { + client->logout(true, true); + } else { + g_game.removeCreature(this, true); } } } @@ -903,7 +982,7 @@ void Player::sendHouseWindow(House* house, uint32_t listId) const } } -//container +// container void Player::sendAddContainerItem(const Container* container, const Item* item) { if (!client) { @@ -979,12 +1058,54 @@ void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) sendContainer(it.first, container, false, firstIndex); } - client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex), container->getItemByIndex(container->capacity() + firstIndex)); + client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex), + container->getItemByIndex(container->capacity() + firstIndex)); } } -void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType) +void Player::openSavedContainers() +{ + std::map openContainersList; + + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + Container* itemContainer = item->getContainer(); + if (itemContainer) { + uint8_t cid = item->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (cid > 0) { + openContainersList.emplace(cid, itemContainer); + } + for (ContainerIterator it = itemContainer->iterator(); it.hasNext(); it.advance()) { + Container* subContainer = (*it)->getContainer(); + if (subContainer) { + uint8_t subcid = (*it)->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (subcid > 0) { + openContainersList.emplace(subcid, subContainer); + } + } + } + } + } + + // fix broken containers when logged in from another location + for (uint8_t i = 0; i < 16; i++) { + client->sendEmptyContainer(i); + client->sendCloseContainer(i); + } + + // send actual containers + for (auto& it : openContainersList) { + addContainer(it.first - 1, it.second); + onSendContainer(it.second); + } +} + +void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, const ItemType& oldType, + const Item* newItem, const ItemType& newType) { Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); @@ -999,8 +1120,7 @@ void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* } } -void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item) +void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, const Item* item) { Creature::onRemoveTileItem(tile, pos, iType, item); @@ -1036,14 +1156,31 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) } storedConditionList.clear(); + updateRegeneration(); + BedItem* bed = g_game.getBedBySleeper(guid); if (bed) { bed->wakeUp(this); } - Account account = IOLoginData::loadAccount(accountNumber); + // load mount speed bonus + uint16_t currentMountId = currentOutfit.lookMount; + if (currentMountId != 0) { + Mount* currentMount = g_game.mounts.getMountByClientID(currentMountId); + if (currentMount && hasMount(currentMount)) { + g_game.changeSpeed(this, currentMount->speed); + } else { + defaultOutfit.lookMount = 0; + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + } + } + + // mounted player moved to pz on login, update mount status + onChangeZone(getZone()); - std::cout << name << " has logged in." << std::endl; + if (g_config.getBoolean(ConfigManager::PLAYER_CONSOLE_LOGS)) { + std::cout << name << " has logged in." << std::endl; + } if (guild) { guild->addMember(this); @@ -1126,7 +1263,7 @@ void Player::onAttackedCreatureChangeZone(ZoneType_t zone) } } } else if (zone == ZONE_NORMAL) { - //attackedCreature can leave a pvp zone if not pzlocked + // attackedCreature can leave a pvp zone if not pzlocked if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { if (attackedCreature->getPlayer()) { setAttackedCreature(nullptr); @@ -1165,7 +1302,9 @@ void Player::onRemoveCreature(Creature* creature, bool isLogout) g_chat->removeUserFromAllChannels(*this); - std::cout << getName() << " has logged out." << std::endl; + if (g_config.getBoolean(ConfigManager::PLAYER_CONSOLE_LOGS)) { + std::cout << getName() << " has logged out." << std::endl; + } if (guild) { guild->removeMember(this); @@ -1196,7 +1335,7 @@ void Player::openShopWindow(Npc* npc, const std::list& shop) bool Player::closeShopWindow(bool sendCloseShopWindow /*= true*/) { - //unreference callbacks + // unreference callbacks int32_t onBuy; int32_t onSell; @@ -1224,14 +1363,14 @@ void Player::onWalk(Direction& dir) setNextAction(OTSYS_TIME() + getStepDuration(dir)); } -void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) +void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) { isUpdatingPath = false; - g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + g_dispatcher.addTask(createTask([id = getID()]() { g_game.updateCreatureWalk(id); })); } if (creature != this) { @@ -1239,7 +1378,7 @@ void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Posit } if (tradeState != TRADE_TRANSFER) { - //check if we should close trade + // check if we should close trade if (tradeItem && !Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) { g_game.internalCloseTrade(this); } @@ -1280,11 +1419,8 @@ void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Posit } } -//container -void Player::onAddContainerItem(const Item* item) -{ - checkTradeState(item); -} +// container +void Player::onAddContainerItem(const Item* item) { checkTradeState(item); } void Player::onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem) { @@ -1338,7 +1474,7 @@ void Player::onSendContainer(const Container* container) } } -//inventory +// inventory void Player::onUpdateInventoryItem(Item* oldItem, Item* newItem) { if (oldItem != newItem) { @@ -1424,10 +1560,7 @@ void Player::setNextActionTask(SchedulerTask* task, bool resetIdleTime /*= true } } -uint32_t Player::getNextActionTime() const -{ - return std::max(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME()); -} +uint32_t Player::getNextActionTime() const { return std::max(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME()); } void Player::onThink(uint32_t interval) { @@ -1447,9 +1580,11 @@ void Player::onThink(uint32_t interval) if (idleTime > (kickAfterMinutes * 60000) + 60000) { kickPlayer(true); } else if (client && idleTime == 60000 * kickAfterMinutes) { - std::ostringstream ss; - ss << "There was no variation in your behaviour for " << kickAfterMinutes << " minutes. You will be disconnected in one minute if there is no change in your actions until then."; - client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, ss.str())); + client->sendTextMessage(TextMessage( + MESSAGE_STATUS_WARNING, + fmt::format( + "There was no variation in your behaviour for {:d} minutes. You will be disconnected in one minute if there is no change in your actions until then.", + kickAfterMinutes))); } } @@ -1480,7 +1615,8 @@ uint32_t Player::isMuted() const void Player::addMessageBuffer() { - if (MessageBufferCount > 0 && g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER) != 0 && !hasFlag(PlayerFlag_CannotBeMuted)) { + if (MessageBufferCount > 0 && g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER) != 0 && + !hasFlag(PlayerFlag_CannotBeMuted)) { --MessageBufferCount; } } @@ -1505,9 +1641,7 @@ void Player::removeMessageBuffer() Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, muteTime * 1000, 0); addCondition(condition); - std::ostringstream ss; - ss << "You are muted for " << muteTime << " seconds."; - sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + sendTextMessage(MESSAGE_STATUS_SMALL, fmt::format("You are muted for {:d} seconds.", muteTime)); } } } @@ -1539,7 +1673,7 @@ void Player::addManaSpent(uint64_t amount) uint64_t currReqMana = vocation->getReqMana(magLevel); uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); if (currReqMana >= nextReqMana) { - //player has reached max magic level + // player has reached max magic level return; } @@ -1555,9 +1689,7 @@ void Player::addManaSpent(uint64_t amount) magLevel++; manaSpent = 0; - std::ostringstream ss; - ss << "You advanced to magic level " << magLevel << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced to magic level {:d}.", magLevel)); g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); @@ -1584,16 +1716,55 @@ void Player::addManaSpent(uint64_t amount) if (sendUpdateStats) { sendStats(); + sendSkills(); } } -void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = false*/) +void Player::removeManaSpent(uint64_t amount, bool notify /* = false*/) +{ + if (amount == 0) { + return; + } + + uint32_t oldLevel = magLevel; + uint8_t oldPercent = magLevelPercent; + + while (amount > manaSpent && magLevel > 0) { + amount -= manaSpent; + manaSpent = vocation->getReqMana(magLevel); + magLevel--; + } + + manaSpent -= amount; + + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + if (nextReqMana > vocation->getReqMana(magLevel)) { + magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + magLevelPercent = 0; + } + + if (notify) { + bool sendUpdateStats = false; + if (oldLevel != magLevel) { + sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You were downgraded to magic level {:d}.", magLevel)); + sendUpdateStats = true; + } + + if (sendUpdateStats || oldPercent != magLevelPercent) { + sendStats(); + sendSkills(); + } + } +} + +void Player::addExperience(Creature* source, uint64_t exp, bool sendText /* = false*/) { uint64_t currLevelExp = Player::getExpForLevel(level); uint64_t nextLevelExp = Player::getExpForLevel(level + 1); uint64_t rawExp = exp; if (currLevelExp >= nextLevelExp) { - //player has reached max level + // player has reached max level levelPercent = 0; sendStats(); return; @@ -1639,7 +1810,7 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal currLevelExp = nextLevelExp; nextLevelExp = Player::getExpForLevel(level + 1); if (currLevelExp >= nextLevelExp) { - //player has reached max level + // player has reached max level break; } } @@ -1665,9 +1836,8 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level); - std::ostringstream ss; - ss << "You advanced from Level " << prevLevel << " to Level " << level << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, + fmt::format("You advanced from Level {:d} to Level {:d}.", prevLevel, level)); } if (nextLevelExp > currLevelExp) { @@ -1676,9 +1846,11 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal levelPercent = 0; } sendStats(); + + sendExperienceTracker(rawExp * g_config.getExperienceStage(getLevel()), exp); } -void Player::removeExperience(uint64_t exp, bool sendText/* = false*/) +void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) { if (experience == 0 || exp == 0) { return; @@ -1745,9 +1917,8 @@ void Player::removeExperience(uint64_t exp, bool sendText/* = false*/) party->updateSharedExperience(); } - std::ostringstream ss; - ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, + fmt::format("You were downgraded from Level {:d} to Level {:d}.", oldLevel, level)); } uint64_t nextLevelExp = Player::getExpForLevel(level + 1); @@ -1757,6 +1928,8 @@ void Player::removeExperience(uint64_t exp, bool sendText/* = false*/) levelPercent = 0; } sendStats(); + + sendExperienceTracker(0, -static_cast(exp)); } uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) @@ -1797,7 +1970,7 @@ void Player::onAttackedCreatureBlockHit(BlockType_t blockType) case BLOCK_DEFENSE: case BLOCK_ARMOR: { - //need to draw blood every 30 hits + // need to draw blood every 30 hits if (bloodHitCount > 0) { addAttackSkillPoint = true; --bloodHitCount; @@ -1829,9 +2002,11 @@ bool Player::hasShield() const } BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/, bool ignoreResistances /* = false*/) + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/, + bool ignoreResistances /* = false*/) { - BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field, ignoreResistances); + BlockType_t blockType = + Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field, ignoreResistances); if (attacker) { sendCreatureSquare(attacker, SQ_COLOR_BLACK); @@ -1847,7 +2022,10 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ } if (!ignoreResistances) { - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_AMMO; ++slot) { + Reflect reflect; + + size_t combatIndex = combatTypeToIndex(combatType); + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { if (!isItemAbilityEnabled(static_cast(slot))) { continue; } @@ -1867,7 +2045,7 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ continue; } - const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; + const int16_t& absorbPercent = it.abilities->absorbPercent[combatIndex]; if (absorbPercent != 0) { damage -= std::round(damage * (absorbPercent / 100.)); @@ -1877,8 +2055,10 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ } } + reflect += item->getReflect(combatType); + if (field) { - const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; + const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatIndex]; if (fieldAbsorbPercent != 0) { damage -= std::round(damage * (fieldAbsorbPercent / 100.)); @@ -1889,6 +2069,14 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ } } } + + if (attacker && reflect.chance > 0 && reflect.percent != 0 && uniform_random(1, 100) <= reflect.chance) { + CombatDamage reflectDamage; + reflectDamage.primary.type = combatType; + reflectDamage.primary.value = -std::round(damage * (reflect.percent / 100.)); + reflectDamage.origin = ORIGIN_REFLECT; + g_game.combatChangeHealth(this, attacker, reflectDamage); + } } if (damage <= 0) { @@ -1934,65 +2122,29 @@ void Player::death(Creature* lastHitCreature) } } - //Magic level loss + // Magic level loss uint64_t sumMana = 0; - uint64_t lostMana = 0; - - //sum up all the mana for (uint32_t i = 1; i <= magLevel; ++i) { sumMana += vocation->getReqMana(i); } - sumMana += manaSpent; - double deathLossPercent = getLostPercent() * (unfairFightReduction / 100.); + removeManaSpent(static_cast((sumMana + manaSpent) * deathLossPercent), false); - lostMana = static_cast(sumMana * deathLossPercent); - - while (lostMana > manaSpent && magLevel > 0) { - lostMana -= manaSpent; - manaSpent = vocation->getReqMana(magLevel); - magLevel--; - } - - manaSpent -= lostMana; - - uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); - if (nextReqMana > vocation->getReqMana(magLevel)) { - magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); - } else { - magLevelPercent = 0; - } - - //Skill loss - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + // Skill loss + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { // for each skill uint64_t sumSkillTries = 0; - for (uint16_t c = 11; c <= skills[i].level; ++c) { //sum up all required tries for all skill levels + for (uint16_t c = MINIMUM_SKILL_LEVEL + 1; c <= skills[i].level; + ++c) { // sum up all required tries for all skill levels sumSkillTries += vocation->getReqSkillTries(i, c); } sumSkillTries += skills[i].tries; - uint32_t lostSkillTries = static_cast(sumSkillTries * deathLossPercent); - while (lostSkillTries > skills[i].tries) { - lostSkillTries -= skills[i].tries; - - if (skills[i].level <= 10) { - skills[i].level = 10; - skills[i].tries = 0; - lostSkillTries = 0; - break; - } - - skills[i].tries = vocation->getReqSkillTries(i, skills[i].level); - skills[i].level--; - } - - skills[i].tries = std::max(0, skills[i].tries - lostSkillTries); - skills[i].percent = Player::getPercentLevel(skills[i].tries, vocation->getReqSkillTries(i, skills[i].level)); + removeSkillTries(static_cast(i), sumSkillTries * deathLossPercent, false); } - //Level loss + // Level loss uint64_t expLoss = static_cast(experience * deathLossPercent); g_events->eventPlayerOnLoseExperience(this, expLoss); @@ -2011,9 +2163,8 @@ void Player::death(Creature* lastHitCreature) } if (oldLevel != level) { - std::ostringstream ss; - ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, + fmt::format("You were downgraded from Level {:d} to Level {:d}.", oldLevel, level)); } uint64_t currLevelExp = Player::getExpForLevel(level); @@ -2025,16 +2176,15 @@ void Player::death(Creature* lastHitCreature) } } - std::bitset<6> bitset(blessings); - if (bitset[5]) { + if (blessings.test(5)) { if (lastHitPlayer) { - bitset.reset(5); - blessings = bitset.to_ulong(); + blessings.reset(5); } else { - blessings = 32; + blessings.reset(); + blessings.set(5); } } else { - blessings = 0; + blessings.reset(); } sendStats(); @@ -2088,7 +2238,8 @@ void Player::death(Creature* lastHitCreature) } } -bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) +bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, + bool mostDamageUnjustified) { if (getZone() != ZONE_PVP || !Player::lastHitIsPlayer(lastHitCreature)) { return Creature::dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); @@ -2102,14 +2253,42 @@ Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) { Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); if (corpse && corpse->getContainer()) { - std::ostringstream ss; + std::unordered_map names; + for (const auto& killer : getKillers()) { + ++names[killer->getName()]; + } + if (lastHitCreature) { - ss << "You recognize " << getNameDescription() << ". " << (getSex() == PLAYERSEX_FEMALE ? "She" : "He") << " was killed by " << lastHitCreature->getNameDescription() << '.'; + if (!mostDamageCreature) { + corpse->setSpecialDescription( + fmt::format("You recognize {:s}. {:s} was killed by {:s}{:s}", getNameDescription(), + getSex() == PLAYERSEX_FEMALE ? "She" : "He", lastHitCreature->getNameDescription(), + names.size() > 1 ? " and others." : ".")); + } else if (lastHitCreature != mostDamageCreature && names[lastHitCreature->getName()] == 1) { + corpse->setSpecialDescription( + fmt::format("You recognize {:s}. {:s} was killed by {:s}, {:s}{:s}", getNameDescription(), + getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription(), + lastHitCreature->getNameDescription(), names.size() > 2 ? " and others." : ".")); + } else { + corpse->setSpecialDescription( + fmt::format("You recognize {:s}. {:s} was killed by {:s} and others.", getNameDescription(), + getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription())); + } + } else if (mostDamageCreature) { + if (names.size() > 1) { + corpse->setSpecialDescription(fmt::format( + "You recognize {:s}. {:s} was killed by something evil, {:s}, and others", getNameDescription(), + getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription())); + } else { + corpse->setSpecialDescription(fmt::format( + "You recognize {:s}. {:s} was killed by something evil and others", getNameDescription(), + getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription())); + } } else { - ss << "You recognize " << getNameDescription() << '.'; + corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by something evil {:s}", + getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", + names.size() ? " and others." : ".")); } - - corpse->setSpecialDescription(ss.str()); } return corpse; } @@ -2124,7 +2303,8 @@ void Player::addInFightTicks(bool pzlock /*= false*/) pzLocked = true; } - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::PZ_LOCKED), 0); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, + g_config.getNumber(ConfigManager::PZ_LOCKED), 0); addCondition(condition); } @@ -2226,7 +2406,7 @@ bool Player::editVIP(uint32_t vipGuid, const std::string& description, uint32_t return true; } -//close container and its child containers +// close container and its child containers void Player::autoCloseContainers(const Container* container) { std::vector closeList; @@ -2260,7 +2440,7 @@ bool Player::hasCapacity(const Item* item, uint32_t count) const return true; } - uint32_t itemWeight = item->getContainer() != nullptr ? item->getWeight() : item->getBaseWeight(); + uint32_t itemWeight = item->getContainer() ? item->getWeight() : item->getBaseWeight(); if (item->isStackable()) { itemWeight *= count; } @@ -2270,13 +2450,13 @@ bool Player::hasCapacity(const Item* item, uint32_t count) const ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature*) const { const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return RETURNVALUE_NOTPOSSIBLE; } bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); if (childIsOwner) { - //a child container is querying the player, just check if enough capacity + // a child container is querying the player, just check if enough capacity bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); if (skipLimit || hasCapacity(item, count)) { return RETURNVALUE_NOERROR; @@ -2292,13 +2472,12 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE; } - ReturnValue ret = RETURNVALUE_NOERROR; + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; const int32_t& slotPosition = item->getSlotPosition(); - if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || - (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || - (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || - (slotPosition & SLOTP_RING)) { + if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || (slotPosition & SLOTP_BACKPACK) || + (slotPosition & SLOTP_ARMOR) || (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || + (slotPosition & SLOTP_RING)) { ret = RETURNVALUE_CANNOTBEDRESSED; } else if (slotPosition & SLOTP_TWO_HAND) { ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS; @@ -2372,9 +2551,8 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, ret = RETURNVALUE_NOERROR; } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; - } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || - leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO - || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || leftType == WEAPON_SHIELD || + leftType == WEAPON_AMMO || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; @@ -2390,7 +2568,7 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, if (slotPosition & SLOTP_LEFT) { if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { WeaponType_t type = item->getWeaponType(); - if (type == WEAPON_NONE || type == WEAPON_SHIELD) { + if (type == WEAPON_NONE || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_CANNOTBEDRESSED; } else if (inventory[CONST_SLOT_RIGHT] && (slotPosition & SLOTP_TWO_HAND)) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; @@ -2413,9 +2591,8 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, ret = RETURNVALUE_NOERROR; } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; - } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || - rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO - || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || rightType == WEAPON_SHIELD || + rightType == WEAPON_AMMO || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; @@ -2469,17 +2646,20 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, return ret; } - //check if enough capacity + // check if enough capacity if (!hasCapacity(item, count)) { return RETURNVALUE_NOTENOUGHCAPACITY; } - ret = g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true); - if (ret != RETURNVALUE_NOERROR) { - return ret; + if (index != CONST_SLOT_WHEREEVER && index != -1) { // we don't try to equip whereever call + ret = g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), + static_cast(index), true); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } } - //need an exchange with source? (destination item is swapped with currently moved item) + // need an exchange with source? (destination item is swapped with currently moved item) const Item* inventoryItem = getInventoryItem(static_cast(index)); if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { const Cylinder* cylinder = item->getTopParent(); @@ -2493,10 +2673,10 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, - uint32_t flags) const + uint32_t flags) const { const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { maxQueryCount = 0; return RETURNVALUE_NOTPOSSIBLE; } @@ -2511,22 +2691,24 @@ ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t co subContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); n += queryCount; - //iterate through all items, including sub-containers (deep search) + // iterate through all items, including sub-containers (deep search) for (ContainerIterator it = subContainer->iterator(); it.hasNext(); it.advance()) { if (Container* tmpContainer = (*it)->getContainer()) { queryCount = 0; - tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); + tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, + flags); n += queryCount; } } - } else if (inventoryItem->isStackable() && item->equals(inventoryItem) && inventoryItem->getItemCount() < 100) { + } else if (inventoryItem->isStackable() && item->equals(inventoryItem) && + inventoryItem->getItemCount() < 100) { uint32_t remainder = (100 - inventoryItem->getItemCount()); if (queryAdd(slotIndex, *item, remainder, flags) == RETURNVALUE_NOERROR) { n += remainder; } } - } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot + } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { // empty slot if (item->isStackable()) { n += 100; } else { @@ -2550,7 +2732,7 @@ ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t co } else { maxQueryCount = 0; } - } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot + } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { // empty slot if (item->isStackable()) { maxQueryCount = 100; } else { @@ -2563,9 +2745,8 @@ ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t co if (maxQueryCount < count) { return RETURNVALUE_NOTENOUGHROOM; - } else { - return RETURNVALUE_NOERROR; } + return RETURNVALUE_NOERROR; } ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* /*= nullptr*/) const @@ -2576,7 +2757,7 @@ ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t fla } const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -2591,14 +2772,13 @@ ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t fla return RETURNVALUE_NOERROR; } -Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) +Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) { if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { *destItem = nullptr; const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return this; } @@ -2619,7 +2799,7 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de } if (autoStack && isStackable) { - //try find an already existing item to stack with + // try find an already existing item to stack with if (queryAdd(slotIndex, *item, item->getItemCount(), 0) == RETURNVALUE_NOERROR) { if (inventoryItem->equals(item) && inventoryItem->getItemCount() < 100) { index = slotIndex; @@ -2634,7 +2814,7 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de } else if (Container* subContainer = inventoryItem->getContainer()) { containers.push_back(subContainer); } - } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot + } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { // empty slot index = slotIndex; *destItem = nullptr; return this; @@ -2645,16 +2825,18 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de while (i < containers.size()) { Container* tmpContainer = containers[i++]; if (!autoStack || !isStackable) { - //we need to find first empty container as fast as we can for non-stackable items - uint32_t n = tmpContainer->capacity() - tmpContainer->size(); + // we need to find first empty container as fast as we can for non-stackable items + uint32_t n = tmpContainer->capacity() - + std::min(tmpContainer->capacity(), static_cast(tmpContainer->size())); while (n) { - if (tmpContainer->queryAdd(tmpContainer->capacity() - n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { + if (tmpContainer->queryAdd(tmpContainer->capacity() - n, *item, item->getItemCount(), flags) == + RETURNVALUE_NOERROR) { index = tmpContainer->capacity() - n; *destItem = nullptr; return tmpContainer; } - n--; + --n; } for (Item* tmpContainerItem : tmpContainer->getItemList()) { @@ -2677,7 +2859,7 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de continue; } - //try find an already existing item to stack with + // try find an already existing item to stack with if (tmpItem->equals(item) && tmpItem->getItemCount() < 100) { index = n; *destItem = tmpItem; @@ -2691,7 +2873,8 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de n++; } - if (n < tmpContainer->capacity() && tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { + if (n < tmpContainer->capacity() && + tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { index = n; *destItem = nullptr; return tmpContainer; @@ -2711,9 +2894,8 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de index = INDEX_WHEREEVER; *destItem = nullptr; return subCylinder; - } else { - return this; } + return this; } void Player::addThing(int32_t index, Thing* thing) @@ -2730,7 +2912,7 @@ void Player::addThing(int32_t index, Thing* thing) item->setParent(this); inventory[index] = item; - //send to client + // send to client sendInventoryItem(static_cast(index), item); } @@ -2749,10 +2931,10 @@ void Player::updateThing(Thing* thing, uint16_t itemId, uint32_t count) item->setID(itemId); item->setSubType(count); - //send to client + // send to client sendInventoryItem(static_cast(index), item); - //event methods + // event methods onUpdateInventoryItem(item, item); } @@ -2772,10 +2954,10 @@ void Player::replaceThing(uint32_t index, Thing* thing) return /*RETURNVALUE_NOTPOSSIBLE*/; } - //send to client + // send to client sendInventoryItem(static_cast(index), item); - //event methods + // event methods onUpdateInventoryItem(oldItem, item); item->setParent(this); @@ -2797,10 +2979,10 @@ void Player::removeThing(Thing* thing, uint32_t count) if (item->isStackable()) { if (count == item->getItemCount()) { - //send change to client + // send change to client sendInventoryItem(static_cast(index), nullptr); - //event methods + // event methods onRemoveInventoryItem(item); item->setParent(nullptr); @@ -2809,17 +2991,17 @@ void Player::removeThing(Thing* thing, uint32_t count) uint8_t newCount = static_cast(std::max(0, item->getItemCount() - count)); item->setItemCount(newCount); - //send change to client + // send change to client sendInventoryItem(static_cast(index), item); - //event methods + // event methods onUpdateInventoryItem(item, item); } } else { - //send change to client + // send change to client sendInventoryItem(static_cast(index), nullptr); - //event methods + // event methods onRemoveInventoryItem(item); item->setParent(nullptr); @@ -2837,15 +3019,9 @@ int32_t Player::getThingIndex(const Thing* thing) const return -1; } -size_t Player::getFirstIndex() const -{ - return CONST_SLOT_FIRST; -} +size_t Player::getFirstIndex() const { return CONST_SLOT_FIRST; } -size_t Player::getLastIndex() const -{ - return CONST_SLOT_LAST + 1; -} +size_t Player::getLastIndex() const { return CONST_SLOT_LAST + 1; } uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const { @@ -2871,7 +3047,7 @@ uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) con return count; } -bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const +bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped /* = false*/) const { if (amount == 0) { return true; @@ -2949,11 +3125,13 @@ Thing* Player::getThing(size_t index) const return nullptr; } -void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { - //calling movement scripts + // calling movement scripts g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast(index), false); + g_events->eventPlayerOnInventoryUpdate(this, thing->getItem(), static_cast(index), true); } bool requireListUpdate = false; @@ -2974,6 +3152,7 @@ void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_ updateInventoryWeight(); updateItemsLight(); sendStats(); + sendItems(); } if (const Item* item = thing->getItem()) { @@ -2986,7 +3165,7 @@ void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_ } } else if (const Creature* creature = thing->getCreature()) { if (creature == this) { - //check containers + // check containers std::vector containers; for (const auto& it : openContainers) { @@ -3003,11 +3182,13 @@ void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_ } } -void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { - //calling movement scripts + // calling movement scripts g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast(index)); + g_events->eventPlayerOnInventoryUpdate(this, thing->getItem(), static_cast(index), false); } bool requireListUpdate = false; @@ -3028,9 +3209,16 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int updateInventoryWeight(); updateItemsLight(); sendStats(); + sendItems(); } if (const Item* item = thing->getItem()) { + if (item->isSupply()) { + if (const Player* player = item->getHoldingPlayer()) { + player->sendSupplyUsed(item->getClientID()); + } + } + if (const Container* container = item->getContainer()) { if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) { autoCloseContainers(container); @@ -3067,8 +3255,18 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int bool Player::updateSaleShopList(const Item* item) { uint16_t itemId = item->getID(); - if (itemId != ITEM_GOLD_COIN && itemId != ITEM_PLATINUM_COIN && itemId != ITEM_CRYSTAL_COIN) { - auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; }); + bool isCurrency = false; + for (const auto& it : Item::items.currencyItems) { + if (it.second == itemId) { + isCurrency = true; + break; + } + } + + if (!isCurrency) { + auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { + return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; + }); if (it == shopItemList.end()) { const Container* container = item->getContainer(); if (!container) { @@ -3076,9 +3274,8 @@ bool Player::updateSaleShopList(const Item* item) } const auto& items = container->getItemList(); - return std::any_of(items.begin(), items.end(), [this](const Item* containerItem) { - return updateSaleShopList(containerItem); - }); + return std::any_of(items.begin(), items.end(), + [this](const Item* containerItem) { return updateSaleShopList(containerItem); }); } } @@ -3092,14 +3289,12 @@ bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) const { const ItemType& itemType = Item::items[itemId]; return std::any_of(shopItemList.begin(), shopItemList.end(), [&](const ShopInfo& shopInfo) { - return shopInfo.itemId == itemId && shopInfo.buyPrice != 0 && (!itemType.isFluidContainer() || shopInfo.subType == subType); + return shopInfo.itemId == itemId && shopInfo.buyPrice != 0 && + (!itemType.isFluidContainer() || shopInfo.subType == subType); }); } -void Player::internalAddThing(Thing* thing) -{ - internalAddThing(0, thing); -} +void Player::internalAddThing(Thing* thing) { internalAddThing(0, thing); } void Player::internalAddThing(uint32_t index, Thing* thing) { @@ -3108,7 +3303,7 @@ void Player::internalAddThing(uint32_t index, Thing* thing) return; } - //index == 0 means we should equip this item at the most appropriate slot (no action required here) + // index == 0 means we should equip this item at the most appropriate slot (no action required here) if (index > CONST_SLOT_WHEREEVER && index <= CONST_SLOT_LAST) { if (inventory[index]) { return; @@ -3142,7 +3337,7 @@ bool Player::setAttackedCreature(Creature* creature) if (chaseMode && creature) { if (followCreature != creature) { - //chase opponent + // chase opponent setFollowCreature(creature); } } else if (followCreature) { @@ -3150,7 +3345,7 @@ bool Player::setAttackedCreature(Creature* creature) } if (creature) { - g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + g_dispatcher.addTask(createTask([id = getID()]() { g_game.checkCreatureAttack(id); })); } return true; } @@ -3206,7 +3401,8 @@ void Player::doAttacking(uint32_t) result = Weapon::useFist(this, attackedCreature); } - SchedulerTask* task = createSchedulerTask(std::max(SCHEDULER_MINTICKS, delay), std::bind(&Game::checkCreatureAttack, &g_game, getID())); + SchedulerTask* task = createSchedulerTask(std::max(SCHEDULER_MINTICKS, delay), + [id = getID()]() { g_game.checkCreatureAttack(id); }); if (!classicSpeed) { setNextActionTask(task, false); } else { @@ -3223,7 +3419,9 @@ uint64_t Player::getGainedExperience(Creature* attacker) const { if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) { Player* attackerPlayer = attacker->getPlayer(); - if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs(static_cast(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) { + if (attackerPlayer && attackerPlayer != this && skillLoss && + std::abs(static_cast(attackerPlayer->getLevel() - level)) <= + g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) { return std::max(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75)); } } @@ -3245,7 +3443,7 @@ void Player::setChaseMode(bool mode) if (prevChaseMode != chaseMode) { if (chaseMode) { if (!followCreature && attackedCreature) { - //chase opponent + // chase opponent setFollowCreature(attackedCreature); } } else if (attackedCreature) { @@ -3269,10 +3467,7 @@ void Player::onWalkComplete() } } -void Player::stopWalk() -{ - cancelNextWalk = true; -} +void Player::stopWalk() { cancelNextWalk = true; } LightInfo Player::getCreatureLight() const { @@ -3352,6 +3547,10 @@ void Player::onAddCombatCondition(ConditionType_t type) sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are bleeding."); break; + case CONDITION_ROOT: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are rooted."); + break; + default: break; } @@ -3376,13 +3575,13 @@ void Player::onEndCondition(ConditionType_t type) void Player::onCombatRemoveCondition(Condition* condition) { - //Creature::onCombatRemoveCondition(condition); + // Creature::onCombatRemoveCondition(condition); if (condition->getId() > 0) { - //Means the condition is from an item, id == slot + // Means the condition is from an item, id == slot if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { Item* item = getInventoryItem(static_cast(condition->getId())); if (item) { - //25% chance to destroy the item + // 25% chance to destroy the item if (25 >= uniform_random(1, 100)) { g_game.internalRemoveItem(item); } @@ -3477,7 +3676,7 @@ void Player::onIdleStatus() void Player::onPlacedCreature() { - //scripting event - onLogin + // scripting event - onLogin if (!g_creatureEvents->playerLogin(this)) { kickPlayer(true); } @@ -3491,7 +3690,7 @@ void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) if (party && !Combat::isPlayerCombat(target)) { Monster* tmpMonster = target->getMonster(); if (tmpMonster && tmpMonster->isHostile()) { - //We have fulfilled a requirement for shared experience + // We have fulfilled a requirement for shared experience party->updatePlayerTicks(this, points); } } @@ -3517,7 +3716,7 @@ void Player::onTargetCreatureGainHealth(Creature* target, int32_t points) } } -bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) +bool Player::onKilledCreature(Creature* target, bool lastHit /* = true*/) { bool unjustified = false; @@ -3536,7 +3735,8 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) targetPlayer->setDropLoot(false); targetPlayer->setSkillLoss(false); } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { - if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && !isGuildMate(targetPlayer) && targetPlayer != this) { + if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && + !isGuildMate(targetPlayer) && targetPlayer != this) { if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { unjustified = true; addUnjustifiedDead(targetPlayer); @@ -3544,7 +3744,9 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) if (lastHit && hasCondition(CONDITION_INFIGHT)) { pzLocked = true; - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME), 0); + Condition* condition = + Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0); addCondition(condition); } } @@ -3568,9 +3770,10 @@ void Player::onGainExperience(uint64_t gainExp, Creature* target) return; } - if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && + party->isSharedExperienceEnabled()) { party->shareExperience(gainExp, target); - //We will get a share of the experience through the sharing mechanism + // We will get a share of the experience through the sharing mechanism return; } @@ -3578,10 +3781,7 @@ void Player::onGainExperience(uint64_t gainExp, Creature* target) gainExperience(gainExp, target); } -void Player::onGainSharedExperience(uint64_t gainExp, Creature* source) -{ - gainExperience(gainExp, source); -} +void Player::onGainSharedExperience(uint64_t gainExp, Creature* source) { gainExperience(gainExp, source); } bool Player::isImmune(CombatType_t type) const { @@ -3599,10 +3799,7 @@ bool Player::isImmune(ConditionType_t type) const return Creature::isImmune(type); } -bool Player::isAttackable() const -{ - return !hasFlag(PlayerFlag_CannotBeAttacked); -} +bool Player::isAttackable() const { return !hasFlag(PlayerFlag_CannotBeAttacked); } bool Player::lastHitIsPlayer(Creature* lastHitCreature) { @@ -3618,7 +3815,7 @@ bool Player::lastHitIsPlayer(Creature* lastHitCreature) return lastHitMaster && lastHitMaster->getPlayer(); } -void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +void Player::changeHealth(int32_t healthChange, bool sendHealthChange /* = true*/) { Creature::changeHealth(healthChange, sendHealthChange); sendStats(); @@ -3672,7 +3869,7 @@ bool Player::canWear(uint32_t lookType, uint8_t addons) const if (outfitEntry.addons == addons || outfitEntry.addons == 3 || addons == 0) { return true; } - return false; //have lookType on list and addons don't match + return false; // have lookType on list and addons don't match } } return false; @@ -3691,35 +3888,18 @@ bool Player::hasOutfit(uint32_t lookType, uint8_t addons) for (const OutfitEntry& outfitEntry : outfits) { if (outfitEntry.lookType == lookType) { - if (outfitEntry.addons == addons || outfitEntry.addons == 3 || addons == 0){ + if (outfitEntry.addons == addons || outfitEntry.addons == 3 || addons == 0) { return true; } - return false; //have lookType on list and addons don't match + return false; // have lookType on list and addons don't match } } return false; } -bool Player::canLogout() -{ - if (isConnecting) { - return false; - } - - if (getTile()->hasFlag(TILESTATE_NOLOGOUT)) { - return false; - } - - if (getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { - return true; - } - - return !isPzLocked() && !hasCondition(CONDITION_INFIGHT); -} - void Player::genReservedStorageRange() { - //generate outfits range + // generate outfits range uint32_t base_key = PSTRG_OUTFITS_RANGE_START; for (const OutfitEntry& entry : outfits) { storageMap[++base_key] = (entry.lookType << 16) | entry.addons; @@ -3788,10 +3968,7 @@ bool Player::getOutfitAddons(const Outfit& outfit, uint8_t& addons) const return true; } -void Player::setSex(PlayerSex_t newSex) -{ - sex = newSex; -} +void Player::setSex(PlayerSex_t newSex) { sex = newSex; } Skulls_t Player::getSkull() const { @@ -3816,7 +3993,7 @@ Skulls_t Player::getSkullClient(const Creature* creature) const return SKULL_YELLOW; } - if (isPartner(player)) { + if (party && party == player->party) { return SKULL_GREEN; } return Creature::getSkullClient(creature); @@ -3852,10 +4029,7 @@ void Player::removeAttacked(const Player* attacked) } } -void Player::clearAttacked() -{ - attackedSet.clear(); -} +void Player::clearAttacked() { attackedSet.clear(); } void Player::addUnjustifiedDead(const Player* attacked) { @@ -3863,7 +4037,7 @@ void Player::addUnjustifiedDead(const Player* attacked) return; } - sendTextMessage(MESSAGE_EVENT_ADVANCE, "Warning! The murder of " + attacked->getName() + " was not justified."); + sendTextMessage(MESSAGE_EVENT_ADVANCE, "Warning! The murder of " + attacked->getName() + " was not justified."); time_t now = time(nullptr), today = (now - 84600), week = (now - (7 * 84600)); std::vector killsList = IOLoginData::getUnjustifiedDates(name, now); // get kills from last month @@ -3920,22 +4094,21 @@ bool Player::isPromoted() const double Player::getLostPercent() const { - int32_t blessingCount = std::bitset<5>(blessings).count(); - int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT); if (deathLosePercent != -1) { if (isPromoted()) { deathLosePercent -= 3; } - deathLosePercent -= blessingCount; + deathLosePercent -= blessings.count(); return std::max(0, deathLosePercent) / 100.; } double lossPercent; if (level >= 25) { double tmpLevel = level + (levelPercent / 100.); - lossPercent = static_cast((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience; + lossPercent = + static_cast((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience; } else { lossPercent = 10; } @@ -3944,7 +4117,7 @@ double Player::getLostPercent() const if (isPromoted()) { percentReduction += 30; } - percentReduction += blessingCount * 8; + percentReduction += blessings.count() * 8; return lossPercent * (1 - (percentReduction / 100.)) / 100.; } @@ -3955,10 +4128,7 @@ void Player::learnInstantSpell(const std::string& spellName) } } -void Player::forgetInstantSpell(const std::string& spellName) -{ - learnedInstantSpellList.remove(spellName); -} +void Player::forgetInstantSpell(const std::string& spellName) { learnedInstantSpellList.remove(spellName); } bool Player::hasLearnedInstantSpell(const std::string& spellName) const { @@ -3971,7 +4141,7 @@ bool Player::hasLearnedInstantSpell(const std::string& spellName) const } for (const auto& learnedSpellName : learnedInstantSpellList) { - if (strcasecmp(learnedSpellName.c_str(), spellName.c_str()) == 0) { + if (caseInsensitiveEqual(learnedSpellName, spellName)) { return true; } } @@ -4051,7 +4221,8 @@ PartyShields_t Player::getPartyShield(const Player* player) const return SHIELD_BLUE; } - if (isInviting(player)) { + // isInviting(player) if members aren't supposed to see the invited player emblem + if (party->isPlayerInvited(player)) { return SHIELD_WHITEBLUE; } } @@ -4108,10 +4279,7 @@ bool Player::addPartyInvitation(Party* party) return true; } -void Player::removePartyInvitation(Party* party) -{ - invitePartyList.remove(party); -} +void Player::removePartyInvitation(Party* party) { invitePartyList.remove(party); } void Player::clearPartyInvitations() { @@ -4135,9 +4303,8 @@ GuildEmblems_t Player::getGuildEmblem(const Player* player) const if (player->getGuildWarVector().empty()) { if (guild == playerGuild) { return GUILDEMBLEM_MEMBER; - } else { - return GUILDEMBLEM_OTHER; } + return GUILDEMBLEM_OTHER; } else if (guild == playerGuild) { return GUILDEMBLEM_ALLY; } else if (isInWar(player)) { @@ -4156,10 +4323,7 @@ uint8_t Player::getCurrentMount() const return 0; } -void Player::setCurrentMount(uint8_t mountId) -{ - addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId); -} +void Player::setCurrentMount(uint8_t mountId) { addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId); } bool Player::toggleMount(bool mount) { @@ -4352,9 +4516,7 @@ bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) manaSpent += tries; if (magLevel != currMagLevel) { - std::ostringstream ss; - ss << "You advanced to magic level " << magLevel << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, fmt::format("You advanced to magic level {:d}.", magLevel)); } uint8_t newPercent; @@ -4407,9 +4569,8 @@ bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) skills[skill].tries += tries; if (currSkillLevel != skills[skill].level) { - std::ostringstream ss; - ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage(MESSAGE_EVENT_ADVANCE, + fmt::format("You advanced to {:s} level {:d}.", getSkillName(skill), skills[skill].level)); } uint8_t newPercent; @@ -4433,9 +4594,12 @@ bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) sendSkills(); } - std::ostringstream ss; - ss << std::fixed << std::setprecision(2) << "Your " << ucwords(getSkillName(skill)) << " skill changed from level " << oldSkillValue << " (with " << oldPercentToNextLevel << "% progress towards level " << (oldSkillValue + 1) << ") to level " << newSkillValue << " (with " << newPercentToNextLevel << "% progress towards level " << (newSkillValue + 1) << ')'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + sendTextMessage( + MESSAGE_EVENT_ADVANCE, + fmt::format( + "Your {:s} skill changed from level {:d} (with {:.2f}% progress towards level {:d}) to level {:d} (with {:.2f}% progress towards level {:d})", + ucwords(getSkillName(skill)), oldSkillValue, oldPercentToNextLevel, (oldSkillValue + 1), newSkillValue, + newPercentToNextLevel, (newSkillValue + 1))); return sendUpdate; } @@ -4444,10 +4608,7 @@ bool Player::hasModalWindowOpen(uint32_t modalWindowId) const return find(modalWindows.begin(), modalWindows.end(), modalWindowId) != modalWindows.end(); } -void Player::onModalWindowHandled(uint32_t modalWindowId) -{ - modalWindows.remove(modalWindowId); -} +void Player::onModalWindowHandled(uint32_t modalWindowId) { modalWindows.remove(modalWindowId); } void Player::sendModalWindow(const ModalWindow& modalWindow) { @@ -4459,10 +4620,7 @@ void Player::sendModalWindow(const ModalWindow& modalWindow) client->sendModalWindow(modalWindow); } -void Player::clearModalWindows() -{ - modalWindows.clear(); -} +void Player::clearModalWindows() { modalWindows.clear(); } uint16_t Player::getHelpers() const { @@ -4545,19 +4703,45 @@ size_t Player::getMaxVIPEntries() const return group->maxVipEntries; } - return g_config.getNumber(isPremium() ? - ConfigManager::VIP_PREMIUM_LIMIT : ConfigManager::VIP_FREE_LIMIT - ); + return g_config.getNumber(isPremium() ? ConfigManager::VIP_PREMIUM_LIMIT : ConfigManager::VIP_FREE_LIMIT); } size_t Player::getMaxDepotItems() const { if (group->maxDepotItems != 0) { return group->maxDepotItems; - } else if (isPremium()) { - return 2000; } - return 1000; + + return g_config.getNumber(isPremium() ? ConfigManager::DEPOT_PREMIUM_LIMIT : ConfigManager::DEPOT_FREE_LIMIT); +} + +size_t Player::getMaxTrackedQuests() const +{ + return g_config.getNumber(isPremium() ? ConfigManager::QUEST_TRACKER_PREMIUM_LIMIT + : ConfigManager::QUEST_TRACKER_FREE_LIMIT); +} + +void Player::resetQuestTracker(const std::vector& missionIds) +{ + const size_t maxTrackedQuests = getMaxTrackedQuests(); + trackedQuests.clear(); + size_t index = 0; + + const QuestsList& quests = g_game.quests.getQuests(); + for (const uint8_t missionId : missionIds) { + for (const Quest& quest : quests) { + for (const Mission& mission : quest.getMissions()) { + if (mission.getID() == missionId && mission.isStarted(this)) { + trackedQuests.emplace_back(quest.getID(), missionId); + if (++index == maxTrackedQuests) { + break; + } + } + } + } + } + + sendQuestTracker(); } std::forward_list Player::getMuteConditions() const @@ -4605,3 +4789,18 @@ void Player::setGuild(Guild* guild) oldGuild->removeMember(this); } } + +void Player::updateRegeneration() +{ + if (!vocation) { + return; + } + + Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + if (condition) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount()); + condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); + condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount()); + condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000); + } +} diff --git a/src/player.h b/src/player.h index fd8e048557..9d6e715736 100644 --- a/src/player.h +++ b/src/player.h @@ -1,73 +1,50 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PLAYER_H_4083D3D3A05B4EDE891B31BB720CD06F -#define FS_PLAYER_H_4083D3D3A05B4EDE891B31BB720CD06F +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_PLAYER_H +#define FS_PLAYER_H #include "creature.h" -#include "container.h" #include "cylinder.h" -#include "outfit.h" -#include "enums.h" -#include "vocation.h" -#include "protocolgame.h" -#include "ioguild.h" -#include "party.h" -#include "inbox.h" -#include "depotchest.h" #include "depotlocker.h" -#include "guild.h" +#include "enums.h" #include "groups.h" +#include "guild.h" +#include "protocolgame.h" #include "town.h" -#include "mounts.h" -#include "storeinbox.h" +#include "vocation.h" +class DepotChest; class House; class NetworkMessage; -class Weapon; -class ProtocolGame; class Npc; class Party; class SchedulerTask; -class Bed; -class Guild; -enum skillsid_t { +enum skillsid_t +{ SKILLVALUE_LEVEL = 0, SKILLVALUE_TRIES = 1, SKILLVALUE_PERCENT = 2, }; -enum fightMode_t : uint8_t { +enum fightMode_t : uint8_t +{ FIGHTMODE_ATTACK = 1, FIGHTMODE_BALANCED = 2, FIGHTMODE_DEFENSE = 3, }; -enum pvpMode_t : uint8_t { +enum pvpMode_t : uint8_t +{ PVP_MODE_DOVE = 0, PVP_MODE_WHITE_HAND = 1, PVP_MODE_YELLOW_HAND = 2, PVP_MODE_RED_FIST = 3, }; -enum tradestate_t : uint8_t { +enum tradestate_t : uint8_t +{ TRADE_NONE, TRADE_INITIATED, TRADE_ACCEPT, @@ -75,9 +52,11 @@ enum tradestate_t : uint8_t { TRADE_TRANSFER, }; -struct VIPEntry { +struct VIPEntry +{ VIPEntry(uint32_t guid, std::string name, std::string description, uint32_t icon, bool notify) : - guid(guid), name(std::move(name)), description(std::move(description)), icon(icon), notify(notify) {} + guid(guid), name(std::move(name)), description(std::move(description)), icon(icon), notify(notify) + {} uint32_t guid; std::string name; @@ -86,21 +65,26 @@ struct VIPEntry { bool notify; }; -struct OpenContainer { +struct OpenContainer +{ Container* container; uint16_t index; }; -struct OutfitEntry { +struct OutfitEntry +{ constexpr OutfitEntry(uint16_t lookType, uint8_t addons) : lookType(lookType), addons(addons) {} uint16_t lookType; uint8_t addons; }; -struct Skill { +static constexpr int16_t MINIMUM_SKILL_LEVEL = 10; + +struct Skill +{ uint64_t tries = 0; - uint16_t level = 10; + uint16_t level = MINIMUM_SKILL_LEVEL; uint8_t percent = 0; }; @@ -109,1258 +93,1225 @@ using MuteCountMap = std::map; static constexpr int32_t PLAYER_MAX_SPEED = 1500; static constexpr int32_t PLAYER_MIN_SPEED = 10; +static constexpr int32_t NOTIFY_DEPOT_BOX_RANGE = 1; + class Player final : public Creature, public Cylinder { - public: - explicit Player(ProtocolGame_ptr p); - ~Player(); - - // non-copyable - Player(const Player&) = delete; - Player& operator=(const Player&) = delete; - - Player* getPlayer() override { - return this; - } - const Player* getPlayer() const override { - return this; - } - - void setID() override { - if (id == 0) { - id = playerAutoID++; - } - } - - static MuteCountMap muteCountMap; - - const std::string& getName() const override { - return name; - } - void setName(std::string name) { - this->name = std::move(name); - } - const std::string& getNameDescription() const override { - return name; - } - std::string getDescription(int32_t lookDistance) const override; - - CreatureType_t getType() const override { - return CREATURETYPE_PLAYER; - } - - uint8_t getCurrentMount() const; - void setCurrentMount(uint8_t mountId); - bool isMounted() const { - return defaultOutfit.lookMount != 0; - } - bool toggleMount(bool mount); - bool tameMount(uint8_t mountId); - bool untameMount(uint8_t mountId); - bool hasMount(const Mount* mount) const; - void dismount(); - - void sendFYIBox(const std::string& message) { - if (client) { - client->sendFYIBox(message); - } - } - - void setGUID(uint32_t guid) { - this->guid = guid; - } - uint32_t getGUID() const { - return guid; - } - bool canSeeInvisibility() const override { - return hasFlag(PlayerFlag_CanSenseInvisibility) || group->access; - } - - void removeList() override; - void addList() override; - void kickPlayer(bool displayEffect); - - static uint64_t getExpForLevel(uint64_t lv) { - return (((lv - 6ULL) * lv + 17ULL) * lv - 12ULL) / 6ULL * 100ULL; - } - - uint16_t getStaminaMinutes() const { - return staminaMinutes; - } - - bool addOfflineTrainingTries(skills_t skill, uint64_t tries); - - void addOfflineTrainingTime(int32_t addTime) { - offlineTrainingTime = std::min(12 * 3600 * 1000, offlineTrainingTime + addTime); - } - void removeOfflineTrainingTime(int32_t removeTime) { - offlineTrainingTime = std::max(0, offlineTrainingTime - removeTime); - } - int32_t getOfflineTrainingTime() const { - return offlineTrainingTime; - } +public: + explicit Player(ProtocolGame_ptr p); + ~Player(); - int32_t getOfflineTrainingSkill() const { - return offlineTrainingSkill; - } - void setOfflineTrainingSkill(int32_t skill) { - offlineTrainingSkill = skill; - } + // non-copyable + Player(const Player&) = delete; + Player& operator=(const Player&) = delete; - uint64_t getBankBalance() const { - return bankBalance; - } - void setBankBalance(uint64_t balance) { - bankBalance = balance; - } + Player* getPlayer() override { return this; } + const Player* getPlayer() const override { return this; } - Guild* getGuild() const { - return guild; - } - void setGuild(Guild* guild); + void setID() final; - GuildRank_ptr getGuildRank() const { - return guildRank; - } - void setGuildRank(GuildRank_ptr newGuildRank) { - guildRank = newGuildRank; - } - - bool isGuildMate(const Player* player) const; - - const std::string& getGuildNick() const { - return guildNick; - } - void setGuildNick(std::string nick) { - guildNick = nick; - } - - bool isInWar(const Player* player) const; - bool isInWarList(uint32_t guildId) const; - - void setLastWalkthroughAttempt(int64_t walkthroughAttempt) { - lastWalkthroughAttempt = walkthroughAttempt; - } - void setLastWalkthroughPosition(Position walkthroughPosition) { - lastWalkthroughPosition = walkthroughPosition; - } - - Inbox* getInbox() const { - return inbox; - } - - StoreInbox* getStoreInbox() const { - return storeInbox; - } - - uint16_t getClientIcons() const; - - const GuildWarVector& getGuildWarVector() const { - return guildWarVector; - } - - Vocation* getVocation() const { - return vocation; - } - - OperatingSystem_t getOperatingSystem() const { - return operatingSystem; - } - void setOperatingSystem(OperatingSystem_t clientos) { - operatingSystem = clientos; - } + static MuteCountMap muteCountMap; - uint16_t getProtocolVersion() const { - if (!client) { - return 0; - } + const std::string& getName() const override { return name; } + void setName(const std::string& name) { this->name = name; } + const std::string& getNameDescription() const override { return name; } + std::string getDescription(int32_t lookDistance) const override; - return client->getVersion(); - } + CreatureType_t getType() const override { return CREATURETYPE_PLAYER; } - bool hasSecureMode() const { - return secureMode; - } + uint8_t getCurrentMount() const; + void setCurrentMount(uint8_t mountId); + bool isMounted() const { return defaultOutfit.lookMount != 0; } + bool toggleMount(bool mount); + bool tameMount(uint8_t mountId); + bool untameMount(uint8_t mountId); + bool hasMount(const Mount* mount) const; + void dismount(); - void setParty(Party* party) { - this->party = party; - } - Party* getParty() const { - return party; + void sendFYIBox(const std::string& message) + { + if (client) { + client->sendFYIBox(message); } - PartyShields_t getPartyShield(const Player* player) const; - bool isInviting(const Player* player) const; - bool isPartner(const Player* player) const; - void sendPlayerPartyIcons(Player* player); - bool addPartyInvitation(Party* party); - void removePartyInvitation(Party* party); - void clearPartyInvitations(); + } - GuildEmblems_t getGuildEmblem(const Player* player) const; + void setGUID(uint32_t guid) { this->guid = guid; } + uint32_t getGUID() const { return guid; } + bool canSeeInvisibility() const override { return hasFlag(PlayerFlag_CanSenseInvisibility) || group->access; } - uint64_t getSpentMana() const { - return manaSpent; - } + void removeList() override; + void addList() override; + void kickPlayer(bool displayEffect); - bool hasFlag(PlayerFlags value) const { - return (group->flags & value) != 0; - } + static uint64_t getExpForLevel(const uint64_t lv) + { + return (((lv - 6ULL) * lv + 17ULL) * lv - 12ULL) / 6ULL * 100ULL; + } - BedItem* getBedItem() { - return bedItem; - } - void setBedItem(BedItem* b) { - bedItem = b; - } + uint16_t getStaminaMinutes() const { return staminaMinutes; } - void addBlessing(uint8_t blessing) { - blessings |= blessing; - } - void removeBlessing(uint8_t blessing) { - blessings &= ~blessing; - } - bool hasBlessing(uint8_t value) const { - return (blessings & (static_cast(1) << value)) != 0; - } + bool addOfflineTrainingTries(skills_t skill, uint64_t tries); - bool isOffline() const { - return (getID() == 0); - } - void disconnect() { - if (client) { - client->disconnect(); - } - } - uint32_t getIP() const; + void addOfflineTrainingTime(int32_t addTime) + { + offlineTrainingTime = std::min(12 * 3600 * 1000, offlineTrainingTime + addTime); + } + void removeOfflineTrainingTime(int32_t removeTime) + { + offlineTrainingTime = std::max(0, offlineTrainingTime - removeTime); + } + int32_t getOfflineTrainingTime() const { return offlineTrainingTime; } - void addContainer(uint8_t cid, Container* container); - void closeContainer(uint8_t cid); - void setContainerIndex(uint8_t cid, uint16_t index); + int32_t getOfflineTrainingSkill() const { return offlineTrainingSkill; } + void setOfflineTrainingSkill(int32_t skill) { offlineTrainingSkill = skill; } - Container* getContainerByID(uint8_t cid); - int8_t getContainerID(const Container* container) const; - uint16_t getContainerIndex(uint8_t cid) const; + uint64_t getBankBalance() const { return bankBalance; } + void setBankBalance(uint64_t balance) { bankBalance = balance; } - bool canOpenCorpse(uint32_t ownerId) const; + Guild* getGuild() const { return guild; } + void setGuild(Guild* guild); - void addStorageValue(const uint32_t key, const int32_t value, const bool isLogin = false); - bool getStorageValue(const uint32_t key, int32_t& value) const; - void genReservedStorageRange(); + GuildRank_ptr getGuildRank() const { return guildRank; } + void setGuildRank(GuildRank_ptr newGuildRank) { guildRank = newGuildRank; } - void setGroup(Group* newGroup) { - group = newGroup; - } - Group* getGroup() const { - return group; - } + bool isGuildMate(const Player* player) const; - void setInMarket(bool value) { - inMarket = value; - } - bool isInMarket() const { - return inMarket; - } + const std::string& getGuildNick() const { return guildNick; } + void setGuildNick(std::string nick) { guildNick = nick; } - void setLastDepotId(int16_t newId) { - lastDepotId = newId; - } - int16_t getLastDepotId() const { - return lastDepotId; - } + bool isInWar(const Player* player) const; + bool isInWarList(uint32_t guildId) const; - void resetIdleTime() { - idleTime = 0; - } + void setLastWalkthroughAttempt(int64_t walkthroughAttempt) { lastWalkthroughAttempt = walkthroughAttempt; } + void setLastWalkthroughPosition(Position walkthroughPosition) { lastWalkthroughPosition = walkthroughPosition; } - bool isInGhostMode() const override { - return ghostMode; - } - void switchGhostMode() { - ghostMode = !ghostMode; - } + Inbox* getInbox() const { return inbox; } - uint32_t getAccount() const { - return accountNumber; - } - AccountType_t getAccountType() const { - return accountType; - } - uint32_t getLevel() const { - return level; - } - uint8_t getLevelPercent() const { - return levelPercent; - } - uint32_t getMagicLevel() const { - return std::max(0, magLevel + varStats[STAT_MAGICPOINTS]); - } - uint32_t getBaseMagicLevel() const { - return magLevel; - } - uint8_t getMagicLevelPercent() const { - return magLevelPercent; - } - uint8_t getSoul() const { - return soul; - } - bool isAccessPlayer() const { - return group->access; - } - bool isPremium() const; - void setPremiumTime(time_t premiumEndsAt); + StoreInbox* getStoreInbox() const { return storeInbox; } - uint16_t getHelpers() const; + uint32_t getClientIcons() const; - bool setVocation(uint16_t vocId); - uint16_t getVocationId() const { - return vocation->getId(); - } + const GuildWarVector& getGuildWarVector() const { return guildWarVector; } - PlayerSex_t getSex() const { - return sex; - } - void setSex(PlayerSex_t); - uint64_t getExperience() const { - return experience; - } - - time_t getLastLoginSaved() const { - return lastLoginSaved; - } + Vocation* getVocation() const { return vocation; } - time_t getLastLogout() const { - return lastLogout; - } + OperatingSystem_t getOperatingSystem() const { return operatingSystem; } + void setOperatingSystem(OperatingSystem_t clientos) { operatingSystem = clientos; } - const Position& getLoginPosition() const { - return loginPosition; - } - const Position& getTemplePosition() const { - return town->getTemplePosition(); - } - Town* getTown() const { - return town; - } - void setTown(Town* town) { - this->town = town; + uint16_t getProtocolVersion() const + { + if (!client) { + return 0; } - void clearModalWindows(); - bool hasModalWindowOpen(uint32_t modalWindowId) const; - void onModalWindowHandled(uint32_t modalWindowId); + return client->getVersion(); + } - bool isPushable() const override; - uint32_t isMuted() const; - void addMessageBuffer(); - void removeMessageBuffer(); + bool hasSecureMode() const { return secureMode; } - bool removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped = false) const; + void setParty(Party* party) { this->party = party; } + Party* getParty() const { return party; } + PartyShields_t getPartyShield(const Player* player) const; + bool isInviting(const Player* player) const; + bool isPartner(const Player* player) const; + void sendPlayerPartyIcons(Player* player); + bool addPartyInvitation(Party* party); + void removePartyInvitation(Party* party); + void clearPartyInvitations(); - uint32_t getCapacity() const { - if (hasFlag(PlayerFlag_CannotPickupItem)) { - return 0; - } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { - return std::numeric_limits::max(); - } - return capacity; - } + GuildEmblems_t getGuildEmblem(const Player* player) const; - uint32_t getFreeCapacity() const { - if (hasFlag(PlayerFlag_CannotPickupItem)) { - return 0; - } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { - return std::numeric_limits::max(); - } else { - return std::max(0, capacity - inventoryWeight); - } - } + uint64_t getSpentMana() const { return manaSpent; } - int32_t getMaxHealth() const override { - return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); - } - uint32_t getMana() const { - return mana; - } - uint32_t getMaxMana() const { - return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); - } + bool hasFlag(PlayerFlags value) const { return (group->flags & value) != 0; } - Item* getInventoryItem(slots_t slot) const; + BedItem* getBedItem() { return bedItem; } + void setBedItem(BedItem* b) { bedItem = b; } - bool isItemAbilityEnabled(slots_t slot) const { - return inventoryAbilities[slot]; - } - void setItemAbility(slots_t slot, bool enabled) { - inventoryAbilities[slot] = enabled; - } + void addBlessing(uint8_t blessing) { blessings.set(blessing); } + void removeBlessing(uint8_t blessing) { blessings.reset(blessing); } + bool hasBlessing(uint8_t blessing) const { return blessings.test(blessing); } - void setVarSkill(skills_t skill, int32_t modifier) { - varSkills[skill] += modifier; + bool isOffline() const { return (getID() == 0); } + void disconnect() + { + if (client) { + client->disconnect(); } + } + uint32_t getIP() const; - void setVarSpecialSkill(SpecialSkills_t skill, int32_t modifier) { - varSpecialSkills[skill] += modifier; - } + void addContainer(uint8_t cid, Container* container); + void closeContainer(uint8_t cid); + void setContainerIndex(uint8_t cid, uint16_t index); - void setVarStats(stats_t stat, int32_t modifier); - int32_t getDefaultStats(stats_t stat) const; + Container* getContainerByID(uint8_t cid); + int8_t getContainerID(const Container* container) const; + uint16_t getContainerIndex(uint8_t cid) const; - void addConditionSuppressions(uint32_t conditions); - void removeConditionSuppressions(uint32_t conditions); + bool canOpenCorpse(uint32_t ownerId) const; - DepotChest* getDepotChest(uint32_t depotId, bool autoCreate); - DepotLocker* getDepotLocker(uint32_t depotId); - void onReceiveMail() const; - bool isNearDepotBox() const; + void addStorageValue(const uint32_t key, const int32_t value, const bool isLogin = false); + bool getStorageValue(const uint32_t key, int32_t& value) const; + void genReservedStorageRange(); - bool canSee(const Position& pos) const override; - bool canSeeCreature(const Creature* creature) const override; + void setGroup(Group* newGroup) { group = newGroup; } + Group* getGroup() const { return group; } - bool canWalkthrough(const Creature* creature) const; - bool canWalkthroughEx(const Creature* creature) const; + void setInMarket(bool value) { inMarket = value; } + bool isInMarket() const { return inMarket; } - RaceType_t getRace() const override { - return RACE_BLOOD; - } + int32_t getIdleTime() const { return idleTime; } - uint64_t getMoney() const; + void resetIdleTime() { idleTime = 0; } - //safe-trade functions - void setTradeState(tradestate_t state) { - tradeState = state; - } - tradestate_t getTradeState() const { - return tradeState; - } - Item* getTradeItem() { - return tradeItem; - } + bool isInGhostMode() const override { return ghostMode; } + bool canSeeGhostMode(const Creature* creature) const override; + void switchGhostMode() { ghostMode = !ghostMode; } - //shop functions - void setShopOwner(Npc* owner, int32_t onBuy, int32_t onSell) { - shopOwner = owner; - purchaseCallback = onBuy; - saleCallback = onSell; - } + uint32_t getAccount() const { return accountNumber; } + AccountType_t getAccountType() const { return accountType; } + uint32_t getLevel() const { return level; } + uint8_t getLevelPercent() const { return levelPercent; } + uint32_t getMagicLevel() const { return std::max(0, magLevel + varStats[STAT_MAGICPOINTS]); } + uint32_t getSpecialMagicLevel(CombatType_t type) const + { + return std::max(0, specialMagicLevelSkill[combatTypeToIndex(type)]); + } + uint32_t getBaseMagicLevel() const { return magLevel; } + uint8_t getMagicLevelPercent() const { return magLevelPercent; } + uint8_t getSoul() const { return soul; } + bool isAccessPlayer() const { return group->access; } + bool isPremium() const; + void setPremiumTime(time_t premiumEndsAt); - Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) { - onBuy = purchaseCallback; - onSell = saleCallback; - return shopOwner; - } + uint16_t getHelpers() const; - const Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) const { - onBuy = purchaseCallback; - onSell = saleCallback; - return shopOwner; - } - - //V.I.P. functions - void notifyStatusChange(Player* loginPlayer, VipStatus_t status); - bool removeVIP(uint32_t vipGuid); - bool addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status); - bool addVIPInternal(uint32_t vipGuid); - bool editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify); - - //follow functions - bool setFollowCreature(Creature* creature) override; - void goToFollowCreature() override; + bool setVocation(uint16_t vocId); + uint16_t getVocationId() const { return vocation->getId(); } - //follow events - void onFollowCreature(const Creature* creature) override; + PlayerSex_t getSex() const { return sex; } + void setSex(PlayerSex_t); + uint64_t getExperience() const { return experience; } - //walk events - void onWalk(Direction& dir) override; - void onWalkAborted() override; - void onWalkComplete() override; + time_t getLastLoginSaved() const { return lastLoginSaved; } - void stopWalk(); - void openShopWindow(Npc* npc, const std::list& shop); - bool closeShopWindow(bool sendCloseShopWindow = true); - bool updateSaleShopList(const Item* item); - bool hasShopItemForSale(uint32_t itemId, uint8_t subType) const; + time_t getLastLogout() const { return lastLogout; } - void setChaseMode(bool mode); - void setFightMode(fightMode_t mode) { - fightMode = mode; - } - void setSecureMode(bool mode) { - secureMode = mode; - } + const Position& getLoginPosition() const { return loginPosition; } + const Position& getTemplePosition() const { return town->getTemplePosition(); } + Town* getTown() const { return town; } + void setTown(Town* town) { this->town = town; } - //combat functions - bool setAttackedCreature(Creature* creature) override; - bool isImmune(CombatType_t type) const override; - bool isImmune(ConditionType_t type) const override; - bool hasShield() const; - bool isAttackable() const override; - static bool lastHitIsPlayer(Creature* lastHitCreature); + void clearModalWindows(); + bool hasModalWindowOpen(uint32_t modalWindowId) const; + void onModalWindowHandled(uint32_t modalWindowId); - void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; - void changeMana(int32_t manaChange); - void changeSoul(int32_t soulChange); + bool isPushable() const override; + uint32_t isMuted() const; + void addMessageBuffer(); + void removeMessageBuffer(); - bool isPzLocked() const { - return pzLocked; - } - BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false, bool ignoreResistances = false) override; - void doAttacking(uint32_t interval) override; - bool hasExtraSwing() override { - return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); - } + bool removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped = false) const; - uint16_t getSpecialSkill(uint8_t skill) const { - return std::max(0, varSpecialSkills[skill]); - } - uint16_t getSkillLevel(uint8_t skill) const { - return std::max(0, skills[skill].level + varSkills[skill]); - } - uint16_t getBaseSkill(uint8_t skill) const { - return skills[skill].level; - } - uint8_t getSkillPercent(uint8_t skill) const { - return skills[skill].percent; + uint32_t getCapacity() const + { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return std::numeric_limits::max(); } + return capacity; + } - bool getAddAttackSkill() const { - return addAttackSkillPoint; - } - BlockType_t getLastAttackBlockType() const { - return lastAttackBlockType; - } - - Item* getWeapon(slots_t slot, bool ignoreAmmo) const; - Item* getWeapon(bool ignoreAmmo = false) const; - WeaponType_t getWeaponType() const; - int32_t getWeaponSkill(const Item* item) const; - void getShieldAndWeapon(const Item*& shield, const Item*& weapon) const; - - void drainHealth(Creature* attacker, int32_t damage) override; - void drainMana(Creature* attacker, int32_t manaLoss); - void addManaSpent(uint64_t amount); - void addSkillAdvance(skills_t skill, uint64_t count); - - int32_t getArmor() const override; - int32_t getDefense() const override; - float getAttackFactor() const override; - float getDefenseFactor() const override; - - void addInFightTicks(bool pzlock = false); - - uint64_t getGainedExperience(Creature* attacker) const override; - - //combat event functions - void onAddCondition(ConditionType_t type) override; - void onAddCombatCondition(ConditionType_t type) override; - void onEndCondition(ConditionType_t type) override; - void onCombatRemoveCondition(Condition* condition) override; - void onAttackedCreature(Creature* target, bool addFightTicks = true) override; - void onAttacked() override; - void onAttackedCreatureDrainHealth(Creature* target, int32_t points) override; - void onTargetCreatureGainHealth(Creature* target, int32_t points) override; - bool onKilledCreature(Creature* target, bool lastHit = true) override; - void onGainExperience(uint64_t gainExp, Creature* target) override; - void onGainSharedExperience(uint64_t gainExp, Creature* source); - void onAttackedCreatureBlockHit(BlockType_t blockType) override; - void onBlockHit() override; - void onChangeZone(ZoneType_t zone) override; - void onAttackedCreatureChangeZone(ZoneType_t zone) override; - void onIdleStatus() override; - void onPlacedCreature() override; - - LightInfo getCreatureLight() const override; - - Skulls_t getSkull() const override; - Skulls_t getSkullClient(const Creature* creature) const override; - int64_t getSkullTicks() const { return skullTicks; } - void setSkullTicks(int64_t ticks) { skullTicks = ticks; } - - bool hasAttacked(const Player* attacked) const; - void addAttacked(const Player* attacked); - void removeAttacked(const Player* attacked); - void clearAttacked(); - void addUnjustifiedDead(const Player* attacked); - void sendCreatureSkull(const Creature* creature) const { - if (client) { - client->sendCreatureSkull(creature); - } - } - void checkSkullTicks(int64_t ticks); - - bool canWear(uint32_t lookType, uint8_t addons) const; - bool hasOutfit(uint32_t lookType, uint8_t addons); - void addOutfit(uint16_t lookType, uint8_t addons); - bool removeOutfit(uint16_t lookType); - bool removeOutfitAddon(uint16_t lookType, uint8_t addons); - bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const; - - bool canLogout(); - - size_t getMaxVIPEntries() const; - size_t getMaxDepotItems() const; - - //tile - //send methods - void sendAddTileItem(const Tile* tile, const Position& pos, const Item* item) { - if (client) { - int32_t stackpos = tile->getStackposOfItem(this, item); - if (stackpos != -1) { - client->sendAddTileItem(pos, stackpos, item); - } - } - } - void sendUpdateTileItem(const Tile* tile, const Position& pos, const Item* item) { - if (client) { - int32_t stackpos = tile->getStackposOfItem(this, item); - if (stackpos != -1) { - client->sendUpdateTileItem(pos, stackpos, item); - } - } - } - void sendRemoveTileThing(const Position& pos, int32_t stackpos) { - if (stackpos != -1 && client) { - client->sendRemoveTileThing(pos, stackpos); - } - } - void sendRemoveTileCreature(const Creature* creature, const Position& pos, int32_t stackpos) { - if (client) { - client->sendRemoveTileCreature(creature, pos, stackpos); - } - } - void sendUpdateTile(const Tile* tile, const Position& pos) { - if (client) { - client->sendUpdateTile(tile, pos); - } + uint32_t getFreeCapacity() const + { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return std::numeric_limits::max(); } + return std::max(0, capacity - inventoryWeight); + } + + int32_t getMaxHealth() const override { return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); } + uint32_t getMana() const { return mana; } + uint32_t getMaxMana() const { return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); } - void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) { - if (client) { - client->sendChannelMessage(author, text, type, channel); - } - } - void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) { - if (client) { - client->sendChannelEvent(channelId, playerName, channelEvent); - } - } - void sendCreatureAppear(const Creature* creature, const Position& pos, bool isLogin) { - if (client) { - client->sendAddCreature(creature, pos, creature->getTile()->getClientIndexOfCreature(this, creature), isLogin); - } - } - void sendCreatureMove(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) { - if (client) { - client->sendMoveCreature(creature, newPos, newStackPos, oldPos, oldStackPos, teleport); - } - } - void sendCreatureTurn(const Creature* creature) { - if (client && canSeeCreature(creature)) { - int32_t stackpos = creature->getTile()->getClientIndexOfCreature(this, creature); - if (stackpos != -1) { - client->sendCreatureTurn(creature, stackpos); - } - } - } - void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr) { - if (client) { - client->sendCreatureSay(creature, type, text, pos); - } - } - void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text) { - if (client) { - client->sendPrivateMessage(speaker, type, text); - } - } - void sendCreatureSquare(const Creature* creature, SquareColor_t color) { - if (client) { - client->sendCreatureSquare(creature, color); - } - } - void sendCreatureChangeOutfit(const Creature* creature, const Outfit_t& outfit) { - if (client) { + Item* getInventoryItem(slots_t slot) const; + + bool isItemAbilityEnabled(slots_t slot) const { return inventoryAbilities[slot]; } + void setItemAbility(slots_t slot, bool enabled) { inventoryAbilities[slot] = enabled; } + + void setVarSkill(skills_t skill, int32_t modifier) { varSkills[skill] += modifier; } + + void setVarSpecialSkill(SpecialSkills_t skill, int32_t modifier) { varSpecialSkills[skill] += modifier; } + + void setSpecialMagicLevelSkill(CombatType_t type, int16_t modifier) + { + specialMagicLevelSkill[combatTypeToIndex(type)] += modifier; + } + + void setVarStats(stats_t stat, int32_t modifier); + + int32_t getDefaultStats(stats_t stat) const; + + void addConditionSuppressions(uint32_t conditions); + void removeConditionSuppressions(uint32_t conditions); + + DepotChest* getDepotChest(uint32_t depotId, bool autoCreate); + DepotLocker& getDepotLocker(); + void onReceiveMail() const; + bool isNearDepotBox() const; + + bool canSee(const Position& pos) const override; + bool canSeeCreature(const Creature* creature) const override; + + bool canWalkthrough(const Creature* creature) const; + bool canWalkthroughEx(const Creature* creature) const; + + RaceType_t getRace() const override { return RACE_BLOOD; } + + uint64_t getMoney() const; + + // safe-trade functions + void setTradeState(tradestate_t state) { tradeState = state; } + tradestate_t getTradeState() const { return tradeState; } + Item* getTradeItem() { return tradeItem; } + + // shop functions + void setShopOwner(Npc* owner, int32_t onBuy, int32_t onSell) + { + shopOwner = owner; + purchaseCallback = onBuy; + saleCallback = onSell; + } + + Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) + { + onBuy = purchaseCallback; + onSell = saleCallback; + return shopOwner; + } + + const Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) const + { + onBuy = purchaseCallback; + onSell = saleCallback; + return shopOwner; + } + + // V.I.P. functions + void notifyStatusChange(Player* loginPlayer, VipStatus_t status); + bool removeVIP(uint32_t vipGuid); + bool addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status); + bool addVIPInternal(uint32_t vipGuid); + bool editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify); + + // follow functions + bool setFollowCreature(Creature* creature) override; + void goToFollowCreature() override; + + // follow events + void onFollowCreature(const Creature* creature) override; + + // walk events + void onWalk(Direction& dir) override; + void onWalkAborted() override; + void onWalkComplete() override; + + void stopWalk(); + void openShopWindow(Npc* npc, const std::list& shop); + bool closeShopWindow(bool sendCloseShopWindow = true); + bool updateSaleShopList(const Item* item); + bool hasShopItemForSale(uint32_t itemId, uint8_t subType) const; + + void setChaseMode(bool mode); + void setFightMode(fightMode_t mode) { fightMode = mode; } + void setSecureMode(bool mode) { secureMode = mode; } + + // combat functions + bool setAttackedCreature(Creature* creature) override; + bool isImmune(CombatType_t type) const override; + bool isImmune(ConditionType_t type) const override; + bool hasShield() const; + bool isAttackable() const override; + static bool lastHitIsPlayer(Creature* lastHitCreature); + + void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; + void changeMana(int32_t manaChange); + void changeSoul(int32_t soulChange); + + bool isPzLocked() const { return pzLocked; } + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense = false, + bool checkArmor = false, bool field = false, bool ignoreResistances = false) override; + void doAttacking(uint32_t interval) override; + bool hasExtraSwing() override { return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); } + + uint16_t getSpecialSkill(uint8_t skill) const { return std::max(0, varSpecialSkills[skill]); } + uint16_t getSkillLevel(uint8_t skill) const + { + return std::max(0, skills[skill].level + varSkills[skill]); + } + uint16_t getSpecialMagicLevelSkill(CombatType_t type) const + { + return std::max(0, specialMagicLevelSkill[combatTypeToIndex(type)]); + } + uint16_t getBaseSkill(uint8_t skill) const { return skills[skill].level; } + uint8_t getSkillPercent(uint8_t skill) const { return skills[skill].percent; } + + bool getAddAttackSkill() const { return addAttackSkillPoint; } + BlockType_t getLastAttackBlockType() const { return lastAttackBlockType; } + + Item* getWeapon(slots_t slot, bool ignoreAmmo) const; + Item* getWeapon(bool ignoreAmmo = false) const; + WeaponType_t getWeaponType() const; + int32_t getWeaponSkill(const Item* item) const; + void getShieldAndWeapon(const Item*& shield, const Item*& weapon) const; + + void drainHealth(Creature* attacker, int32_t damage) override; + void drainMana(Creature* attacker, int32_t manaLoss); + void addManaSpent(uint64_t amount); + void removeManaSpent(uint64_t amount, bool notify = false); + void addSkillAdvance(skills_t skill, uint64_t count); + void removeSkillTries(skills_t skill, uint64_t count, bool notify = false); + + int32_t getArmor() const override; + int32_t getDefense() const override; + float getAttackFactor() const override; + float getDefenseFactor() const override; + + void addInFightTicks(bool pzlock = false); + + uint64_t getGainedExperience(Creature* attacker) const override; + + // combat event functions + void onAddCondition(ConditionType_t type) override; + void onAddCombatCondition(ConditionType_t type) override; + void onEndCondition(ConditionType_t type) override; + void onCombatRemoveCondition(Condition* condition) override; + void onAttackedCreature(Creature* target, bool addFightTicks = true) override; + void onAttacked() override; + void onAttackedCreatureDrainHealth(Creature* target, int32_t points) override; + void onTargetCreatureGainHealth(Creature* target, int32_t points) override; + bool onKilledCreature(Creature* target, bool lastHit = true) override; + void onGainExperience(uint64_t gainExp, Creature* target) override; + void onGainSharedExperience(uint64_t gainExp, Creature* source); + void onAttackedCreatureBlockHit(BlockType_t blockType) override; + void onBlockHit() override; + void onChangeZone(ZoneType_t zone) override; + void onAttackedCreatureChangeZone(ZoneType_t zone) override; + void onIdleStatus() override; + void onPlacedCreature() override; + + LightInfo getCreatureLight() const override; + + Skulls_t getSkull() const override; + Skulls_t getSkullClient(const Creature* creature) const override; + int64_t getSkullTicks() const { return skullTicks; } + void setSkullTicks(int64_t ticks) { skullTicks = ticks; } + + bool hasAttacked(const Player* attacked) const; + void addAttacked(const Player* attacked); + void removeAttacked(const Player* attacked); + void clearAttacked(); + void addUnjustifiedDead(const Player* attacked); + void sendCreatureSkull(const Creature* creature) const + { + if (client) { + client->sendCreatureSkull(creature); + } + } + void checkSkullTicks(int64_t ticks); + + bool canWear(uint32_t lookType, uint8_t addons) const; + bool hasOutfit(uint32_t lookType, uint8_t addons); + void addOutfit(uint16_t lookType, uint8_t addons); + bool removeOutfit(uint16_t lookType); + bool removeOutfitAddon(uint16_t lookType, uint8_t addons); + bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const; + + size_t getMaxVIPEntries() const; + size_t getMaxDepotItems() const; + + // quest tracker + size_t getMaxTrackedQuests() const; + void resetQuestTracker(const std::vector& missionIds); + + // tile + // send methods + void sendAddTileItem(const Tile* tile, const Position& pos, const Item* item) + { + if (client) { + int32_t stackpos = tile->getStackposOfItem(this, item); + if (stackpos != -1) { + client->sendAddTileItem(pos, stackpos, item); + } + } + } + void sendUpdateTileItem(const Tile* tile, const Position& pos, const Item* item) + { + if (client) { + int32_t stackpos = tile->getStackposOfItem(this, item); + if (stackpos != -1) { + client->sendUpdateTileItem(pos, stackpos, item); + } + } + } + void sendRemoveTileThing(const Position& pos, int32_t stackpos) + { + if (stackpos != -1 && client) { + client->sendRemoveTileThing(pos, stackpos); + } + } + void sendUpdateTileCreature(const Creature* creature) + { + if (client) { + client->sendUpdateTileCreature(creature->getPosition(), + creature->getTile()->getClientIndexOfCreature(this, creature), creature); + } + } + void sendRemoveTileCreature(const Creature* creature, const Position& pos, int32_t stackpos) + { + if (client) { + client->sendRemoveTileCreature(creature, pos, stackpos); + } + } + void sendUpdateTile(const Tile* tile, const Position& pos) + { + if (client) { + client->sendUpdateTile(tile, pos); + } + } + + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) + { + if (client) { + client->sendChannelMessage(author, text, type, channel); + } + } + void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) + { + if (client) { + client->sendChannelEvent(channelId, playerName, channelEvent); + } + } + void sendCreatureAppear(const Creature* creature, const Position& pos, + MagicEffectClasses magicEffect = CONST_ME_NONE) + { + if (client) { + client->sendAddCreature(creature, pos, creature->getTile()->getClientIndexOfCreature(this, creature), + magicEffect); + } + } + void sendCreatureMove(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, + int32_t oldStackPos, bool teleport) + { + if (client) { + client->sendMoveCreature(creature, newPos, newStackPos, oldPos, oldStackPos, teleport); + } + } + void sendCreatureTurn(const Creature* creature) + { + if (client && canSeeCreature(creature)) { + int32_t stackpos = creature->getTile()->getClientIndexOfCreature(this, creature); + if (stackpos != -1) { + client->sendCreatureTurn(creature, stackpos); + } + } + } + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, + const Position* pos = nullptr) + { + if (client) { + client->sendCreatureSay(creature, type, text, pos); + } + } + void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text) + { + if (client) { + client->sendPrivateMessage(speaker, type, text); + } + } + void sendCreatureSquare(const Creature* creature, SquareColor_t color) + { + if (client) { + client->sendCreatureSquare(creature, color); + } + } + void sendCreatureChangeOutfit(const Creature* creature, const Outfit_t& outfit) + { + if (client) { + client->sendCreatureOutfit(creature, outfit); + } + } + void sendCreatureChangeVisible(const Creature* creature, bool visible) + { + if (!client) { + return; + } + + if (creature->getPlayer()) { + if (visible) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + static Outfit_t outfit; client->sendCreatureOutfit(creature, outfit); } - } - void sendCreatureChangeVisible(const Creature* creature, bool visible) { - if (!client) { + } else if (canSeeInvisibility()) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + int32_t stackpos = creature->getTile()->getClientIndexOfCreature(this, creature); + if (stackpos == -1) { return; } - if (creature->getPlayer()) { - if (visible) { - client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); - } else { - static Outfit_t outfit; - client->sendCreatureOutfit(creature, outfit); - } - } else if (canSeeInvisibility()) { - client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + if (visible) { + client->sendAddCreature(creature, creature->getPosition(), stackpos); } else { - int32_t stackpos = creature->getTile()->getClientIndexOfCreature(this, creature); - if (stackpos == -1) { - return; - } - - if (visible) { - client->sendAddCreature(creature, creature->getPosition(), stackpos, false); - } else { - client->sendRemoveTileCreature(creature, creature->getPosition(), stackpos); - } - } - } - void sendCreatureLight(const Creature* creature) { - if (client) { - client->sendCreatureLight(creature); - } - } - void sendCreatureWalkthrough(const Creature* creature, bool walkthrough) { - if (client) { - client->sendCreatureWalkthrough(creature, walkthrough); - } - } - void sendCreatureShield(const Creature* creature) { - if (client) { - client->sendCreatureShield(creature); - } - } - void sendCreatureType(uint32_t creatureId, uint8_t creatureType) { - if (client) { - client->sendCreatureType(creatureId, creatureType); - } - } - void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) { - if (client) { - client->sendCreatureHelpers(creatureId, helpers); - } - } - void sendSpellCooldown(uint8_t spellId, uint32_t time) { - if (client) { - client->sendSpellCooldown(spellId, time); - } - } - void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) { - if (client) { - client->sendSpellGroupCooldown(groupId, time); - } - } - void sendModalWindow(const ModalWindow& modalWindow); - - //container - void sendAddContainerItem(const Container* container, const Item* item); - void sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem); - void sendRemoveContainerItem(const Container* container, uint16_t slot); - void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) { - if (client) { - client->sendContainer(cid, container, hasParent, firstIndex); - } - } - - //inventory - void sendInventoryItem(slots_t slot, const Item* item) { - if (client) { - client->sendInventoryItem(slot, item); - } - } - void sendItems() { - if (client) { - client->sendItems(); - } - } - - //event methods - void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType) override; - void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item) override; + client->sendRemoveTileCreature(creature, creature->getPosition(), stackpos); + } + } + } + void sendCreatureLight(const Creature* creature) + { + if (client) { + client->sendCreatureLight(creature); + } + } + void sendCreatureWalkthrough(const Creature* creature, bool walkthrough) + { + if (client) { + client->sendCreatureWalkthrough(creature, walkthrough); + } + } + void sendCreatureShield(const Creature* creature) + { + if (client) { + client->sendCreatureShield(creature); + } + } + void sendSpellCooldown(uint8_t spellId, uint32_t time) + { + if (client) { + client->sendSpellCooldown(spellId, time); + } + } + void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) + { + if (client) { + client->sendSpellGroupCooldown(groupId, time); + } + } + void sendUseItemCooldown(uint32_t time) + { + if (client) { + client->sendUseItemCooldown(time); + } + } + void sendSupplyUsed(const uint16_t clientId) const + { + if (client) { + client->sendSupplyUsed(clientId); + } + } + void sendModalWindow(const ModalWindow& modalWindow); + + // container + void sendAddContainerItem(const Container* container, const Item* item); + void sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem); + void sendRemoveContainerItem(const Container* container, uint16_t slot); + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) + { + if (client) { + client->sendContainer(cid, container, hasParent, firstIndex); + } + } + + // inventory + void sendInventoryItem(slots_t slot, const Item* item) + { + if (client) { + client->sendInventoryItem(slot, item); + } + } + void sendItems() + { + if (client) { + client->sendItems(); + } + } + void openSavedContainers(); + + // event methods + void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, const ItemType& oldType, + const Item* newItem, const ItemType& newType) override; + void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, const Item* item) override; + + void onCreatureAppear(Creature* creature, bool isLogin) override; + void onRemoveCreature(Creature* creature, bool isLogout) override; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, + const Position& oldPos, bool teleport) override; + + void onAttackedCreatureDisappear(bool isLogout) override; + void onFollowCreatureDisappear(bool isLogout) override; + + // container + void onAddContainerItem(const Item* item); + void onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem); + void onRemoveContainerItem(const Container* container, const Item* item); + + void onCloseContainer(const Container* container); + void onSendContainer(const Container* container); + void autoCloseContainers(const Container* container); + + // inventory + void onUpdateInventoryItem(Item* oldItem, Item* newItem); + void onRemoveInventoryItem(Item* item); + + void sendCancelMessage(const std::string& msg) const + { + if (client) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, msg)); + } + } + void sendCancelMessage(ReturnValue message) const; + void sendCancelTarget() const + { + if (client) { + client->sendCancelTarget(); + } + } + void sendCancelWalk() const + { + if (client) { + client->sendCancelWalk(); + } + } + void sendChangeSpeed(const Creature* creature, uint32_t newSpeed) const + { + if (client) { + client->sendChangeSpeed(creature, newSpeed); + } + } + void sendCreatureHealth(const Creature* creature) const + { + if (client) { + client->sendCreatureHealth(creature); + } + } + void sendDistanceShoot(const Position& from, const Position& to, unsigned char type) const + { + if (client) { + client->sendDistanceShoot(from, to, type); + } + } + void sendHouseWindow(House* house, uint32_t listId) const; + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) + { + if (client) { + client->sendCreatePrivateChannel(channelId, channelName); + } + } + void sendClosePrivate(uint16_t channelId); + void sendIcons() const + { + if (client) { + client->sendIcons(getClientIcons()); + } + } + void sendMagicEffect(const Position& pos, uint8_t type) const + { + if (client) { + client->sendMagicEffect(pos, type); + } + } + void sendPing(); + void sendPingBack() const + { + if (client) { + client->sendPingBack(); + } + } + void sendStats(); + + void sendExperienceTracker(int64_t rawExp, int64_t finalExp) const + { + if (client) { + client->sendExperienceTracker(rawExp, finalExp); + } + } + + void sendBasicData() const + { + if (client) { + client->sendBasicData(); + } + } + void sendSkills() const + { + if (client) { + client->sendSkills(); + } + } + void sendTextMessage(MessageClasses mclass, const std::string& message) const + { + if (client) { + client->sendTextMessage(TextMessage(mclass, message)); + } + } + void sendTextMessage(const TextMessage& message) const + { + if (client) { + client->sendTextMessage(message); + } + } + void sendReLoginWindow(uint8_t unfairFightReduction) const + { + if (client) { + client->sendReLoginWindow(unfairFightReduction); + } + } + void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const + { + if (client) { + client->sendTextWindow(windowTextId, item, maxlen, canWrite); + } + } + void sendTextWindow(uint32_t itemId, const std::string& text) const + { + if (client) { + client->sendTextWindow(windowTextId, itemId, text); + } + } + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) const + { + if (client) { + client->sendToChannel(creature, type, text, channelId); + } + } + void sendShop(Npc* npc) const + { + if (client) { + client->sendShop(npc, shopItemList); + } + } + void sendSaleItemList() const + { + if (client) { + client->sendSaleItemList(shopItemList); + } + } + void sendCloseShop() const + { + if (client) { + client->sendCloseShop(); + } + } + void sendMarketEnter() const + { + if (client) { + client->sendMarketEnter(); + } + } + void sendMarketLeave() + { + inMarket = false; + if (client) { + client->sendMarketLeave(); + } + } + void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, + const MarketOfferList& sellOffers) const + { + if (client) { + client->sendMarketBrowseItem(itemId, buyOffers, sellOffers); + } + } + void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const + { + if (client) { + client->sendMarketBrowseOwnOffers(buyOffers, sellOffers); + } + } + void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, + const HistoryMarketOfferList& sellOffers) const + { + if (client) { + client->sendMarketBrowseOwnHistory(buyOffers, sellOffers); + } + } + void sendMarketAcceptOffer(const MarketOfferEx& offer) const + { + if (client) { + client->sendMarketAcceptOffer(offer); + } + } + void sendMarketCancelOffer(const MarketOfferEx& offer) const + { + if (client) { + client->sendMarketCancelOffer(offer); + } + } + void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const + { + if (client) { + client->sendTradeItemRequest(traderName, item, ack); + } + } + void sendTradeClose() const + { + if (client) { + client->sendCloseTrade(); + } + } + void sendWorldLight(LightInfo lightInfo) + { + if (client) { + client->sendWorldLight(lightInfo); + } + } + void sendWorldTime() + { + if (client) { + client->sendWorldTime(); + } + } + void sendChannelsDialog() + { + if (client) { + client->sendChannelsDialog(); + } + } + void sendOpenPrivateChannel(const std::string& receiver) + { + if (client) { + client->sendOpenPrivateChannel(receiver); + } + } + void sendOutfitWindow() + { + if (client) { + client->sendOutfitWindow(); + } + } + void sendPodiumWindow(const Item* item) + { + if (client) { + client->sendPodiumWindow(item); + } + } + void sendCloseContainer(uint8_t cid) + { + if (client) { + client->sendCloseContainer(cid); + } + } + + void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, + const InvitedMap* invitedUsers) + { + if (client) { + client->sendChannel(channelId, channelName, channelUsers, invitedUsers); + } + } + void sendTutorial(uint8_t tutorialId) + { + if (client) { + client->sendTutorial(tutorialId); + } + } + void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) + { + if (client) { + client->sendAddMarker(pos, markType, desc); + } + } + void sendQuestLog() + { + if (client) { + client->sendQuestLog(); + } + } + void sendQuestLine(const Quest* quest) + { + if (client) { + client->sendQuestLine(quest); + } + } + void sendQuestTracker() + { + if (client) { + client->sendQuestTracker(); + } + } + void sendUpdateQuestTracker(const TrackedQuest& trackedQuest) + { + if (client) { + client->sendUpdateQuestTracker(trackedQuest); + } + } + void sendEnterWorld() + { + if (client) { + client->sendEnterWorld(); + } + } + void sendFightModes() + { + if (client) { + client->sendFightModes(); + } + } + void sendNetworkMessage(const NetworkMessage& message) + { + if (client) { + client->writeToOutputBuffer(message); + } + } + + void receivePing() { lastPong = OTSYS_TIME(); } + + void onThink(uint32_t interval) override; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + + void setNextAction(int64_t time) + { + if (time > nextAction) { + nextAction = time; + } + } + bool canDoAction() const { return nextAction <= OTSYS_TIME(); } + uint32_t getNextActionTime() const; + + Item* getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen); + void setWriteItem(Item* item, uint16_t maxWriteLen = 0); + + House* getEditHouse(uint32_t& windowTextId, uint32_t& listId); + void setEditHouse(House* house, uint32_t listId = 0); + + void learnInstantSpell(const std::string& spellName); + void forgetInstantSpell(const std::string& spellName); + bool hasLearnedInstantSpell(const std::string& spellName) const; + + void updateRegeneration(); + + const std::map& getOpenContainers() const { return openContainers; } + +private: + std::forward_list getMuteConditions() const; - void onCreatureAppear(Creature* creature, bool isLogin) override; - void onRemoveCreature(Creature* creature, bool isLogout) override; - void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) override; + void checkTradeState(const Item* item); + bool hasCapacity(const Item* item, uint32_t count) const; + + void gainExperience(uint64_t gainExp, Creature* source); + void addExperience(Creature* source, uint64_t exp, bool sendText = false); + void removeExperience(uint64_t exp, bool sendText = false); + + void updateInventoryWeight(); - void onAttackedCreatureDisappear(bool isLogout) override; - void onFollowCreatureDisappear(bool isLogout) override; + void setNextWalkActionTask(SchedulerTask* task); + void setNextWalkTask(SchedulerTask* task); + void setNextActionTask(SchedulerTask* task, bool resetIdleTime = true); - //container - void onAddContainerItem(const Item* item); - void onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem); - void onRemoveContainerItem(const Container* container, const Item* item); + void death(Creature* lastHitCreature) override; + bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, + bool mostDamageUnjustified) override; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; - void onCloseContainer(const Container* container); - void onSendContainer(const Container* container); - void autoCloseContainers(const Container* container); + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - //inventory - void onUpdateInventoryItem(Item* oldItem, Item* newItem); - void onRemoveInventoryItem(Item* item); - - void sendCancelMessage(const std::string& msg) const { - if (client) { - client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, msg)); - } - } - void sendCancelMessage(ReturnValue message) const; - void sendCancelTarget() const { - if (client) { - client->sendCancelTarget(); - } - } - void sendCancelWalk() const { - if (client) { - client->sendCancelWalk(); - } - } - void sendChangeSpeed(const Creature* creature, uint32_t newSpeed) const { - if (client) { - client->sendChangeSpeed(creature, newSpeed); - } - } - void sendCreatureHealth(const Creature* creature) const { - if (client) { - client->sendCreatureHealth(creature); - } - } - void sendDistanceShoot(const Position& from, const Position& to, unsigned char type) const { - if (client) { - client->sendDistanceShoot(from, to, type); - } - } - void sendHouseWindow(House* house, uint32_t listId) const; - void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) { - if (client) { - client->sendCreatePrivateChannel(channelId, channelName); - } - } - void sendClosePrivate(uint16_t channelId); - void sendIcons() const { - if (client) { - client->sendIcons(getClientIcons()); - } - } - void sendMagicEffect(const Position& pos, uint8_t type) const { - if (client) { - client->sendMagicEffect(pos, type); - } - } - void sendPing(); - void sendPingBack() const { - if (client) { - client->sendPingBack(); - } - } - void sendStats(); - void sendBasicData() const { - if (client) { - client->sendBasicData(); - } - } - void sendSkills() const { - if (client) { - client->sendSkills(); - } - } - void sendTextMessage(MessageClasses mclass, const std::string& message) const { - if (client) { - client->sendTextMessage(TextMessage(mclass, message)); - } - } - void sendTextMessage(const TextMessage& message) const { - if (client) { - client->sendTextMessage(message); - } - } - void sendReLoginWindow(uint8_t unfairFightReduction) const { - if (client) { - client->sendReLoginWindow(unfairFightReduction); - } - } - void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const { - if (client) { - client->sendTextWindow(windowTextId, item, maxlen, canWrite); - } - } - void sendTextWindow(uint32_t itemId, const std::string& text) const { - if (client) { - client->sendTextWindow(windowTextId, itemId, text); - } - } - void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) const { - if (client) { - client->sendToChannel(creature, type, text, channelId); - } - } - void sendShop(Npc* npc) const { - if (client) { - client->sendShop(npc, shopItemList); - } - } - void sendSaleItemList() const { - if (client) { - client->sendSaleItemList(shopItemList); - } - } - void sendCloseShop() const { - if (client) { - client->sendCloseShop(); - } - } - void sendMarketEnter(uint32_t depotId) const { - if (client) { - client->sendMarketEnter(depotId); - } - } - void sendMarketLeave() { - inMarket = false; - if (client) { - client->sendMarketLeave(); - } - } - void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { - if (client) { - client->sendMarketBrowseItem(itemId, buyOffers, sellOffers); - } - } - void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { - if (client) { - client->sendMarketBrowseOwnOffers(buyOffers, sellOffers); - } - } - void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) const { - if (client) { - client->sendMarketBrowseOwnHistory(buyOffers, sellOffers); - } - } - void sendMarketDetail(uint16_t itemId) const { - if (client) { - client->sendMarketDetail(itemId); - } - } - void sendMarketAcceptOffer(const MarketOfferEx& offer) const { - if (client) { - client->sendMarketAcceptOffer(offer); - } - } - void sendMarketCancelOffer(const MarketOfferEx& offer) const { - if (client) { - client->sendMarketCancelOffer(offer); - } - } - void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const { - if (client) { - client->sendTradeItemRequest(traderName, item, ack); - } - } - void sendTradeClose() const { - if (client) { - client->sendCloseTrade(); - } - } - void sendWorldLight(LightInfo lightInfo) { - if (client) { - client->sendWorldLight(lightInfo); - } - } - void sendChannelsDialog() { - if (client) { - client->sendChannelsDialog(); - } - } - void sendOpenPrivateChannel(const std::string& receiver) { - if (client) { - client->sendOpenPrivateChannel(receiver); - } - } - void sendOutfitWindow() { - if (client) { - client->sendOutfitWindow(); - } - } - void sendCloseContainer(uint8_t cid) { - if (client) { - client->sendCloseContainer(cid); - } - } + void addThing(Thing*) override {} + void addThing(int32_t index, Thing* thing) override; - void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers) { - if (client) { - client->sendChannel(channelId, channelName, channelUsers, invitedUsers); - } - } - void sendTutorial(uint8_t tutorialId) { - if (client) { - client->sendTutorial(tutorialId); - } - } - void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) { - if (client) { - client->sendAddMarker(pos, markType, desc); - } - } - void sendQuestLog() { - if (client) { - client->sendQuestLog(); - } - } - void sendQuestLine(const Quest* quest) { - if (client) { - client->sendQuestLine(quest); - } - } - void sendEnterWorld() { - if (client) { - client->sendEnterWorld(); - } - } - void sendFightModes() { - if (client) { - client->sendFightModes(); - } - } - void sendNetworkMessage(const NetworkMessage& message) { - if (client) { - client->writeToOutputBuffer(message); - } - } + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void receivePing() { - lastPong = OTSYS_TIME(); - } + void removeThing(Thing* thing, uint32_t count) override; - void onThink(uint32_t interval) override; + int32_t getThingIndex(const Thing* thing) const override; + size_t getFirstIndex() const override; + size_t getLastIndex() const override; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override; + std::map& getAllItemTypeCount(std::map& countMap) const override; + Thing* getThing(size_t index) const override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void internalAddThing(Thing* thing) override; + void internalAddThing(uint32_t index, Thing* thing) override; - void setNextAction(int64_t time) { - if (time > nextAction) { - nextAction = time; - } - } - bool canDoAction() const { - return nextAction <= OTSYS_TIME(); - } - uint32_t getNextActionTime() const; + std::unordered_set attackedSet; + std::unordered_set VIPList; - Item* getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen); - void setWriteItem(Item* item, uint16_t maxWriteLen = 0); + std::map openContainers; + std::map depotChests; + std::map storageMap; - House* getEditHouse(uint32_t& windowTextId, uint32_t& listId); - void setEditHouse(House* house, uint32_t listId = 0); - - void learnInstantSpell(const std::string& spellName); - void forgetInstantSpell(const std::string& spellName); - bool hasLearnedInstantSpell(const std::string& spellName) const; - - private: - std::forward_list getMuteConditions() const; - - void checkTradeState(const Item* item); - bool hasCapacity(const Item* item, uint32_t count) const; - - void gainExperience(uint64_t gainExp, Creature* source); - void addExperience(Creature* source, uint64_t exp, bool sendText = false); - void removeExperience(uint64_t exp, bool sendText = false); - - void updateInventoryWeight(); - - void setNextWalkActionTask(SchedulerTask* task); - void setNextWalkTask(SchedulerTask* task); - void setNextActionTask(SchedulerTask* task, bool resetIdleTime = true); - - void death(Creature* lastHitCreature) override; - bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) override; - Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; - - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, - uint32_t flags) const override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; - Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) override; - - void addThing(Thing*) override {} - void addThing(int32_t index, Thing* thing) override; - - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; - - void removeThing(Thing* thing, uint32_t count) override; - - int32_t getThingIndex(const Thing* thing) const override; - size_t getFirstIndex() const override; - size_t getLastIndex() const override; - uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override; - std::map& getAllItemTypeCount(std::map& countMap) const override; - Thing* getThing(size_t index) const override; - - void internalAddThing(Thing* thing) override; - void internalAddThing(uint32_t index, Thing* thing) override; - - std::unordered_set attackedSet; - std::unordered_set VIPList; - - std::map openContainers; - std::map depotLockerMap; - std::map depotChests; - std::map storageMap; - - std::vector outfits; - GuildWarVector guildWarVector; - - std::list shopItemList; - - std::forward_list invitePartyList; - std::forward_list modalWindows; - std::forward_list learnedInstantSpellList; - std::forward_list storedConditionList; // TODO: This variable is only temporarily used when logging in, get rid of it somehow - - std::string name; - std::string guildNick; - - Skill skills[SKILL_LAST + 1]; - LightInfo itemsLight; - Position loginPosition; - Position lastWalkthroughPosition; - - time_t lastLoginSaved = 0; - time_t lastLogout = 0; - time_t premiumEndsAt = 0; - - uint64_t experience = 0; - uint64_t manaSpent = 0; - uint64_t lastAttack = 0; - uint64_t bankBalance = 0; - uint64_t lastQuestlogUpdate = 0; - int64_t lastFailedFollow = 0; - int64_t skullTicks = 0; - int64_t lastWalkthroughAttempt = 0; - int64_t lastToggleMount = 0; - int64_t lastPing; - int64_t lastPong; - int64_t nextAction = 0; - - BedItem* bedItem = nullptr; - Guild* guild = nullptr; - GuildRank_ptr guildRank = nullptr; - Group* group = nullptr; - Inbox* inbox; - Item* tradeItem = nullptr; - Item* inventory[CONST_SLOT_LAST + 1] = {}; - Item* writeItem = nullptr; - House* editHouse = nullptr; - Npc* shopOwner = nullptr; - Party* party = nullptr; - Player* tradePartner = nullptr; - ProtocolGame_ptr client; - SchedulerTask* walkTask = nullptr; - Town* town = nullptr; - Vocation* vocation = nullptr; - StoreInbox* storeInbox = nullptr; - - uint32_t inventoryWeight = 0; - uint32_t capacity = 40000; - uint32_t damageImmunities = 0; - uint32_t conditionImmunities = 0; - uint32_t conditionSuppressions = 0; - uint32_t level = 1; - uint32_t magLevel = 0; - uint32_t actionTaskEvent = 0; - uint32_t nextStepEvent = 0; - uint32_t walkTaskEvent = 0; - uint32_t MessageBufferTicks = 0; - uint32_t lastIP = 0; - uint32_t accountNumber = 0; - uint32_t guid = 0; - uint32_t windowTextId = 0; - uint32_t editListId = 0; - uint32_t mana = 0; - uint32_t manaMax = 0; - int32_t varSkills[SKILL_LAST + 1] = {}; - int32_t varSpecialSkills[SPECIALSKILL_LAST + 1] = {}; - int32_t varStats[STAT_LAST + 1] = {}; - int32_t purchaseCallback = -1; - int32_t saleCallback = -1; - int32_t MessageBufferCount = 0; - int32_t bloodHitCount = 0; - int32_t shieldBlockCount = 0; - int32_t offlineTrainingSkill = -1; - int32_t offlineTrainingTime = 0; - int32_t idleTime = 0; - - uint16_t lastStatsTrainingTime = 0; - uint16_t staminaMinutes = 2520; - uint16_t maxWriteLen = 0; - int16_t lastDepotId = -1; - - uint8_t soul = 0; - uint8_t blessings = 0; - uint8_t levelPercent = 0; - uint8_t magLevelPercent = 0; - - PlayerSex_t sex = PLAYERSEX_FEMALE; - OperatingSystem_t operatingSystem = CLIENTOS_NONE; - BlockType_t lastAttackBlockType = BLOCK_NONE; - tradestate_t tradeState = TRADE_NONE; - fightMode_t fightMode = FIGHTMODE_ATTACK; - AccountType_t accountType = ACCOUNT_TYPE_NORMAL; - - bool chaseMode = false; - bool secureMode = false; - bool inMarket = false; - bool wasMounted = false; - bool ghostMode = false; - bool pzLocked = false; - bool isConnecting = false; - bool addAttackSkillPoint = false; - bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; - - static uint32_t playerAutoID; - - void updateItemsLight(bool internal = false); - int32_t getStepSpeed() const override { - return std::max(PLAYER_MIN_SPEED, std::min(PLAYER_MAX_SPEED, getSpeed())); - } - void updateBaseSpeed() { - if (!hasFlag(PlayerFlag_SetMaxSpeed)) { - baseSpeed = vocation->getBaseSpeed() + (2 * (level - 1)); - } else { - baseSpeed = PLAYER_MAX_SPEED; - } - } + std::vector outfits; + GuildWarVector guildWarVector; - bool isPromoted() const; + std::list shopItemList; - uint32_t getAttackSpeed() const { - return vocation->getAttackSpeed(); - } + std::forward_list invitePartyList; + std::forward_list modalWindows; + std::forward_list learnedInstantSpellList; + std::forward_list + storedConditionList; // TODO: This variable is only temporarily used when logging in, get rid of it somehow - static uint8_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); - double getLostPercent() const; - uint64_t getLostExperience() const override { - return skillLoss ? static_cast(experience * getLostPercent()) : 0; - } - uint32_t getDamageImmunities() const override { - return damageImmunities; - } - uint32_t getConditionImmunities() const override { - return conditionImmunities; - } - uint32_t getConditionSuppressions() const override { - return conditionSuppressions; - } - uint16_t getLookCorpse() const override; - void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; + // quest tracker + std::vector trackedQuests; - friend class Game; - friend class Npc; - friend class LuaScriptInterface; - friend class Map; - friend class Actions; - friend class IOLoginData; - friend class ProtocolGame; + std::string name; + std::string guildNick; + + Skill skills[SKILL_LAST + 1]; + LightInfo itemsLight; + Position loginPosition; + Position lastWalkthroughPosition; + + time_t lastLoginSaved = 0; + time_t lastLogout = 0; + time_t premiumEndsAt = 0; + + uint64_t experience = 0; + uint64_t manaSpent = 0; + uint64_t lastAttack = 0; + uint64_t bankBalance = 0; + uint64_t lastQuestlogUpdate = 0; + int64_t lastFailedFollow = 0; + int64_t skullTicks = 0; + int64_t lastWalkthroughAttempt = 0; + int64_t lastToggleMount = 0; + int64_t lastPing; + int64_t lastPong; + int64_t nextAction = 0; + + ProtocolGame_ptr client; + BedItem* bedItem = nullptr; + Guild* guild = nullptr; + GuildRank_ptr guildRank = nullptr; + Group* group = nullptr; + Inbox* inbox; + Item* tradeItem = nullptr; + Item* inventory[CONST_SLOT_LAST + 1] = {}; + Item* writeItem = nullptr; + House* editHouse = nullptr; + Npc* shopOwner = nullptr; + Party* party = nullptr; + Player* tradePartner = nullptr; + SchedulerTask* walkTask = nullptr; + Town* town = nullptr; + Vocation* vocation = nullptr; + StoreInbox* storeInbox = nullptr; + DepotLocker_ptr depotLocker = nullptr; + + uint32_t inventoryWeight = 0; + uint32_t capacity = 40000; + uint32_t damageImmunities = 0; + uint32_t conditionImmunities = 0; + uint32_t conditionSuppressions = 0; + uint32_t level = 1; + uint32_t magLevel = 0; + uint32_t actionTaskEvent = 0; + uint32_t nextStepEvent = 0; + uint32_t walkTaskEvent = 0; + uint32_t MessageBufferTicks = 0; + uint32_t lastIP = 0; + uint32_t accountNumber = 0; + uint32_t guid = 0; + uint32_t windowTextId = 0; + uint32_t editListId = 0; + uint32_t mana = 0; + uint32_t manaMax = 0; + int32_t varSkills[SKILL_LAST + 1] = {}; + int32_t varSpecialSkills[SPECIALSKILL_LAST + 1] = {}; + int32_t varStats[STAT_LAST + 1] = {}; + std::array specialMagicLevelSkill = {0}; + int32_t purchaseCallback = -1; + int32_t saleCallback = -1; + int32_t MessageBufferCount = 0; + int32_t bloodHitCount = 0; + int32_t shieldBlockCount = 0; + int32_t offlineTrainingSkill = -1; + int32_t offlineTrainingTime = 0; + int32_t idleTime = 0; + + uint16_t lastStatsTrainingTime = 0; + uint16_t staminaMinutes = 2520; + uint16_t maxWriteLen = 0; + + uint8_t soul = 0; + std::bitset<6> blessings; + uint8_t levelPercent = 0; + uint8_t magLevelPercent = 0; + + PlayerSex_t sex = PLAYERSEX_FEMALE; + OperatingSystem_t operatingSystem = CLIENTOS_NONE; + BlockType_t lastAttackBlockType = BLOCK_NONE; + tradestate_t tradeState = TRADE_NONE; + fightMode_t fightMode = FIGHTMODE_ATTACK; + AccountType_t accountType = ACCOUNT_TYPE_NORMAL; + + bool chaseMode = false; + bool secureMode = false; + bool inMarket = false; + bool wasMounted = false; + bool ghostMode = false; + bool pzLocked = false; + bool isConnecting = false; + bool addAttackSkillPoint = false; + bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; + + static uint32_t playerAutoID; + static uint32_t playerIDLimit; + + void updateItemsLight(bool internal = false); + int32_t getStepSpeed() const override + { + return std::max(PLAYER_MIN_SPEED, std::min(PLAYER_MAX_SPEED, getSpeed())); + } + void updateBaseSpeed() + { + if (!hasFlag(PlayerFlag_SetMaxSpeed)) { + baseSpeed = vocation->getBaseSpeed() + (2 * (level - 1)); + } else { + baseSpeed = PLAYER_MAX_SPEED; + } + } + + bool isPromoted() const; + + uint32_t getAttackSpeed() const; + + static uint8_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); + double getLostPercent() const; + uint64_t getLostExperience() const override + { + return skillLoss ? static_cast(experience * getLostPercent()) : 0; + } + uint32_t getDamageImmunities() const override { return damageImmunities; } + uint32_t getConditionImmunities() const override { return conditionImmunities; } + uint32_t getConditionSuppressions() const override { return conditionSuppressions; } + uint16_t getLookCorpse() const override; + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; + + friend class Game; + friend class Npc; + friend class LuaScriptInterface; + friend class Map; + friend class Actions; + friend class IOLoginData; + friend class ProtocolGame; }; -#endif +#endif // FS_PLAYER_H diff --git a/src/podium.cpp b/src/podium.cpp new file mode 100644 index 0000000000..b148526ac1 --- /dev/null +++ b/src/podium.cpp @@ -0,0 +1,70 @@ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#include "otpch.h" + +#include "podium.h" + +#include "game.h" + +extern Game g_game; + +Attr_ReadValue Podium::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_PODIUMOUTFIT: { + if (propStream.size() < 15) { + return ATTR_READ_ERROR; + } + + uint8_t flags; + propStream.read(flags); + setFlags(flags); + + uint8_t newDirection; + propStream.read(newDirection); + setDirection(static_cast(newDirection)); + + Outfit_t newOutfit; + propStream.read(newOutfit.lookType); + propStream.read(newOutfit.lookHead); + propStream.read(newOutfit.lookBody); + propStream.read(newOutfit.lookLegs); + propStream.read(newOutfit.lookFeet); + propStream.read(newOutfit.lookAddons); + propStream.read(newOutfit.lookMount); + propStream.read(newOutfit.lookMountHead); + propStream.read(newOutfit.lookMountBody); + propStream.read(newOutfit.lookMountLegs); + propStream.read(newOutfit.lookMountFeet); + setOutfit(newOutfit); + + g_game.updatePodium(this); + return ATTR_READ_CONTINUE; + } + + default: + break; + } + return Item::readAttr(attr, propStream); +} + +void Podium::serializeAttr(PropWriteStream& propWriteStream) const +{ + if (ATTR_PODIUMOUTFIT != 0) { + propWriteStream.write(ATTR_PODIUMOUTFIT); + propWriteStream.write(static_cast(flags.to_ulong())); + propWriteStream.write(direction); + propWriteStream.write(outfit.lookType); + propWriteStream.write(outfit.lookHead); + propWriteStream.write(outfit.lookBody); + propWriteStream.write(outfit.lookLegs); + propWriteStream.write(outfit.lookFeet); + propWriteStream.write(outfit.lookAddons); + propWriteStream.write(outfit.lookMount); + propWriteStream.write(outfit.lookMountHead); + propWriteStream.write(outfit.lookMountBody); + propWriteStream.write(outfit.lookMountLegs); + propWriteStream.write(outfit.lookMountFeet); + } +} diff --git a/src/podium.h b/src/podium.h new file mode 100644 index 0000000000..4e544c12bb --- /dev/null +++ b/src/podium.h @@ -0,0 +1,45 @@ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_PODIUM_H +#define FS_PODIUM_H + +#include "item.h" + +class Podium final : public Item +{ +public: + explicit Podium(uint16_t type) : Item(type){}; + + Podium* getPodium() override { return this; } + const Podium* getPodium() const override { return this; } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream& propWriteStream) const override; + + void setOutfit(const Outfit_t& newOutfit) { outfit = newOutfit; } + const Outfit_t getOutfit() const { return outfit; } + + bool hasFlag(PodiumFlags flag) const { return flags.test(flag); } + void setFlagValue(PodiumFlags flag, bool value) + { + if (value) { + flags.set(flag); + } else { + flags.reset(flag); + } + } + void setFlags(uint8_t newFlags) { flags = newFlags; } + + const Direction getDirection() const { return direction; } + void setDirection(Direction newDirection) { direction = newDirection; } + +protected: + Outfit_t outfit; + +private: + std::bitset<3> flags = {true}; // show platform only + Direction direction = DIRECTION_SOUTH; +}; + +#endif // FS_PODIUM_H diff --git a/src/position.cpp b/src/position.cpp index 247aace377..b1a4a51c5d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,26 +1,12 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "position.h" +#include + std::ostream& operator<<(std::ostream& os, const Position& pos) { os << "( " << std::setw(5) << std::setfill('0') << pos.x; diff --git a/src/position.h b/src/position.h index 1d488401a6..9bbfc80990 100644 --- a/src/position.h +++ b/src/position.h @@ -1,26 +1,11 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75 -#define FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75 - -enum Direction : uint8_t { +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_POSITION_H +#define FS_POSITION_H + +enum Direction : uint8_t +{ DIRECTION_NORTH = 0, DIRECTION_EAST = 1, DIRECTION_SOUTH = 2, @@ -41,41 +26,47 @@ struct Position constexpr Position() = default; constexpr Position(uint16_t x, uint16_t y, uint8_t z) : x(x), y(y), z(z) {} - template - static bool areInRange(const Position& p1, const Position& p2) { + template + static bool areInRange(const Position& p1, const Position& p2) + { return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay; } - template - static bool areInRange(const Position& p1, const Position& p2) { - return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay && Position::getDistanceZ(p1, p2) <= deltaz; + template + static bool areInRange(const Position& p1, const Position& p2) + { + return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay && + Position::getDistanceZ(p1, p2) <= deltaz; } - static int_fast32_t getOffsetX(const Position& p1, const Position& p2) { - return p1.getX() - p2.getX(); - } - static int_fast32_t getOffsetY(const Position& p1, const Position& p2) { - return p1.getY() - p2.getY(); - } - static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) { - return p1.getZ() - p2.getZ(); + static bool areInRange(const Position& p1, const Position& p2, int_fast32_t deltax, int_fast32_t deltay) + { + return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay; } - static int32_t getDistanceX(const Position& p1, const Position& p2) { + static int_fast32_t getOffsetX(const Position& p1, const Position& p2) { return p1.getX() - p2.getX(); } + static int_fast32_t getOffsetY(const Position& p1, const Position& p2) { return p1.getY() - p2.getY(); } + static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) { return p1.getZ() - p2.getZ(); } + + static int32_t getDistanceX(const Position& p1, const Position& p2) + { return std::abs(Position::getOffsetX(p1, p2)); } - static int32_t getDistanceY(const Position& p1, const Position& p2) { + static int32_t getDistanceY(const Position& p1, const Position& p2) + { return std::abs(Position::getOffsetY(p1, p2)); } - static int16_t getDistanceZ(const Position& p1, const Position& p2) { - return std::abs(Position::getOffsetZ(p1, p2)); + static int16_t getDistanceZ(const Position& p1, const Position& p2) + { + return static_cast(std::abs(Position::getOffsetZ(p1, p2))); } uint16_t x = 0; uint16_t y = 0; uint8_t z = 0; - bool operator<(const Position& p) const { + bool operator<(const Position& p) const + { if (z < p.z) { return true; } @@ -103,25 +94,15 @@ struct Position return false; } - bool operator>(const Position& p) const { - return ! (*this < p); - } + bool operator>(const Position& p) const { return !(*this < p); } - bool operator==(const Position& p) const { - return p.x == x && p.y == y && p.z == z; - } + bool operator==(const Position& p) const { return p.x == x && p.y == y && p.z == z; } - bool operator!=(const Position& p) const { - return p.x != x || p.y != y || p.z != z; - } + bool operator!=(const Position& p) const { return p.x != x || p.y != y || p.z != z; } - Position operator+(const Position& p1) const { - return Position(x + p1.x, y + p1.y, z + p1.z); - } + Position operator+(const Position& p1) const { return Position(x + p1.x, y + p1.y, z + p1.z); } - Position operator-(const Position& p1) const { - return Position(x - p1.x, y - p1.y, z - p1.z); - } + Position operator-(const Position& p1) const { return Position(x - p1.x, y - p1.y, z - p1.z); } int_fast32_t getX() const { return x; } int_fast32_t getY() const { return y; } @@ -131,4 +112,4 @@ struct Position std::ostream& operator<<(std::ostream&, const Position&); std::ostream& operator<<(std::ostream&, const Direction&); -#endif +#endif // FS_POSITION_H diff --git a/src/protocol.cpp b/src/protocol.cpp index 01a50ae0da..09d86bf092 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "protocol.h" + #include "outputmessage.h" #include "rsa.h" #include "xtea.h" @@ -28,7 +13,7 @@ extern RSA g_RSA; namespace { -void XTEA_encrypt(OutputMessage& msg, const xtea::key& key) +void XTEA_encrypt(OutputMessage& msg, const xtea::round_keys& key) { // The message must be a multiple of 8 size_t paddingBytes = msg.getLength() % 8u; @@ -40,7 +25,7 @@ void XTEA_encrypt(OutputMessage& msg, const xtea::key& key) xtea::encrypt(buffer, msg.getLength(), key); } -bool XTEA_decrypt(NetworkMessage& msg, const xtea::key& key) +bool XTEA_decrypt(NetworkMessage& msg, const xtea::round_keys& key) { if (((msg.getLength() - 6) & 7) != 0) { return false; @@ -58,16 +43,16 @@ bool XTEA_decrypt(NetworkMessage& msg, const xtea::key& key) return true; } -} +} // namespace -void Protocol::onSendMessage(const OutputMessage_ptr& msg) const +void Protocol::onSendMessage(const OutputMessage_ptr& msg) { if (!rawMessages) { msg->writeMessageLength(); if (encryptionEnabled) { XTEA_encrypt(*msg, key); - msg->addCryptoHeader(checksumEnabled); + msg->addCryptoHeader(checksumMode, sequenceNumber); } } } @@ -83,7 +68,7 @@ void Protocol::onRecvMessage(NetworkMessage& msg) OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) { - //dispatcher thread + // dispatcher thread if (!outputBuffer) { outputBuffer = OutputMessagePool::getOutputMessage(); } else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { @@ -99,7 +84,7 @@ bool Protocol::RSA_decrypt(NetworkMessage& msg) return false; } - g_RSA.decrypt(reinterpret_cast(msg.getBuffer()) + msg.getBufferPosition()); //does not break strict aliasing + g_RSA.decrypt(reinterpret_cast(msg.getBuffer()) + msg.getBufferPosition()); // does not break strict aliasing return msg.getByte() == 0; } diff --git a/src/protocol.h b/src/protocol.h index 75ca0254cf..ccef5f7a75 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,102 +1,75 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 -#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_PROTOCOL_H +#define FS_PROTOCOL_H #include "connection.h" #include "xtea.h" class Protocol : public std::enable_shared_from_this { - public: - explicit Protocol(Connection_ptr connection) : connection(connection) {} - virtual ~Protocol() = default; +public: + explicit Protocol(Connection_ptr connection) : connection(connection) {} + virtual ~Protocol() = default; - // non-copyable - Protocol(const Protocol&) = delete; - Protocol& operator=(const Protocol&) = delete; + // non-copyable + Protocol(const Protocol&) = delete; + Protocol& operator=(const Protocol&) = delete; - virtual void parsePacket(NetworkMessage&) {} + virtual void parsePacket(NetworkMessage&) {} - virtual void onSendMessage(const OutputMessage_ptr& msg) const; - void onRecvMessage(NetworkMessage& msg); - virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; - virtual void onConnect() {} + virtual void onSendMessage(const OutputMessage_ptr& msg); + void onRecvMessage(NetworkMessage& msg); + virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; + virtual void onConnect() {} - bool isConnectionExpired() const { - return connection.expired(); - } + bool isConnectionExpired() const { return connection.expired(); } - Connection_ptr getConnection() const { - return connection.lock(); - } + Connection_ptr getConnection() const { return connection.lock(); } - uint32_t getIP() const; + uint32_t getIP() const; - //Use this function for autosend messages only - OutputMessage_ptr getOutputBuffer(int32_t size); + // Use this function for autosend messages only + OutputMessage_ptr getOutputBuffer(int32_t size); - OutputMessage_ptr& getCurrentBuffer() { - return outputBuffer; - } + OutputMessage_ptr& getCurrentBuffer() { return outputBuffer; } - void send(OutputMessage_ptr msg) const { - if (auto connection = getConnection()) { - connection->send(msg); - } + void send(OutputMessage_ptr msg) const + { + if (auto connection = getConnection()) { + connection->send(msg); } + } - protected: - void disconnect() const { - if (auto connection = getConnection()) { - connection->close(); - } - } - void enableXTEAEncryption() { - encryptionEnabled = true; - } - void setXTEAKey(xtea::key key) { - this->key = std::move(key); - } - void disableChecksum() { - checksumEnabled = false; +protected: + void disconnect() const + { + if (auto connection = getConnection()) { + connection->close(); } + } + void enableXTEAEncryption() { encryptionEnabled = true; } + void setXTEAKey(const xtea::key& key) { this->key = xtea::expand_key(key); } + void setChecksumMode(checksumMode_t newMode) { checksumMode = newMode; } - static bool RSA_decrypt(NetworkMessage& msg); + static bool RSA_decrypt(NetworkMessage& msg); - void setRawMessages(bool value) { - rawMessages = value; - } + void setRawMessages(bool value) { rawMessages = value; } - virtual void release() {} + virtual void release() {} - private: - friend class Connection; +private: + friend class Connection; - OutputMessage_ptr outputBuffer; + OutputMessage_ptr outputBuffer; - const ConnectionWeak_ptr connection; - xtea::key key; - bool encryptionEnabled = false; - bool checksumEnabled = true; - bool rawMessages = false; + const ConnectionWeak_ptr connection; + xtea::round_keys key; + uint32_t sequenceNumber = 0; + bool encryptionEnabled = false; + checksumMode_t checksumMode = CHECKSUM_ADLER; + bool rawMessages = false; }; -#endif +#endif // FS_PROTOCOL_H diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 143606ae6c..2b4200de8a 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1,39 +1,26 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include - #include "protocolgame.h" -#include "outputmessage.h" - -#include "player.h" - -#include "configmanager.h" #include "actions.h" +#include "ban.h" +#include "condition.h" +#include "configmanager.h" +#include "depotchest.h" #include "game.h" +#include "inbox.h" #include "iologindata.h" #include "iomarket.h" -#include "ban.h" +#include "npc.h" +#include "outfit.h" +#include "outputmessage.h" +#include "player.h" +#include "podium.h" #include "scheduler.h" +#include "storeinbox.h" extern ConfigManager g_config; extern Actions actions; @@ -42,27 +29,21 @@ extern Chat* g_chat; namespace { -using WaitList = std::deque>; // (timeout, player guid) - -WaitList priorityWaitList, waitList; +std::deque> waitList; // (timeout, player guid) +auto priorityEnd = waitList.end(); -std::tuple findClient(const Player& player) { - const auto fn = [&](const WaitList::value_type& it) { return it.second == player.getGUID(); }; - - auto it = std::find_if(priorityWaitList.begin(), priorityWaitList.end(), fn); - if (it != priorityWaitList.end()) { - return std::make_tuple(std::ref(priorityWaitList), it, std::distance(it, priorityWaitList.end()) + 1); - } - - it = std::find_if(waitList.begin(), waitList.end(), fn); - if (it != waitList.end()) { - return std::make_tuple(std::ref(waitList), it, priorityWaitList.size() + std::distance(it, waitList.end()) + 1); +auto findClient(uint32_t guid) +{ + std::size_t slot = 1; + for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it, ++slot) { + if (it->second == guid) { + return std::make_pair(it, slot); + } } - - return std::make_tuple(std::ref(waitList), waitList.end(), priorityWaitList.size() + waitList.size()); + return std::make_pair(waitList.end(), slot); } -uint8_t getWaitTime(std::size_t slot) +constexpr int64_t getWaitTime(std::size_t slot) { if (slot < 5) { return 5; @@ -72,75 +53,66 @@ uint8_t getWaitTime(std::size_t slot) return 20; } else if (slot < 50) { return 60; - } else { - return 120; } + return 120; } -int64_t getTimeout(std::size_t slot) +constexpr int64_t getTimeout(std::size_t slot) { // timeout is set to 15 seconds longer than expected retry attempt return getWaitTime(slot) + 15; } -void cleanupList(WaitList& list) -{ - int64_t time = OTSYS_TIME(); - - auto it = list.begin(); - while (it != list.end()) { - if (it->first <= time) { - it = list.erase(it); - } else { - ++it; - } - } -} - std::size_t clientLogin(const Player& player) { - // Currentslot = position in wait list, 0 for direct access if (player.hasFlag(PlayerFlag_CanAlwaysLogin) || player.getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { return 0; } - cleanupList(priorityWaitList); - cleanupList(waitList); - uint32_t maxPlayers = static_cast(g_config.getNumber(ConfigManager::MAX_PLAYERS)); - if (maxPlayers == 0 || (priorityWaitList.empty() && waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { + if (maxPlayers == 0 || (waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { return 0; } - auto result = findClient(player); - if (std::get<1>(result) != std::get<0>(result).end()) { - auto currentSlot = std::get<2>(result); + int64_t time = OTSYS_TIME(); + + auto it = waitList.begin(); + while (it != waitList.end()) { + if ((it->first - time) <= 0) { + it = waitList.erase(it); + } else { + ++it; + } + } + + std::size_t slot; + std::tie(it, slot) = findClient(player.getGUID()); + if (it != waitList.end()) { // If server has capacity for this client, let him in even though his current slot might be higher than 0. - if ((g_game.getPlayersOnline() + currentSlot) <= maxPlayers) { - std::get<0>(result).erase(std::get<1>(result)); + if ((g_game.getPlayersOnline() + slot) <= maxPlayers) { + waitList.erase(it); return 0; } - //let them wait a bit longer - std::get<1>(result)->second = OTSYS_TIME() + (getTimeout(currentSlot) * 1000); - return currentSlot; + // let them wait a bit longer + it->first = time + (getTimeout(slot) * 1000); + return slot; } - auto currentSlot = priorityWaitList.size(); if (player.isPremium()) { - priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(++currentSlot) * 1000), player.getGUID()); - } else { - currentSlot += waitList.size(); - waitList.emplace_back(OTSYS_TIME() + (getTimeout(++currentSlot) * 1000), player.getGUID()); + priorityEnd = waitList.emplace(priorityEnd, time + (getTimeout(slot + 1) * 1000), player.getGUID()); + return std::distance(waitList.begin(), priorityEnd); } - return currentSlot; -} + waitList.emplace_back(time + (getTimeout(waitList.size() + 1) * 1000), player.getGUID()); + return waitList.size(); } +} // namespace + void ProtocolGame::release() { - //dispatcher thread + // dispatcher thread if (player && player->client == shared_from_this()) { player->client.reset(); player->decrementReferenceCounter(); @@ -153,7 +125,7 @@ void ProtocolGame::release() void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem) { - //dispatcher thread + // dispatcher thread Player* foundPlayer = g_game.getPlayerByName(name); if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { player = new Player(getThis()); @@ -182,7 +154,8 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS return; } - if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) { + if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && + player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) { disconnectClient("You may only login with one character\nof your account at the same time."); return; } @@ -194,27 +167,25 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS banInfo.reason = "(none)"; } - std::ostringstream ss; if (banInfo.expiresAt > 0) { - ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + disconnectClient( + fmt::format("Your account has been banned until {:s} by {:s}.\n\nReason specified:\n{:s}", + formatDateShort(banInfo.expiresAt), banInfo.bannedBy, banInfo.reason)); } else { - ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + disconnectClient( + fmt::format("Your account has been permanently banned by {:s}.\n\nReason specified:\n{:s}", + banInfo.bannedBy, banInfo.reason)); } - disconnectClient(ss.str()); return; } } if (std::size_t currentSlot = clientLogin(*player)) { uint8_t retryTime = getWaitTime(currentSlot); - std::ostringstream ss; - - ss << "Too many players online.\nYou are at place " - << currentSlot << " on the waiting list."; - auto output = OutputMessagePool::getOutputMessage(); output->addByte(0x16); - output->addString(ss.str()); + output->addString( + fmt::format("Too many players online.\nYou are at place {:d} on the waiting list.", currentSlot)); output->addByte(retryTime); send(output); disconnect(); @@ -244,7 +215,7 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS acceptPackets = true; } else { if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) { - //Already trying to connect + // Already trying to connect disconnectClient("You are already logged in."); return; } @@ -253,7 +224,10 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS foundPlayer->disconnect(); foundPlayer->isConnecting = true; - eventConnect = g_scheduler.addEvent(createSchedulerTask(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getID(), operatingSystem))); + eventConnect = g_scheduler.addEvent( + createSchedulerTask(1000, [=, thisPtr = getThis(), playerID = foundPlayer->getID()]() { + thisPtr->connect(playerID, operatingSystem); + })); } else { connect(foundPlayer->getID(), operatingSystem); } @@ -272,8 +246,8 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) } if (isConnectionExpired()) { - //ProtocolGame::release() has been called at this point and the Connection object - //no longer exists, so we return to prevent leakage of the Player. + // ProtocolGame::release() has been called at this point and the Connection object no longer exists, so we + // return to prevent leakage of the Player. return; } @@ -286,15 +260,16 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) player->isConnecting = false; player->client = getThis(); - sendAddCreature(player, player->getPosition(), 0, false); + sendAddCreature(player, player->getPosition(), 0); player->lastIP = player->getIP(); player->lastLoginSaved = std::max(time(nullptr), player->lastLoginSaved + 1); + player->resetIdleTime(); acceptPackets = true; } void ProtocolGame::logout(bool displayEffect, bool forced) { - //dispatcher thread + // dispatcher thread if (!player) { return; } @@ -313,40 +288,55 @@ void ProtocolGame::logout(bool displayEffect, bool forced) } } - //scripting event - onLogout + // scripting event - onLogout if (!g_creatureEvents->playerLogout(player)) { - //Let the script handle the error message + // Let the script handle the error message return; } } - if (displayEffect && player->getHealth() > 0) { + if (displayEffect && player->getHealth() > 0 && !player->isInGhostMode()) { g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); } } + sendSessionEnd(forced ? SESSION_END_FORCECLOSE : SESSION_END_LOGOUT); disconnect(); g_game.removeCreature(player); } +// Login to the game world request void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) { + // Server is shutting down if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { disconnect(); return; } + // Client type and OS used OperatingSystem_t operatingSystem = static_cast(msg.get()); - version = msg.get(); - msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision + version = msg.get(); // U16 client version + msg.skipBytes(4); // U32 client version + + // String client version + if (version >= 1240) { + if (msg.getLength() - msg.getBufferPosition() > 132) { + msg.getString(); + } + } + msg.skipBytes(3); // U16 dat revision, U8 preview state + + // Disconnect if RSA decrypt fails if (!Protocol::RSA_decrypt(msg)) { disconnect(); return; } + // Get XTEA key xtea::key key; key[0] = msg.get(); key[1] = msg.get(); @@ -355,6 +345,7 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) enableXTEAEncryption(); setXTEAKey(std::move(key)); + // Enable extended opcode feature for otclient if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { NetworkMessage opcodeMessage; opcodeMessage.addByte(0x32); @@ -363,37 +354,65 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) writeToOutputBuffer(opcodeMessage); } - msg.skipBytes(1); // gamemaster flag - - std::string sessionKey = msg.getString(); + // Change packet verifying mode for QT clients + if (version >= 1111 && operatingSystem >= CLIENTOS_QT_LINUX && operatingSystem < CLIENTOS_OTCLIENT_LINUX) { + setChecksumMode(CHECKSUM_SEQUENCE); + } - auto sessionArgs = explodeString(sessionKey, "\n", 4); - if (sessionArgs.size() != 4) { - disconnect(); + // Web login skips the character list request so we need to check the client version again + if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR)); return; } - std::string& accountName = sessionArgs[0]; - std::string& password = sessionArgs[1]; - std::string& token = sessionArgs[2]; - uint32_t tokenTime = 0; - try { - tokenTime = std::stoul(sessionArgs[3]); - } catch (const std::invalid_argument&) { - disconnectClient("Malformed token packet."); - return; - } catch (const std::out_of_range&) { - disconnectClient("Token time is too long."); + msg.skipBytes(1); // Gamemaster flag + + std::string sessionKey = msg.getString(); + auto sessionArgs = explodeString(sessionKey, "\n", + 4); // acc name or email, password, token, timestamp divided by 30 + if (sessionArgs.size() < 2) { + disconnectClient("Malformed session key."); return; } + if (operatingSystem == CLIENTOS_QT_LINUX) { + msg.getString(); // OS name (?) + msg.getString(); // OS version (?) + } + + std::string& accountName = sessionArgs[0]; + std::string& password = sessionArgs[1]; if (accountName.empty()) { disconnectClient("You must enter your account name."); return; } - std::string characterName = msg.getString(); + std::string token; + uint32_t tokenTime = 0; + // two-factor auth + if (g_config.getBoolean(ConfigManager::TWO_FACTOR_AUTH)) { + if (sessionArgs.size() < 4) { + disconnectClient("Authentication failed. Incomplete session key."); + return; + } + + token = sessionArgs[2]; + + try { + tokenTime = std::stoul(sessionArgs[3]); + } catch (const std::invalid_argument&) { + disconnectClient("Malformed token packet."); + return; + } catch (const std::out_of_range&) { + disconnectClient("Token time is too long."); + return; + } + } else { + tokenTime = std::floor(challengeTimestamp / 30); + } + + std::string characterName = msg.getString(); uint32_t timeStamp = msg.get(); uint8_t randNumber = msg.getByte(); if (challengeTimestamp != timeStamp || challengeRandom != randNumber) { @@ -401,13 +420,6 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) return; } - if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { - std::ostringstream ss; - ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; - disconnectClient(ss.str()); - return; - } - if (g_game.getGameState() == GAME_STATE_STARTUP) { disconnectClient("Gameworld is starting up. Please wait."); return; @@ -424,9 +436,8 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) banInfo.reason = "(none)"; } - std::ostringstream ss; - ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; - disconnectClient(ss.str()); + disconnectClient(fmt::format("Your IP has been banned until {:s} by {:s}.\n\nReason specified:\n{:s}", + formatDateShort(banInfo.expiresAt), banInfo.bannedBy, banInfo.reason)); return; } @@ -436,7 +447,9 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) return; } - g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem))); + addGameTask([=, thisPtr = getThis(), characterName = std::move(characterName)]() { + thisPtr->login(characterName, accountId, operatingSystem); + }); } void ProtocolGame::onConnect() @@ -498,7 +511,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) return; } - //a dead player can not performs actions + // a dead player can not performs actions if (player->isRemoved() || player->getHealth() <= 0) { if (recvbyte == 0x0F) { disconnect(); @@ -511,91 +524,294 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) } switch (recvbyte) { - case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break; - case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break; - case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break; - case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode - case 0x64: parseAutoWalk(msg); break; - case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break; - case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break; - case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break; - case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break; - case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break; - case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break; - case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break; - case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break; - case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break; - case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break; - case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break; - case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break; - case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break; - case 0x77: parseEquipObject(msg); break; - case 0x78: parseThrow(msg); break; - case 0x79: parseLookInShop(msg); break; - case 0x7A: parsePlayerPurchase(msg); break; - case 0x7B: parsePlayerSale(msg); break; - case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break; - case 0x7D: parseRequestTrade(msg); break; - case 0x7E: parseLookInTrade(msg); break; - case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break; - case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break; - case 0x82: parseUseItem(msg); break; - case 0x83: parseUseItemEx(msg); break; - case 0x84: parseUseWithCreature(msg); break; - case 0x85: parseRotateItem(msg); break; - case 0x87: parseCloseContainer(msg); break; - case 0x88: parseUpArrowContainer(msg); break; - case 0x89: parseTextWindow(msg); break; - case 0x8A: parseHouseWindow(msg); break; - case 0x8B: parseWrapItem(msg); break; - case 0x8C: parseLookAt(msg); break; - case 0x8D: parseLookInBattleList(msg); break; - case 0x8E: /* join aggression */ break; - case 0x96: parseSay(msg); break; - case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; - case 0x98: parseOpenChannel(msg); break; - case 0x99: parseCloseChannel(msg); break; - case 0x9A: parseOpenPrivateChannel(msg); break; - case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break; - case 0xA0: parseFightModes(msg); break; - case 0xA1: parseAttack(msg); break; - case 0xA2: parseFollow(msg); break; - case 0xA3: parseInviteToParty(msg); break; - case 0xA4: parseJoinParty(msg); break; - case 0xA5: parseRevokePartyInvite(msg); break; - case 0xA6: parsePassPartyLeadership(msg); break; - case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break; - case 0xA8: parseEnableSharedPartyExperience(msg); break; - case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break; - case 0xAB: parseChannelInvite(msg); break; - case 0xAC: parseChannelExclude(msg); break; - case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; - case 0xC9: /* update tile */ break; - case 0xCA: parseUpdateContainer(msg); break; - case 0xCB: parseBrowseField(msg); break; - case 0xCC: parseSeekInContainer(msg); break; - case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; - case 0xD3: parseSetOutfit(msg); break; - case 0xD4: parseToggleMount(msg); break; - case 0xDC: parseAddVip(msg); break; - case 0xDD: parseRemoveVip(msg); break; - case 0xDE: parseEditVip(msg); break; - case 0xE6: parseBugReport(msg); break; - case 0xE7: /* thank you */ break; - case 0xE8: parseDebugAssert(msg); break; - case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break; - case 0xF1: parseQuestLine(msg); break; - case 0xF2: parseRuleViolationReport(msg); break; - case 0xF3: /* get object info */ break; - case 0xF4: parseMarketLeave(); break; - case 0xF5: parseMarketBrowse(msg); break; - case 0xF6: parseMarketCreateOffer(msg); break; - case 0xF7: parseMarketCancelOffer(msg); break; - case 0xF8: parseMarketAcceptOffer(msg); break; - case 0xF9: parseModalWindowAnswer(msg); break; + case 0x14: + addGameTask([thisPtr = getThis()]() { thisPtr->logout(true, false); }); + break; + case 0x1D: + addGameTask([playerID = player->getID()]() { g_game.playerReceivePingBack(playerID); }); + break; + case 0x1E: + addGameTask([playerID = player->getID()]() { g_game.playerReceivePing(playerID); }); + break; + // case 0x2A: break; // bestiary tracker + // case 0x2C: break; // team finder (leader) + // case 0x2D: break; // team finder (member) + // case 0x28: break; // stash withdraw + case 0x32: + parseExtendedOpcode(msg); + break; // otclient extended opcode + case 0x64: + parseAutoWalk(msg); + break; + case 0x65: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_NORTH); }); + break; + case 0x66: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_EAST); }); + break; + case 0x67: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_SOUTH); }); + break; + case 0x68: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_WEST); }); + break; + case 0x69: + addGameTask([playerID = player->getID()]() { g_game.playerStopAutoWalk(playerID); }); + break; + case 0x6A: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_NORTHEAST); }); + break; + case 0x6B: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_SOUTHEAST); }); + break; + case 0x6C: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_SOUTHWEST); }); + break; + case 0x6D: + addGameTask([playerID = player->getID()]() { g_game.playerMove(playerID, DIRECTION_NORTHWEST); }); + break; + case 0x6F: + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [playerID = player->getID()]() { g_game.playerTurn(playerID, DIRECTION_NORTH); }); + break; + case 0x70: + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [playerID = player->getID()]() { g_game.playerTurn(playerID, DIRECTION_EAST); }); + break; + case 0x71: + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [playerID = player->getID()]() { g_game.playerTurn(playerID, DIRECTION_SOUTH); }); + break; + case 0x72: + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [playerID = player->getID()]() { g_game.playerTurn(playerID, DIRECTION_WEST); }); + break; + // case 0x73: break; // map click(?) + case 0x77: + parseEquipObject(msg); + break; + case 0x78: + parseThrow(msg); + break; + case 0x79: + parseLookInShop(msg); + break; + case 0x7A: + parsePlayerPurchase(msg); + break; + case 0x7B: + parsePlayerSale(msg); + break; + case 0x7C: + addGameTask([playerID = player->getID()]() { g_game.playerCloseShop(playerID); }); + break; + case 0x7D: + parseRequestTrade(msg); + break; + case 0x7E: + parseLookInTrade(msg); + break; + case 0x7F: + addGameTask([playerID = player->getID()]() { g_game.playerAcceptTrade(playerID); }); + break; + case 0x80: + addGameTask([playerID = player->getID()]() { g_game.playerCloseTrade(playerID); }); + break; + case 0x82: + parseUseItem(msg); + break; + case 0x83: + parseUseItemEx(msg); + break; + case 0x84: + parseUseWithCreature(msg); + break; + case 0x85: + parseRotateItem(msg); + break; + case 0x86: + parseEditPodiumRequest(msg); + break; + case 0x87: + parseCloseContainer(msg); + break; + case 0x88: + parseUpArrowContainer(msg); + break; + case 0x89: + parseTextWindow(msg); + break; + case 0x8A: + parseHouseWindow(msg); + break; + case 0x8B: + parseWrapItem(msg); + break; + case 0x8C: + parseLookAt(msg); + break; + case 0x8D: + parseLookInBattleList(msg); + break; + case 0x8E: /* join aggression */ + break; + // case 0x8F: break; // quick loot + // case 0x90: break; // loot container + // case 0x91: break; // update loot whitelist + // case 0x92: break; // request locker items + case 0x96: + parseSay(msg); + break; + case 0x97: + addGameTask([playerID = player->getID()]() { g_game.playerRequestChannels(playerID); }); + break; + case 0x98: + parseOpenChannel(msg); + break; + case 0x99: + parseCloseChannel(msg); + break; + case 0x9A: + parseOpenPrivateChannel(msg); + break; + case 0x9E: + addGameTask([playerID = player->getID()]() { g_game.playerCloseNpcChannel(playerID); }); + break; + case 0xA0: + parseFightModes(msg); + break; + case 0xA1: + parseAttack(msg); + break; + case 0xA2: + parseFollow(msg); + break; + case 0xA3: + parseInviteToParty(msg); + break; + case 0xA4: + parseJoinParty(msg); + break; + case 0xA5: + parseRevokePartyInvite(msg); + break; + case 0xA6: + parsePassPartyLeadership(msg); + break; + case 0xA7: + addGameTask([playerID = player->getID()]() { g_game.playerLeaveParty(playerID); }); + break; + case 0xA8: + parseEnableSharedPartyExperience(msg); + break; + case 0xAA: + addGameTask([playerID = player->getID()]() { g_game.playerCreatePrivateChannel(playerID); }); + break; + case 0xAB: + parseChannelInvite(msg); + break; + case 0xAC: + parseChannelExclude(msg); + break; + // case 0xB1: break; // request highscores + case 0xBE: + addGameTask([playerID = player->getID()]() { g_game.playerCancelAttackAndFollow(playerID); }); + break; + // case 0xC7: break; // request tournament leaderboard + case 0xC9: /* update tile */ + break; + case 0xCA: + parseUpdateContainer(msg); + break; + case 0xCB: + parseBrowseField(msg); + break; + case 0xCC: + parseSeekInContainer(msg); + break; + // case 0xCD: break; // request inspect window + case 0xD0: + parseQuestTracker(msg); + break; + case 0xD2: + addGameTask([playerID = player->getID()]() { g_game.playerRequestOutfit(playerID); }); + break; + case 0xD3: + parseSetOutfit(msg); + break; + case 0xD4: + parseToggleMount(msg); + break; + // case 0xD5: break; // apply imbuement + // case 0xD6: break; // clear imbuement + // case 0xD7: break; // close imbuing window + case 0xDC: + parseAddVip(msg); + break; + case 0xDD: + parseRemoveVip(msg); + break; + case 0xDE: + parseEditVip(msg); + break; + // case 0xDF: break; // premium shop (?) + // case 0xE0: break; // premium shop (?) + // case 0xE1: break; // bestiary 1 + // case 0xE2: break; // bestiary 2 + // case 0xE3: break; // bestiary 3 + // case 0xE4: break; // buy charm rune + // case 0xE5: break; // request character info (cyclopedia) + case 0xE6: + parseBugReport(msg); + break; + case 0xE7: /* thank you */ + break; + case 0xE8: + parseDebugAssert(msg); + break; + case 0xEE: + addGameTask([playerID = player->getID()]() { g_game.playerSay(playerID, 0, TALKTYPE_SAY, "", "hi"); }); + break; + // case 0xEF: break; // request store coins transfer + case 0xF0: + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [playerID = player->getID()]() { g_game.playerShowQuestLog(playerID); }); + break; + case 0xF1: + parseQuestLine(msg); + break; + case 0xF2: + parseRuleViolationReport(msg); + break; + case 0xF3: /* get object info */ + break; + case 0xF4: + parseMarketLeave(); + break; + case 0xF5: + parseMarketBrowse(msg); + break; + case 0xF6: + parseMarketCreateOffer(msg); + break; + case 0xF7: + parseMarketCancelOffer(msg); + break; + case 0xF8: + parseMarketAcceptOffer(msg); + break; + case 0xF9: + parseModalWindowAnswer(msg); + break; + // case 0xFA: break; // store window open + // case 0xFB: break; // store window click + // case 0xFC: break; // store window buy + // case 0xFD: break; // store window history 1 + // case 0xFE: break; // store window history 2 default: - // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; + // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << + // static_cast(recvbyte) << std::dec << "!" << std::endl; break; } @@ -606,8 +822,6 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) { - msg.add(0x00); //environmental effects - int32_t count; Item* ground = tile->getGround(); if (ground) { @@ -630,7 +844,8 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) const CreatureVector* creatures = tile->getCreatures(); if (creatures) { - for (const Creature* creature : boost::adaptors::reverse(*creatures)) { + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + const Creature* creature = (*it); if (!player->canSeeCreature(creature)) { continue; } @@ -654,7 +869,8 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) } } -void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg) +void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, + NetworkMessage& msg) { int32_t skip = -1; int32_t startz, endz, zstep; @@ -679,7 +895,8 @@ void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t wi } } -void ProtocolGame::GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, int32_t offset, int32_t& skip) +void ProtocolGame::GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, int32_t width, + int32_t height, int32_t offset, int32_t& skip) { for (int32_t nx = 0; nx < width; nx++) { for (int32_t ny = 0; ny < height; ny++) { @@ -750,10 +967,7 @@ bool ProtocolGame::canSee(const Creature* c) const return canSee(c->getPosition()); } -bool ProtocolGame::canSee(const Position& pos) const -{ - return canSee(pos.x, pos.y, pos.z); -} +bool ProtocolGame::canSee(const Position& pos) const { return canSee(pos.x, pos.y, pos.z); } bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const { @@ -763,23 +977,22 @@ bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const const Position& myPos = player->getPosition(); if (myPos.z <= 7) { - //we are on ground level or above (7 -> 0) - //view is from 7 -> 0 + // we are on ground level or above (7 -> 0) view is from 7 -> 0 if (z > 7) { return false; } - } else { // if (myPos.z >= 8) { - //we are underground (8 -> 15) - //view is +/- 2 from the floor we stand on + } else { // if (myPos.z >= 8) { we are underground (8 -> 15) view is +/- 2 from the floor we stand on if (std::abs(myPos.getZ() - z) > 2) { return false; } } - //negative offset means that the action taken place is on a lower floor than ourself + // negative offset means that the action taken place is on a lower floor than ourself int32_t offsetz = myPos.getZ() - z; - if ((x >= myPos.getX() - 8 + offsetz) && (x <= myPos.getX() + 9 + offsetz) && - (y >= myPos.getY() - 6 + offsetz) && (y <= myPos.getY() + 7 + offsetz)) { + if ((x >= myPos.getX() - Map::maxClientViewportX + offsetz) && + (x <= myPos.getX() + (Map::maxClientViewportX + 1) + offsetz) && + (y >= myPos.getY() - Map::maxClientViewportY + offsetz) && + (y <= myPos.getY() + (Map::maxClientViewportY + 1) + offsetz)) { return true; } return false; @@ -788,32 +1001,35 @@ bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const // Parse methods void ProtocolGame::parseChannelInvite(NetworkMessage& msg) { - const std::string name = msg.getString(); - addGameTask(&Game::playerChannelInvite, player->getID(), name); + std::string name = msg.getString(); + addGameTask([playerID = player->getID(), name = std::move(name)]() { g_game.playerChannelInvite(playerID, name); }); } void ProtocolGame::parseChannelExclude(NetworkMessage& msg) { - const std::string name = msg.getString(); - addGameTask(&Game::playerChannelExclude, player->getID(), name); + std::string name = msg.getString(); + addGameTask( + [=, playerID = player->getID(), name = std::move(name)]() { g_game.playerChannelExclude(playerID, name); }); } void ProtocolGame::parseOpenChannel(NetworkMessage& msg) { - uint16_t channelId = msg.get(); - addGameTask(&Game::playerOpenChannel, player->getID(), channelId); + uint16_t channelID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerOpenChannel(playerID, channelID); }); } void ProtocolGame::parseCloseChannel(NetworkMessage& msg) { - uint16_t channelId = msg.get(); - addGameTask(&Game::playerCloseChannel, player->getID(), channelId); + uint16_t channelID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerCloseChannel(playerID, channelID); }); } void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg) { - const std::string receiver = msg.getString(); - addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver); + std::string receiver = msg.getString(); + addGameTask([playerID = player->getID(), receiver = std::move(receiver)]() { + g_game.playerOpenPrivateChannel(playerID, receiver); + }); } void ProtocolGame::parseAutoWalk(NetworkMessage& msg) @@ -831,15 +1047,32 @@ void ProtocolGame::parseAutoWalk(NetworkMessage& msg) for (uint8_t i = 0; i < numdirs; ++i) { uint8_t rawdir = msg.getPreviousByte(); switch (rawdir) { - case 1: path.push_back(DIRECTION_EAST); break; - case 2: path.push_back(DIRECTION_NORTHEAST); break; - case 3: path.push_back(DIRECTION_NORTH); break; - case 4: path.push_back(DIRECTION_NORTHWEST); break; - case 5: path.push_back(DIRECTION_WEST); break; - case 6: path.push_back(DIRECTION_SOUTHWEST); break; - case 7: path.push_back(DIRECTION_SOUTH); break; - case 8: path.push_back(DIRECTION_SOUTHEAST); break; - default: break; + case 1: + path.push_back(DIRECTION_EAST); + break; + case 2: + path.push_back(DIRECTION_NORTHEAST); + break; + case 3: + path.push_back(DIRECTION_NORTH); + break; + case 4: + path.push_back(DIRECTION_NORTHWEST); + break; + case 5: + path.push_back(DIRECTION_WEST); + break; + case 6: + path.push_back(DIRECTION_SOUTHWEST); + break; + case 7: + path.push_back(DIRECTION_SOUTH); + break; + case 8: + path.push_back(DIRECTION_SOUTHEAST); + break; + default: + break; } } @@ -847,11 +1080,13 @@ void ProtocolGame::parseAutoWalk(NetworkMessage& msg) return; } - addGameTask(&Game::playerAutoWalk, player->getID(), std::move(path)); + addGameTask([playerID = player->getID(), path = std::move(path)]() { g_game.playerAutoWalk(playerID, path); }); } void ProtocolGame::parseSetOutfit(NetworkMessage& msg) { + uint8_t outfitType = msg.getByte(); + Outfit_t newOutfit; newOutfit.lookType = msg.get(); newOutfit.lookHead = msg.getByte(); @@ -859,14 +1094,73 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg) newOutfit.lookLegs = msg.getByte(); newOutfit.lookFeet = msg.getByte(); newOutfit.lookAddons = msg.getByte(); - newOutfit.lookMount = msg.get(); - addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); + + // Set outfit window + if (outfitType == 0) { + newOutfit.lookMount = msg.get(); + if (newOutfit.lookMount != 0) { + newOutfit.lookMountHead = msg.getByte(); + newOutfit.lookMountBody = msg.getByte(); + newOutfit.lookMountLegs = msg.getByte(); + newOutfit.lookMountFeet = msg.getByte(); + } else { + msg.skipBytes(4); + + // prevent mount color settings from resetting + const Outfit_t& currentOutfit = player->getCurrentOutfit(); + newOutfit.lookMountHead = currentOutfit.lookMountHead; + newOutfit.lookMountBody = currentOutfit.lookMountBody; + newOutfit.lookMountLegs = currentOutfit.lookMountLegs; + newOutfit.lookMountFeet = currentOutfit.lookMountFeet; + } + + msg.get(); // familiar looktype + addGameTask([=, playerID = player->getID()]() { g_game.playerChangeOutfit(playerID, newOutfit); }); + + // Store "try outfit" window + } else if (outfitType == 1) { + newOutfit.lookMount = 0; + // mount colors or store offerId (needs testing) + newOutfit.lookMountHead = msg.getByte(); + newOutfit.lookMountBody = msg.getByte(); + newOutfit.lookMountLegs = msg.getByte(); + newOutfit.lookMountFeet = msg.getByte(); + // player->? (open store?) + + // Podium interaction + } else if (outfitType == 2) { + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + newOutfit.lookMount = msg.get(); + newOutfit.lookMountHead = msg.getByte(); + newOutfit.lookMountBody = msg.getByte(); + newOutfit.lookMountLegs = msg.getByte(); + newOutfit.lookMountFeet = msg.getByte(); + Direction direction = static_cast(msg.getByte()); + bool podiumVisible = msg.getByte() == 1; + + // apply to podium + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerEditPodium(playerID, newOutfit, pos, stackpos, spriteId, podiumVisible, direction); + }); + } +} + +void ProtocolGame::parseEditPodiumRequest(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerRequestEditPodium(playerID, pos, stackpos, spriteId); + }); } void ProtocolGame::parseToggleMount(NetworkMessage& msg) { bool mount = msg.getByte() != 0; - addGameTask(&Game::playerToggleMount, player->getID(), mount); + addGameTask([=, playerID = player->getID()]() { g_game.playerToggleMount(playerID, mount); }); } void ProtocolGame::parseUseItem(NetworkMessage& msg) @@ -875,7 +1169,9 @@ void ProtocolGame::parseUseItem(NetworkMessage& msg) uint16_t spriteId = msg.get(); uint8_t stackpos = msg.getByte(); uint8_t index = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem, player->getID(), pos, stackpos, index, spriteId); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerUseItem(playerID, pos, stackpos, index, spriteId); + }); } void ProtocolGame::parseUseItemEx(NetworkMessage& msg) @@ -886,7 +1182,9 @@ void ProtocolGame::parseUseItemEx(NetworkMessage& msg) Position toPos = msg.getPosition(); uint16_t toSpriteId = msg.get(); uint8_t toStackPos = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx, player->getID(), fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerUseItemEx(playerID, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId); + }); } void ProtocolGame::parseUseWithCreature(NetworkMessage& msg) @@ -895,25 +1193,27 @@ void ProtocolGame::parseUseWithCreature(NetworkMessage& msg) uint16_t spriteId = msg.get(); uint8_t fromStackPos = msg.getByte(); uint32_t creatureId = msg.get(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature, player->getID(), fromPos, fromStackPos, creatureId, spriteId); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerUseWithCreature(playerID, fromPos, fromStackPos, creatureId, spriteId); + }); } void ProtocolGame::parseCloseContainer(NetworkMessage& msg) { uint8_t cid = msg.getByte(); - addGameTask(&Game::playerCloseContainer, player->getID(), cid); + addGameTask([=, playerID = player->getID()]() { g_game.playerCloseContainer(playerID, cid); }); } void ProtocolGame::parseUpArrowContainer(NetworkMessage& msg) { uint8_t cid = msg.getByte(); - addGameTask(&Game::playerMoveUpContainer, player->getID(), cid); + addGameTask([=, playerID = player->getID()]() { g_game.playerMoveUpContainer(playerID, cid); }); } void ProtocolGame::parseUpdateContainer(NetworkMessage& msg) { uint8_t cid = msg.getByte(); - addGameTask(&Game::playerUpdateContainer, player->getID(), cid); + addGameTask([=, playerID = player->getID()]() { g_game.playerUpdateContainer(playerID, cid); }); } void ProtocolGame::parseThrow(NetworkMessage& msg) @@ -925,7 +1225,9 @@ void ProtocolGame::parseThrow(NetworkMessage& msg) uint8_t count = msg.getByte(); if (toPos != fromPos) { - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing, player->getID(), fromPos, spriteId, fromStackpos, toPos, count); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerMoveThing(playerID, fromPos, spriteId, fromStackpos, toPos, count); + }); } } @@ -934,13 +1236,15 @@ void ProtocolGame::parseLookAt(NetworkMessage& msg) Position pos = msg.getPosition(); msg.skipBytes(2); // spriteId uint8_t stackpos = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt, player->getID(), pos, stackpos); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerLookAt(playerID, pos, stackpos); }); } void ProtocolGame::parseLookInBattleList(NetworkMessage& msg) { - uint32_t creatureId = msg.get(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId); + uint32_t creatureID = msg.get(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerLookInBattleList(playerID, creatureID); }); } void ProtocolGame::parseSay(NetworkMessage& msg) @@ -966,18 +1270,20 @@ void ProtocolGame::parseSay(NetworkMessage& msg) break; } - const std::string text = msg.getString(); + std::string text = msg.getString(); if (text.length() > 255) { return; } - addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver, text); + addGameTask([=, playerID = player->getID(), receiver = std::move(receiver), text = std::move(text)]() { + g_game.playerSay(playerID, channelId, type, receiver, text); + }); } void ProtocolGame::parseFightModes(NetworkMessage& msg) { - uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive - uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fighting, 1 - chase opponent + uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive + uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fighting, 1 - chase opponent uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 @@ -990,36 +1296,42 @@ void ProtocolGame::parseFightModes(NetworkMessage& msg) fightMode = FIGHTMODE_DEFENSE; } - addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, rawChaseMode != 0, rawSecureMode != 0); + addGameTask([=, playerID = player->getID()]() { + g_game.playerSetFightModes(playerID, fightMode, rawChaseMode != 0, rawSecureMode != 0); + }); } void ProtocolGame::parseAttack(NetworkMessage& msg) { - uint32_t creatureId = msg.get(); - // msg.get(); creatureId (same as above) - addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId); + uint32_t creatureID = msg.get(); + // msg.get(); creatureID (same as above) + addGameTask([=, playerID = player->getID()]() { g_game.playerSetAttackedCreature(playerID, creatureID); }); } void ProtocolGame::parseFollow(NetworkMessage& msg) { - uint32_t creatureId = msg.get(); - // msg.get(); creatureId (same as above) - addGameTask(&Game::playerFollowCreature, player->getID(), creatureId); + uint32_t creatureID = msg.get(); + // msg.get(); creatureID (same as above) + addGameTask([=, playerID = player->getID()]() { g_game.playerFollowCreature(playerID, creatureID); }); } void ProtocolGame::parseEquipObject(NetworkMessage& msg) { - uint16_t spriteId = msg.get(); - // msg.get(); + // hotkey equip (?) + uint16_t spriteID = msg.get(); + // msg.get(); // bool smartMode (?) - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerEquipItem, player->getID(), spriteId); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerEquipItem(playerID, spriteID); }); } void ProtocolGame::parseTextWindow(NetworkMessage& msg) { - uint32_t windowTextId = msg.get(); - const std::string newText = msg.getString(); - addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText); + uint32_t windowTextID = msg.get(); + std::string newText = msg.getString(); + addGameTask([playerID = player->getID(), windowTextID, newText = std::move(newText)]() { + g_game.playerWriteItem(playerID, windowTextID, newText); + }); } void ProtocolGame::parseHouseWindow(NetworkMessage& msg) @@ -1027,7 +1339,7 @@ void ProtocolGame::parseHouseWindow(NetworkMessage& msg) uint8_t doorId = msg.getByte(); uint32_t id = msg.get(); const std::string text = msg.getString(); - addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text); + addGameTask([=, playerID = player->getID()]() { g_game.playerUpdateHouseWindow(playerID, doorId, id, text); }); } void ProtocolGame::parseWrapItem(NetworkMessage& msg) @@ -1035,14 +1347,16 @@ void ProtocolGame::parseWrapItem(NetworkMessage& msg) Position pos = msg.getPosition(); uint16_t spriteId = msg.get(); uint8_t stackpos = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerWrapItem, player->getID(), pos, stackpos, spriteId); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerWrapItem(playerID, pos, stackpos, spriteId); }); } void ProtocolGame::parseLookInShop(NetworkMessage& msg) { uint16_t id = msg.get(); uint8_t count = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop, player->getID(), id, count); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerLookInShop(playerID, id, count); }); } void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg) @@ -1052,7 +1366,9 @@ void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg) uint8_t amount = msg.getByte(); bool ignoreCap = msg.getByte() != 0; bool inBackpacks = msg.getByte() != 0; - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem, player->getID(), id, count, amount, ignoreCap, inBackpacks); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerPurchaseItem(playerID, id, count, amount, ignoreCap, inBackpacks); + }); } void ProtocolGame::parsePlayerSale(NetworkMessage& msg) @@ -1061,7 +1377,9 @@ void ProtocolGame::parsePlayerSale(NetworkMessage& msg) uint8_t count = msg.getByte(); uint8_t amount = msg.getByte(); bool ignoreEquipped = msg.getByte() != 0; - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem, player->getID(), id, count, amount, ignoreEquipped); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, [=, playerID = player->getID()]() { + g_game.playerSellItem(playerID, id, count, amount, ignoreEquipped); + }); } void ProtocolGame::parseRequestTrade(NetworkMessage& msg) @@ -1070,35 +1388,39 @@ void ProtocolGame::parseRequestTrade(NetworkMessage& msg) uint16_t spriteId = msg.get(); uint8_t stackpos = msg.getByte(); uint32_t playerId = msg.get(); - addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos, playerId, spriteId); + addGameTask( + [=, playerID = player->getID()]() { g_game.playerRequestTrade(playerID, pos, stackpos, playerId, spriteId); }); } void ProtocolGame::parseLookInTrade(NetworkMessage& msg) { bool counterOffer = (msg.getByte() == 0x01); uint8_t index = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade, player->getID(), counterOffer, index); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerLookInTrade(playerID, counterOffer, index); }); } void ProtocolGame::parseAddVip(NetworkMessage& msg) { - const std::string name = msg.getString(); - addGameTask(&Game::playerRequestAddVip, player->getID(), name); + std::string name = msg.getString(); + addGameTask([playerID = player->getID(), name = std::move(name)]() { g_game.playerRequestAddVip(playerID, name); }); } void ProtocolGame::parseRemoveVip(NetworkMessage& msg) { uint32_t guid = msg.get(); - addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid); + addGameTask([=, playerID = player->getID()]() { g_game.playerRequestRemoveVip(playerID, guid); }); } void ProtocolGame::parseEditVip(NetworkMessage& msg) { uint32_t guid = msg.get(); - const std::string description = msg.getString(); + std::string description = msg.getString(); uint32_t icon = std::min(10, msg.get()); // 10 is max icon in 9.63 bool notify = msg.getByte() != 0; - addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description, icon, notify); + addGameTask([=, playerID = player->getID(), description = std::move(description)]() { + g_game.playerRequestEditVip(playerID, guid, description, icon, notify); + }); } void ProtocolGame::parseRotateItem(NetworkMessage& msg) @@ -1106,15 +1428,16 @@ void ProtocolGame::parseRotateItem(NetworkMessage& msg) Position pos = msg.getPosition(); uint16_t spriteId = msg.get(); uint8_t stackpos = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, + [=, playerID = player->getID()]() { g_game.playerRotateItem(playerID, pos, stackpos, spriteId); }); } void ProtocolGame::parseRuleViolationReport(NetworkMessage& msg) { uint8_t reportType = msg.getByte(); uint8_t reportReason = msg.getByte(); - const std::string& targetName = msg.getString(); - const std::string& comment = msg.getString(); + std::string targetName = msg.getString(); + std::string comment = msg.getString(); std::string translation; if (reportType == REPORT_TYPE_NAME) { translation = msg.getString(); @@ -1123,7 +1446,10 @@ void ProtocolGame::parseRuleViolationReport(NetworkMessage& msg) msg.get(); // statement id, used to get whatever player have said, we don't log that. } - addGameTask(&Game::playerReportRuleViolation, player->getID(), targetName, reportType, reportReason, comment, translation); + addGameTask([=, playerID = player->getID(), targetName = std::move(targetName), comment = std::move(comment), + translation = std::move(translation)]() { + g_game.playerReportRuleViolation(playerID, targetName, reportType, reportReason, comment, translation); + }); } void ProtocolGame::parseBugReport(NetworkMessage& msg) @@ -1136,7 +1462,9 @@ void ProtocolGame::parseBugReport(NetworkMessage& msg) position = msg.getPosition(); } - addGameTask(&Game::playerReportBug, player->getID(), message, position, category); + addGameTask([=, playerID = player->getID(), message = std::move(message)]() { + g_game.playerReportBug(playerID, message, position, category); + }); } void ProtocolGame::parseDebugAssert(NetworkMessage& msg) @@ -1151,60 +1479,78 @@ void ProtocolGame::parseDebugAssert(NetworkMessage& msg) std::string date = msg.getString(); std::string description = msg.getString(); std::string comment = msg.getString(); - addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment); + addGameTask([playerID = player->getID(), assertLine = std::move(assertLine), date = std::move(date), + description = std::move(description), comment = std::move(comment)]() { + g_game.playerDebugAssert(playerID, assertLine, date, description, comment); + }); } void ProtocolGame::parseInviteToParty(NetworkMessage& msg) { - uint32_t targetId = msg.get(); - addGameTask(&Game::playerInviteToParty, player->getID(), targetId); + uint32_t targetID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerInviteToParty(playerID, targetID); }); } void ProtocolGame::parseJoinParty(NetworkMessage& msg) { - uint32_t targetId = msg.get(); - addGameTask(&Game::playerJoinParty, player->getID(), targetId); + uint32_t targetID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerJoinParty(playerID, targetID); }); } void ProtocolGame::parseRevokePartyInvite(NetworkMessage& msg) { - uint32_t targetId = msg.get(); - addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId); + uint32_t targetID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerRevokePartyInvitation(playerID, targetID); }); } void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg) { - uint32_t targetId = msg.get(); - addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); + uint32_t targetID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerPassPartyLeadership(playerID, targetID); }); } void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg) { bool sharedExpActive = msg.getByte() == 1; - addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive); + addGameTask( + [=, playerID = player->getID()]() { g_game.playerEnableSharedPartyExperience(playerID, sharedExpActive); }); } void ProtocolGame::parseQuestLine(NetworkMessage& msg) { - uint16_t questId = msg.get(); - addGameTask(&Game::playerShowQuestLine, player->getID(), questId); + uint16_t questID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerShowQuestLine(playerID, questID); }); +} + +void ProtocolGame::parseQuestTracker(NetworkMessage& msg) +{ + uint8_t missions = msg.getByte(); + std::vector missionIDs; + missionIDs.reserve(missions); + for (uint8_t i = 0; i < missions; i++) { + missionIDs.push_back(msg.get()); + } + + addGameTask([playerID = player->getID(), missionIDs = std::move(missionIDs)]() { + g_game.playerResetQuestTracker(playerID, missionIDs); + }); } void ProtocolGame::parseMarketLeave() { - addGameTask(&Game::playerLeaveMarket, player->getID()); + addGameTask([playerID = player->getID()]() { g_game.playerLeaveMarket(playerID); }); } void ProtocolGame::parseMarketBrowse(NetworkMessage& msg) { - uint16_t browseId = msg.get(); - + uint8_t browseId = msg.get(); if (browseId == MARKETREQUEST_OWN_OFFERS) { - addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID()); + addGameTask([playerID = player->getID()]() { g_game.playerBrowseMarketOwnOffers(playerID); }); } else if (browseId == MARKETREQUEST_OWN_HISTORY) { - addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID()); + addGameTask([playerID = player->getID()]() { g_game.playerBrowseMarketOwnHistory(playerID); }); } else { - addGameTask(&Game::playerBrowseMarket, player->getID(), browseId); + uint16_t spriteID = msg.get(); + addGameTask([=, playerID = player->getID()]() { g_game.playerBrowseMarket(playerID, spriteID); }); } } @@ -1212,17 +1558,29 @@ void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg) { uint8_t type = msg.getByte(); uint16_t spriteId = msg.get(); + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0 || it.wareId == 0) { + return; + } else if (it.classification > 0) { + msg.getByte(); // item tier + } + uint16_t amount = msg.get(); - uint32_t price = msg.get(); + uint64_t price = msg.get(); bool anonymous = (msg.getByte() != 0); - addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous); + addGameTask([=, playerID = player->getID()]() { + g_game.playerCreateMarketOffer(playerID, type, spriteId, amount, price, anonymous); + }); + sendStoreBalance(); } void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg) { uint32_t timestamp = msg.get(); uint16_t counter = msg.get(); - addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter); + addGameTask([=, playerID = player->getID()]() { g_game.playerCancelMarketOffer(playerID, timestamp, counter); }); + sendStoreBalance(); } void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg) @@ -1230,7 +1588,8 @@ void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg) uint32_t timestamp = msg.get(); uint16_t counter = msg.get(); uint16_t amount = msg.get(); - addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount); + addGameTask( + [=, playerID = player->getID()]() { g_game.playerAcceptMarketOffer(playerID, timestamp, counter, amount); }); } void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg) @@ -1238,20 +1597,20 @@ void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg) uint32_t id = msg.get(); uint8_t button = msg.getByte(); uint8_t choice = msg.getByte(); - addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button, choice); + addGameTask([=, playerID = player->getID()]() { g_game.playerAnswerModalWindow(playerID, id, button, choice); }); } void ProtocolGame::parseBrowseField(NetworkMessage& msg) { - const Position& pos = msg.getPosition(); - addGameTask(&Game::playerBrowseField, player->getID(), pos); + Position pos = msg.getPosition(); + addGameTask([=, playerID = player->getID()]() { g_game.playerBrowseField(playerID, pos); }); } void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) { uint8_t containerId = msg.getByte(); uint16_t index = msg.get(); - addGameTask(&Game::playerSeekInContainer, player->getID(), containerId, index); + addGameTask([=, playerID = player->getID()]() { g_game.playerSeekInContainer(playerID, containerId, index); }); } // Send methods @@ -1304,6 +1663,16 @@ void ProtocolGame::sendWorldLight(LightInfo lightInfo) writeToOutputBuffer(msg); } +void ProtocolGame::sendWorldTime() +{ + int16_t time = g_game.getWorldTime(); + NetworkMessage msg; + msg.addByte(0xEF); + msg.addByte(time / 60); // hour + msg.addByte(time % 60); // min + writeToOutputBuffer(msg); +} + void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough) { if (!canSee(creature)) { @@ -1347,24 +1716,6 @@ void ProtocolGame::sendCreatureSkull(const Creature* creature) writeToOutputBuffer(msg); } -void ProtocolGame::sendCreatureType(uint32_t creatureId, uint8_t creatureType) -{ - NetworkMessage msg; - msg.addByte(0x95); - msg.add(creatureId); - msg.addByte(creatureType); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) -{ - NetworkMessage msg; - msg.addByte(0x94); - msg.add(creatureId); - msg.add(helpers); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) { if (!canSee(creature)) { @@ -1391,6 +1742,7 @@ void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const st { NetworkMessage msg; msg.addByte(0xDD); + msg.addByte(0x00); // unknown msg.addPosition(pos); msg.addByte(markType); msg.addString(desc); @@ -1403,6 +1755,7 @@ void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction) msg.addByte(0x28); msg.addByte(0x00); msg.addByte(unfairFightReduction); + msg.addByte(0x00); // can use death redemption (bool) writeToOutputBuffer(msg); } @@ -1413,6 +1766,42 @@ void ProtocolGame::sendStats() writeToOutputBuffer(msg); } +void ProtocolGame::sendExperienceTracker(int64_t rawExp, int64_t finalExp) +{ + NetworkMessage msg; + msg.addByte(0xAF); + msg.add(rawExp); + msg.add(finalExp); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendClientFeatures() +{ + NetworkMessage msg; + msg.addByte(0x17); + + msg.add(player->getID()); + msg.add(50); // beat duration + + msg.addDouble(Creature::speedA, 3); + msg.addDouble(Creature::speedB, 3); + msg.addDouble(Creature::speedC, 3); + + // can report bugs? + msg.addByte(player->getAccountType() >= ACCOUNT_TYPE_TUTOR ? 0x01 : 0x00); + + msg.addByte(0x00); // can change pvp framing option + msg.addByte(0x00); // expert mode button enabled + + msg.add(0x00); // store images url (string or u16 0x00) + msg.add(25); // premium coin package size + + msg.addByte(0x00); // exiva button enabled (bool) + msg.addByte(0x00); // Tournament button (bool) + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendBasicData() { NetworkMessage msg; @@ -1425,10 +1814,15 @@ void ProtocolGame::sendBasicData() msg.add(0); } msg.addByte(player->getVocation()->getClientId()); - msg.add(0xFF); // number of known spells + msg.addByte(0x00); // is prey system enabled (bool) + + // unlock spells on action bar + msg.add(0xFF); for (uint8_t spellId = 0x00; spellId < 0xFF; spellId++) { msg.addByte(spellId); } + + msg.addByte(0x00); // is magic shield active (bool) writeToOutputBuffer(msg); } @@ -1505,7 +1899,8 @@ void ProtocolGame::sendChannelsDialog() writeToOutputBuffer(msg); } -void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers) +void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, + const InvitedMap* invitedUsers) { NetworkMessage msg; msg.addByte(0xAC); @@ -1533,7 +1928,8 @@ void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelNam writeToOutputBuffer(msg); } -void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) +void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, + uint16_t channel) { NetworkMessage msg; msg.addByte(0xAA); @@ -1546,11 +1942,11 @@ void ProtocolGame::sendChannelMessage(const std::string& author, const std::stri writeToOutputBuffer(msg); } -void ProtocolGame::sendIcons(uint16_t icons) +void ProtocolGame::sendIcons(uint32_t icons) { NetworkMessage msg; msg.addByte(0xA2); - msg.add(icons); + msg.add(icons); writeToOutputBuffer(msg); } @@ -1570,17 +1966,17 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h } msg.addByte(container->capacity()); - msg.addByte(hasParent ? 0x01 : 0x00); - - msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop + msg.addByte(0x00); // show search icon (boolean) + msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop msg.addByte(container->hasPagination() ? 0x01 : 0x00); // Pagination uint32_t containerSize = container->size(); msg.add(containerSize); msg.add(firstIndex); if (firstIndex < containerSize) { - uint8_t itemsToSend = std::min(std::min(container->capacity(), containerSize - firstIndex), std::numeric_limits::max()); + uint8_t itemsToSend = std::min(std::min(container->capacity(), containerSize - firstIndex), + std::numeric_limits::max()); msg.addByte(itemsToSend); for (auto it = container->getItemList().begin() + firstIndex, end = it + itemsToSend; it != end; ++it) { @@ -1592,21 +1988,47 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h writeToOutputBuffer(msg); } -void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) +void ProtocolGame::sendEmptyContainer(uint8_t cid) { NetworkMessage msg; - msg.addByte(0x7A); - msg.addString(npc->getName()); + msg.addByte(0x6E); - uint16_t itemsToSend = std::min(itemList.size(), std::numeric_limits::max()); - msg.add(itemsToSend); + msg.addByte(cid); - uint16_t i = 0; - for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) { - AddShopItem(msg, *it); - } + msg.addItem(ITEM_BAG, 1); + msg.addString("Placeholder"); - writeToOutputBuffer(msg); + msg.addByte(8); + msg.addByte(0x00); + msg.addByte(0x00); + msg.addByte(0x01); + msg.addByte(0x00); + msg.add(0); + msg.add(0); + msg.addByte(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) +{ + NetworkMessage msg; + msg.addByte(0x7A); + msg.addString(npc->getName()); + + // currency displayed in trade window (currently only gold supported) if item other than gold coin is sent, the shop + // window takes information about currency amount from player items packet (the one that updates action bars) + msg.add(Item::items[ITEM_GOLD_COIN].clientId); + msg.addString(""); // doesn't show anywhere, could be used in otclient for currency name + + uint16_t itemsToSend = std::min(itemList.size(), std::numeric_limits::max()); + msg.add(itemsToSend); + + uint16_t i = 0; + for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) { + AddShopItem(msg, *it); + } + + writeToOutputBuffer(msg); } void ProtocolGame::sendCloseShop() @@ -1618,9 +2040,14 @@ void ProtocolGame::sendCloseShop() void ProtocolGame::sendSaleItemList(const std::list& shop) { + uint64_t playerBank = player->getBankBalance(); + uint64_t playerMoney = player->getMoney(); + sendResourceBalance(RESOURCE_BANK_BALANCE, playerBank); + sendResourceBalance(RESOURCE_GOLD_EQUIPPED, playerMoney); + NetworkMessage msg; msg.addByte(0x7B); - msg.add(player->getMoney() + player->getBankBalance()); + msg.add(playerBank + playerMoney); // deprecated and ignored by QT client. OTClient still uses it. std::map saleMap; @@ -1644,15 +2071,13 @@ void ProtocolGame::sendSaleItemList(const std::list& shop) } } } else { - // Large shop, it's better to get a cached map of all item counts and use it - // We need a temporary map since the finished map should only contain items - // available in the shop + // Large shop, it's better to get a cached map of all item counts and use it We need a temporary map since the + // finished map should only contain items available in the shop std::map tempSaleMap; player->getAllItemTypeCount(tempSaleMap); - // We must still check manually for the special items that require subtype matches - // (That is, fluids such as potions etc., actually these items are very few since - // health potions now use their own ID) + // We must still check manually for the special items that require subtype matches (That is, fluids such as + // potions etc., actually these items are very few since health potions now use their own ID) for (const ShopInfo& shopInfo : shop) { if (shopInfo.sellPrice == 0) { continue; @@ -1667,8 +2092,9 @@ void ProtocolGame::sendSaleItemList(const std::list& shop) if (subtype != -1) { uint32_t count; - if (!itemType.isFluidContainer() && !itemType.isSplash()) { - count = player->getItemTypeCount(shopInfo.itemId, subtype); // This shop item requires extra checks + if (itemType.isFluidContainer() || itemType.isSplash()) { + count = player->getItemTypeCount(shopInfo.itemId, + subtype); // This shop item requires extra checks } else { count = subtype; } @@ -1697,25 +2123,46 @@ void ProtocolGame::sendSaleItemList(const std::list& shop) writeToOutputBuffer(msg); } -void ProtocolGame::sendMarketEnter(uint32_t depotId) +void ProtocolGame::sendResourceBalance(const ResourceTypes_t resourceType, uint64_t amount) { NetworkMessage msg; - msg.addByte(0xF6); + msg.addByte(0xEE); + msg.addByte(resourceType); + msg.add(amount); + writeToOutputBuffer(msg); +} - msg.add(player->getBankBalance()); - msg.addByte(std::min(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits::max())); +void ProtocolGame::sendStoreBalance() +{ + NetworkMessage msg; + msg.addByte(0xDF); + msg.addByte(0x01); - DepotChest* depotChest = player->getDepotChest(depotId, false); - if (!depotChest) { - msg.add(0x00); - writeToOutputBuffer(msg); - return; - } + // placeholder packet / to do + msg.add(0); // total store coins (transferable + non-t) + msg.add(0); // transferable store coins + msg.add(0); // reserved auction coins + msg.add(0); // tournament coins + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketEnter() +{ + NetworkMessage msg; + msg.addByte(0xF6); + msg.addByte( + std::min(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits::max())); player->setInMarket(true); std::map depotItems; - std::forward_list containerList { depotChest, player->getInbox() }; + std::forward_list containerList{player->getInbox()}; + + for (const auto& chest : player->depotChests) { + if (!chest.second->empty()) { + containerList.push_front(chest.second); + } + } do { Container* container = containerList.front(); @@ -1741,20 +2188,27 @@ void ProtocolGame::sendMarketEnter(uint32_t depotId) continue; } - depotItems[itemType.wareId] += Item::countByType(item, -1); + depotItems[itemType.id] += Item::countByType(item, -1); } } while (!containerList.empty()); uint16_t itemsToSend = std::min(depotItems.size(), std::numeric_limits::max()); - msg.add(itemsToSend); - uint16_t i = 0; + + msg.add(itemsToSend); for (std::map::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) { - msg.add(it->first); + const ItemType& itemType = Item::items[it->first]; + msg.add(itemType.wareId); + if (itemType.classification > 0) { + msg.addByte(0); + } msg.add(std::min(0xFFFF, it->second)); } - writeToOutputBuffer(msg); + + sendResourceBalance(RESOURCE_BANK_BALANCE, player->getBankBalance()); + sendResourceBalance(RESOURCE_GOLD_EQUIPPED, player->getMoney()); + sendStoreBalance(); } void ProtocolGame::sendMarketLeave() @@ -1764,19 +2218,26 @@ void ProtocolGame::sendMarketLeave() writeToOutputBuffer(msg); } -void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) +void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, + const MarketOfferList& sellOffers) { - NetworkMessage msg; + sendStoreBalance(); + NetworkMessage msg; msg.addByte(0xF9); + msg.addByte(MARKETREQUEST_ITEM); msg.addItemId(itemId); + if (Item::items[itemId].classification > 0) { + msg.addByte(0); // item tier + } + msg.add(buyOffers.size()); for (const MarketOffer& offer : buyOffers) { msg.add(offer.timestamp); msg.add(offer.counter); msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); msg.addString(offer.playerName); } @@ -1785,7 +2246,7 @@ void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& msg.add(offer.timestamp); msg.add(offer.counter); msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); msg.addString(offer.playerName); } @@ -1796,14 +2257,18 @@ void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer) { NetworkMessage msg; msg.addByte(0xF9); + msg.addByte(MARKETREQUEST_ITEM); msg.addItemId(offer.itemId); + if (Item::items[offer.itemId].classification > 0) { + msg.addByte(0); + } if (offer.type == MARKETACTION_BUY) { msg.add(0x01); msg.add(offer.timestamp); msg.add(offer.counter); msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); msg.addString(offer.playerName); msg.add(0x00); } else { @@ -1812,7 +2277,7 @@ void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer) msg.add(offer.timestamp); msg.add(offer.counter); msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); msg.addString(offer.playerName); } @@ -1823,15 +2288,18 @@ void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, c { NetworkMessage msg; msg.addByte(0xF9); - msg.add(MARKETREQUEST_OWN_OFFERS); + msg.addByte(MARKETREQUEST_OWN_OFFERS); msg.add(buyOffers.size()); for (const MarketOffer& offer : buyOffers) { msg.add(offer.timestamp); msg.add(offer.counter); msg.addItemId(offer.itemId); + if (Item::items[offer.itemId].classification > 0) { + msg.addByte(0); + } msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); } msg.add(sellOffers.size()); @@ -1839,8 +2307,11 @@ void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, c msg.add(offer.timestamp); msg.add(offer.counter); msg.addItemId(offer.itemId); + if (Item::items[offer.itemId].classification > 0) { + msg.addByte(0); + } msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); } writeToOutputBuffer(msg); @@ -1850,15 +2321,18 @@ void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer) { NetworkMessage msg; msg.addByte(0xF9); - msg.add(MARKETREQUEST_OWN_OFFERS); + msg.addByte(MARKETREQUEST_OWN_OFFERS); if (offer.type == MARKETACTION_BUY) { msg.add(0x01); msg.add(offer.timestamp); msg.add(offer.counter); msg.addItemId(offer.itemId); + if (Item::items[offer.itemId].classification > 0) { + msg.addByte(0); + } msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); msg.add(0x00); } else { msg.add(0x00); @@ -1866,31 +2340,40 @@ void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer) msg.add(offer.timestamp); msg.add(offer.counter); msg.addItemId(offer.itemId); + if (Item::items[offer.itemId].classification > 0) { + msg.addByte(0); + } msg.add(offer.amount); - msg.add(offer.price); + msg.add(offer.price); } writeToOutputBuffer(msg); } -void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) +void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, + const HistoryMarketOfferList& sellOffers) { uint32_t i = 0; std::map counterMap; - uint32_t buyOffersToSend = std::min(buyOffers.size(), 810 + std::max(0, 810 - sellOffers.size())); - uint32_t sellOffersToSend = std::min(sellOffers.size(), 810 + std::max(0, 810 - buyOffers.size())); + uint32_t buyOffersToSend = + std::min(buyOffers.size(), 810 + std::max(0, 810 - sellOffers.size())); + uint32_t sellOffersToSend = + std::min(sellOffers.size(), 810 + std::max(0, 810 - buyOffers.size())); NetworkMessage msg; msg.addByte(0xF9); - msg.add(MARKETREQUEST_OWN_HISTORY); + msg.addByte(MARKETREQUEST_OWN_HISTORY); msg.add(buyOffersToSend); for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) { msg.add(it->timestamp); msg.add(counterMap[it->timestamp]++); msg.addItemId(it->itemId); + if (Item::items[it->itemId].classification > 0) { + msg.addByte(0); + } msg.add(it->amount); - msg.add(it->price); + msg.add(it->price); msg.addByte(it->state); } @@ -1902,219 +2385,17 @@ void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyO msg.add(it->timestamp); msg.add(counterMap[it->timestamp]++); msg.addItemId(it->itemId); + if (Item::items[it->itemId].classification > 0) { + msg.addByte(0); + } msg.add(it->amount); - msg.add(it->price); + msg.add(it->price); msg.addByte(it->state); } writeToOutputBuffer(msg); } -void ProtocolGame::sendMarketDetail(uint16_t itemId) -{ - NetworkMessage msg; - msg.addByte(0xF8); - msg.addItemId(itemId); - - const ItemType& it = Item::items[itemId]; - if (it.armor != 0) { - msg.addString(std::to_string(it.armor)); - } else { - msg.add(0x00); - } - - if (it.attack != 0) { - // TODO: chance to hit, range - // example: - // "attack +x, chance to hit +y%, z fields" - if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { - std::ostringstream ss; - ss << it.attack << " physical +" << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); - msg.addString(ss.str()); - } else { - msg.addString(std::to_string(it.attack)); - } - } else { - msg.add(0x00); - } - - if (it.isContainer()) { - msg.addString(std::to_string(it.maxItems)); - } else { - msg.add(0x00); - } - - if (it.defense != 0) { - if (it.extraDefense != 0) { - std::ostringstream ss; - ss << it.defense << ' ' << std::showpos << it.extraDefense << std::noshowpos; - msg.addString(ss.str()); - } else { - msg.addString(std::to_string(it.defense)); - } - } else { - msg.add(0x00); - } - - if (!it.description.empty()) { - const std::string& descr = it.description; - if (descr.back() == '.') { - msg.addString(std::string(descr, 0, descr.length() - 1)); - } else { - msg.addString(descr); - } - } else { - msg.add(0x00); - } - - if (it.decayTime != 0) { - std::ostringstream ss; - ss << it.decayTime << " seconds"; - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - if (it.abilities) { - std::ostringstream ss; - bool separator = false; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - if (it.minReqLevel != 0) { - msg.addString(std::to_string(it.minReqLevel)); - } else { - msg.add(0x00); - } - - if (it.minReqMagicLevel != 0) { - msg.addString(std::to_string(it.minReqMagicLevel)); - } else { - msg.add(0x00); - } - - msg.addString(it.vocationString); - - msg.addString(it.runeSpellName); - - if (it.abilities) { - std::ostringstream ss; - bool separator = false; - - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { - continue; - } - - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; - } - - if (it.abilities->stats[STAT_MAGICPOINTS] != 0) { - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - } - - if (it.abilities->speed != 0) { - if (separator) { - ss << ", "; - } - - ss << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - } - - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - if (it.charges != 0) { - msg.addString(std::to_string(it.charges)); - } else { - msg.add(0x00); - } - - std::string weaponName = getWeaponName(it.weaponType); - - if (it.slotPosition & SLOTP_TWO_HAND) { - if (!weaponName.empty()) { - weaponName += ", two-handed"; - } else { - weaponName = "two-handed"; - } - } - - msg.addString(weaponName); - - if (it.weight != 0) { - std::ostringstream ss; - if (it.weight < 10) { - ss << "0.0" << it.weight; - } else if (it.weight < 100) { - ss << "0." << it.weight; - } else { - std::string weightString = std::to_string(it.weight); - weightString.insert(weightString.end() - 2, '.'); - ss << weightString; - } - ss << " oz"; - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemId); - if (statistics) { - msg.addByte(0x01); - msg.add(statistics->numTransactions); - msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice)); - msg.add(statistics->highestPrice); - msg.add(statistics->lowestPrice); - } else { - msg.addByte(0x00); - } - - statistics = IOMarket::getInstance().getSaleStatistics(itemId); - if (statistics) { - msg.addByte(0x01); - msg.add(statistics->numTransactions); - msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice)); - msg.add(statistics->highestPrice); - msg.add(statistics->lowestPrice); - } else { - msg.addByte(0x00); - } - - writeToOutputBuffer(msg); -} - void ProtocolGame::sendQuestLog() { NetworkMessage msg; @@ -2141,6 +2422,7 @@ void ProtocolGame::sendQuestLine(const Quest* quest) for (const Mission& mission : quest->getMissions()) { if (mission.isStarted(player)) { + msg.add(mission.getID()); msg.addString(mission.getName(player)); msg.addString(mission.getDescription(player)); } @@ -2149,6 +2431,42 @@ void ProtocolGame::sendQuestLine(const Quest* quest) writeToOutputBuffer(msg); } +void ProtocolGame::sendQuestTracker() +{ + NetworkMessage msg; + msg.addByte(0xD0); + msg.addByte(1); + size_t trackeds = player->trackedQuests.size(); + msg.addByte(player->getMaxTrackedQuests() - trackeds); + msg.addByte(trackeds); + + for (const TrackedQuest& trackedQuest : player->trackedQuests) { + const Quest* quest = g_game.quests.getQuestByID(trackedQuest.getQuestId()); + const Mission* mission = quest->getMissionById(trackedQuest.getMissionId()); + msg.add(trackedQuest.getMissionId()); + msg.addString(quest->getName()); + msg.addString(mission->getName(player)); + msg.addString(mission->getDescription(player)); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateQuestTracker(const TrackedQuest& trackedQuest) +{ + NetworkMessage msg; + msg.addByte(0xD0); + msg.addByte(0); + + const Quest* quest = g_game.quests.getQuestByID(trackedQuest.getQuestId()); + const Mission* mission = quest->getMissionById(trackedQuest.getMissionId()); + msg.add(trackedQuest.getMissionId()); + msg.addString(mission->getName(player)); + msg.addString(mission->getDescription(player)); + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) { NetworkMessage msg; @@ -2162,8 +2480,8 @@ void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Ite msg.addString(traderName); if (const Container* tradeContainer = item->getContainer()) { - std::list listContainer {tradeContainer}; - std::list itemList {tradeContainer}; + std::list listContainer{tradeContainer}; + std::list itemList{tradeContainer}; while (!listContainer.empty()) { const Container* container = listContainer.front(); listContainer.pop_front(); @@ -2226,7 +2544,8 @@ void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos) writeToOutputBuffer(msg); } -void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos/* = nullptr*/) +void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, + const Position* pos /* = nullptr*/) { NetworkMessage msg; msg.addByte(0xAA); @@ -2235,8 +2554,9 @@ void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, msg.add(++statementId); msg.addString(creature->getName()); + msg.addByte(0x00); // "(Traded)" suffix after player name - //Add level only for players + // Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); } else { @@ -2254,7 +2574,8 @@ void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, writeToOutputBuffer(msg); } -void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) +void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, + uint16_t channelId) { NetworkMessage msg; msg.addByte(0xAA); @@ -2263,9 +2584,12 @@ void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, co msg.add(++statementId); if (!creature) { msg.add(0x00); + msg.addByte(0x00); // "(Traded)" suffix after player name } else { msg.addString(creature->getName()); - //Add level only for players + msg.addByte(0x00); // "(Traded)" suffix after player name + + // Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); } else { @@ -2287,9 +2611,11 @@ void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, msg.add(++statementId); if (speaker) { msg.addString(speaker->getName()); + msg.addByte(0x00); // "(Traded)" suffix after player name msg.add(speaker->getLevel()); } else { msg.add(0x00); + msg.addByte(0x00); // "(Traded)" suffix after player name } msg.addByte(type); msg.addString(text); @@ -2346,10 +2672,13 @@ void ProtocolGame::sendPingBack() void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type) { NetworkMessage msg; - msg.addByte(0x85); + msg.addByte(0x83); msg.addPosition(from); - msg.addPosition(to); + msg.addByte(MAGIC_EFFECTS_CREATE_DISTANCEEFFECT); msg.addByte(type); + msg.addByte(static_cast(static_cast(static_cast(to.x) - static_cast(from.x)))); + msg.addByte(static_cast(static_cast(static_cast(to.y) - static_cast(from.y)))); + msg.addByte(MAGIC_EFFECTS_END_LOOP); writeToOutputBuffer(msg); } @@ -2362,7 +2691,9 @@ void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type) NetworkMessage msg; msg.addByte(0x83); msg.addPosition(pos); + msg.addByte(MAGIC_EFFECTS_CREATE_EFFECT); msg.addByte(type); + msg.addByte(MAGIC_EFFECTS_END_LOOP); writeToOutputBuffer(msg); } @@ -2375,7 +2706,8 @@ void ProtocolGame::sendCreatureHealth(const Creature* creature) if (creature->isHealthHidden()) { msg.addByte(0x00); } else { - msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); + msg.addByte(std::ceil( + (static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); } writeToOutputBuffer(msg); } @@ -2388,13 +2720,14 @@ void ProtocolGame::sendFYIBox(const std::string& message) writeToOutputBuffer(msg); } -//tile +// tile void ProtocolGame::sendMapDescription(const Position& pos) { NetworkMessage msg; msg.addByte(0x64); msg.addPosition(player->getPosition()); - GetMapDescription(pos.x - 8, pos.y - 6, pos.z, 18, 14, msg); + GetMapDescription(pos.x - Map::maxClientViewportX, pos.y - Map::maxClientViewportY, pos.z, + (Map::maxClientViewportX * 2) + 2, (Map::maxClientViewportY * 2) + 2, msg); writeToOutputBuffer(msg); } @@ -2437,10 +2770,42 @@ void ProtocolGame::sendRemoveTileThing(const Position& pos, uint32_t stackpos) writeToOutputBuffer(msg); } +void ProtocolGame::sendUpdateTileCreature(const Position& pos, uint32_t stackpos, const Creature* creature) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6B); + msg.addPosition(pos); + msg.addByte(stackpos); + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown(creature->getID(), known, removedKnown); + AddCreature(msg, creature, false, removedKnown); + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendRemoveTileCreature(const Creature* creature, const Position& pos, uint32_t stackpos) { + if (stackpos < 10) { + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + RemoveTileThing(msg, pos, stackpos); + writeToOutputBuffer(msg); + return; + } + NetworkMessage msg; - RemoveTileCreature(msg, creature, pos, stackpos); + msg.addByte(0x6C); + msg.add(0xFFFF); + msg.add(creature->getID()); writeToOutputBuffer(msg); } @@ -2491,19 +2856,19 @@ void ProtocolGame::sendFightModes() writeToOutputBuffer(msg); } -void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin) +void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, + MagicEffectClasses magicEffect /*= CONST_ME_NONE*/) { if (!canSee(pos)) { return; } if (creature != player) { - // stack pos is always real index now, so it can exceed the limit - // if stack pos exceeds the limit, we need to refresh the tile instead + // stack pos is always real index now, so it can exceed the limit if stack pos exceeds the limit, we need to + // refresh the tile instead // 1. this is a rare case, and is only triggered by forcing summon in a position - // 2. since no stackpos will be send to the client about that creature, removing - // it must be done with its id if its stackpos remains >= 10. this is done to - // add creatures to battle list instead of rendering on screen + // 2. since no stackpos will be send to the client about that creature, removing it must be done with its id if + // its stackpos remains >= 10. this is done to add creatures to battle list instead of rendering on screen if (stackpos >= 10) { // @todo: should we avoid this check? if (const Tile* tile = creature->getTile()) { @@ -2523,67 +2888,59 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos writeToOutputBuffer(msg); } - if (isLogin) { - sendMagicEffect(pos, CONST_ME_TELEPORT); + if (magicEffect != CONST_ME_NONE) { + sendMagicEffect(pos, magicEffect); } return; } - NetworkMessage msg; - msg.addByte(0x17); - - msg.add(player->getID()); - msg.add(0x32); // beat duration (50) - - msg.addDouble(Creature::speedA, 3); - msg.addDouble(Creature::speedB, 3); - msg.addDouble(Creature::speedC, 3); - - // can report bugs? - if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { - msg.addByte(0x01); - } else { - msg.addByte(0x00); - } - - msg.addByte(0x00); // can change pvp framing option - msg.addByte(0x00); // expert mode button enabled - - msg.add(0x00); // URL (string) to ingame store images - msg.add(25); // premium coin package size + // send player stats + sendStats(); // hp, cap, level, xp rate, etc. + sendSkills(); // skills and special skills + player->sendIcons(); // active conditions - writeToOutputBuffer(msg); + // send client info + sendClientFeatures(); // player speed, bug reports, store url, pvp mode, etc + sendBasicData(); // premium account, vocation, known spells, prey system status, magic shield status + sendItems(); // send carried items for action bars + // enter world and send game screen sendPendingStateEntered(); sendEnterWorld(); sendMapDescription(pos); - if (isLogin) { - sendMagicEffect(pos, CONST_ME_TELEPORT); + // send login effect + if (magicEffect != CONST_ME_NONE) { + sendMagicEffect(pos, magicEffect); } + // send equipment for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { sendInventoryItem(static_cast(i), player->getInventoryItem(static_cast(i))); } + // send store inbox sendInventoryItem(CONST_SLOT_STORE_INBOX, player->getStoreInbox()->getItem()); - sendStats(); - sendSkills(); - - //gameworld light-settings + // gameworld time of the day sendWorldLight(g_game.getWorldLightInfo()); + sendWorldTime(); - //player light level + // player light level sendCreatureLight(creature); + // player vip list sendVIPEntries(); - sendBasicData(); - player->sendIcons(); + // tiers for forge and market + sendItemClasses(); + + // opened containers + player->openSavedContainers(); } -void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) +void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, + const Position& oldPos, int32_t oldStackPos, bool teleport) { if (creature == player) { if (teleport) { @@ -2613,25 +2970,29 @@ void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& ne if (oldPos.y > newPos.y) { // north, for old x msg.addByte(0x65); - GetMapDescription(oldPos.x - 8, newPos.y - 6, newPos.z, 18, 1, msg); + GetMapDescription(oldPos.x - Map::maxClientViewportX, newPos.y - Map::maxClientViewportY, newPos.z, + (Map::maxClientViewportX * 2) + 2, 1, msg); } else if (oldPos.y < newPos.y) { // south, for old x msg.addByte(0x67); - GetMapDescription(oldPos.x - 8, newPos.y + 7, newPos.z, 18, 1, msg); + GetMapDescription(oldPos.x - Map::maxClientViewportX, newPos.y + (Map::maxClientViewportY + 1), + newPos.z, (Map::maxClientViewportX * 2) + 2, 1, msg); } if (oldPos.x < newPos.x) { // east, [with new y] msg.addByte(0x66); - GetMapDescription(newPos.x + 9, newPos.y - 6, newPos.z, 1, 14, msg); + GetMapDescription(newPos.x + (Map::maxClientViewportX + 1), newPos.y - Map::maxClientViewportY, + newPos.z, 1, (Map::maxClientViewportY * 2) + 2, msg); } else if (oldPos.x > newPos.x) { // west, [with new y] msg.addByte(0x68); - GetMapDescription(newPos.x - 8, newPos.y - 6, newPos.z, 1, 14, msg); + GetMapDescription(newPos.x - Map::maxClientViewportX, newPos.y - Map::maxClientViewportY, newPos.z, 1, + (Map::maxClientViewportY * 2) + 2, msg); } writeToOutputBuffer(msg); } } else if (canSee(oldPos) && canSee(creature->getPosition())) { if (teleport || (oldPos.z == 7 && newPos.z >= 8)) { sendRemoveTileCreature(creature, oldPos, oldStackPos); - sendAddCreature(creature, newPos, newStackPos, false); + sendAddCreature(creature, newPos, newStackPos); } else { NetworkMessage msg; msg.addByte(0x6D); @@ -2648,7 +3009,7 @@ void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& ne } else if (canSee(oldPos)) { sendRemoveTileCreature(creature, oldPos, oldStackPos); } else if (canSee(creature->getPosition())) { - sendAddCreature(creature, newPos, newStackPos, false); + sendAddCreature(creature, newPos, newStackPos); } } @@ -2666,23 +3027,27 @@ void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) writeToOutputBuffer(msg); } +// to do: make it lightweight, update each time player gets/loses an item void ProtocolGame::sendItems() { NetworkMessage msg; msg.addByte(0xF5); - const std::vector& inventory = Item::items.getInventory(); + // find all items carried by character (itemId, amount) + std::map inventory; + player->getAllItemTypeCount(inventory); + msg.add(inventory.size() + 11); for (uint16_t i = 1; i <= 11; i++) { - msg.add(i); - msg.addByte(0); //always 0 + msg.add(i); // slotId + msg.addByte(0); // always 0 msg.add(1); // always 1 } - for (auto clientId : inventory) { - msg.add(clientId); - msg.addByte(0); //always 0 - msg.add(1); + for (const auto& item : inventory) { + msg.add(Item::items[item.first].clientId); // item clientId + msg.addByte(0); // always 0 + msg.add(item.second); // count } writeToOutputBuffer(msg); @@ -2745,6 +3110,8 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t ma msg.add(0x00); } + msg.addByte(0x00); // "(traded)" suffix after player name (bool) + time_t writtenDate = item->getDate(); if (writtenDate != 0) { msg.addString(formatDateShort(writtenDate)); @@ -2763,8 +3130,9 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const msg.addItem(itemId, 1); msg.add(text.size()); msg.addString(text); - msg.add(0x00); - msg.add(0x00); + msg.add(0x00); // writer name + msg.addByte(0x00); // "(traded)" byte + msg.add(0x00); // date writeToOutputBuffer(msg); } @@ -2780,10 +3148,23 @@ void ProtocolGame::sendHouseWindow(uint32_t windowTextId, const std::string& tex void ProtocolGame::sendOutfitWindow() { + const auto& outfits = Outfits::getInstance().getOutfits(player->getSex()); + if (outfits.size() == 0) { + return; + } + NetworkMessage msg; msg.addByte(0xC8); Outfit_t currentOutfit = player->getDefaultOutfit(); + bool mounted = currentOutfit.lookMount != 0; + + if (currentOutfit.lookType == 0) { + Outfit_t newOutfit; + newOutfit.lookType = outfits.front().lookType; + currentOutfit = newOutfit; + } + Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount()); if (currentMount) { currentOutfit.lookMount = currentMount->clientId; @@ -2791,13 +3172,22 @@ void ProtocolGame::sendOutfitWindow() AddOutfit(msg, currentOutfit); + // mount color bytes are required here regardless of having one + if (currentOutfit.lookMount == 0) { + msg.addByte(currentOutfit.lookMountHead); + msg.addByte(currentOutfit.lookMountBody); + msg.addByte(currentOutfit.lookMountLegs); + msg.addByte(currentOutfit.lookMountFeet); + } + + msg.add(0); // current familiar looktype + std::vector protocolOutfits; if (player->isAccessPlayer()) { static const std::string gamemasterOutfitName = "Gamemaster"; protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0); } - const auto& outfits = Outfits::getInstance().getOutfits(player->getSex()); protocolOutfits.reserve(outfits.size()); for (const Outfit& outfit : outfits) { uint8_t addons; @@ -2806,16 +3196,15 @@ void ProtocolGame::sendOutfitWindow() } protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); - if (protocolOutfits.size() == std::numeric_limits::max()) { // Game client currently doesn't allow more than 255 outfits - break; - } } - msg.addByte(protocolOutfits.size()); + msg.add(protocolOutfits.size()); for (const ProtocolOutfit& outfit : protocolOutfits) { msg.add(outfit.lookType); msg.addString(outfit.name); msg.addByte(outfit.addons); + msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit + // tooltip (hardcoded) } std::vector mounts; @@ -2825,12 +3214,160 @@ void ProtocolGame::sendOutfitWindow() } } - msg.addByte(mounts.size()); + msg.add(mounts.size()); for (const Mount* mount : mounts) { msg.add(mount->clientId); msg.addString(mount->name); + msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) } + msg.add(0x00); // familiars.size() + // size > 0 + // U16 looktype + // String name + // 0x00 // mode: 0x00 - available, 0x01 store (requires U32 store offerId) + + msg.addByte(0x00); // Try outfit mode (?) + msg.addByte(mounted ? 0x01 : 0x00); + msg.addByte(0x00); // randomize mount (bool) + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPodiumWindow(const Item* item) +{ + if (!item) { + return; + } + + const Podium* podium = item->getPodium(); + if (!podium) { + return; + } + + const Tile* tile = item->getTile(); + if (!tile) { + return; + } + + int32_t stackpos = tile->getThingIndex(item); + + // read podium outfit + Outfit_t podiumOutfit = podium->getOutfit(); + Outfit_t playerOutfit = player->getDefaultOutfit(); + bool isEmpty = podiumOutfit.lookType == 0 && podiumOutfit.lookMount == 0; + + if (podiumOutfit.lookType == 0) { + // copy player outfit + podiumOutfit.lookType = playerOutfit.lookType; + podiumOutfit.lookHead = playerOutfit.lookHead; + podiumOutfit.lookBody = playerOutfit.lookBody; + podiumOutfit.lookLegs = playerOutfit.lookLegs; + podiumOutfit.lookFeet = playerOutfit.lookFeet; + podiumOutfit.lookAddons = playerOutfit.lookAddons; + } + + if (podiumOutfit.lookMount == 0) { + // copy player mount + podiumOutfit.lookMount = playerOutfit.lookMount; + podiumOutfit.lookMountHead = playerOutfit.lookMountHead; + podiumOutfit.lookMountBody = playerOutfit.lookMountBody; + podiumOutfit.lookMountLegs = playerOutfit.lookMountLegs; + podiumOutfit.lookMountFeet = playerOutfit.lookMountFeet; + } + + // fetch player outfits + const auto& outfits = Outfits::getInstance().getOutfits(player->getSex()); + if (outfits.size() == 0) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + // add GM outfit for staff members + std::vector protocolOutfits; + if (player->isAccessPlayer()) { + static const std::string gamemasterOutfitName = "Gamemaster"; + protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0); + } + + // fetch player addons info + protocolOutfits.reserve(outfits.size()); + for (const Outfit& outfit : outfits) { + uint8_t addons; + if (!player->getOutfitAddons(outfit, addons)) { + continue; + } + + protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); + } + + // select first outfit available when the one from podium is not unlocked + if (!player->canWear(podiumOutfit.lookType, 0)) { + podiumOutfit.lookType = outfits.front().lookType; + } + + // fetch player mounts + std::vector mounts; + for (const Mount& mount : g_game.mounts.getMounts()) { + if (player->hasMount(&mount)) { + mounts.push_back(&mount); + } + } + + // packet header + NetworkMessage msg; + msg.addByte(0xC8); + + // current outfit + msg.add(podiumOutfit.lookType); + msg.addByte(podiumOutfit.lookHead); + msg.addByte(podiumOutfit.lookBody); + msg.addByte(podiumOutfit.lookLegs); + msg.addByte(podiumOutfit.lookFeet); + msg.addByte(podiumOutfit.lookAddons); + + // current mount + msg.add(podiumOutfit.lookMount); + msg.addByte(podiumOutfit.lookMountHead); + msg.addByte(podiumOutfit.lookMountBody); + msg.addByte(podiumOutfit.lookMountLegs); + msg.addByte(podiumOutfit.lookMountFeet); + + // current familiar (not used in podium mode) + msg.add(0); + + // available outfits + msg.add(protocolOutfits.size()); + for (const ProtocolOutfit& outfit : protocolOutfits) { + msg.add(outfit.lookType); + msg.addString(outfit.name); + msg.addByte(outfit.addons); + msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit + // tooltip (hardcoded) + } + + // available mounts + msg.add(mounts.size()); + for (const Mount* mount : mounts) { + msg.add(mount->clientId); + msg.addString(mount->name); + msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) + } + + // available familiars (not used in podium mode) + msg.add(0); + + msg.addByte(0x05); // "set outfit" window mode (5 = podium) + msg.addByte((isEmpty && playerOutfit.lookMount != 0) || podium->hasFlag(PODIUM_SHOW_MOUNT) + ? 0x01 + : 0x00); // "mount" checkbox + msg.add(0); // unknown + msg.addPosition(item->getPosition()); + msg.add(item->getClientID()); + msg.addByte(stackpos); + + msg.addByte(podium->hasFlag(PODIUM_SHOW_PLATFORM) ? 0x01 : 0x00); // is platform visible + msg.addByte(0x01); // "outfit" checkbox, ignored by the client + msg.addByte(podium->getDirection()); // outfit direction writeToOutputBuffer(msg); } @@ -2843,7 +3380,8 @@ void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) writeToOutputBuffer(msg); } -void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status) +void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, + bool notify, VipStatus_t status) { NetworkMessage msg; msg.addByte(0xD2); @@ -2853,6 +3391,7 @@ void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::st msg.add(std::min(10, icon)); msg.addByte(notify ? 0x01 : 0x00); msg.addByte(status); + msg.addByte(0x00); // vipGroups (placeholder) writeToOutputBuffer(msg); } @@ -2865,7 +3404,7 @@ void ProtocolGame::sendVIPEntries() Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); - if (!vipPlayer || vipPlayer->isInGhostMode() || player->isAccessPlayer()) { + if (!vipPlayer || !player->canSeeCreature(vipPlayer)) { vipStatus = VIPSTATUS_OFFLINE; } @@ -2873,6 +3412,35 @@ void ProtocolGame::sendVIPEntries() } } +void ProtocolGame::sendItemClasses() +{ + NetworkMessage msg; + msg.addByte(0x86); + + uint8_t classSize = 4; + uint8_t tiersSize = 10; + + // item classes + msg.addByte(classSize); + for (uint8_t i = 0; i < classSize; i++) { + msg.addByte(i + 1); // class id + + // item tiers + msg.addByte(tiersSize); // tiers size + for (uint8_t j = 0; j < tiersSize; j++) { + msg.addByte(j); // tier id + msg.add(10000); // upgrade cost + } + } + + // unknown + for (uint8_t i = 0; i < tiersSize + 1; i++) { + msg.addByte(0); + } + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time) { NetworkMessage msg; @@ -2891,6 +3459,23 @@ void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) writeToOutputBuffer(msg); } +void ProtocolGame::sendUseItemCooldown(uint32_t time) +{ + NetworkMessage msg; + msg.addByte(0xA6); + msg.add(time); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSupplyUsed(const uint16_t clientId) +{ + NetworkMessage msg; + msg.addByte(0xCE); + msg.add(clientId); + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow) { NetworkMessage msg; @@ -2912,19 +3497,39 @@ void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow) msg.addByte(it.second); } - msg.addByte(modalWindow.defaultEscapeButton); msg.addByte(modalWindow.defaultEnterButton); + msg.addByte(modalWindow.defaultEscapeButton); msg.addByte(modalWindow.priority ? 0x01 : 0x00); writeToOutputBuffer(msg); } +void ProtocolGame::sendSessionEnd(SessionEndTypes_t reason) +{ + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x18); + output->addByte(reason); + send(output); +} + ////////////// Add common messages void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) { CreatureType_t creatureType = creature->getType(); - const Player* otherPlayer = creature->getPlayer(); + const Player* masterPlayer = nullptr; + uint32_t masterId = 0; + + if (creatureType == CREATURETYPE_MONSTER) { + const Creature* master = creature->getMaster(); + if (master) { + masterPlayer = master->getPlayer(); + if (masterPlayer) { + masterId = master->getID(); + creatureType = CREATURETYPE_SUMMON_OWN; + } + } + } if (known) { msg.add(0x62); @@ -2933,20 +3538,27 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.add(0x61); msg.add(remove); msg.add(creature->getID()); - msg.addByte(creatureType); - msg.addString(creature->getName()); + msg.addByte(creature->isHealthHidden() ? CREATURETYPE_HIDDEN : creatureType); + + if (creatureType == CREATURETYPE_SUMMON_OWN) { + msg.add(masterId); + } + + msg.addString(creature->isHealthHidden() ? "" : creature->getName()); } if (creature->isHealthHidden()) { msg.addByte(0x00); } else { - msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); + msg.addByte(std::ceil( + (static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); } msg.addByte(creature->getDirection()); if (!creature->isInGhostMode() && !creature->isInvisible()) { - AddOutfit(msg, creature->getCurrentOutfit()); + const Outfit_t& outfit = creature->getCurrentOutfit(); + AddOutfit(msg, outfit); } else { static Outfit_t outfit; AddOutfit(msg, outfit); @@ -2958,6 +3570,15 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.add(creature->getStepSpeed() / 2); + msg.addByte(0x00); // creature debuffs, to do + /* + if (icon != CREATUREICON_NONE) { + msg.addByte(icon); + msg.addByte(1); + msg.add(0); + } + */ + msg.addByte(player->getSkullClient(creature)); msg.addByte(player->getPartyShield(otherPlayer)); @@ -2965,29 +3586,20 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.addByte(player->getGuildEmblem(otherPlayer)); } - if (creatureType == CREATURETYPE_MONSTER) { - const Creature* master = creature->getMaster(); - if (master) { - const Player* masterPlayer = master->getPlayer(); - if (masterPlayer) { - if (masterPlayer == player) { - creatureType = CREATURETYPE_SUMMON_OWN; - } else { - creatureType = CREATURETYPE_SUMMON_OTHERS; - } - } - } + // Creature type and summon emblem + msg.addByte(creature->isHealthHidden() ? CREATURETYPE_HIDDEN : creatureType); + if (creatureType == CREATURETYPE_SUMMON_OWN) { + msg.add(masterId); + } + + // Player vocation info + if (creatureType == CREATURETYPE_PLAYER) { + msg.addByte(otherPlayer ? otherPlayer->getVocation()->getClientId() : 0x00); } - msg.addByte(creatureType); // Type (for summons) msg.addByte(creature->getSpeechBubble()); msg.addByte(0xFF); // MARK_UNMARKED - - if (otherPlayer) { - msg.add(otherPlayer->getHelpers()); - } else { - msg.add(0x00); - } + msg.addByte(0x00); // inspection type (bool?) msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); } @@ -2999,62 +3611,77 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg) msg.add(std::min(player->getHealth(), std::numeric_limits::max())); msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); - msg.add(player->getFreeCapacity()); - msg.add(player->getCapacity()); - + msg.add(player->hasFlag(PlayerFlag_HasInfiniteCapacity) ? 1000000 : player->getFreeCapacity()); msg.add(player->getExperience()); msg.add(player->getLevel()); msg.addByte(player->getLevelPercent()); msg.add(100); // base xp gain rate - msg.add(0); // xp voucher - msg.add(0); // low level bonus - msg.add(0); // xp boost + msg.add(0); // low level bonus + msg.add(0); // xp boost msg.add(100); // stamina multiplier (100 = x1.0) msg.add(std::min(player->getMana(), std::numeric_limits::max())); msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); - msg.addByte(std::min(player->getMagicLevel(), std::numeric_limits::max())); - msg.addByte(std::min(player->getBaseMagicLevel(), std::numeric_limits::max())); - msg.addByte(player->getMagicLevelPercent()); - msg.addByte(player->getSoul()); - msg.add(player->getStaminaMinutes()); - msg.add(player->getBaseSpeed() / 2); - Condition* condition = player->getCondition(CONDITION_REGENERATION); + Condition* condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); msg.add(condition ? condition->getTicks() / 1000 : 0x00); msg.add(player->getOfflineTrainingTime() / 60 / 1000); msg.add(0); // xp boost time (seconds) - msg.addByte(0); // enables exp boost in the store + msg.addByte(0x00); // enables exp boost in the store + + msg.add(0); // remaining mana shield + msg.add(0); // total mana shield } void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) { msg.addByte(0xA1); + msg.add(player->getMagicLevel()); + msg.add(player->getBaseMagicLevel()); + msg.add(player->getBaseMagicLevel()); // base + loyalty bonus(?) + msg.add(player->getMagicLevelPercent() * 100); for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { msg.add(std::min(player->getSkillLevel(i), std::numeric_limits::max())); msg.add(player->getBaseSkill(i)); - msg.addByte(player->getSkillPercent(i)); + msg.add(player->getBaseSkill(i)); // base + loyalty bonus(?) + msg.add(player->getSkillPercent(i) * 100); } for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - msg.add(std::min(100, player->varSpecialSkills[i])); - msg.add(0); + msg.add(std::min(100, player->varSpecialSkills[i])); // base + bonus special skill + msg.add(0); // base special skill } + + // fatal, dodge, momentum + msg.add(0); + msg.add(0); + + msg.add(0); + msg.add(0); + + msg.add(0); + msg.add(0); + + // to do: bonus cap + msg.add(player->hasFlag(PlayerFlag_HasInfiniteCapacity) ? 1000000 + : player->getCapacity()); // base + bonus capacity + msg.add(player->hasFlag(PlayerFlag_HasInfiniteCapacity) ? 1000000 + : player->getCapacity()); // base capacity } void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) { + // outfit msg.add(outfit.lookType); - if (outfit.lookType != 0) { msg.addByte(outfit.lookHead); msg.addByte(outfit.lookBody); @@ -3065,7 +3692,14 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) msg.addItemId(outfit.lookTypeEx); } + // mount msg.add(outfit.lookMount); + if (outfit.lookMount != 0) { + msg.addByte(outfit.lookMountHead); + msg.addByte(outfit.lookMountBody); + msg.addByte(outfit.lookMountLegs); + msg.addByte(outfit.lookMountFeet); + } } void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo) @@ -3085,7 +3719,7 @@ void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creatur msg.addByte(lightInfo.color); } -//tile +// tile void ProtocolGame::RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos) { if (stackpos >= 10) { @@ -3097,7 +3731,8 @@ void ProtocolGame::RemoveTileThing(NetworkMessage& msg, const Position& pos, uin msg.addByte(stackpos); } -void ProtocolGame::RemoveTileCreature(NetworkMessage& msg, const Creature* creature, const Position& pos, uint32_t stackpos) +void ProtocolGame::RemoveTileCreature(NetworkMessage& msg, const Creature* creature, const Position& pos, + uint32_t stackpos) { if (stackpos < 10) { RemoveTileThing(msg, pos, stackpos); @@ -3109,34 +3744,36 @@ void ProtocolGame::RemoveTileCreature(NetworkMessage& msg, const Creature* creat msg.add(creature->getID()); } -void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos) +void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, + const Position& oldPos) { if (creature != player) { return; } - //floor change up + // floor change up msg.addByte(0xBE); - //going to surface + // going to surface if (newPos.z == 7) { int32_t skip = -1; - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 5, 18, 14, 3, skip); //(floor 7 and 6 already set) - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 4, 18, 14, 4, skip); - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 3, 18, 14, 5, skip); - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 2, 18, 14, 6, skip); - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 1, 18, 14, 7, skip); - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 0, 18, 14, 8, skip); + // floor 7 and 6 already set + for (int i = 5; i >= 0; --i) { + GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, i, + (Map::maxClientViewportX * 2) + 2, (Map::maxClientViewportY * 2) + 2, 8 - i, skip); + } if (skip >= 0) { msg.addByte(skip); msg.addByte(0xFF); } } - //underground, going one floor up (still underground) + // underground, going one floor up (still underground) else if (newPos.z > 7) { int32_t skip = -1; - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, oldPos.getZ() - 3, 18, 14, 3, skip); + GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, + oldPos.getZ() - 3, (Map::maxClientViewportX * 2) + 2, (Map::maxClientViewportY * 2) + 2, 3, + skip); if (skip >= 0) { msg.addByte(skip); @@ -3144,42 +3781,47 @@ void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, } } - //moving up a floor up makes us out of sync - //west + // moving up a floor up makes us out of sync + // west msg.addByte(0x68); - GetMapDescription(oldPos.x - 8, oldPos.y - 5, newPos.z, 1, 14, msg); + GetMapDescription(oldPos.x - Map::maxClientViewportX, oldPos.y - (Map::maxClientViewportY - 1), newPos.z, 1, + (Map::maxClientViewportY * 2) + 2, msg); - //north + // north msg.addByte(0x65); - GetMapDescription(oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 1, msg); + GetMapDescription(oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z, + (Map::maxClientViewportX * 2) + 2, 1, msg); } -void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos) +void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, + const Position& oldPos) { if (creature != player) { return; } - //floor change down + // floor change down msg.addByte(0xBF); - //going from surface to underground + // going from surface to underground if (newPos.z == 8) { int32_t skip = -1; - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 14, -1, skip); - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 1, 18, 14, -2, skip); - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); - + for (int i = 0; i < 3; ++i) { + GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, + newPos.z + i, (Map::maxClientViewportX * 2) + 2, (Map::maxClientViewportY * 2) + 2, + -i - 1, skip); + } if (skip >= 0) { msg.addByte(skip); msg.addByte(0xFF); } } - //going further down + // going further down else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 14) { int32_t skip = -1; - GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + GetFloorDescription(msg, oldPos.x - Map::maxClientViewportX, oldPos.y - Map::maxClientViewportY, newPos.z + 2, + (Map::maxClientViewportX * 2) + 2, (Map::maxClientViewportY * 2) + 2, -3, skip); if (skip >= 0) { msg.addByte(skip); @@ -3187,14 +3829,16 @@ void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creatur } } - //moving down a floor makes us out of sync - //east + // moving down a floor makes us out of sync + // east msg.addByte(0x66); - GetMapDescription(oldPos.x + 9, oldPos.y - 7, newPos.z, 1, 14, msg); + GetMapDescription(oldPos.x + (Map::maxClientViewportX + 1), oldPos.y - (Map::maxClientViewportY + 1), newPos.z, 1, + (Map::maxClientViewportY * 2) + 2, msg); - //south + // south msg.addByte(0x67); - GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg); + GetMapDescription(oldPos.x - Map::maxClientViewportX, oldPos.y + (Map::maxClientViewportY + 1), newPos.z, + (Map::maxClientViewportX * 2) + 2, 1, msg); } void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item) @@ -3210,15 +3854,17 @@ void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item) msg.addString(item.realName); msg.add(it.weight); - msg.add(item.buyPrice); - msg.add(item.sellPrice); + msg.add(std::max(item.buyPrice, 0)); + msg.add(std::max(item.sellPrice, 0)); } void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) { uint8_t opcode = msg.getByte(); - const std::string& buffer = msg.getString(); + std::string buffer = msg.getString(); // process additional opcodes via lua script event - addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer); + addGameTask([=, playerID = player->getID(), buffer = std::move(buffer)]() { + g_game.parsePlayerExtendedOpcode(playerID, opcode, buffer); + }); } diff --git a/src/protocolgame.h b/src/protocolgame.h index 71c7f2de79..c57e523792 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -1,39 +1,31 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87 -#define FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_PROTOCOLGAME_H +#define FS_PROTOCOLGAME_H -#include "protocol.h" #include "chat.h" #include "creature.h" +#include "protocol.h" #include "tasks.h" +class Container; +class Game; class NetworkMessage; class Player; -class Game; -class House; -class Container; -class Tile; -class Connection; -class Quest; class ProtocolGame; +class Quest; +class Tile; +class TrackedQuest; + +enum SessionEndTypes_t : uint8_t +{ + SESSION_END_LOGOUT = 0, + SESSION_END_UNKNOWN = 1, // unknown, no difference from logout + SESSION_END_FORCECLOSE = 2, + SESSION_END_UNKNOWN2 = 3, // unknown, no difference from logout +}; + using ProtocolGame_ptr = std::shared_ptr; extern Game g_game; @@ -44,7 +36,8 @@ struct TextMessage std::string text; Position position; uint16_t channelId; - struct { + struct + { int32_t value = 0; TextColor_t color; } primary, secondary; @@ -55,279 +48,306 @@ struct TextMessage class ProtocolGame final : public Protocol { - public: - // static protocol information - enum {server_sends_first = true}; - enum {protocol_identifier = 0}; // Not required as we send first - enum {use_checksum = true}; - static const char* protocol_name() { - return "gameworld protocol"; - } - - explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {} - - void login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem); - void logout(bool displayEffect, bool forced); - - uint16_t getVersion() const { - return version; - } - - private: - ProtocolGame_ptr getThis() { - return std::static_pointer_cast(shared_from_this()); - } - void connect(uint32_t playerId, OperatingSystem_t operatingSystem); - void disconnectClient(const std::string& message) const; - void writeToOutputBuffer(const NetworkMessage& msg); - - void release() override; - - void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); - - bool canSee(int32_t x, int32_t y, int32_t z) const; - bool canSee(const Creature*) const; - bool canSee(const Position& pos) const; - - // we have all the parse methods - void parsePacket(NetworkMessage& msg) override; - void onRecvFirstMessage(NetworkMessage& msg) override; - void onConnect() override; - - //Parse methods - void parseAutoWalk(NetworkMessage& msg); - void parseSetOutfit(NetworkMessage& msg); - void parseSay(NetworkMessage& msg); - void parseLookAt(NetworkMessage& msg); - void parseLookInBattleList(NetworkMessage& msg); - void parseFightModes(NetworkMessage& msg); - void parseAttack(NetworkMessage& msg); - void parseFollow(NetworkMessage& msg); - void parseEquipObject(NetworkMessage& msg); - - void parseBugReport(NetworkMessage& msg); - void parseDebugAssert(NetworkMessage& msg); - void parseRuleViolationReport(NetworkMessage& msg); - - void parseThrow(NetworkMessage& msg); - void parseUseItemEx(NetworkMessage& msg); - void parseUseWithCreature(NetworkMessage& msg); - void parseUseItem(NetworkMessage& msg); - void parseCloseContainer(NetworkMessage& msg); - void parseUpArrowContainer(NetworkMessage& msg); - void parseUpdateContainer(NetworkMessage& msg); - void parseTextWindow(NetworkMessage& msg); - void parseHouseWindow(NetworkMessage& msg); - void parseWrapItem(NetworkMessage& msg); - - void parseLookInShop(NetworkMessage& msg); - void parsePlayerPurchase(NetworkMessage& msg); - void parsePlayerSale(NetworkMessage& msg); - - void parseQuestLine(NetworkMessage& msg); - - void parseInviteToParty(NetworkMessage& msg); - void parseJoinParty(NetworkMessage& msg); - void parseRevokePartyInvite(NetworkMessage& msg); - void parsePassPartyLeadership(NetworkMessage& msg); - void parseEnableSharedPartyExperience(NetworkMessage& msg); - - void parseToggleMount(NetworkMessage& msg); - - void parseModalWindowAnswer(NetworkMessage& msg); - - void parseBrowseField(NetworkMessage& msg); - void parseSeekInContainer(NetworkMessage& msg); - - //trade methods - void parseRequestTrade(NetworkMessage& msg); - void parseLookInTrade(NetworkMessage& msg); - - //market methods - void parseMarketLeave(); - void parseMarketBrowse(NetworkMessage& msg); - void parseMarketCreateOffer(NetworkMessage& msg); - void parseMarketCancelOffer(NetworkMessage& msg); - void parseMarketAcceptOffer(NetworkMessage& msg); - - //VIP methods - void parseAddVip(NetworkMessage& msg); - void parseRemoveVip(NetworkMessage& msg); - void parseEditVip(NetworkMessage& msg); - - void parseRotateItem(NetworkMessage& msg); - - //Channel tabs - void parseChannelInvite(NetworkMessage& msg); - void parseChannelExclude(NetworkMessage& msg); - void parseOpenChannel(NetworkMessage& msg); - void parseOpenPrivateChannel(NetworkMessage& msg); - void parseCloseChannel(NetworkMessage& msg); - - //Send functions - void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel); - void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent); - void sendClosePrivate(uint16_t channelId); - void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); - void sendChannelsDialog(); - void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers); - void sendOpenPrivateChannel(const std::string& receiver); - void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); - void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); - void sendIcons(uint16_t icons); - void sendFYIBox(const std::string& message); - - void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); - void sendMagicEffect(const Position& pos, uint8_t type); - void sendCreatureHealth(const Creature* creature); - void sendSkills(); - void sendPing(); - void sendPingBack(); - void sendCreatureTurn(const Creature* creature, uint32_t stackPos); - void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr); - - void sendQuestLog(); - void sendQuestLine(const Quest* quest); - - void sendCancelWalk(); - void sendChangeSpeed(const Creature* creature, uint32_t speed); - void sendCancelTarget(); - void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); - void sendStats(); - void sendBasicData(); - void sendTextMessage(const TextMessage& message); - void sendReLoginWindow(uint8_t unfairFightReduction); - - void sendTutorial(uint8_t tutorialId); - void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc); - - void sendCreatureWalkthrough(const Creature* creature, bool walkthrough); - void sendCreatureShield(const Creature* creature); - void sendCreatureSkull(const Creature* creature); - void sendCreatureType(uint32_t creatureId, uint8_t creatureType); - void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers); - - void sendShop(Npc* npc, const ShopInfoList& itemList); - void sendCloseShop(); - void sendSaleItemList(const std::list& shop); - void sendMarketEnter(uint32_t depotId); - void sendMarketLeave(); - void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); - void sendMarketAcceptOffer(const MarketOfferEx& offer); - void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); - void sendMarketCancelOffer(const MarketOfferEx& offer); - void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers); - void sendMarketDetail(uint16_t itemId); - void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack); - void sendCloseTrade(); - - void sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite); - void sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text); - void sendHouseWindow(uint32_t windowTextId, const std::string& text); - void sendOutfitWindow(); - - void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); - void sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status); - void sendVIPEntries(); - - void sendPendingStateEntered(); - void sendEnterWorld(); - - void sendFightModes(); - - void sendCreatureLight(const Creature* creature); - void sendWorldLight(LightInfo lightInfo); - - void sendCreatureSquare(const Creature* creature, SquareColor_t color); - - void sendSpellCooldown(uint8_t spellId, uint32_t time); - void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time); - - //tiles - void sendMapDescription(const Position& pos); - - void sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item); - void sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item); - void sendRemoveTileThing(const Position& pos, uint32_t stackpos); - void sendRemoveTileCreature(const Creature* creature, const Position& pos, uint32_t stackpos); - void sendUpdateTile(const Tile* tile, const Position& pos); - - void sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin); - void sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, - const Position& oldPos, int32_t oldStackPos, bool teleport); - - //containers - void sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item); - void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); - void sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem); - - void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); - void sendCloseContainer(uint8_t cid); - - //inventory - void sendInventoryItem(slots_t slot, const Item* item); - void sendItems(); - - //messages - void sendModalWindow(const ModalWindow& modalWindow); - - //Help functions - - // translate a tile to client-readable format - void GetTileDescription(const Tile* tile, NetworkMessage& msg); - - // translate a floor to client-readable format - void GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, - int32_t width, int32_t height, int32_t offset, int32_t& skip); - - // translate a map area to client-readable format - void GetMapDescription(int32_t x, int32_t y, int32_t z, - int32_t width, int32_t height, NetworkMessage& msg); - - void AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove); - void AddPlayerStats(NetworkMessage& msg); - void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); - void AddPlayerSkills(NetworkMessage& msg); - void AddWorldLight(NetworkMessage& msg, LightInfo lightInfo); - void AddCreatureLight(NetworkMessage& msg, const Creature* creature); - - //tiles - static void RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos); - static void RemoveTileCreature(NetworkMessage& msg, const Creature* creature, const Position& pos, uint32_t stackpos); - - void MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); - void MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); +public: + // static protocol information + enum + { + server_sends_first = true + }; + enum + { + protocol_identifier = 0 + }; // Not required as we send first + enum + { + use_checksum = true + }; + static const char* protocol_name() { return "gameworld protocol"; } + + explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {} + + void login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem); + void logout(bool displayEffect, bool forced); + + uint16_t getVersion() const { return version; } + +private: + ProtocolGame_ptr getThis() { return std::static_pointer_cast(shared_from_this()); } + void connect(uint32_t playerId, OperatingSystem_t operatingSystem); + void disconnectClient(const std::string& message) const; + void writeToOutputBuffer(const NetworkMessage& msg); + + void release() override; + + void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); + + bool canSee(int32_t x, int32_t y, int32_t z) const; + bool canSee(const Creature*) const; + bool canSee(const Position& pos) const; + + // we have all the parse methods + void parsePacket(NetworkMessage& msg) override; + void onRecvFirstMessage(NetworkMessage& msg) override; + void onConnect() override; + + // Parse methods + void parseAutoWalk(NetworkMessage& msg); + void parseSetOutfit(NetworkMessage& msg); + void parseEditPodiumRequest(NetworkMessage& msg); + void parseSay(NetworkMessage& msg); + void parseLookAt(NetworkMessage& msg); + void parseLookInBattleList(NetworkMessage& msg); + void parseFightModes(NetworkMessage& msg); + void parseAttack(NetworkMessage& msg); + void parseFollow(NetworkMessage& msg); + void parseEquipObject(NetworkMessage& msg); + + void parseBugReport(NetworkMessage& msg); + void parseDebugAssert(NetworkMessage& msg); + void parseRuleViolationReport(NetworkMessage& msg); + + void parseThrow(NetworkMessage& msg); + void parseUseItemEx(NetworkMessage& msg); + void parseUseWithCreature(NetworkMessage& msg); + void parseUseItem(NetworkMessage& msg); + void parseCloseContainer(NetworkMessage& msg); + void parseUpArrowContainer(NetworkMessage& msg); + void parseUpdateContainer(NetworkMessage& msg); + void parseTextWindow(NetworkMessage& msg); + void parseHouseWindow(NetworkMessage& msg); + void parseWrapItem(NetworkMessage& msg); + + void parseLookInShop(NetworkMessage& msg); + void parsePlayerPurchase(NetworkMessage& msg); + void parsePlayerSale(NetworkMessage& msg); + + void parseQuestLine(NetworkMessage& msg); + void parseQuestTracker(NetworkMessage& msg); + + void parseInviteToParty(NetworkMessage& msg); + void parseJoinParty(NetworkMessage& msg); + void parseRevokePartyInvite(NetworkMessage& msg); + void parsePassPartyLeadership(NetworkMessage& msg); + void parseEnableSharedPartyExperience(NetworkMessage& msg); + + void parseToggleMount(NetworkMessage& msg); + + void parseModalWindowAnswer(NetworkMessage& msg); + + void parseBrowseField(NetworkMessage& msg); + void parseSeekInContainer(NetworkMessage& msg); + + // trade methods + void parseRequestTrade(NetworkMessage& msg); + void parseLookInTrade(NetworkMessage& msg); + + // market methods + void parseMarketLeave(); + void parseMarketBrowse(NetworkMessage& msg); + void parseMarketCreateOffer(NetworkMessage& msg); + void parseMarketCancelOffer(NetworkMessage& msg); + void parseMarketAcceptOffer(NetworkMessage& msg); + + // VIP methods + void parseAddVip(NetworkMessage& msg); + void parseRemoveVip(NetworkMessage& msg); + void parseEditVip(NetworkMessage& msg); + + void parseRotateItem(NetworkMessage& msg); + + // Channel tabs + void parseChannelInvite(NetworkMessage& msg); + void parseChannelExclude(NetworkMessage& msg); + void parseOpenChannel(NetworkMessage& msg); + void parseOpenPrivateChannel(NetworkMessage& msg); + void parseCloseChannel(NetworkMessage& msg); + + // Send functions + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel); + void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent); + void sendClosePrivate(uint16_t channelId); + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); + void sendChannelsDialog(); + void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, + const InvitedMap* invitedUsers); + void sendOpenPrivateChannel(const std::string& receiver); + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); + void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); + void sendIcons(uint32_t icons); + void sendFYIBox(const std::string& message); + + void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); + void sendMagicEffect(const Position& pos, uint8_t type); + void sendCreatureHealth(const Creature* creature); + void sendSkills(); + void sendPing(); + void sendPingBack(); + void sendCreatureTurn(const Creature* creature, uint32_t stackPos); + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, + const Position* pos = nullptr); + + void sendQuestLog(); + void sendQuestLine(const Quest* quest); + void sendQuestTracker(); + void sendUpdateQuestTracker(const TrackedQuest& trackedQuest); + + void sendCancelWalk(); + void sendChangeSpeed(const Creature* creature, uint32_t speed); + void sendCancelTarget(); + void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); + void sendStats(); + void sendExperienceTracker(int64_t rawExp, int64_t finalExp); + void sendClientFeatures(); + void sendBasicData(); + void sendTextMessage(const TextMessage& message); + void sendReLoginWindow(uint8_t unfairFightReduction); + + void sendTutorial(uint8_t tutorialId); + void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc); + + void sendCreatureWalkthrough(const Creature* creature, bool walkthrough); + void sendCreatureShield(const Creature* creature); + void sendCreatureSkull(const Creature* creature); + + void sendShop(Npc* npc, const ShopInfoList& itemList); + void sendCloseShop(); + void sendSaleItemList(const std::list& shop); + void sendResourceBalance(const ResourceTypes_t resourceType, uint64_t amount); + void sendStoreBalance(); + void sendMarketEnter(); + void sendMarketLeave(); + void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); + void sendMarketAcceptOffer(const MarketOfferEx& offer); + void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); + void sendMarketCancelOffer(const MarketOfferEx& offer); + void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers); + void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack); + void sendCloseTrade(); + + void sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite); + void sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text); + void sendHouseWindow(uint32_t windowTextId, const std::string& text); + void sendOutfitWindow(); + + void sendPodiumWindow(const Item* item); + + void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); + void sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, + VipStatus_t status); + void sendVIPEntries(); + + void sendItemClasses(); + + void sendPendingStateEntered(); + void sendEnterWorld(); + + void sendFightModes(); + + void sendCreatureLight(const Creature* creature); + void sendWorldLight(LightInfo lightInfo); + void sendWorldTime(); + + void sendCreatureSquare(const Creature* creature, SquareColor_t color); + + void sendSpellCooldown(uint8_t spellId, uint32_t time); + void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time); + void sendUseItemCooldown(uint32_t time); + void sendSupplyUsed(const uint16_t clientId); + + // tiles + void sendMapDescription(const Position& pos); + + void sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item); + void sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item); + void sendRemoveTileThing(const Position& pos, uint32_t stackpos); + void sendUpdateTileCreature(const Position& pos, uint32_t stackpos, const Creature* creature); + void sendRemoveTileCreature(const Creature* creature, const Position& pos, uint32_t stackpos); + void sendUpdateTile(const Tile* tile, const Position& pos); + + void sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, + MagicEffectClasses magicEffect = CONST_ME_NONE); + void sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, + int32_t oldStackPos, bool teleport); + + // containers + void sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem); + + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); + void sendEmptyContainer(uint8_t cid); + void sendCloseContainer(uint8_t cid); + + // inventory + void sendInventoryItem(slots_t slot, const Item* item); + void sendItems(); + + // messages + void sendModalWindow(const ModalWindow& modalWindow); + + // session end + void sendSessionEnd(SessionEndTypes_t reason); + + // Help functions + + // translate a tile to client-readable format + void GetTileDescription(const Tile* tile, NetworkMessage& msg); + + // translate a floor to client-readable format + void GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, + int32_t offset, int32_t& skip); + + // translate a map area to client-readable format + void GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg); + + void AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove); + void AddPlayerStats(NetworkMessage& msg); + void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); + void AddPlayerSkills(NetworkMessage& msg); + void AddWorldLight(NetworkMessage& msg, LightInfo lightInfo); + void AddCreatureLight(NetworkMessage& msg, const Creature* creature); + + // tiles + static void RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos); + static void RemoveTileCreature(NetworkMessage& msg, const Creature* creature, const Position& pos, + uint32_t stackpos); + + void MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); + void MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, + const Position& oldPos); - //shop - void AddShopItem(NetworkMessage& msg, const ShopInfo& item); + // shop + void AddShopItem(NetworkMessage& msg, const ShopInfo& item); - //otclient - void parseExtendedOpcode(NetworkMessage& msg); + // otclient + void parseExtendedOpcode(NetworkMessage& msg); - friend class Player; + friend class Player; - // Helpers so we don't need to bind every time - template - void addGameTask(Callable&& function, Args&&... args) { - g_dispatcher.addTask(createTask(std::bind(std::forward(function), &g_game, std::forward(args)...))); - } + // Helpers so we don't need to bind every time + template + void addGameTask(Callable&& function) + { + g_dispatcher.addTask(createTask(std::forward(function))); + } - template - void addGameTaskTimed(uint32_t delay, Callable&& function, Args&&... args) { - g_dispatcher.addTask(createTask(delay, std::bind(std::forward(function), &g_game, std::forward(args)...))); - } + template + void addGameTaskTimed(uint32_t delay, Callable&& function) + { + g_dispatcher.addTask(createTask(delay, std::forward(function))); + } - std::unordered_set knownCreatureSet; - Player* player = nullptr; + std::unordered_set knownCreatureSet; + Player* player = nullptr; - uint32_t eventConnect = 0; - uint32_t challengeTimestamp = 0; - uint16_t version = CLIENT_VERSION_MIN; + uint32_t eventConnect = 0; + uint32_t challengeTimestamp = 0; + uint16_t version = CLIENT_VERSION_MIN; - uint8_t challengeRandom = 0; + uint8_t challengeRandom = 0; - bool debugAssertSent = false; - bool acceptPackets = false; + bool debugAssertSent = false; + bool acceptPackets = false; }; -#endif +#endif // FS_PROTOCOLGAME_H diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp index 309dce5c89..760812dac9 100644 --- a/src/protocollogin.cpp +++ b/src/protocollogin.cpp @@ -1,35 +1,16 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "protocollogin.h" -#include "outputmessage.h" -#include "tasks.h" - -#include "configmanager.h" -#include "iologindata.h" #include "ban.h" +#include "configmanager.h" #include "game.h" - -#include +#include "iologindata.h" +#include "outputmessage.h" +#include "tasks.h" extern ConfigManager g_config; extern Game g_game; @@ -45,7 +26,8 @@ void ProtocolLogin::disconnectClient(const std::string& message, uint16_t versio disconnect(); } -void ProtocolLogin::getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version) +void ProtocolLogin::getCharacterList(const std::string& accountName, const std::string& password, + const std::string& token, uint16_t version) { Account account; if (!IOLoginData::loginserverAuthentication(accountName, password, account)) { @@ -57,7 +39,9 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: auto output = OutputMessagePool::getOutputMessage(); if (!account.key.empty()) { - if (token.empty() || !(token == generateToken(account.key, ticks) || token == generateToken(account.key, ticks - 1) || token == generateToken(account.key, ticks + 1))) { + if (token.empty() || + !(token == generateToken(account.key, ticks) || token == generateToken(account.key, ticks - 1) || + token == generateToken(account.key, ticks + 1))) { output->addByte(0x0D); output->addByte(0); send(output); @@ -68,18 +52,11 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: output->addByte(0); } - const std::string& motd = g_config.getString(ConfigManager::MOTD); - if (!motd.empty()) { - //Add MOTD - output->addByte(0x14); - output->addString(fmt::format("{:d}\n{:s}", g_game.getMotdNum(), motd)); - } - - //Add session key + // Add session key output->addByte(0x28); output->addString(accountName + "\n" + password + "\n" + token + "\n" + std::to_string(ticks)); - //Add char list + // Add char list output->addByte(0x64); uint8_t size = std::min(std::numeric_limits::max(), account.characters.size()); @@ -114,7 +91,7 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: output->addString(character); } - //Add premium days + // Add premium days output->addByte(0); if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { output->addByte(1); @@ -129,6 +106,7 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: disconnect(); } +// Character list request void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) { if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { @@ -139,6 +117,15 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) msg.skipBytes(2); // client OS uint16_t version = msg.get(); + if (version <= 822) { + setChecksumMode(CHECKSUM_DISABLED); + } + + if (version <= 760) { + disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR), version); + return; + } + if (version >= 971) { msg.skipBytes(17); } else { @@ -151,11 +138,6 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) * 1 byte: 0 */ - if (version <= 760) { - disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR), version); - return; - } - if (!Protocol::RSA_decrypt(msg)) { disconnect(); return; @@ -195,7 +177,9 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) banInfo.reason = "(none)"; } - disconnectClient(fmt::format("Your IP has been banned until {:s} by {:s}.\n\nReason specified:\n{:s}", formatDateShort(banInfo.expiresAt), banInfo.bannedBy, banInfo.reason), version); + disconnectClient(fmt::format("Your IP has been banned until {:s} by {:s}.\n\nReason specified:\n{:s}", + formatDateShort(banInfo.expiresAt), banInfo.bannedBy, banInfo.reason), + version); return; } @@ -220,6 +204,9 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) std::string authToken = msg.getString(); - auto thisPtr = std::static_pointer_cast(shared_from_this()); - g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version))); + g_dispatcher.addTask(createTask([=, thisPtr = std::static_pointer_cast(shared_from_this()), + accountName = std::move(accountName), password = std::move(password), + authToken = std::move(authToken)]() { + thisPtr->getCharacterList(accountName, password, authToken, version); + })); } diff --git a/src/protocollogin.h b/src/protocollogin.h index 978144656d..c3d2957b91 100644 --- a/src/protocollogin.h +++ b/src/protocollogin.h @@ -1,49 +1,40 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D -#define FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_PROTOCOLLOGIN_H +#define FS_PROTOCOLLOGIN_H #include "protocol.h" class NetworkMessage; -class OutputMessage; class ProtocolLogin : public Protocol { - public: - // static protocol information - enum {server_sends_first = false}; - enum {protocol_identifier = 0x01}; - enum {use_checksum = true}; - static const char* protocol_name() { - return "login protocol"; - } - - explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {} - - void onRecvFirstMessage(NetworkMessage& msg) override; - - private: - void disconnectClient(const std::string& message, uint16_t version); - - void getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version); +public: + // static protocol information + enum + { + server_sends_first = false + }; + enum + { + protocol_identifier = 0x01 + }; + enum + { + use_checksum = true + }; + static const char* protocol_name() { return "login protocol"; } + + explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) override; + +private: + void disconnectClient(const std::string& message, uint16_t version); + + void getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, + uint16_t version); }; -#endif +#endif // FS_PROTOCOLLOGIN_H diff --git a/src/protocolold.cpp b/src/protocolold.cpp index 49900b2d7d..cb5d506a27 100644 --- a/src/protocolold.cpp +++ b/src/protocolold.cpp @@ -1,30 +1,12 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "protocolold.h" -#include "outputmessage.h" #include "game.h" - -#include +#include "outputmessage.h" extern Game g_game; @@ -45,7 +27,7 @@ void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg) return; } - /*uint16_t clientOS =*/ msg.get(); + /*uint16_t clientOS =*/msg.get(); uint16_t version = msg.get(); msg.skipBytes(12); @@ -68,7 +50,7 @@ void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg) setXTEAKey(std::move(key)); if (version <= 822) { - disableChecksum(); + setChecksumMode(CHECKSUM_DISABLED); } disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR)); diff --git a/src/protocolold.h b/src/protocolold.h index 708e763ff0..7809608ba9 100644 --- a/src/protocolold.h +++ b/src/protocolold.h @@ -1,24 +1,8 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161 -#define FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_PROTOCOLOLD_H +#define FS_PROTOCOLOLD_H #include "protocol.h" @@ -26,21 +10,28 @@ class NetworkMessage; class ProtocolOld final : public Protocol { - public: - // static protocol information - enum {server_sends_first = false}; - enum {protocol_identifier = 0x01}; - enum {use_checksum = false}; - static const char* protocol_name() { - return "old login protocol"; - } - - explicit ProtocolOld(Connection_ptr connection) : Protocol(connection) {} - - void onRecvFirstMessage(NetworkMessage& msg) override; - - private: - void disconnectClient(const std::string& message); +public: + // static protocol information + enum + { + server_sends_first = false + }; + enum + { + protocol_identifier = 0x01 + }; + enum + { + use_checksum = false + }; + static const char* protocol_name() { return "old login protocol"; } + + explicit ProtocolOld(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) override; + +private: + void disconnectClient(const std::string& message); }; -#endif +#endif // FS_PROTOCOLOLD_H diff --git a/src/protocolstatus.cpp b/src/protocolstatus.cpp index aba1bb23d8..3bc5b7ced3 100644 --- a/src/protocolstatus.cpp +++ b/src/protocolstatus.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "protocolstatus.h" + #include "configmanager.h" #include "game.h" #include "outputmessage.h" @@ -30,7 +15,8 @@ extern Game g_game; std::map ProtocolStatus::ipConnectMap; const uint64_t ProtocolStatus::start = OTSYS_TIME(); -enum RequestedInfo_t : uint16_t { +enum RequestedInfo_t : uint16_t +{ REQUEST_BASIC_SERVER_INFO = 1 << 0, REQUEST_OWNER_SERVER_INFO = 1 << 1, REQUEST_MISC_SERVER_INFO = 1 << 2, @@ -48,7 +34,8 @@ void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg) std::string ipStr = convertIPToString(ip); if (ipStr != g_config.getString(ConfigManager::IP)) { std::map::const_iterator it = ipConnectMap.find(ip); - if (it != ipConnectMap.end() && (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT)))) { + if (it != ipConnectMap.end() && + (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT)))) { disconnect(); return; } @@ -58,25 +45,26 @@ void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg) ipConnectMap[ip] = OTSYS_TIME(); switch (msg.getByte()) { - //XML info protocol + // XML info protocol case 0xFF: { if (msg.getString(4) == "info") { - g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendStatusString, - std::static_pointer_cast(shared_from_this())))); + g_dispatcher.addTask(createTask([thisPtr = std::static_pointer_cast( + shared_from_this())]() { thisPtr->sendStatusString(); })); return; } break; } - //Another ServerInfo protocol + // Another ServerInfo protocol case 0x01: { uint16_t requestedInfo = msg.get(); // only a Byte is necessary, though we could add new info here std::string characterName; if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { characterName = msg.getString(); } - g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast(shared_from_this()), - requestedInfo, characterName))); + g_dispatcher.addTask(createTask( + [=, thisPtr = std::static_pointer_cast(shared_from_this()), + characterName = std::move(characterName)]() { thisPtr->sendInfo(requestedInfo, characterName); })); return; } @@ -144,7 +132,7 @@ void ProtocolStatus::sendStatusString() map.append_attribute("height") = std::to_string(mapHeight).c_str(); pugi::xml_node motd = tsqp.append_child("motd"); - motd.text() = g_config.getString(ConfigManager::MOTD).c_str(); + motd.text() = "N/A"; std::ostringstream ss; doc.save(ss, "", pugi::format_raw); @@ -174,7 +162,7 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& charact if (requestedInfo & REQUEST_MISC_SERVER_INFO) { output->addByte(0x12); - output->addString(g_config.getString(ConfigManager::MOTD)); + output->addString("N/A"); // MOTD output->addString(g_config.getString(ConfigManager::LOCATION)); output->addString(g_config.getString(ConfigManager::URL)); output->add((OTSYS_TIME() - ProtocolStatus::start) / 1000); @@ -210,7 +198,7 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& charact if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { output->addByte(0x22); // players info - online status info of a player - if (g_game.getPlayerByName(characterName) != nullptr) { + if (g_game.getPlayerByName(characterName)) { output->addByte(0x01); } else { output->addByte(0x00); diff --git a/src/protocolstatus.h b/src/protocolstatus.h index 5df99e49e9..26df294e97 100644 --- a/src/protocolstatus.h +++ b/src/protocolstatus.h @@ -1,50 +1,42 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4 -#define FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4 - -#include "networkmessage.h" -#include "protocol.h" - -class ProtocolStatus final : public Protocol -{ - public: - // static protocol information - enum {server_sends_first = false}; - enum {protocol_identifier = 0xFF}; - enum {use_checksum = false}; - static const char* protocol_name() { - return "status protocol"; - } +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. - explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {} +#ifndef FS_PROTOCOLSTATUS_H +#define FS_PROTOCOLSTATUS_H - void onRecvFirstMessage(NetworkMessage& msg) override; - - void sendStatusString(); - void sendInfo(uint16_t requestedInfo, const std::string& characterName); +#include "protocol.h" - static const uint64_t start; +class NetworkMessage; - private: - static std::map ipConnectMap; +class ProtocolStatus final : public Protocol +{ +public: + // static protocol information + enum + { + server_sends_first = false + }; + enum + { + protocol_identifier = 0xFF + }; + enum + { + use_checksum = false + }; + static const char* protocol_name() { return "status protocol"; } + + explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) override; + + void sendStatusString(); + void sendInfo(uint16_t requestedInfo, const std::string& characterName); + + static const uint64_t start; + +private: + static std::map ipConnectMap; }; -#endif +#endif // FS_PROTOCOLSTATUS_H diff --git a/src/pugicast.h b/src/pugicast.h index 9b188ed1ed..a22f89c35a 100644 --- a/src/pugicast.h +++ b/src/pugicast.h @@ -1,39 +1,81 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548 -#define FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548 - -#include +#ifndef FS_PUGICAST_H +#define FS_PUGICAST_H namespace pugi { - template - T cast(const pugi::char_t* str) - { - T value; - try { - value = boost::lexical_cast(str); - } catch (boost::bad_lexical_cast&) { - value = T(); - } - return value; - } + +template +T cast(const char* str); + +template <> +inline float cast(const char* str) +{ + return std::strtof(str, nullptr); +} +template <> +inline double cast(const char* str) +{ + return std::strtod(str, nullptr); +} +template <> +inline long cast(const char* str) +{ + return std::strtol(str, nullptr, 0); } +template <> +inline long long cast(const char* str) +{ + return std::strtoll(str, nullptr, 0); +} +template <> +inline unsigned long cast(const char* str) +{ + return std::strtoul(str, nullptr, 0); +} +template <> +inline unsigned long long cast(const char* str) +{ + return std::strtoull(str, nullptr, 0); +} + +template <> +inline char cast(const char* str) +{ + return static_cast(cast(str)); +} +template <> +inline signed char cast(const char* str) +{ + return static_cast(cast(str)); +} +template <> +inline short cast(const char* str) +{ + return static_cast(cast(str)); +} +template <> +inline int cast(const char* str) +{ + return static_cast(cast(str)); +} +template <> +inline unsigned char cast(const char* str) +{ + return static_cast(cast(str)); +} +template <> +inline unsigned short cast(const char* str) +{ + return static_cast(cast(str)); +} +template <> +inline unsigned int cast(const char* str) +{ + return static_cast(cast(str)); +} + +} // namespace pugi -#endif +#endif // FS_PUGICAST_H diff --git a/src/quests.cpp b/src/quests.cpp index 2e4f552858..eff25f4bd6 100644 --- a/src/quests.cpp +++ b/src/quests.cpp @@ -1,26 +1,11 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "quests.h" +#include "player.h" #include "pugicast.h" std::string Mission::getDescription(Player* player) const @@ -30,8 +15,8 @@ std::string Mission::getDescription(Player* player) const if (!mainDescription.empty()) { std::string desc = mainDescription; - replaceString(desc, "|STATE|", std::to_string(value)); - replaceString(desc, "\\n", "\n"); + boost::algorithm::replace_all(desc, "|STATE|", std::to_string(value)); + boost::algorithm::replace_all(desc, "\\n", "\n"); return desc; } @@ -140,6 +125,29 @@ bool Quest::isStarted(Player* player) const return true; } +const Mission* Quest::getMissionById(uint16_t missionId) const +{ + auto it = std::find_if(missions.cbegin(), missions.cend(), + [missionId](const Mission& mission) { return mission.id == missionId; }); + + return it != missions.cend() ? &*it : nullptr; +} + +bool Quest::isTracking(const uint32_t key, const int32_t value) const +{ + if (startStorageID == key && startStorageValue == value) { + return true; + } + + for (const Mission& mission : missions) { + if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && + value <= mission.getEndStorageValue()) { + return true; + } + } + return false; +} + bool Quests::reload() { quests.clear(); @@ -157,24 +165,20 @@ bool Quests::loadFromXml() uint16_t id = 0; for (auto questNode : doc.child("quests").children()) { - quests.emplace_back( - questNode.attribute("name").as_string(), - ++id, - pugi::cast(questNode.attribute("startstorageid").value()), - pugi::cast(questNode.attribute("startstoragevalue").value()) - ); + quests.emplace_back(questNode.attribute("name").as_string(), ++id, + pugi::cast(questNode.attribute("startstorageid").value()), + pugi::cast(questNode.attribute("startstoragevalue").value())); Quest& quest = quests.back(); + uint16_t missionAutoId = 0; for (auto missionNode : questNode.children()) { std::string mainDescription = missionNode.attribute("description").as_string(); - quest.missions.emplace_back( - missionNode.attribute("name").as_string(), - pugi::cast(missionNode.attribute("storageid").value()), - pugi::cast(missionNode.attribute("startvalue").value()), - pugi::cast(missionNode.attribute("endvalue").value()), - missionNode.attribute("ignoreendvalue").as_bool() - ); + quest.missions.emplace_back(missionNode.attribute("name").as_string(), ++missionAutoId, + pugi::cast(missionNode.attribute("storageid").value()), + pugi::cast(missionNode.attribute("startvalue").value()), + pugi::cast(missionNode.attribute("endvalue").value()), + missionNode.attribute("ignoreendvalue").as_bool()); Mission& mission = quest.missions.back(); if (mainDescription.empty()) { @@ -219,8 +223,10 @@ bool Quests::isQuestStorage(const uint32_t key, const int32_t value, const int32 } for (const Mission& mission : quest.getMissions()) { - if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && value <= mission.getEndStorageValue()) { - return mission.mainDescription.empty() || oldValue < mission.getStartStorageValue() || oldValue > mission.getEndStorageValue(); + if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && + value <= mission.getEndStorageValue()) { + return mission.mainDescription.empty() || oldValue < mission.getStartStorageValue() || + oldValue > mission.getEndStorageValue(); } } } diff --git a/src/quests.h b/src/quests.h index 3ccd180af5..638db42920 100644 --- a/src/quests.h +++ b/src/quests.h @@ -1,119 +1,112 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 -#define FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 - -#include "player.h" -#include "networkmessage.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_QUESTS_H +#define FS_QUESTS_H class Mission; class Quest; +class Player; using MissionsList = std::list; using QuestsList = std::list; class Mission { - public: - Mission(std::string name, int32_t storageID, int32_t startValue, int32_t endValue, bool ignoreEndValue) : - name(std::move(name)), storageID(storageID), startValue(startValue), endValue(endValue), ignoreEndValue(ignoreEndValue) {} - - bool isCompleted(Player* player) const; - bool isStarted(Player* player) const; - std::string getName(Player* player) const; - std::string getDescription(Player* player) const; - - uint32_t getStorageId() const { - return storageID; - } - int32_t getStartStorageValue() const { - return startValue; - } - int32_t getEndStorageValue() const { - return endValue; - } - - std::map descriptions; - std::string mainDescription; - - private: - std::string name; - uint32_t storageID; - int32_t startValue, endValue; - bool ignoreEndValue; +public: + Mission(std::string name, uint16_t id, int32_t storageID, int32_t startValue, int32_t endValue, + bool ignoreEndValue) : + name(std::move(name)), + storageID(storageID), + startValue(startValue), + endValue(endValue), + ignoreEndValue(ignoreEndValue), + id(id) + {} + + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + std::string getName(Player* player) const; + std::string getDescription(Player* player) const; + uint16_t getID() const { return id; } + uint32_t getStorageId() const { return storageID; } + int32_t getStartStorageValue() const { return startValue; } + int32_t getEndStorageValue() const { return endValue; } + + std::map descriptions; + std::string mainDescription; + +private: + std::string name; + uint32_t storageID; + int32_t startValue, endValue; + bool ignoreEndValue; + uint16_t id; + + friend class Quest; }; class Quest { - public: - Quest(std::string name, uint16_t id, int32_t startStorageID, int32_t startStorageValue) : - name(std::move(name)), startStorageID(startStorageID), startStorageValue(startStorageValue), id(id) {} - - bool isCompleted(Player* player) const; - bool isStarted(Player* player) const; - uint16_t getID() const { - return id; - } - std::string getName() const { - return name; - } - uint16_t getMissionsCount(Player* player) const; - - uint32_t getStartStorageId() const { - return startStorageID; - } - int32_t getStartStorageValue() const { - return startStorageValue; - } - - const MissionsList& getMissions() const { - return missions; - } - - private: - std::string name; - - uint32_t startStorageID; - int32_t startStorageValue; - uint16_t id; - - MissionsList missions; +public: + Quest(std::string name, uint16_t id, int32_t startStorageID, int32_t startStorageValue) : + name(std::move(name)), startStorageID(startStorageID), startStorageValue(startStorageValue), id(id) + {} + + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + uint16_t getID() const { return id; } + std::string getName() const { return name; } + uint16_t getMissionsCount(Player* player) const; + + uint32_t getStartStorageId() const { return startStorageID; } + int32_t getStartStorageValue() const { return startStorageValue; } + + const MissionsList& getMissions() const { return missions; } + + const Mission* getMissionById(uint16_t id) const; + + bool isTracking(const uint32_t key, const int32_t value) const; + +private: + std::string name; + + uint32_t startStorageID; + int32_t startStorageValue; + uint16_t id; + + MissionsList missions; friend class Quests; }; class Quests { - public: - const QuestsList& getQuests() const { - return quests; - } - - bool loadFromXml(); - Quest* getQuestByID(uint16_t id); - bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const; - uint16_t getQuestsCount(Player* player) const; - bool reload(); - - private: - QuestsList quests; +public: + const QuestsList& getQuests() const { return quests; } + + bool loadFromXml(); + Quest* getQuestByID(uint16_t id); + bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const; + uint16_t getQuestsCount(Player* player) const; + bool reload(); + +private: + QuestsList quests; +}; + +class TrackedQuest +{ +public: + TrackedQuest(uint16_t questId, uint16_t missionId) : questId(questId), missionId(missionId) {} + + uint16_t getQuestId() const { return questId; } + + uint16_t getMissionId() const { return missionId; } + +private: + uint16_t questId = 0; + uint16_t missionId = 0; }; -#endif +#endif // FS_QUESTS_H diff --git a/src/raids.cpp b/src/raids.cpp index d831bdcb93..94692b0109 100644 --- a/src/raids.cpp +++ b/src/raids.cpp @@ -1,40 +1,20 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "raids.h" -#include "pugicast.h" - -#include "game.h" #include "configmanager.h" -#include "scheduler.h" +#include "game.h" #include "monster.h" +#include "pugicast.h" +#include "scheduler.h" extern Game g_game; extern ConfigManager g_config; -Raids::Raids() -{ - scriptInterface.initState(); -} +Raids::Raids() { scriptInterface.initState(); } Raids::~Raids() { @@ -71,15 +51,15 @@ bool Raids::loadFromXml() if ((attr = raidNode.attribute("file"))) { file = attr.as_string(); } else { - std::ostringstream ss; - ss << "raids/" << name << ".xml"; - file = ss.str(); - std::cout << "[Warning - Raids::loadFromXml] File tag missing for raid " << name << ". Using default: " << file << std::endl; + file = fmt::format("raids/{:s}.xml", name); + std::cout << "[Warning - Raids::loadFromXml] File tag missing for raid " << name + << ". Using default: " << file << std::endl; } interval = pugi::cast(raidNode.attribute("interval2").value()) * 60; if (interval == 0) { - std::cout << "[Error - Raids::loadFromXml] interval2 tag missing or zero (would divide by 0) for raid: " << name << std::endl; + std::cout << "[Error - Raids::loadFromXml] interval2 tag missing or zero (would divide by 0) for raid: " + << name << std::endl; continue; } @@ -120,7 +100,8 @@ bool Raids::startup() setLastRaidEnd(OTSYS_TIME()); - checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this))); + checkRaidsEvent = + g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, [this]() { checkRaids(); })); started = true; return started; @@ -134,7 +115,8 @@ void Raids::checkRaids() for (auto it = raidList.begin(), end = raidList.end(); it != end; ++it) { Raid* raid = *it; if (now >= (getLastRaidEnd() + raid->getMargin())) { - if (((MAX_RAND_RANGE * CHECK_RAIDS_INTERVAL) / raid->getInterval()) >= static_cast(uniform_random(0, MAX_RAND_RANGE))) { + if (((MAX_RAND_RANGE * CHECK_RAIDS_INTERVAL) / raid->getInterval()) >= + static_cast(uniform_random(0, MAX_RAND_RANGE))) { setRunning(raid); raid->startRaid(); @@ -147,7 +129,8 @@ void Raids::checkRaids() } } - checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this))); + checkRaidsEvent = + g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, [this]() { checkRaids(); })); } void Raids::clear() @@ -178,7 +161,7 @@ bool Raids::reload() Raid* Raids::getRaidByName(const std::string& name) { for (Raid* raid : raidList) { - if (strcasecmp(raid->getName().c_str(), name.c_str()) == 0) { + if (caseInsensitiveEqual(raid->getName(), name)) { return raid; } } @@ -207,13 +190,13 @@ bool Raid::loadFromXml(const std::string& filename) for (auto eventNode : doc.child("raid").children()) { RaidEvent* event; - if (strcasecmp(eventNode.name(), "announce") == 0) { + if (caseInsensitiveEqual(eventNode.name(), "announce")) { event = new AnnounceEvent(); - } else if (strcasecmp(eventNode.name(), "singlespawn") == 0) { + } else if (caseInsensitiveEqual(eventNode.name(), "singlespawn")) { event = new SingleSpawnEvent(); - } else if (strcasecmp(eventNode.name(), "areaspawn") == 0) { + } else if (caseInsensitiveEqual(eventNode.name(), "areaspawn")) { event = new AreaSpawnEvent(); - } else if (strcasecmp(eventNode.name(), "script") == 0) { + } else if (caseInsensitiveEqual(eventNode.name(), "script")) { event = new ScriptEvent(&g_game.raids.getScriptInterface()); } else { continue; @@ -222,15 +205,15 @@ bool Raid::loadFromXml(const std::string& filename) if (event->configureRaidEvent(eventNode)) { raidEvents.push_back(event); } else { - std::cout << "[Error - Raid::loadFromXml] In file (" << filename << "), eventNode: " << eventNode.name() << std::endl; + std::cout << "[Error - Raid::loadFromXml] In file (" << filename << "), eventNode: " << eventNode.name() + << std::endl; delete event; } } - //sort by delay time - std::sort(raidEvents.begin(), raidEvents.end(), [](const RaidEvent* lhs, const RaidEvent* rhs) { - return lhs->getDelay() < rhs->getDelay(); - }); + // sort by delay time + std::sort(raidEvents.begin(), raidEvents.end(), + [](const RaidEvent* lhs, const RaidEvent* rhs) { return lhs->getDelay() < rhs->getDelay(); }); loaded = true; return true; @@ -241,7 +224,8 @@ void Raid::startRaid() RaidEvent* raidEvent = getNextRaidEvent(); if (raidEvent) { state = RAIDSTATE_EXECUTING; - nextEventEvent = g_scheduler.addEvent(createSchedulerTask(raidEvent->getDelay(), std::bind(&Raid::executeRaidEvent, this, raidEvent))); + nextEventEvent = + g_scheduler.addEvent(createSchedulerTask(raidEvent->getDelay(), [=]() { executeRaidEvent(raidEvent); })); } } @@ -252,8 +236,10 @@ void Raid::executeRaidEvent(RaidEvent* raidEvent) RaidEvent* newRaidEvent = getNextRaidEvent(); if (newRaidEvent) { - uint32_t ticks = static_cast(std::max(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay())); - nextEventEvent = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, this, newRaidEvent))); + uint32_t ticks = static_cast( + std::max(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay())); + nextEventEvent = + g_scheduler.addEvent(createSchedulerTask(ticks, [=]() { executeRaidEvent(newRaidEvent); })); } else { resetRaid(); } @@ -282,9 +268,8 @@ RaidEvent* Raid::getNextRaidEvent() { if (nextEvent < raidEvents.size()) { return raidEvents[nextEvent]; - } else { - return nullptr; } + return nullptr; } bool RaidEvent::configureRaidEvent(const pugi::xml_node& eventNode) @@ -314,7 +299,7 @@ bool AnnounceEvent::configureRaidEvent(const pugi::xml_node& eventNode) pugi::xml_attribute typeAttribute = eventNode.attribute("type"); if (typeAttribute) { - std::string tmpStrValue = asLowerCaseString(typeAttribute.as_string()); + std::string tmpStrValue = boost::algorithm::to_lower_copy(typeAttribute.as_string()); if (tmpStrValue == "warning") { messageType = MESSAGE_STATUS_WARNING; } else if (tmpStrValue == "event") { @@ -325,16 +310,17 @@ bool AnnounceEvent::configureRaidEvent(const pugi::xml_node& eventNode) messageType = MESSAGE_INFO_DESCR; } else if (tmpStrValue == "smallstatus") { messageType = MESSAGE_STATUS_SMALL; - } else if (tmpStrValue == "blueconsole") { - messageType = MESSAGE_STATUS_CONSOLE_BLUE; - } else if (tmpStrValue == "redconsole") { - messageType = MESSAGE_STATUS_CONSOLE_RED; + } else if (tmpStrValue == "blueconsole" || tmpStrValue == "redconsole") { + std::cout << "[Notice] Raid: Deprecated type tag for announce event. Using default: " + << static_cast(messageType) << std::endl; } else { - std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; + std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " + << static_cast(messageType) << std::endl; } } else { messageType = MESSAGE_EVENT_ADVANCE; - std::cout << "[Notice] Raid: type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; + std::cout << "[Notice] Raid: type tag missing for announce event. Using default: " + << static_cast(messageType) << std::endl; } return true; } @@ -533,8 +519,10 @@ bool AreaSpawnEvent::executeEvent() bool success = false; for (int32_t tries = 0; tries < MAXIMUM_TRIES_PER_MONSTER; tries++) { - Tile* tile = g_game.map.getTile(uniform_random(fromPos.x, toPos.x), uniform_random(fromPos.y, toPos.y), uniform_random(fromPos.z, toPos.z)); - if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == nullptr && g_game.placeCreature(monster, tile->getPosition(), false, true)) { + Tile* tile = g_game.map.getTile(uniform_random(fromPos.x, toPos.x), uniform_random(fromPos.y, toPos.y), + uniform_random(fromPos.z, toPos.z)); + if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && + !tile->getTopCreature() && g_game.placeCreature(monster, tile->getPosition(), false, true)) { success = true; break; } @@ -567,14 +555,11 @@ bool ScriptEvent::configureRaidEvent(const pugi::xml_node& eventNode) return true; } -std::string ScriptEvent::getScriptEventName() const -{ - return "onRaid"; -} +std::string ScriptEvent::getScriptEventName() const { return "onRaid"; } bool ScriptEvent::executeEvent() { - //onRaid() + // onRaid() if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - ScriptEvent::onRaid] Call stack overflow" << std::endl; return false; diff --git a/src/raids.h b/src/raids.h index bb6d75dbb0..8d70f86fbf 100644 --- a/src/raids.h +++ b/src/raids.h @@ -1,44 +1,33 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9 -#define FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. +#ifndef FS_RAIDS_H +#define FS_RAIDS_H + +#include "baseevents.h" #include "const.h" +#include "luascript.h" #include "position.h" -#include "baseevents.h" -enum RaidState_t { +enum RaidState_t +{ RAIDSTATE_IDLE, RAIDSTATE_EXECUTING, }; -struct MonsterSpawn { +struct MonsterSpawn +{ MonsterSpawn(std::string name, uint32_t minAmount, uint32_t maxAmount) : - name(std::move(name)), minAmount(minAmount), maxAmount(maxAmount) {} + name(std::move(name)), minAmount(minAmount), maxAmount(maxAmount) + {} std::string name; uint32_t minAmount; uint32_t maxAmount; }; -//How many times it will try to find a tile to add the monster to before giving up +// How many times it will try to find a tile to add the monster to before giving +// up static constexpr int32_t MAXIMUM_TRIES_PER_MONSTER = 10; static constexpr int32_t CHECK_RAIDS_INTERVAL = 60; static constexpr int32_t RAID_MINTICKS = 1000; @@ -48,181 +37,152 @@ class RaidEvent; class Raids { - public: - Raids(); - ~Raids(); - - // non-copyable - Raids(const Raids&) = delete; - Raids& operator=(const Raids&) = delete; - - bool loadFromXml(); - bool startup(); - - void clear(); - bool reload(); - - bool isLoaded() const { - return loaded; - } - bool isStarted() const { - return started; - } - - Raid* getRunning() { - return running; - } - void setRunning(Raid* newRunning) { - running = newRunning; - } - - Raid* getRaidByName(const std::string& name); - - uint64_t getLastRaidEnd() const { - return lastRaidEnd; - } - void setLastRaidEnd(uint64_t newLastRaidEnd) { - lastRaidEnd = newLastRaidEnd; - } - - void checkRaids(); - - LuaScriptInterface& getScriptInterface() { - return scriptInterface; - } - - private: - LuaScriptInterface scriptInterface{"Raid Interface"}; - - std::list raidList; - Raid* running = nullptr; - uint64_t lastRaidEnd = 0; - uint32_t checkRaidsEvent = 0; - bool loaded = false; - bool started = false; +public: + Raids(); + ~Raids(); + + // non-copyable + Raids(const Raids&) = delete; + Raids& operator=(const Raids&) = delete; + + bool loadFromXml(); + bool startup(); + + void clear(); + bool reload(); + + bool isLoaded() const { return loaded; } + bool isStarted() const { return started; } + + Raid* getRunning() { return running; } + void setRunning(Raid* newRunning) { running = newRunning; } + + Raid* getRaidByName(const std::string& name); + + uint64_t getLastRaidEnd() const { return lastRaidEnd; } + void setLastRaidEnd(uint64_t newLastRaidEnd) { lastRaidEnd = newLastRaidEnd; } + + void checkRaids(); + + LuaScriptInterface& getScriptInterface() { return scriptInterface; } + +private: + LuaScriptInterface scriptInterface{"Raid Interface"}; + + std::list raidList; + Raid* running = nullptr; + uint64_t lastRaidEnd = 0; + uint32_t checkRaidsEvent = 0; + bool loaded = false; + bool started = false; }; class Raid { - public: - Raid(std::string name, uint32_t interval, uint32_t marginTime, bool repeat) : - name(std::move(name)), interval(interval), margin(marginTime), repeat(repeat) {} - ~Raid(); - - // non-copyable - Raid(const Raid&) = delete; - Raid& operator=(const Raid&) = delete; - - bool loadFromXml(const std::string& filename); - - void startRaid(); - - void executeRaidEvent(RaidEvent* raidEvent); - void resetRaid(); - - RaidEvent* getNextRaidEvent(); - void setState(RaidState_t newState) { - state = newState; - } - const std::string& getName() const { - return name; - } - - bool isLoaded() const { - return loaded; - } - uint64_t getMargin() const { - return margin; - } - uint32_t getInterval() const { - return interval; - } - bool canBeRepeated() const { - return repeat; - } - - void stopEvents(); - - private: - std::vector raidEvents; - std::string name; - uint32_t interval; - uint32_t nextEvent = 0; - uint64_t margin; - RaidState_t state = RAIDSTATE_IDLE; - uint32_t nextEventEvent = 0; - bool loaded = false; - bool repeat; +public: + Raid(std::string name, uint32_t interval, uint32_t marginTime, bool repeat) : + name(std::move(name)), interval(interval), margin(marginTime), repeat(repeat) + {} + ~Raid(); + + // non-copyable + Raid(const Raid&) = delete; + Raid& operator=(const Raid&) = delete; + + bool loadFromXml(const std::string& filename); + + void startRaid(); + + void executeRaidEvent(RaidEvent* raidEvent); + void resetRaid(); + + RaidEvent* getNextRaidEvent(); + void setState(RaidState_t newState) { state = newState; } + const std::string& getName() const { return name; } + + bool isLoaded() const { return loaded; } + uint64_t getMargin() const { return margin; } + uint32_t getInterval() const { return interval; } + bool canBeRepeated() const { return repeat; } + + void stopEvents(); + +private: + std::vector raidEvents; + std::string name; + uint32_t interval; + uint32_t nextEvent = 0; + uint64_t margin; + RaidState_t state = RAIDSTATE_IDLE; + uint32_t nextEventEvent = 0; + bool loaded = false; + bool repeat; }; class RaidEvent { - public: - virtual ~RaidEvent() = default; +public: + virtual ~RaidEvent() = default; - virtual bool configureRaidEvent(const pugi::xml_node& eventNode); + virtual bool configureRaidEvent(const pugi::xml_node& eventNode); - virtual bool executeEvent() = 0; - uint32_t getDelay() const { - return delay; - } + virtual bool executeEvent() = 0; + uint32_t getDelay() const { return delay; } - private: - uint32_t delay; +private: + uint32_t delay; }; class AnnounceEvent final : public RaidEvent { - public: - AnnounceEvent() = default; +public: + AnnounceEvent() = default; - bool configureRaidEvent(const pugi::xml_node& eventNode) override; + bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool executeEvent() override; + bool executeEvent() override; - private: - std::string message; - MessageClasses messageType = MESSAGE_EVENT_ADVANCE; +private: + std::string message; + MessageClasses messageType = MESSAGE_EVENT_ADVANCE; }; class SingleSpawnEvent final : public RaidEvent { - public: - bool configureRaidEvent(const pugi::xml_node& eventNode) override; +public: + bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool executeEvent() override; + bool executeEvent() override; - private: - std::string monsterName; - Position position; +private: + std::string monsterName; + Position position; }; class AreaSpawnEvent final : public RaidEvent { - public: - bool configureRaidEvent(const pugi::xml_node& eventNode) override; +public: + bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool executeEvent() override; + bool executeEvent() override; - private: - std::list spawnList; - Position fromPos, toPos; +private: + std::list spawnList; + Position fromPos, toPos; }; class ScriptEvent final : public RaidEvent, public Event { - public: - explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {} +public: + explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {} - bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool configureEvent(const pugi::xml_node&) override { - return false; - } + bool configureRaidEvent(const pugi::xml_node& eventNode) override; + bool configureEvent(const pugi::xml_node&) override { return false; } - bool executeEvent() override; + bool executeEvent() override; - private: - std::string getScriptEventName() const override; +private: + std::string getScriptEventName() const override; }; -#endif +#endif // FS_RAIDS_H diff --git a/src/rsa.cpp b/src/rsa.cpp index 0b754a78fe..7c8a8d89ce 100644 --- a/src/rsa.cpp +++ b/src/rsa.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -23,9 +7,7 @@ #include #include - #include -#include static CryptoPP::AutoSeededRandomPool prng; @@ -36,7 +18,7 @@ void RSA::decrypt(char* msg) const auto c = pk.CalculateInverse(prng, m); c.Encode(reinterpret_cast(msg), 128); } catch (const CryptoPP::Exception& e) { - std::cout << e.what() << '\n'; + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, e.what(), "\n"); } } @@ -49,21 +31,24 @@ void RSA::loadPEM(const std::string& filename) if (!file.is_open()) { throw std::runtime_error("Missing file " + filename + "."); - } + } std::ostringstream oss; - for (std::string line; std::getline(file, line); oss << line); + for (std::string line; std::getline(file, line); oss << line) + ; std::string key = oss.str(); - if (key.substr(0, header.size()) != header) { - throw std::runtime_error("Missing RSA private key header."); + auto headerIndex = key.find(header); + if (headerIndex == std::string::npos) { + throw std::runtime_error("Missing RSA private key PEM header."); } - if (key.substr(key.size() - footer.size(), footer.size()) != footer) { - throw std::runtime_error("Missing RSA private key footer."); + auto footerIndex = key.find(footer, headerIndex + 1); + if (footerIndex == std::string::npos) { + throw std::runtime_error("Missing RSA private key PEM footer."); } - key = key.substr(header.size(), key.size() - footer.size()); + key = key.substr(headerIndex + header.size(), footerIndex - headerIndex - header.size()); CryptoPP::ByteQueue queue; CryptoPP::Base64Decoder decoder; @@ -71,13 +56,9 @@ void RSA::loadPEM(const std::string& filename) decoder.Put(reinterpret_cast(key.c_str()), key.size()); decoder.MessageEnd(); - try { - pk.BERDecodePrivateKey(queue, false, queue.MaxRetrievable()); + pk.BERDecodePrivateKey(queue, false, queue.MaxRetrievable()); - if (!pk.Validate(prng, 3)) { - throw std::runtime_error("RSA private key is not valid."); - } - } catch (const CryptoPP::Exception& e) { - std::cout << e.what() << '\n'; + if (!pk.Validate(prng, 3)) { + throw std::runtime_error("RSA private key is not valid."); } } diff --git a/src/rsa.h b/src/rsa.h index 888e3b6eab..192a5eaca4 100644 --- a/src/rsa.h +++ b/src/rsa.h @@ -1,43 +1,23 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 -#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 - -#include - -#include +#ifndef FS_RSA_H +#define FS_RSA_H class RSA { - public: - RSA() = default; +public: + RSA() = default; - // non-copyable - RSA(const RSA&) = delete; - RSA& operator=(const RSA&) = delete; + // non-copyable + RSA(const RSA&) = delete; + RSA& operator=(const RSA&) = delete; - void loadPEM(const std::string& filename); - void decrypt(char* msg) const; + void loadPEM(const std::string& filename); + void decrypt(char* msg) const; - private: - CryptoPP::RSA::PrivateKey pk; +private: + CryptoPP::RSA::PrivateKey pk; }; -#endif +#endif // FS_RSA_H diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 37cbf3c4be..c7d26a3af9 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -1,27 +1,9 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "scheduler.h" -#include -#include uint32_t Scheduler::addEvent(SchedulerTask* task) { @@ -80,7 +62,4 @@ void Scheduler::shutdown() }); } -SchedulerTask* createSchedulerTask(uint32_t delay, TaskFunc&& f) -{ - return new SchedulerTask(delay, std::move(f)); -} +SchedulerTask* createSchedulerTask(uint32_t delay, TaskFunc&& f) { return new SchedulerTask(delay, std::move(f)); } diff --git a/src/scheduler.h b/src/scheduler.h index d7f161dde5..03835c0f77 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -1,72 +1,50 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1 -#define FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1 +#ifndef FS_SCHEDULER_H +#define FS_SCHEDULER_H #include "tasks.h" -#include - #include "thread_holder_base.h" static constexpr int32_t SCHEDULER_MINTICKS = 50; class SchedulerTask : public Task { - public: - void setEventId(uint32_t id) { - eventId = id; - } - uint32_t getEventId() const { - return eventId; - } +public: + void setEventId(uint32_t id) { eventId = id; } + uint32_t getEventId() const { return eventId; } + + uint32_t getDelay() const { return delay; } - uint32_t getDelay() const { - return delay; - } - private: - SchedulerTask(uint32_t delay, TaskFunc&& f) : Task(std::move(f)), delay(delay) {} +private: + SchedulerTask(uint32_t delay, TaskFunc&& f) : Task(std::move(f)), delay(delay) {} - uint32_t eventId = 0; - uint32_t delay = 0; + uint32_t eventId = 0; + uint32_t delay = 0; - friend SchedulerTask* createSchedulerTask(uint32_t, TaskFunc&&); + friend SchedulerTask* createSchedulerTask(uint32_t, TaskFunc&&); }; SchedulerTask* createSchedulerTask(uint32_t delay, TaskFunc&& f); class Scheduler : public ThreadHolder { - public: - uint32_t addEvent(SchedulerTask* task); - void stopEvent(uint32_t eventId); +public: + uint32_t addEvent(SchedulerTask* task); + void stopEvent(uint32_t eventId); + + void shutdown(); - void shutdown(); + void threadMain() { io_context.run(); } - void threadMain() { io_context.run(); } - private: - std::atomic lastEventId{0}; - std::unordered_map eventIdTimerMap; - boost::asio::io_context io_context; - boost::asio::io_context::work work{io_context}; +private: + std::atomic lastEventId{0}; + std::unordered_map eventIdTimerMap; + boost::asio::io_context io_context; + boost::asio::io_context::work work{io_context}; }; extern Scheduler g_scheduler; -#endif +#endif // FS_SCHEDULER_H diff --git a/src/script.cpp b/src/script.cpp index 144d9d25ec..f1b4fe2df3 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,48 +1,27 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "script.h" -#include + #include "configmanager.h" +#include + extern LuaEnvironment g_luaEnvironment; extern ConfigManager g_config; -Scripts::Scripts() : - scriptInterface("Scripts Interface") -{ - scriptInterface.initState(); -} +Scripts::Scripts() : scriptInterface("Scripts Interface") { scriptInterface.initState(); } -Scripts::~Scripts() -{ - scriptInterface.reInitState(); -} +Scripts::~Scripts() { scriptInterface.reInitState(); } bool Scripts::loadScripts(std::string folderName, bool isLib, bool reload) { - namespace fs = boost::filesystem; + namespace fs = std::filesystem; const auto dir = fs::current_path() / "data" / folderName; - if(!fs::exists(dir) || !fs::is_directory(dir)) { + if (!fs::exists(dir) || !fs::is_directory(dir)) { std::cout << "[Warning - Scripts::loadScripts] Can not load folder '" << folderName << "'." << std::endl; return false; } @@ -50,12 +29,12 @@ bool Scripts::loadScripts(std::string folderName, bool isLib, bool reload) fs::recursive_directory_iterator endit; std::vector v; std::string disable = ("#"); - for(fs::recursive_directory_iterator it(dir); it != endit; ++it) { + for (fs::recursive_directory_iterator it(dir); it != endit; ++it) { auto fn = it->path().parent_path().filename(); if ((fn == "lib" && !isLib) || fn == "events") { continue; } - if(fs::is_regular_file(*it) && it->path().extension() == ".lua") { + if (fs::is_regular_file(*it) && it->path().extension() == ".lua") { size_t found = it->path().filename().string().find(disable); if (found != std::string::npos) { if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { @@ -80,7 +59,7 @@ bool Scripts::loadScripts(std::string folderName, bool isLib, bool reload) } } - if(scriptInterface.loadFile(scriptFile) == -1) { + if (scriptInterface.loadFile(scriptFile) == -1) { std::cout << "> " << it->filename().string() << " [error]" << std::endl; std::cout << "^ " << scriptInterface.getLastLuaError() << std::endl; continue; diff --git a/src/script.h b/src/script.h index 1a23cb3e86..8af7bfc87c 100644 --- a/src/script.h +++ b/src/script.h @@ -1,40 +1,22 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_SCRIPTS_H -#define FS_SCRIPTS_H +#ifndef FS_SCRIPT_H +#define FS_SCRIPT_H #include "luascript.h" -#include "enums.h" class Scripts { - public: - Scripts(); - ~Scripts(); +public: + Scripts(); + ~Scripts(); - bool loadScripts(std::string folderName, bool isLib, bool reload); - LuaScriptInterface& getScriptInterface() { - return scriptInterface; - } - private: - LuaScriptInterface scriptInterface; + bool loadScripts(std::string folderName, bool isLib, bool reload); + LuaScriptInterface& getScriptInterface() { return scriptInterface; } + +private: + LuaScriptInterface scriptInterface; }; -#endif +#endif // FS_SCRIPT_H diff --git a/src/scriptmanager.cpp b/src/scriptmanager.cpp index b166da716d..a00c08d38a 100644 --- a/src/scriptmanager.cpp +++ b/src/scriptmanager.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -23,13 +7,13 @@ #include "actions.h" #include "chat.h" -#include "talkaction.h" -#include "spells.h" -#include "movement.h" -#include "weapons.h" -#include "globalevent.h" #include "events.h" +#include "globalevent.h" +#include "movement.h" #include "script.h" +#include "spells.h" +#include "talkaction.h" +#include "weapons.h" Actions* g_actions = nullptr; CreatureEvents* g_creatureEvents = nullptr; diff --git a/src/scriptmanager.h b/src/scriptmanager.h index d3b52e480c..f806f52f0b 100644 --- a/src/scriptmanager.h +++ b/src/scriptmanager.h @@ -1,41 +1,26 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B -#define FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B +#ifndef FS_SCRIPTMANAGER_H +#define FS_SCRIPTMANAGER_H class ScriptingManager { - public: - ScriptingManager() = default; - ~ScriptingManager(); +public: + ScriptingManager() = default; + ~ScriptingManager(); - // non-copyable - ScriptingManager(const ScriptingManager&) = delete; - ScriptingManager& operator=(const ScriptingManager&) = delete; + // non-copyable + ScriptingManager(const ScriptingManager&) = delete; + ScriptingManager& operator=(const ScriptingManager&) = delete; - static ScriptingManager& getInstance() { - static ScriptingManager instance; - return instance; - } + static ScriptingManager& getInstance() + { + static ScriptingManager instance; + return instance; + } - bool loadScriptSystems(); + bool loadScriptSystems(); }; -#endif +#endif // FS_SCRIPTMANAGER_H diff --git a/src/server.cpp b/src/server.cpp index d0ed14f769..e7b421ace8 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,42 +1,20 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "outputmessage.h" #include "server.h" -#include "scheduler.h" -#include "configmanager.h" + #include "ban.h" +#include "configmanager.h" +#include "scheduler.h" extern ConfigManager g_config; Ban g_bans; -ServiceManager::~ServiceManager() -{ - stop(); -} +ServiceManager::~ServiceManager() { stop(); } -void ServiceManager::die() -{ - io_service.stop(); -} +void ServiceManager::die() { io_service.stop(); } void ServiceManager::run() { @@ -55,7 +33,7 @@ void ServiceManager::stop() for (auto& servicePortIt : acceptors) { try { - io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); + io_service.post([servicePort = servicePortIt.second]() { servicePort->onStopServer(); }); } catch (boost::system::system_error& e) { std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; } @@ -64,18 +42,12 @@ void ServiceManager::stop() acceptors.clear(); death_timer.expires_from_now(std::chrono::seconds(3)); - death_timer.async_wait(std::bind(&ServiceManager::die, this)); + death_timer.async_wait([this](const boost::system::error_code&) { die(); }); } -ServicePort::~ServicePort() -{ - close(); -} +ServicePort::~ServicePort() { close(); } -bool ServicePort::is_single_socket() const -{ - return !services.empty() && services.front()->is_single_socket(); -} +bool ServicePort::is_single_socket() const { return !services.empty() && services.front()->is_single_socket(); } std::string ServicePort::get_protocol_names() const { @@ -99,7 +71,10 @@ void ServicePort::accept() } auto connection = ConnectionManager::getInstance().createConnection(io_service, shared_from_this()); - acceptor->async_accept(connection->getSocket(), std::bind(&ServicePort::onAccept, shared_from_this(), connection, std::placeholders::_1)); + acceptor->async_accept(connection->getSocket(), + [=, thisPtr = shared_from_this()](const boost::system::error_code& error) { + thisPtr->onAccept(connection, error); + }); } void ServicePort::onAccept(Connection_ptr connection, const boost::system::error_code& error) @@ -126,31 +101,27 @@ void ServicePort::onAccept(Connection_ptr connection, const boost::system::error if (!pendingStart) { close(); pendingStart = true; - g_scheduler.addEvent(createSchedulerTask(15000, - std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); + g_scheduler.addEvent( + createSchedulerTask(15000, [=, thisPtr = std::weak_ptr(shared_from_this())]() { + ServicePort::openAcceptor(thisPtr, serverPort); + })); } } } -Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const +Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const { uint8_t protocolID = msg.getByte(); for (auto& service : services) { if (protocolID != service->get_protocol_identifier()) { continue; } - - if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) { - return service->make_protocol(connection); - } + return service->make_protocol(connection); } return nullptr; } -void ServicePort::onStopServer() -{ - close(); -} +void ServicePort::onStopServer() { close(); } void ServicePort::openAcceptor(std::weak_ptr weak_service, uint16_t port) { @@ -168,11 +139,15 @@ void ServicePort::open(uint16_t port) try { if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { - acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( - boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); + acceptor.reset(new boost::asio::ip::tcp::acceptor( + io_service, + boost::asio::ip::tcp::endpoint(boost::asio::ip::address(boost::asio::ip::address_v4::from_string( + g_config.getString(ConfigManager::IP))), + serverPort))); } else { - acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( - boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); + acceptor.reset(new boost::asio::ip::tcp::acceptor( + io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); } acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); @@ -182,8 +157,10 @@ void ServicePort::open(uint16_t port) std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; pendingStart = true; - g_scheduler.addEvent(createSchedulerTask(15000, - std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); + g_scheduler.addEvent( + createSchedulerTask(15000, [=, thisPtr = std::weak_ptr(shared_from_this())]() { + ServicePort::openAcceptor(thisPtr, serverPort); + })); } } @@ -197,7 +174,7 @@ void ServicePort::close() bool ServicePort::add_service(const Service_ptr& new_svc) { - if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket();})) { + if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) { return svc->is_single_socket(); })) { return false; } diff --git a/src/server.h b/src/server.h index dc29822ca3..6896d16ed0 100644 --- a/src/server.h +++ b/src/server.h @@ -1,133 +1,103 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_SERVER_H_984DA68ABF744127850F90CC710F281B -#define FS_SERVER_H_984DA68ABF744127850F90CC710F281B +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_SERVER_H +#define FS_SERVER_H #include "connection.h" #include "signals.h" -#include - -class Protocol; class ServiceBase { - public: - virtual bool is_single_socket() const = 0; - virtual bool is_checksummed() const = 0; - virtual uint8_t get_protocol_identifier() const = 0; - virtual const char* get_protocol_name() const = 0; +public: + virtual bool is_single_socket() const = 0; + virtual bool is_checksummed() const = 0; + virtual uint8_t get_protocol_identifier() const = 0; + virtual const char* get_protocol_name() const = 0; - virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; + virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; }; template class Service final : public ServiceBase { - public: - bool is_single_socket() const override { - return ProtocolType::server_sends_first; - } - bool is_checksummed() const override { - return ProtocolType::use_checksum; - } - uint8_t get_protocol_identifier() const override { - return ProtocolType::protocol_identifier; - } - const char* get_protocol_name() const override { - return ProtocolType::protocol_name(); - } +public: + bool is_single_socket() const override { return ProtocolType::server_sends_first; } + bool is_checksummed() const override { return ProtocolType::use_checksum; } + uint8_t get_protocol_identifier() const override { return ProtocolType::protocol_identifier; } + const char* get_protocol_name() const override { return ProtocolType::protocol_name(); } - Protocol_ptr make_protocol(const Connection_ptr& c) const override { - return std::make_shared(c); - } + Protocol_ptr make_protocol(const Connection_ptr& c) const override { return std::make_shared(c); } }; class ServicePort : public std::enable_shared_from_this { - public: - explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} - ~ServicePort(); +public: + explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} + ~ServicePort(); - // non-copyable - ServicePort(const ServicePort&) = delete; - ServicePort& operator=(const ServicePort&) = delete; + // non-copyable + ServicePort(const ServicePort&) = delete; + ServicePort& operator=(const ServicePort&) = delete; - static void openAcceptor(std::weak_ptr weak_service, uint16_t port); - void open(uint16_t port); - void close(); - bool is_single_socket() const; - std::string get_protocol_names() const; + static void openAcceptor(std::weak_ptr weak_service, uint16_t port); + void open(uint16_t port); + void close(); + bool is_single_socket() const; + std::string get_protocol_names() const; - bool add_service(const Service_ptr& new_svc); - Protocol_ptr make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const; + bool add_service(const Service_ptr& new_svc); + Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const; - void onStopServer(); - void onAccept(Connection_ptr connection, const boost::system::error_code& error); + void onStopServer(); + void onAccept(Connection_ptr connection, const boost::system::error_code& error); - private: - void accept(); +private: + void accept(); - boost::asio::io_service& io_service; - std::unique_ptr acceptor; - std::vector services; + boost::asio::io_service& io_service; + std::unique_ptr acceptor; + std::vector services; - uint16_t serverPort = 0; - bool pendingStart = false; + uint16_t serverPort = 0; + bool pendingStart = false; }; class ServiceManager { - public: - ServiceManager() = default; - ~ServiceManager(); +public: + ServiceManager() = default; + ~ServiceManager(); - // non-copyable - ServiceManager(const ServiceManager&) = delete; - ServiceManager& operator=(const ServiceManager&) = delete; + // non-copyable + ServiceManager(const ServiceManager&) = delete; + ServiceManager& operator=(const ServiceManager&) = delete; - void run(); - void stop(); + void run(); + void stop(); - template - bool add(uint16_t port); + template + bool add(uint16_t port); - bool is_running() const { - return acceptors.empty() == false; - } + bool is_running() const { return !acceptors.empty(); } - private: - void die(); +private: + void die(); - std::unordered_map acceptors; + std::unordered_map acceptors; - boost::asio::io_service io_service; - Signals signals{io_service}; - boost::asio::steady_timer death_timer { io_service }; - bool running = false; + boost::asio::io_service io_service; + Signals signals{io_service}; + boost::asio::steady_timer death_timer{io_service}; + bool running = false; }; template bool ServiceManager::add(uint16_t port) { if (port == 0) { - std::cout << "ERROR: No port provided for service " << ProtocolType::protocol_name() << ". Service disabled." << std::endl; + std::cout << "ERROR: No port provided for service " << ProtocolType::protocol_name() << ". Service disabled." + << std::endl; return false; } @@ -143,9 +113,8 @@ bool ServiceManager::add(uint16_t port) service_port = foundServicePort->second; if (service_port->is_single_socket() || ProtocolType::server_sends_first) { - std::cout << "ERROR: " << ProtocolType::protocol_name() << - " and " << service_port->get_protocol_names() << - " cannot use the same port " << port << '.' << std::endl; + std::cout << "ERROR: " << ProtocolType::protocol_name() << " and " << service_port->get_protocol_names() + << " cannot use the same port " << port << '.' << std::endl; return false; } } @@ -153,4 +122,4 @@ bool ServiceManager::add(uint16_t port) return service_port->add_service(std::make_shared>()); } -#endif +#endif // FS_SERVER_H diff --git a/src/signals.cpp b/src/signals.cpp index 91af054abb..216cf6ef5f 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -1,42 +1,29 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include #include "signals.h" -#include "tasks.h" -#include "game.h" + #include "actions.h" #include "configmanager.h" -#include "spells.h" -#include "talkaction.h" +#include "databasetasks.h" +#include "events.h" +#include "game.h" +#include "globalevent.h" +#include "monsters.h" +#include "mounts.h" #include "movement.h" -#include "weapons.h" -#include "raids.h" +#include "npc.h" #include "quests.h" -#include "mounts.h" -#include "globalevent.h" -#include "monster.h" -#include "events.h" +#include "raids.h" #include "scheduler.h" -#include "databasetasks.h" +#include "spells.h" +#include "talkaction.h" +#include "tasks.h" +#include "weapons.h" + +#include extern Scheduler g_scheduler; extern DatabaseTasks g_databaseTasks; @@ -58,36 +45,23 @@ extern LuaEnvironment g_luaEnvironment; namespace { -void sigbreakHandler() -{ - //Dispatcher thread - std::cout << "SIGBREAK received, shutting game server down..." << std::endl; - g_game.setGameState(GAME_STATE_SHUTDOWN); -} - -void sigtermHandler() -{ - //Dispatcher thread - std::cout << "SIGTERM received, shutting game server down..." << std::endl; - g_game.setGameState(GAME_STATE_SHUTDOWN); -} - +#ifndef _WIN32 void sigusr1Handler() { - //Dispatcher thread + // Dispatcher thread std::cout << "SIGUSR1 received, saving the game state..." << std::endl; g_game.saveGameState(); } void sighupHandler() { - //Dispatcher thread + // Dispatcher thread std::cout << "SIGHUP received, reloading config files..." << std::endl; g_actions->reload(); std::cout << "Reloaded actions." << std::endl; - g_config.reload(); + g_config.load(); std::cout << "Reloaded config." << std::endl; g_creatureEvents->reload(); @@ -103,10 +77,10 @@ void sighupHandler() g_game.raids.startup(); std::cout << "Reloaded raids." << std::endl; - g_spells->reload(); + g_monsters.reload(); std::cout << "Reloaded monsters." << std::endl; - g_monsters.reload(); + g_spells->reload(); std::cout << "Reloaded spells." << std::endl; g_talkActions->reload(); @@ -139,10 +113,25 @@ void sighupHandler() lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); } +#else +void sigbreakHandler() +{ + // Dispatcher thread + std::cout << "SIGBREAK received, shutting game server down..." << std::endl; + g_game.setGameState(GAME_STATE_SHUTDOWN); +} +#endif + +void sigtermHandler() +{ + // Dispatcher thread + std::cout << "SIGTERM received, shutting game server down..." << std::endl; + g_game.setGameState(GAME_STATE_SHUTDOWN); +} void sigintHandler() { - //Dispatcher thread + // Dispatcher thread std::cout << "SIGINT received, shutting game server down..." << std::endl; g_game.setGameState(GAME_STATE_SHUTDOWN); } @@ -152,22 +141,22 @@ void sigintHandler() // https://github.com/otland/forgottenserver/pull/2473 void dispatchSignalHandler(int signal) { - switch(signal) { - case SIGINT: //Shuts the server down + switch (signal) { + case SIGINT: // Shuts the server down g_dispatcher.addTask(createTask(sigintHandler)); break; - case SIGTERM: //Shuts the server down + case SIGTERM: // Shuts the server down g_dispatcher.addTask(createTask(sigtermHandler)); break; #ifndef _WIN32 - case SIGHUP: //Reload config/data + case SIGHUP: // Reload config/data g_dispatcher.addTask(createTask(sighupHandler)); break; - case SIGUSR1: //Saves game state + case SIGUSR1: // Saves game state g_dispatcher.addTask(createTask(sigusr1Handler)); break; #else - case SIGBREAK: //Shuts the server down + case SIGBREAK: // Shuts the server down g_dispatcher.addTask(createTask(sigbreakHandler)); // hold the thread until other threads end g_scheduler.join(); @@ -180,9 +169,9 @@ void dispatchSignalHandler(int signal) } } -} +} // namespace -Signals::Signals(boost::asio::io_service& service): set(service) +Signals::Signals(boost::asio::io_service& service) : set(service) { set.add(SIGINT); set.add(SIGTERM); @@ -190,9 +179,8 @@ Signals::Signals(boost::asio::io_service& service): set(service) set.add(SIGUSR1); set.add(SIGHUP); #else - // This must be a blocking call as Windows calls it in a new thread and terminates - // the process when the handler returns (or after 5 seconds, whichever is earlier). - // On Windows it is called in a new thread. + // This must be a blocking call as Windows calls it in a new thread and terminates the process when the handler + // returns (or after 5 seconds, whichever is earlier). On Windows it is called in a new thread. signal(SIGBREAK, dispatchSignalHandler); #endif @@ -203,7 +191,7 @@ void Signals::asyncWait() { set.async_wait([this](const boost::system::error_code& err, int signal) { if (err) { - std::cerr << "Signal handling error: " << err.message() << std::endl; + std::cerr << "Signal handling error: " << err.message() << std::endl; return; } dispatchSignalHandler(signal); diff --git a/src/signals.h b/src/signals.h index 2283eaa433..38da6e2421 100644 --- a/src/signals.h +++ b/src/signals.h @@ -1,35 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F -#define FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F - -#include +#ifndef FS_SIGNALS_H +#define FS_SIGNALS_H class Signals { boost::asio::signal_set set; - public: - explicit Signals(boost::asio::io_service& service); - private: - void asyncWait(); +public: + explicit Signals(boost::asio::io_service& service); + +private: + void asyncWait(); }; -#endif +#endif // FS_SIGNALS_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 67d7a9f757..5fcab1dea8 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1,39 +1,25 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "spawn.h" + +#include "configmanager.h" +#include "events.h" #include "game.h" #include "monster.h" -#include "configmanager.h" -#include "scheduler.h" - +#include "npc.h" #include "pugicast.h" -#include "events.h" +#include "scheduler.h" +#include "spectators.h" extern ConfigManager g_config; extern Monsters g_monsters; extern Game g_game; extern Events* g_events; -static constexpr int32_t MINSPAWN_INTERVAL = 10 * 1000; // 10 seconds to match RME +static constexpr int32_t MINSPAWN_INTERVAL = 10 * 1000; // 10 seconds to match RME static constexpr int32_t MAXSPAWN_INTERVAL = 24 * 60 * 60 * 1000; // 1 day bool Spawns::loadFromXml(const std::string& filename) @@ -53,11 +39,9 @@ bool Spawns::loadFromXml(const std::string& filename) loaded = true; for (auto spawnNode : doc.child("spawns").children()) { - Position centerPos( - pugi::cast(spawnNode.attribute("centerx").value()), - pugi::cast(spawnNode.attribute("centery").value()), - pugi::cast(spawnNode.attribute("centerz").value()) - ); + Position centerPos(pugi::cast(spawnNode.attribute("centerx").value()), + pugi::cast(spawnNode.attribute("centery").value()), + pugi::cast(spawnNode.attribute("centerz").value())); int32_t radius; pugi::xml_attribute radiusAttribute = spawnNode.attribute("radius"); @@ -68,11 +52,13 @@ bool Spawns::loadFromXml(const std::string& filename) } if (radius > 30) { - std::cout << "[Warning - Spawns::loadFromXml] Radius size bigger than 30 at position: " << centerPos << ", consider lowering it." << std::endl; + std::cout << "[Warning - Spawns::loadFromXml] Radius size bigger than 30 at position: " << centerPos + << ", consider lowering it." << std::endl; } if (!spawnNode.first_child()) { - std::cout << "[Warning - Spawns::loadFromXml] Empty spawn at position: " << centerPos << " with radius: " << radius << '.' << std::endl; + std::cout << "[Warning - Spawns::loadFromXml] Empty spawn at position: " << centerPos + << " with radius: " << radius << '.' << std::endl; continue; } @@ -80,7 +66,80 @@ bool Spawns::loadFromXml(const std::string& filename) Spawn& spawn = spawnList.front(); for (auto childNode : spawnNode.children()) { - if (strcasecmp(childNode.name(), "monster") == 0) { + if (caseInsensitiveEqual(childNode.name(), "monsters")) { + Position pos(centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), centerPos.z); + + int32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 1000; + if (interval < MINSPAWN_INTERVAL) { + std::cout << "[Warning - Spawns::loadFromXml] " << pos << " spawntime can not be less than " + << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + continue; + } else if (interval > MAXSPAWN_INTERVAL) { + std::cout << "[Warning - Spawns::loadFromXml] " << pos << " spawntime can not be more than " + << MAXSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + continue; + } + + size_t monstersCount = std::distance(childNode.children().begin(), childNode.children().end()); + if (monstersCount == 0) { + std::cout << "[Warning - Spawns::loadFromXml] " << pos << " empty monsters set." << std::endl; + continue; + } + + uint16_t totalChance = 0; + spawnBlock_t sb; + sb.pos = pos; + sb.direction = DIRECTION_NORTH; + sb.interval = interval; + sb.lastSpawn = 0; + + for (auto monsterNode : childNode.children()) { + pugi::xml_attribute nameAttribute = monsterNode.attribute("name"); + if (!nameAttribute) { + continue; + } + + MonsterType* mType = g_monsters.getMonsterType(nameAttribute.as_string()); + if (!mType) { + std::cout << "[Warning - Spawn::loadFromXml] " << pos << " can not find " + << nameAttribute.as_string() << std::endl; + continue; + } + + uint16_t chance = 100 / monstersCount; + pugi::xml_attribute chanceAttribute = monsterNode.attribute("chance"); + if (chanceAttribute) { + chance = pugi::cast(chanceAttribute.value()); + } + + if (chance + totalChance > 100) { + chance = 100 - totalChance; + totalChance = 100; + std::cout << "[Warning - Spawns::loadFromXml] " << mType->name << ' ' << pos + << " total chance for set can not be higher than 100." << std::endl; + } else { + totalChance += chance; + } + + sb.mTypes.push_back({mType, chance}); + } + + if (sb.mTypes.empty()) { + std::cout << "[Warning - Spawns::loadFromXml] " << pos << " empty monsters set." << std::endl; + continue; + } + + sb.mTypes.shrink_to_fit(); + if (sb.mTypes.size() > 1) { + std::sort(sb.mTypes.begin(), sb.mTypes.end(), + [](std::pair a, std::pair b) { + return a.second > b.second; + }); + } + + spawn.addBlock(sb); + } else if (caseInsensitiveEqual(childNode.name(), "monster")) { pugi::xml_attribute nameAttribute = childNode.attribute("name"); if (!nameAttribute) { continue; @@ -95,22 +154,23 @@ bool Spawns::loadFromXml(const std::string& filename) dir = DIRECTION_NORTH; } - Position pos( - centerPos.x + pugi::cast(childNode.attribute("x").value()), - centerPos.y + pugi::cast(childNode.attribute("y").value()), - centerPos.z - ); + Position pos(centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), centerPos.z); int32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 1000; if (interval >= MINSPAWN_INTERVAL && interval <= MAXSPAWN_INTERVAL) { spawn.addMonster(nameAttribute.as_string(), pos, dir, static_cast(interval)); } else { if (interval < MINSPAWN_INTERVAL) { - std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos + << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." + << std::endl; } else { - std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be more than " << MAXSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos + << " spawntime can not be more than " << MAXSPAWN_INTERVAL / 1000 << " seconds." + << std::endl; } } - } else if (strcasecmp(childNode.name(), "npc") == 0) { + } else if (caseInsensitiveEqual(childNode.name(), "npc")) { pugi::xml_attribute nameAttribute = childNode.attribute("name"); if (!nameAttribute) { continue; @@ -126,11 +186,10 @@ bool Spawns::loadFromXml(const std::string& filename) npc->setDirection(static_cast(pugi::cast(directionAttribute.value()))); } - npc->setMasterPos(Position( - centerPos.x + pugi::cast(childNode.attribute("x").value()), - centerPos.y + pugi::cast(childNode.attribute("y").value()), - centerPos.z - ), radius); + npc->setMasterPos( + Position(centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), centerPos.z), + radius); npcList.push_front(npc); } } @@ -146,7 +205,8 @@ void Spawns::startup() for (Npc* npc : npcList) { if (!g_game.placeCreature(npc, npc->getMasterPos(), false, true)) { - std::cout << "[Warning - Spawns::startup] Couldn't spawn npc \"" << npc->getName() << "\" on position: " << npc->getMasterPos() << '.' << std::endl; + std::cout << "[Warning - Spawns::startup] Couldn't spawn npc \"" << npc->getName() + << "\" on position: " << npc->getMasterPos() << '.' << std::endl; delete npc; } } @@ -178,13 +238,13 @@ bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& } return ((pos.getX() >= centerPos.getX() - radius) && (pos.getX() <= centerPos.getX() + radius) && - (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius)); + (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius)); } void Spawn::startSpawnCheck() { if (checkSpawnEvent == 0) { - checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), [this]() { checkSpawn(); })); } } @@ -209,12 +269,49 @@ bool Spawn::findPlayer(const Position& pos) return false; } -bool Spawn::isInSpawnZone(const Position& pos) +bool Spawn::isInSpawnZone(const Position& pos) { return Spawns::isInZone(centerPos, radius, pos); } + +bool Spawn::spawnMonster(uint32_t spawnId, spawnBlock_t sb, bool startup /* = false*/) { - return Spawns::isInZone(centerPos, radius, pos); + bool isBlocked = !startup && findPlayer(sb.pos); + size_t monstersCount = sb.mTypes.size(), blockedMonsters = 0; + + const auto spawnFunc = [&](bool roll) { + for (const auto& pair : sb.mTypes) { + if (isBlocked && !pair.first->info.isIgnoringSpawnBlock) { + ++blockedMonsters; + continue; + } + + if (!roll) { + return spawnMonster(spawnId, pair.first, sb.pos, sb.direction, startup); + } + + if (pair.second >= normal_random(1, 100) && + spawnMonster(spawnId, pair.first, sb.pos, sb.direction, startup)) { + return true; + } + } + + return false; + }; + + // Try to spawn something with chance check, unless it's single spawn + if (spawnFunc(monstersCount > 1)) { + return true; + } + + // Every monster spawn is blocked, bail out + if (monstersCount == blockedMonsters) { + return false; + } + + // Just try to spawn something without chance check + return spawnFunc(false); } -bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/) +bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, + bool startup /*= false*/) { std::unique_ptr monster_ptr(new Monster(mType)); if (!g_events->eventMonsterOnSpawn(monster_ptr.get(), pos, startup, false)) { @@ -222,9 +319,10 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p } if (startup) { - //No need to send out events to the surrounding since there is no one out there to listen! + // No need to send out events to the surrounding since there is no one out there to listen! if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) { - std::cout << "[Warning - Spawns::startup] Couldn't spawn monster \"" << monster_ptr->getName() << "\" on position: " << pos << '.' << std::endl; + std::cout << "[Warning - Spawns::startup] Couldn't spawn monster \"" << monster_ptr->getName() + << "\" on position: " << pos << '.' << std::endl; return false; } } else { @@ -239,7 +337,7 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p monster->setMasterPos(pos); monster->incrementReferenceCounter(); - spawnedMap.insert(spawned_pair(spawnId, monster)); + spawnedMap.insert({spawnId, monster}); spawnMap[spawnId].lastSpawn = OTSYS_TIME(); return true; } @@ -249,7 +347,7 @@ void Spawn::startup() for (const auto& it : spawnMap) { uint32_t spawnId = it.first; const spawnBlock_t& sb = it.second; - spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true); + spawnMonster(spawnId, sb, true); } } @@ -269,12 +367,11 @@ void Spawn::checkSpawn() spawnBlock_t& sb = it.second; if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { - if (findPlayer(sb.pos)) { + if (!spawnMonster(spawnId, sb)) { sb.lastSpawn = OTSYS_TIME(); continue; } - spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); if (++spawnCount >= static_cast(g_config.getNumber(ConfigManager::RATE_SPAWN))) { break; } @@ -282,7 +379,7 @@ void Spawn::checkSpawn() } if (spawnedMap.size() < spawnMap.size()) { - checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), [this]() { checkSpawn(); })); } } @@ -293,14 +390,10 @@ void Spawn::cleanup() uint32_t spawnId = it->first; Monster* monster = it->second; if (monster->isRemoved()) { - if (spawnId != 0) { - spawnMap[spawnId].lastSpawn = OTSYS_TIME(); - } - monster->decrementReferenceCounter(); it = spawnedMap.erase(it); } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) { - spawnedMap.insert(spawned_pair(0, monster)); + spawnedMap.insert({0, monster}); it = spawnedMap.erase(it); } else { ++it; @@ -308,6 +401,14 @@ void Spawn::cleanup() } } +bool Spawn::addBlock(spawnBlock_t sb) +{ + interval = std::min(interval, sb.interval); + spawnMap[spawnMap.size() + 1] = sb; + + return true; +} + bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval) { MonsterType* mType = g_monsters.getMonsterType(name); @@ -316,18 +417,14 @@ bool Spawn::addMonster(const std::string& name, const Position& pos, Direction d return false; } - this->interval = std::min(this->interval, interval); - spawnBlock_t sb; - sb.mType = mType; + sb.mTypes.push_back({mType, 100}); sb.pos = pos; sb.direction = dir; sb.interval = interval; sb.lastSpawn = 0; - uint32_t spawnId = spawnMap.size() + 1; - spawnMap[spawnId] = sb; - return true; + return addBlock(sb); } void Spawn::removeMonster(Monster* monster) diff --git a/src/spawn.h b/src/spawn.h index 15657e66a5..8bca8e8223 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -1,35 +1,19 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B -#define FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B - -#include "tile.h" +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_SPAWN_H +#define FS_SPAWN_H + #include "position.h" class Monster; class MonsterType; class Npc; -struct spawnBlock_t { +struct spawnBlock_t +{ Position pos; - MonsterType* mType; + std::vector> mTypes; int64_t lastSpawn; uint32_t interval; Direction direction; @@ -37,67 +21,64 @@ struct spawnBlock_t { class Spawn { - public: - Spawn(Position pos, int32_t radius) : centerPos(std::move(pos)), radius(radius) {} - ~Spawn(); +public: + Spawn(Position pos, int32_t radius) : centerPos(std::move(pos)), radius(radius) {} + ~Spawn(); - // non-copyable - Spawn(const Spawn&) = delete; - Spawn& operator=(const Spawn&) = delete; + // non-copyable + Spawn(const Spawn&) = delete; + Spawn& operator=(const Spawn&) = delete; - bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); - void removeMonster(Monster* monster); + bool addBlock(spawnBlock_t sb); + bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); + void removeMonster(Monster* monster); - uint32_t getInterval() const { - return interval; - } - void startup(); + uint32_t getInterval() const { return interval; } + void startup(); - void startSpawnCheck(); - void stopEvent(); + void startSpawnCheck(); + void stopEvent(); - bool isInSpawnZone(const Position& pos); - void cleanup(); + bool isInSpawnZone(const Position& pos); + void cleanup(); - private: - //map of the spawned creatures - using SpawnedMap = std::multimap; - using spawned_pair = SpawnedMap::value_type; - SpawnedMap spawnedMap; +private: + // map of the spawned creatures + using SpawnedMap = std::multimap; + SpawnedMap spawnedMap; - //map of creatures in the spawn - std::map spawnMap; + // map of creatures in the spawn + std::map spawnMap; - Position centerPos; - int32_t radius; + Position centerPos; + int32_t radius; - uint32_t interval = 60000; - uint32_t checkSpawnEvent = 0; + uint32_t interval = 60000; + uint32_t checkSpawnEvent = 0; - static bool findPlayer(const Position& pos); - bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false); - void checkSpawn(); + static bool findPlayer(const Position& pos); + bool spawnMonster(uint32_t spawnId, spawnBlock_t sb, bool startup = false); + bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false); + void checkSpawn(); }; class Spawns { - public: - static bool isInZone(const Position& centerPos, int32_t radius, const Position& pos); - - bool loadFromXml(const std::string& filename); - void startup(); - void clear(); - - bool isStarted() const { - return started; - } - - private: - std::forward_list npcList; - std::forward_list spawnList; - std::string filename; - bool loaded = false; - bool started = false; +public: + static bool isInZone(const Position& centerPos, int32_t radius, const Position& pos); + + bool loadFromXml(const std::string& filename); + void startup(); + void clear(); + + bool isStarted() const { return started; } + +private: + std::forward_list npcList; + std::forward_list spawnList; + std::string filename; + bool loaded = false; + bool started = false; }; -#endif +#endif // FS_SPAWN_H diff --git a/src/spectators.h b/src/spectators.h index 167114846a..e520490bf1 100644 --- a/src/spectators.h +++ b/src/spectators.h @@ -1,26 +1,8 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 -#define FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 - -#include +#ifndef FS_SPECTATORS_H +#define FS_SPECTATORS_H class Creature; @@ -29,12 +11,12 @@ class SpectatorVec using Vec = std::vector; using Iterator = Vec::iterator; using ConstIterator = Vec::const_iterator; + public: - SpectatorVec() { - vec.reserve(32); - } + SpectatorVec() { vec.reserve(32); } - void addSpectators(const SpectatorVec& spectators) { + void addSpectators(const SpectatorVec& spectators) + { for (Creature* spectator : spectators.vec) { auto it = std::find(vec.begin(), vec.end(), spectator); if (it != end()) { @@ -44,7 +26,8 @@ class SpectatorVec } } - void erase(Creature* spectator) { + void erase(Creature* spectator) + { auto it = std::find(vec.begin(), vec.end(), spectator); if (it == end()) { return; @@ -65,4 +48,4 @@ class SpectatorVec Vec vec; }; -#endif +#endif // FS_SPECTATORS_H diff --git a/src/spells.cpp b/src/spells.cpp index 6ab0620e2d..dd516bd9c6 100644 --- a/src/spells.cpp +++ b/src/spells.cpp @@ -1,30 +1,16 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" +#include "spells.h" + #include "combat.h" #include "configmanager.h" #include "game.h" -#include "monster.h" +#include "luavariant.h" +#include "monsters.h" #include "pugicast.h" -#include "spells.h" extern Game g_game; extern Spells* g_spells; @@ -33,22 +19,16 @@ extern Vocations g_vocations; extern ConfigManager g_config; extern LuaEnvironment g_luaEnvironment; -Spells::Spells() -{ - scriptInterface.initState(); -} +Spells::Spells() { scriptInterface.initState(); } -Spells::~Spells() -{ - clear(false); -} +Spells::~Spells() { clear(false); } TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) { std::string str_words = words; - //strip trailing spaces - trimString(str_words); + // strip trailing spaces + boost::algorithm::trim(str_words); InstantSpell* instantSpell = getInstantSpell(str_words); if (!instantSpell) { @@ -73,7 +53,7 @@ TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) param = paramText.substr(loc1 + 1, loc2 - loc1 - 1); } else { - trimString(paramText); + boost::algorithm::trim(paramText); loc1 = paramText.find(' ', 0); if (loc1 == std::string::npos) { param = paramText; @@ -99,7 +79,7 @@ TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) void Spells::clearMaps(bool fromLua) { - for (auto instant = instants.begin(); instant != instants.end(); ) { + for (auto instant = instants.begin(); instant != instants.end();) { if (fromLua == instant->second.fromLua) { instant = instants.erase(instant); } else { @@ -107,7 +87,7 @@ void Spells::clearMaps(bool fromLua) } } - for (auto rune = runes.begin(); rune != runes.end(); ) { + for (auto rune = runes.begin(); rune != runes.end();) { if (fromLua == rune->second.fromLua) { rune = runes.erase(rune); } else { @@ -123,21 +103,15 @@ void Spells::clear(bool fromLua) reInitState(fromLua); } -LuaScriptInterface& Spells::getScriptInterface() -{ - return scriptInterface; -} +LuaScriptInterface& Spells::getScriptInterface() { return scriptInterface; } -std::string Spells::getScriptBaseName() const -{ - return "spells"; -} +std::string Spells::getScriptBaseName() const { return "spells"; } Event_ptr Spells::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "rune") == 0) { + if (caseInsensitiveEqual(nodeName, "rune")) { return Event_ptr(new RuneSpell(&scriptInterface)); - } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { + } else if (caseInsensitiveEqual(nodeName, "instant")) { return Event_ptr(new InstantSpell(&scriptInterface)); } return nullptr; @@ -149,7 +123,8 @@ bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) if (instant) { auto result = instants.emplace(instant->getWords(), std::move(*instant)); if (!result.second) { - std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; + std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " + << instant->getWords() << std::endl; } return result.second; } @@ -158,7 +133,8 @@ bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) if (rune) { auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); if (!result.second) { - std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; + std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " + << rune->getRuneItemId() << std::endl; } return result.second; } @@ -168,12 +144,13 @@ bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) bool Spells::registerInstantLuaEvent(InstantSpell* event) { - InstantSpell_ptr instant { event }; + InstantSpell_ptr instant{event}; if (instant) { std::string words = instant->getWords(); auto result = instants.emplace(instant->getWords(), std::move(*instant)); if (!result.second) { - std::cout << "[Warning - Spells::registerInstantLuaEvent] Duplicate registered instant spell with words: " << words << std::endl; + std::cout << "[Warning - Spells::registerInstantLuaEvent] Duplicate registered instant spell with words: " + << words << std::endl; } return result.second; } @@ -183,12 +160,13 @@ bool Spells::registerInstantLuaEvent(InstantSpell* event) bool Spells::registerRuneLuaEvent(RuneSpell* event) { - RuneSpell_ptr rune { event }; + RuneSpell_ptr rune{event}; if (rune) { uint16_t id = rune->getRuneItemId(); auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); if (!result.second) { - std::cout << "[Warning - Spells::registerRuneLuaEvent] Duplicate registered rune with id: " << id << std::endl; + std::cout << "[Warning - Spells::registerRuneLuaEvent] Duplicate registered rune with id: " << id + << std::endl; } return result.second; } @@ -222,7 +200,7 @@ RuneSpell* Spells::getRuneSpell(uint32_t id) RuneSpell* Spells::getRuneSpellByName(const std::string& name) { for (auto& it : runes) { - if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { + if (caseInsensitiveEqual(it.second.getName(), name)) { return &it.second; } } @@ -236,8 +214,8 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) for (auto& it : instants) { const std::string& instantSpellWords = it.second.getWords(); size_t spellLen = instantSpellWords.length(); - if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { - if (!result || spellLen > result->getWords().length()) { + if (caseInsensitiveStartsWith(words, instantSpellWords)) { + if (!result || spellLen > result->getWords().size()) { result = &it.second; if (words.length() == spellLen) { break; @@ -264,20 +242,10 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) return nullptr; } -InstantSpell* Spells::getInstantSpellById(uint32_t spellId) -{ - for (auto& it : instants) { - if (it.second.getId() == spellId) { - return &it.second; - } - } - return nullptr; -} - InstantSpell* Spells::getInstantSpellByName(const std::string& name) { for (auto& it : instants) { - if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { + if (caseInsensitiveEqual(it.second.getName(), name)) { return &it.second; } } @@ -289,20 +257,10 @@ Position Spells::getCasterPosition(Creature* creature, Direction dir) return getNextPosition(dir, creature->getPosition()); } -CombatSpell::CombatSpell(Combat* combat, bool needTarget, bool needDirection) : - Event(&g_spells->getScriptInterface()), - combat(combat), - needDirection(needDirection), - needTarget(needTarget) +CombatSpell::CombatSpell(Combat_ptr combat, bool needTarget, bool needDirection) : + Event(&g_spells->getScriptInterface()), combat(combat), needDirection(needDirection), needTarget(needTarget) {} -CombatSpell::~CombatSpell() -{ - if (!scripted) { - delete combat; - } -} - bool CombatSpell::loadScriptCombat() { combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId); @@ -313,12 +271,11 @@ bool CombatSpell::castSpell(Creature* creature) { if (scripted) { LuaVariant var; - var.type = VARIANT_POSITION; if (needDirection) { - var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + var.setPosition(Spells::getCasterPosition(creature, creature->getDirection())); } else { - var.pos = creature->getPosition(); + var.setPosition(creature->getPosition()); } return executeCastSpell(creature, var); @@ -341,18 +298,15 @@ bool CombatSpell::castSpell(Creature* creature, Creature* target) LuaVariant var; if (combat->hasArea()) { - var.type = VARIANT_POSITION; - if (needTarget) { - var.pos = target->getPosition(); + var.setPosition(target->getPosition()); } else if (needDirection) { - var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + var.setPosition(Spells::getCasterPosition(creature, creature->getDirection())); } else { - var.pos = creature->getPosition(); + var.setPosition(creature->getPosition()); } } else { - var.type = VARIANT_NUMBER; - var.number = target->getID(); + var.setNumber(target->getID()); } return executeCastSpell(creature, var); } @@ -371,7 +325,7 @@ bool CombatSpell::castSpell(Creature* creature, Creature* target) bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var) { - //onCastSpell(creature, var) + // onCastSpell(creature, var) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl; return false; @@ -403,35 +357,15 @@ bool Spell::configureSpell(const pugi::xml_node& node) name = nameAttribute.as_string(); static const char* reservedList[] = { - "melee", - "physical", - "poison", - "fire", - "energy", - "drown", - "lifedrain", - "manadrain", - "healing", - "speed", - "outfit", - "invisible", - "drunk", - "firefield", - "poisonfield", - "energyfield", - "firecondition", - "poisoncondition", - "energycondition", - "drowncondition", - "freezecondition", - "cursecondition", - "dazzlecondition" - }; - - //static size_t size = sizeof(reservedList) / sizeof(const char*); - //for (size_t i = 0; i < size; ++i) { + "melee", "physical", "poison", "fire", "energy", "drown", + "lifedrain", "manadrain", "healing", "speed", "outfit", "invisible", + "drunk", "firefield", "poisonfield", "energyfield", "firecondition", "poisoncondition", + "energycondition", "drowncondition", "freezecondition", "cursecondition", "dazzlecondition"}; + + // static size_t size = sizeof(reservedList) / sizeof(const char*); + // for (size_t i = 0; i < size; ++i) { for (const char* reserved : reservedList) { - if (strcasecmp(reserved, name.c_str()) == 0) { + if (caseInsensitiveEqual(reserved, name)) { std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; return false; } @@ -443,7 +377,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) } if ((attr = node.attribute("group"))) { - std::string tmpStr = asLowerCaseString(attr.as_string()); + std::string tmpStr = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStr == "none" || tmpStr == "0") { group = SPELLGROUP_NONE; } else if (tmpStr == "attack" || tmpStr == "1") { @@ -464,7 +398,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) } if ((attr = node.attribute("secondarygroup"))) { - std::string tmpStr = asLowerCaseString(attr.as_string()); + std::string tmpStr = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStr == "none" || tmpStr == "0") { secondaryGroup = SPELLGROUP_NONE; } else if (tmpStr == "attack" || tmpStr == "1") { @@ -542,7 +476,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) } if ((attr = node.attribute("blocktype"))) { - std::string tmpStrValue = asLowerCaseString(attr.as_string()); + std::string tmpStrValue = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStrValue == "all") { blockingSolid = true; blockingCreature = true; @@ -551,7 +485,8 @@ bool Spell::configureSpell(const pugi::xml_node& node) } else if (tmpStrValue == "creature") { blockingCreature = true; } else { - std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl; + std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." + << std::endl; } } @@ -597,7 +532,8 @@ bool Spell::playerSpellCheck(Player* player) const return false; } - if ((aggressive || pzLock) && (range < 1 || (range > 0 && !player->getAttackedCreature())) && player->getSkull() == SKULL_BLACK) { + if ((aggressive || pzLock) && (range < 1 || (range > 0 && !player->getAttackedCreature())) && + player->getSkull() == SKULL_BLACK) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return false; } @@ -608,12 +544,15 @@ bool Spell::playerSpellCheck(Player* player) const return false; } - if ((aggressive || pzLock) && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { + if ((aggressive || pzLock) && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && + player->getZone() == ZONE_PROTECTION) { player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); return false; } - if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { + if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || + player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || + (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); if (isInstant()) { @@ -706,14 +645,7 @@ bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos) g_game.map.setTile(toPos, tile); } - ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); - if (ret != RETURNVALUE_NOERROR) { - player->sendCancelMessage(ret); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) { + if (blockingCreature && tile->getBottomVisibleCreature(player)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -756,7 +688,7 @@ bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) return false; } - if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) { + if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, true, range, range)) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -788,7 +720,8 @@ bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) { const Player* targetPlayer = topVisibleCreature->getPlayer(); - if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { + if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && + !Combat::isInPvpZone(player, targetPlayer)) { player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -802,17 +735,20 @@ void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool pay if (finishedCast) { if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, + cooldown, 0, false, spellId); player->addCondition(condition); } if (groupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, + groupCooldown, 0, false, group); player->addCondition(condition); } if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, + secondaryGroupCooldown, 0, false, secondaryGroup); player->addCondition(condition); } } @@ -856,10 +792,7 @@ uint32_t Spell::getManaCost(const Player* player) const return 0; } -std::string InstantSpell::getScriptEventName() const -{ - return "onCastSpell"; -} +std::string InstantSpell::getScriptEventName() const { return "onCastSpell"; } bool InstantSpell::configureEvent(const pugi::xml_node& node) { @@ -903,8 +836,7 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) LuaVariant var; if (selfTarget) { - var.type = VARIANT_NUMBER; - var.number = player->getID(); + var.setNumber(player->getID()); } else if (needTarget || casterTargetOrDirection) { Creature* target = nullptr; bool useDirection = false; @@ -921,17 +853,21 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) if (!target || target->getHealth() <= 0) { if (!casterTargetOrDirection) { if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, + cooldown, 0, false, spellId); player->addCondition(condition); } if (groupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + Condition* condition = Condition::createCondition( + CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); player->addCondition(condition); } if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + Condition* condition = + Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, + secondaryGroupCooldown, 0, false, secondaryGroup); player->addCondition(condition); } @@ -966,36 +902,35 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) return false; } - var.type = VARIANT_NUMBER; - var.number = target->getID(); + var.setNumber(target->getID()); } else { - var.type = VARIANT_POSITION; - var.pos = Spells::getCasterPosition(player, player->getDirection()); + var.setPosition(Spells::getCasterPosition(player, player->getDirection())); - if (!playerInstantSpellCheck(player, var.pos)) { + if (!playerInstantSpellCheck(player, var.getPosition())) { return false; } } } else if (hasParam) { - var.type = VARIANT_STRING; - if (getHasPlayerNameParam()) { Player* playerTarget = nullptr; ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); if (ret != RETURNVALUE_NOERROR) { if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, + cooldown, 0, false, spellId); player->addCondition(condition); } if (groupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, + groupCooldown, 0, false, group); player->addCondition(condition); } if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, + secondaryGroupCooldown, 0, false, secondaryGroup); player->addCondition(condition); } @@ -1009,17 +944,15 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) } } - var.text = param; + var.setString(param); } else { - var.type = VARIANT_POSITION; - if (needDirection) { - var.pos = Spells::getCasterPosition(player, player->getDirection()); + var.setPosition(Spells::getCasterPosition(player, player->getDirection())); } else { - var.pos = player->getPosition(); + var.setPosition(player->getPosition()); } - if (!playerInstantSpellCheck(player, var.pos)) { + if (!playerInstantSpellCheck(player, var.getPosition())) { return false; } } @@ -1037,8 +970,9 @@ bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* targe const Position& fromPos = creature->getPosition(); const Position& toPos = target->getPosition(); if (fromPos.z != toPos.z || - (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) || - (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) { + (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, true, Map::maxClientViewportX - 1, + Map::maxClientViewportY - 1)) || + (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, true, range, range))) { return false; } return true; @@ -1055,18 +989,15 @@ bool InstantSpell::castSpell(Creature* creature) return false; } - var.type = VARIANT_NUMBER; - var.number = target->getID(); + var.setNumber(target->getID()); return internalCastSpell(creature, var); } return false; } else if (needDirection) { - var.type = VARIANT_POSITION; - var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + var.setPosition(Spells::getCasterPosition(creature, creature->getDirection())); } else { - var.type = VARIANT_POSITION; - var.pos = creature->getPosition(); + var.setPosition(creature->getPosition()); } return internalCastSpell(creature, var); @@ -1076,12 +1007,10 @@ bool InstantSpell::castSpell(Creature* creature, Creature* target) { if (needTarget) { LuaVariant var; - var.type = VARIANT_NUMBER; - var.number = target->getID(); + var.setNumber(target->getID()); return internalCastSpell(creature, var); - } else { - return castSpell(creature); } + return castSpell(creature); } bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) @@ -1091,7 +1020,7 @@ bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) { - //onCastSpell(creature, var) + // onCastSpell(creature, var) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl; return false; @@ -1135,10 +1064,7 @@ bool InstantSpell::canCast(const Player* player) const return false; } -std::string RuneSpell::getScriptEventName() const -{ - return "onCastSpell"; -} +std::string RuneSpell::getScriptEventName() const { return "onCastSpell"; } bool RuneSpell::configureEvent(const pugi::xml_node& node) { @@ -1167,7 +1093,7 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) hasCharges = (charges > 0); if (magLevel != 0 || level != 0) { - //Change information in the ItemType to get accurate description + // Change information in the ItemType to get accurate description ItemType& iType = Item::items.getItemType(runeId); iType.runeMagLevel = magLevel; iType.runeLevel = level; @@ -1199,7 +1125,8 @@ ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& to return RETURNVALUE_NOERROR; } -bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey) +bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, + bool isHotkey) { if (!playerRuneSpellCheck(player, toPosition)) { return false; @@ -1212,22 +1139,19 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t LuaVariant var; if (needTarget) { - var.type = VARIANT_NUMBER; - - if (target == nullptr) { + if (!target) { Tile* toTile = g_game.map.getTile(toPosition); if (toTile) { const Creature* visibleCreature = toTile->getBottomVisibleCreature(player); if (visibleCreature) { - var.number = visibleCreature->getID(); + var.setNumber(visibleCreature->getID()); } } } else { - var.number = target->getCreature()->getID(); + var.setNumber(target->getCreature()->getID()); } } else { - var.type = VARIANT_POSITION; - var.pos = toPosition; + var.setPosition(toPosition); } if (!internalCastSpell(player, var, isHotkey)) { @@ -1236,13 +1160,16 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t postCastSpell(player); - target = g_game.getCreatureByID(var.number); - if (getPzLock() && target) { - player->onAttackedCreature(target->getCreature()); + if (var.isNumber()) { + target = g_game.getCreatureByID(var.getNumber()); + if (getPzLock() && target) { + player->onAttackedCreature(target->getCreature()); + } } if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) { int32_t newCount = std::max(0, item->getItemCount() - 1); + player->sendSupplyUsed(item->getClientID()); g_game.transformItem(item, item->getID(), newCount); } return true; @@ -1251,16 +1178,14 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t bool RuneSpell::castSpell(Creature* creature) { LuaVariant var; - var.type = VARIANT_NUMBER; - var.number = creature->getID(); + var.setNumber(creature->getID()); return internalCastSpell(creature, var, false); } bool RuneSpell::castSpell(Creature* creature, Creature* target) { LuaVariant var; - var.type = VARIANT_NUMBER; - var.number = target->getID(); + var.setNumber(target->getID()); return internalCastSpell(creature, var, false); } @@ -1277,7 +1202,7 @@ bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, boo bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) { - //onCastSpell(creature, var, isHotkey) + // onCastSpell(creature, var, isHotkey) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl; return false; diff --git a/src/spells.h b/src/spells.h index 0f4d23da37..2e15c0e9bb 100644 --- a/src/spells.h +++ b/src/spells.h @@ -1,30 +1,14 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_SPELLS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 -#define FS_SPELLS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_SPELLS_H +#define FS_SPELLS_H -#include "luascript.h" -#include "player.h" #include "actions.h" -#include "talkaction.h" #include "baseevents.h" +#include "creature.h" +#include "luascript.h" +#include "talkaction.h" class InstantSpell; class RuneSpell; @@ -36,419 +20,277 @@ using RuneSpell_ptr = std::unique_ptr; class Spells final : public BaseEvents { - public: - Spells(); - ~Spells(); - - // non-copyable - Spells(const Spells&) = delete; - Spells& operator=(const Spells&) = delete; +public: + Spells(); + ~Spells(); - Spell* getSpellByName(const std::string& name); - RuneSpell* getRuneSpell(uint32_t id); - RuneSpell* getRuneSpellByName(const std::string& name); + // non-copyable + Spells(const Spells&) = delete; + Spells& operator=(const Spells&) = delete; - InstantSpell* getInstantSpell(const std::string& words); - InstantSpell* getInstantSpellByName(const std::string& name); + Spell* getSpellByName(const std::string& name); + RuneSpell* getRuneSpell(uint32_t id); + RuneSpell* getRuneSpellByName(const std::string& name); - InstantSpell* getInstantSpellById(uint32_t spellId); + InstantSpell* getInstantSpell(const std::string& words); + InstantSpell* getInstantSpellByName(const std::string& name); - TalkActionResult_t playerSaySpell(Player* player, std::string& words); + TalkActionResult_t playerSaySpell(Player* player, std::string& words); - static Position getCasterPosition(Creature* creature, Direction dir); - std::string getScriptBaseName() const override; + static Position getCasterPosition(Creature* creature, Direction dir); + std::string getScriptBaseName() const override; - const std::map& getInstantSpells() const { - return instants; - }; + const std::map& getInstantSpells() const { return instants; }; - void clearMaps(bool fromLua); - void clear(bool fromLua) override final; - bool registerInstantLuaEvent(InstantSpell* event); - bool registerRuneLuaEvent(RuneSpell* event); + void clearMaps(bool fromLua); + void clear(bool fromLua) override final; + bool registerInstantLuaEvent(InstantSpell* event); + bool registerRuneLuaEvent(RuneSpell* event); - private: - LuaScriptInterface& getScriptInterface() override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; +private: + LuaScriptInterface& getScriptInterface() override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - std::map runes; - std::map instants; + std::map runes; + std::map instants; - friend class CombatSpell; - LuaScriptInterface scriptInterface { "Spell Interface" }; + friend class CombatSpell; + LuaScriptInterface scriptInterface{"Spell Interface"}; }; -using RuneSpellFunction = std::function; - class BaseSpell { - public: - constexpr BaseSpell() = default; - virtual ~BaseSpell() = default; +public: + constexpr BaseSpell() = default; + virtual ~BaseSpell() = default; - virtual bool castSpell(Creature* creature) = 0; - virtual bool castSpell(Creature* creature, Creature* target) = 0; + virtual bool castSpell(Creature* creature) = 0; + virtual bool castSpell(Creature* creature, Creature* target) = 0; }; class CombatSpell final : public Event, public BaseSpell { - public: - CombatSpell(Combat* combat, bool needTarget, bool needDirection); - ~CombatSpell(); - - // non-copyable - CombatSpell(const CombatSpell&) = delete; - CombatSpell& operator=(const CombatSpell&) = delete; - - bool castSpell(Creature* creature) override; - bool castSpell(Creature* creature, Creature* target) override; - bool configureEvent(const pugi::xml_node&) override { - return true; - } +public: + CombatSpell(Combat_ptr combat, bool needTarget, bool needDirection); - //scripting - bool executeCastSpell(Creature* creature, const LuaVariant& var); + // non-copyable + CombatSpell(const CombatSpell&) = delete; + CombatSpell& operator=(const CombatSpell&) = delete; - bool loadScriptCombat(); - Combat* getCombat() { - return combat; - } + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; + bool configureEvent(const pugi::xml_node&) override { return true; } - private: - std::string getScriptEventName() const override { - return "onCastSpell"; - } + // scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool loadScriptCombat(); + Combat_ptr getCombat() { return combat; } - Combat* combat; +private: + std::string getScriptEventName() const override { return "onCastSpell"; } - bool needDirection; - bool needTarget; + Combat_ptr combat; + + bool needDirection; + bool needTarget; }; class Spell : public BaseSpell { - public: - Spell() = default; - - bool configureSpell(const pugi::xml_node& node); - const std::string& getName() const { - return name; - } - void setName(std::string n) { - name = n; - } - uint8_t getId() const { - return spellId; - } - void setId(uint8_t id) { - spellId = id; - } - - void postCastSpell(Player* player, bool finishedCast = true, bool payCost = true) const; - static void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost); - - uint32_t getManaCost(const Player* player) const; - uint32_t getSoulCost() const { - return soul; - } - void setSoulCost(uint32_t s) { - soul = s; - } - uint32_t getLevel() const { - return level; - } - void setLevel(uint32_t lvl) { - level = lvl; - } - uint32_t getMagicLevel() const { - return magLevel; - } - void setMagicLevel(uint32_t lvl) { - magLevel = lvl; - } - uint32_t getMana() const { - return mana; - } - void setMana(uint32_t m) { - mana = m; - } - uint32_t getManaPercent() const { - return manaPercent; - } - void setManaPercent(uint32_t m) { - manaPercent = m; - } - bool isPremium() const { - return premium; - } - void setPremium(bool p) { - premium = p; - } - bool isEnabled() const { - return enabled; - } - void setEnabled(bool e) { - enabled = e; - } - - virtual bool isInstant() const = 0; - bool isLearnable() const { - return learnable; - } - void setLearnable(bool l) { - learnable = l; - } - - const VocSpellMap& getVocMap() const { - return vocSpellMap; - } - void addVocMap(uint16_t n, bool b) { - vocSpellMap[n] = b; - } - - const SpellGroup_t getGroup() const { - return group; - } - void setGroup(SpellGroup_t g) { - group = g; - } - const SpellGroup_t getSecondaryGroup() const { - return secondaryGroup; - } - void setSecondaryGroup(SpellGroup_t g) { - secondaryGroup = g; - } - - uint32_t getCooldown() const { - return cooldown; - } - void setCooldown(uint32_t cd) { - cooldown = cd; - } - uint32_t getSecondaryCooldown() const { - return secondaryGroupCooldown; - } - void setSecondaryCooldown(uint32_t cd) { - secondaryGroupCooldown = cd; - } - uint32_t getGroupCooldown() const { - return groupCooldown; - } - void setGroupCooldown(uint32_t cd) { - groupCooldown = cd; - } - - int32_t getRange() const { - return range; - } - void setRange(int32_t r) { - range = r; - } - - bool getNeedTarget() const { - return needTarget; - } - void setNeedTarget(bool n) { - needTarget = n; - } - bool getNeedWeapon() const { - return needWeapon; - } - void setNeedWeapon(bool n) { - needWeapon = n; - } - bool getNeedLearn() const { - return learnable; - } - void setNeedLearn(bool n) { - learnable = n; - } - bool getSelfTarget() const { - return selfTarget; - } - void setSelfTarget(bool s) { - selfTarget = s; - } - bool getBlockingSolid() const { - return blockingSolid; - } - void setBlockingSolid(bool b) { - blockingSolid = b; - } - bool getBlockingCreature() const { - return blockingCreature; - } - void setBlockingCreature(bool b) { - blockingCreature = b; - } - bool getAggressive() const { - return aggressive; - } - void setAggressive(bool a) { - aggressive = a; - } - bool getPzLock() const { - return pzLock; - } - void setPzLock(bool pzLock) { - this->pzLock = pzLock; - } - - SpellType_t spellType = SPELL_UNDEFINED; - - protected: - bool playerSpellCheck(Player* player) const; - bool playerInstantSpellCheck(Player* player, const Position& toPos); - bool playerRuneSpellCheck(Player* player, const Position& toPos); - - VocSpellMap vocSpellMap; - - SpellGroup_t group = SPELLGROUP_NONE; - SpellGroup_t secondaryGroup = SPELLGROUP_NONE; - - uint32_t cooldown = 1000; - uint32_t groupCooldown = 1000; - uint32_t secondaryGroupCooldown = 0; - uint32_t level = 0; - uint32_t magLevel = 0; - int32_t range = -1; - - uint8_t spellId = 0; - - bool selfTarget = false; - bool needTarget = false; - - private: - uint32_t mana = 0; - uint32_t manaPercent = 0; - uint32_t soul = 0; - - bool needWeapon = false; - bool blockingSolid = false; - bool blockingCreature = false; - bool aggressive = true; - bool pzLock = false; - bool learnable = false; - bool enabled = true; - bool premium = false; - - std::string name; +public: + Spell() = default; + + bool configureSpell(const pugi::xml_node& node); + const std::string& getName() const { return name; } + void setName(std::string n) { name = n; } + uint8_t getId() const { return spellId; } + void setId(uint8_t id) { spellId = id; } + + void postCastSpell(Player* player, bool finishedCast = true, bool payCost = true) const; + static void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost); + + uint32_t getManaCost(const Player* player) const; + uint32_t getSoulCost() const { return soul; } + void setSoulCost(uint32_t s) { soul = s; } + uint32_t getLevel() const { return level; } + void setLevel(uint32_t lvl) { level = lvl; } + uint32_t getMagicLevel() const { return magLevel; } + void setMagicLevel(uint32_t lvl) { magLevel = lvl; } + uint32_t getMana() const { return mana; } + void setMana(uint32_t m) { mana = m; } + uint32_t getManaPercent() const { return manaPercent; } + void setManaPercent(uint32_t m) { manaPercent = m; } + bool isPremium() const { return premium; } + void setPremium(bool p) { premium = p; } + bool isEnabled() const { return enabled; } + void setEnabled(bool e) { enabled = e; } + + virtual bool isInstant() const = 0; + bool isLearnable() const { return learnable; } + void setLearnable(bool l) { learnable = l; } + + const VocSpellMap& getVocMap() const { return vocSpellMap; } + void addVocMap(uint16_t n, bool b) { vocSpellMap[n] = b; } + + const SpellGroup_t getGroup() const { return group; } + void setGroup(SpellGroup_t g) { group = g; } + const SpellGroup_t getSecondaryGroup() const { return secondaryGroup; } + void setSecondaryGroup(SpellGroup_t g) { secondaryGroup = g; } + + uint32_t getCooldown() const { return cooldown; } + void setCooldown(uint32_t cd) { cooldown = cd; } + uint32_t getSecondaryCooldown() const { return secondaryGroupCooldown; } + void setSecondaryCooldown(uint32_t cd) { secondaryGroupCooldown = cd; } + uint32_t getGroupCooldown() const { return groupCooldown; } + void setGroupCooldown(uint32_t cd) { groupCooldown = cd; } + + int32_t getRange() const { return range; } + void setRange(int32_t r) { range = r; } + + bool getNeedTarget() const { return needTarget; } + void setNeedTarget(bool n) { needTarget = n; } + bool getNeedWeapon() const { return needWeapon; } + void setNeedWeapon(bool n) { needWeapon = n; } + bool getNeedLearn() const { return learnable; } + void setNeedLearn(bool n) { learnable = n; } + bool getSelfTarget() const { return selfTarget; } + void setSelfTarget(bool s) { selfTarget = s; } + bool getBlockingSolid() const { return blockingSolid; } + void setBlockingSolid(bool b) { blockingSolid = b; } + bool getBlockingCreature() const { return blockingCreature; } + void setBlockingCreature(bool b) { blockingCreature = b; } + bool getAggressive() const { return aggressive; } + void setAggressive(bool a) { aggressive = a; } + bool getPzLock() const { return pzLock; } + void setPzLock(bool pzLock) { this->pzLock = pzLock; } + + SpellType_t spellType = SPELL_UNDEFINED; + +protected: + bool playerSpellCheck(Player* player) const; + bool playerInstantSpellCheck(Player* player, const Position& toPos); + bool playerRuneSpellCheck(Player* player, const Position& toPos); + + VocSpellMap vocSpellMap; + + SpellGroup_t group = SPELLGROUP_NONE; + SpellGroup_t secondaryGroup = SPELLGROUP_NONE; + + uint32_t cooldown = 1000; + uint32_t groupCooldown = 1000; + uint32_t secondaryGroupCooldown = 0; + uint32_t level = 0; + uint32_t magLevel = 0; + int32_t range = -1; + + uint8_t spellId = 0; + + bool selfTarget = false; + bool needTarget = false; + +private: + uint32_t mana = 0; + uint32_t manaPercent = 0; + uint32_t soul = 0; + + bool needWeapon = false; + bool blockingSolid = false; + bool blockingCreature = false; + bool aggressive = true; + bool pzLock = false; + bool learnable = false; + bool enabled = true; + bool premium = false; + + std::string name; }; class InstantSpell final : public TalkAction, public Spell { - public: - explicit InstantSpell(LuaScriptInterface* interface) : TalkAction(interface) {} - - bool configureEvent(const pugi::xml_node& node) override; - - virtual bool playerCastInstant(Player* player, std::string& param); - - bool castSpell(Creature* creature) override; - bool castSpell(Creature* creature, Creature* target) override; - - //scripting - bool executeCastSpell(Creature* creature, const LuaVariant& var); - - bool isInstant() const override { - return true; - } - bool getHasParam() const { - return hasParam; - } - void setHasParam(bool p) { - hasParam = p; - } - bool getHasPlayerNameParam() const { - return hasPlayerNameParam; - } - void setHasPlayerNameParam(bool p) { - hasPlayerNameParam = p; - } - bool getNeedDirection() const { - return needDirection; - } - void setNeedDirection(bool n) { - needDirection = n; - } - bool getNeedCasterTargetOrDirection() const { - return casterTargetOrDirection; - } - void setNeedCasterTargetOrDirection(bool d) { - casterTargetOrDirection = d; - } - bool getBlockWalls() const { - return checkLineOfSight; - } - void setBlockWalls(bool w) { - checkLineOfSight = w; - } - bool canCast(const Player* player) const; - bool canThrowSpell(const Creature* creature, const Creature* target) const; - - private: - std::string getScriptEventName() const override; - - bool internalCastSpell(Creature* creature, const LuaVariant& var); - - bool needDirection = false; - bool hasParam = false; - bool hasPlayerNameParam = false; - bool checkLineOfSight = true; - bool casterTargetOrDirection = false; +public: + explicit InstantSpell(LuaScriptInterface* interface) : TalkAction(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + + virtual bool playerCastInstant(Player* player, std::string& param); + + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; + + // scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool isInstant() const override { return true; } + bool getHasParam() const { return hasParam; } + void setHasParam(bool p) { hasParam = p; } + bool getHasPlayerNameParam() const { return hasPlayerNameParam; } + void setHasPlayerNameParam(bool p) { hasPlayerNameParam = p; } + bool getNeedDirection() const { return needDirection; } + void setNeedDirection(bool n) { needDirection = n; } + bool getNeedCasterTargetOrDirection() const { return casterTargetOrDirection; } + void setNeedCasterTargetOrDirection(bool d) { casterTargetOrDirection = d; } + bool getBlockWalls() const { return checkLineOfSight; } + void setBlockWalls(bool w) { checkLineOfSight = w; } + bool canCast(const Player* player) const; + bool canThrowSpell(const Creature* creature, const Creature* target) const; + +private: + std::string getScriptEventName() const override; + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + + bool needDirection = false; + bool hasParam = false; + bool hasPlayerNameParam = false; + bool checkLineOfSight = true; + bool casterTargetOrDirection = false; }; class RuneSpell final : public Action, public Spell { - public: - explicit RuneSpell(LuaScriptInterface* interface) : Action(interface) {} +public: + explicit RuneSpell(LuaScriptInterface* interface) : Action(interface) {} - bool configureEvent(const pugi::xml_node& node) override; + bool configureEvent(const pugi::xml_node& node) override; - ReturnValue canExecuteAction(const Player* player, const Position& toPos) override; - bool hasOwnErrorHandler() override { - return true; - } - Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const override { - return targetCreature; - } + ReturnValue canExecuteAction(const Player* player, const Position& toPos) override; + bool hasOwnErrorHandler() override { return true; } + Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const override + { + return targetCreature; + } - bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) override; + bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, + bool isHotkey) override; - bool castSpell(Creature* creature) override; - bool castSpell(Creature* creature, Creature* target) override; + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; - //scripting - bool executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); + // scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); - bool isInstant() const override { - return false; - } - uint16_t getRuneItemId() const { - return runeId; - } - void setRuneItemId(uint16_t i) { - runeId = i; - } - uint32_t getCharges() const { - return charges; - } - void setCharges(uint32_t c) { - if (c > 0) { - hasCharges = true; - } - charges = c; + bool isInstant() const override { return false; } + uint16_t getRuneItemId() const { return runeId; } + void setRuneItemId(uint16_t i) { runeId = i; } + uint32_t getCharges() const { return charges; } + void setCharges(uint32_t c) + { + if (c > 0) { + hasCharges = true; } + charges = c; + } - private: - std::string getScriptEventName() const override; +private: + std::string getScriptEventName() const override; - bool internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); + bool internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); - uint16_t runeId = 0; - uint32_t charges = 0; - bool hasCharges = false; + uint16_t runeId = 0; + uint32_t charges = 0; + bool hasCharges = false; }; -#endif +#endif // FS_SPELLS_H diff --git a/src/storeinbox.cpp b/src/storeinbox.cpp index 0ee75c9f70..33e9a4b2eb 100644 --- a/src/storeinbox.cpp +++ b/src/storeinbox.cpp @@ -1,21 +1,5 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" @@ -23,7 +7,7 @@ StoreInbox::StoreInbox(uint16_t type) : Container(type, 20, true, true) {} -ReturnValue StoreInbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t, Creature*) const +ReturnValue StoreInbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const { const Item* item = thing.getItem(); if (!item) { @@ -38,13 +22,15 @@ ReturnValue StoreInbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t return RETURNVALUE_CANNOTPICKUP; } - if (!item->isStoreItem()) { - return RETURNVALUE_CANNOTMOVEITEMISNOTSTOREITEM; - } + if (!hasBitSet(FLAG_NOLIMIT, flags)) { + if (!item->isStoreItem()) { + return RETURNVALUE_CANNOTMOVEITEMISNOTSTOREITEM; + } - const Container* container = item->getContainer(); - if (container && !container->empty()) { - return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE; + const Container* container = item->getContainer(); + if (container && !container->empty()) { + return RETURNVALUE_ITEMCANNOTBEMOVEDTHERE; + } } return RETURNVALUE_NOERROR; @@ -52,15 +38,14 @@ ReturnValue StoreInbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t void StoreInbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) { - if (parent != nullptr) { - parent->postAddNotification(thing, oldParent, index, LINK_PARENT); + if (parent) { + parent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT); } } void StoreInbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) { - if (parent != nullptr) { - parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + if (parent) { + parent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT); } } - diff --git a/src/storeinbox.h b/src/storeinbox.h index d1b35ad1e5..812a2affae 100644 --- a/src/storeinbox.h +++ b/src/storeinbox.h @@ -1,49 +1,29 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_STOREINBOX_H_074FB99DD3FEDB823AAD2D2CD6F10119 -#define FS_STOREINBOX_H_074FB99DD3FEDB823AAD2D2CD6F10119 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_STOREINBOX_H +#define FS_STOREINBOX_H #include "container.h" class StoreInbox final : public Container { - public: - explicit StoreInbox(uint16_t type); - - StoreInbox* getStoreInbox() override { - return this; - } - const StoreInbox* getStoreInbox() const override { - return this; - } - - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - - bool canRemove() const override { - return false; - } +public: + explicit StoreInbox(uint16_t type); + + StoreInbox* getStoreInbox() override { return this; } + const StoreInbox* getStoreInbox() const override { return this; } + + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + + bool canRemove() const override { return false; } }; -#endif +#endif // FS_STOREINBOX_H diff --git a/src/talkaction.cpp b/src/talkaction.cpp index 61678aaa63..b4b3d710bb 100644 --- a/src/talkaction.cpp +++ b/src/talkaction.cpp @@ -1,42 +1,19 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include "player.h" #include "talkaction.h" -#include "pugicast.h" -TalkActions::TalkActions() - : scriptInterface("TalkAction Interface") -{ - scriptInterface.initState(); -} +#include "player.h" -TalkActions::~TalkActions() -{ - clear(false); -} +TalkActions::TalkActions() : scriptInterface("TalkAction Interface") { scriptInterface.initState(); } + +TalkActions::~TalkActions() { clear(false); } void TalkActions::clear(bool fromLua) { - for (auto it = talkActions.begin(); it != talkActions.end(); ) { + for (auto it = talkActions.begin(); it != talkActions.end();) { if (fromLua == it->second.fromLua) { it = talkActions.erase(it); } else { @@ -47,19 +24,13 @@ void TalkActions::clear(bool fromLua) reInitState(fromLua); } -LuaScriptInterface& TalkActions::getScriptInterface() -{ - return scriptInterface; -} +LuaScriptInterface& TalkActions::getScriptInterface() { return scriptInterface; } -std::string TalkActions::getScriptBaseName() const -{ - return "talkactions"; -} +std::string TalkActions::getScriptBaseName() const { return "talkactions"; } Event_ptr TalkActions::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "talkaction") != 0) { + if (!caseInsensitiveEqual(nodeName, "talkaction")) { return nullptr; } return Event_ptr(new TalkAction(&scriptInterface)); @@ -83,7 +54,7 @@ bool TalkActions::registerEvent(Event_ptr event, const pugi::xml_node&) bool TalkActions::registerLuaEvent(TalkAction* event) { - TalkAction_ptr talkAction{ event }; + TalkAction_ptr talkAction{event}; std::vector words = talkAction->getWordsMap(); for (size_t i = 0; i < words.size(); i++) { @@ -100,22 +71,21 @@ bool TalkActions::registerLuaEvent(TalkAction* event) TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const { size_t wordsLength = words.length(); - for (auto it = talkActions.begin(); it != talkActions.end(); ) { + for (auto it = talkActions.begin(); it != talkActions.end();) { const std::string& talkactionWords = it->first; - size_t talkactionLength = talkactionWords.length(); - if (wordsLength < talkactionLength || strncasecmp(words.c_str(), talkactionWords.c_str(), talkactionLength) != 0) { + if (!caseInsensitiveStartsWith(words, talkactionWords)) { ++it; continue; } std::string param; - if (wordsLength != talkactionLength) { - param = words.substr(talkactionLength); + if (wordsLength != talkactionWords.size()) { + param = words.substr(talkactionWords.size()); if (param.front() != ' ') { ++it; continue; } - trim_left(param, ' '); + boost::algorithm::trim_left(param); std::string separator = it->second.getSeparator(); if (separator != " ") { @@ -136,15 +106,14 @@ TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type } if (player->getAccountType() < it->second.getRequiredAccountType()) { - return TALKACTION_BREAK; + return TALKACTION_CONTINUE; } } - if (it->second.executeSay(player, words, param, type)) { + if (it->second.executeSay(player, talkactionWords, param, type)) { return TALKACTION_CONTINUE; - } else { - return TALKACTION_BREAK; } + return TALKACTION_BREAK; } return TALKACTION_CONTINUE; } @@ -159,7 +128,7 @@ bool TalkAction::configureEvent(const pugi::xml_node& node) pugi::xml_attribute separatorAttribute = node.attribute("separator"); if (separatorAttribute) { - separator = pugi::cast(separatorAttribute.value()); + separator = separatorAttribute.as_string(); } for (auto word : explodeString(wordsAttribute.as_string(), ";")) { @@ -168,14 +137,11 @@ bool TalkAction::configureEvent(const pugi::xml_node& node) return true; } -std::string TalkAction::getScriptEventName() const -{ - return "onSay"; -} +std::string TalkAction::getScriptEventName() const { return "onSay"; } bool TalkAction::executeSay(Player* player, const std::string& words, const std::string& param, SpeakClasses type) const { - //onSay(player, words, param, type) + // onSay(player, words, param, type) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - TalkAction::executeSay] Call stack overflow" << std::endl; return false; diff --git a/src/talkaction.h b/src/talkaction.h index 7468af7436..e2e94e5a14 100644 --- a/src/talkaction.h +++ b/src/talkaction.h @@ -1,33 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_TALKACTION_H_E6AABAC0F89843469526ADF310F3131C -#define FS_TALKACTION_H_E6AABAC0F89843469526ADF310F3131C +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_TALKACTION_H +#define FS_TALKACTION_H -#include "luascript.h" #include "baseevents.h" #include "const.h" +#include "luascript.h" class TalkAction; using TalkAction_ptr = std::unique_ptr; -enum TalkActionResult_t { +enum TalkActionResult_t +{ TALKACTION_CONTINUE, TALKACTION_BREAK, TALKACTION_FAILED, @@ -35,81 +20,66 @@ enum TalkActionResult_t { class TalkAction : public Event { - public: - explicit TalkAction(LuaScriptInterface* interface) : Event(interface) {} - - bool configureEvent(const pugi::xml_node& node) override; - - const std::string& getWords() const { - return words; - } - const std::vector& getWordsMap() const { - return wordsMap; - } - void setWords(std::string word) { - words = word; - wordsMap.push_back(word); - } - std::string getSeparator() const { - return separator; - } - void setSeparator(std::string sep) { - separator = sep; - } - - //scripting - bool executeSay(Player* player, const std::string& words, const std::string& param, SpeakClasses type) const; - - AccountType_t getRequiredAccountType() const { - return requiredAccountType; - } - - void setRequiredAccountType(AccountType_t reqAccType) { - requiredAccountType = reqAccType; - } - - bool getNeedAccess() const { - return needAccess; - } - - void setNeedAccess(bool b) { - needAccess = b; - } - - private: - std::string getScriptEventName() const override; - - std::string words; - std::vector wordsMap; - std::string separator = "\""; - bool needAccess = false; - AccountType_t requiredAccountType = ACCOUNT_TYPE_NORMAL; +public: + explicit TalkAction(LuaScriptInterface* interface) : Event(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + + const std::string& getWords() const { return words; } + const std::vector& getWordsMap() const { return wordsMap; } + void setWords(std::string word) + { + words = word; + wordsMap.push_back(word); + } + std::string getSeparator() const { return separator; } + void setSeparator(std::string sep) { separator = sep; } + + // scripting + bool executeSay(Player* player, const std::string& words, const std::string& param, SpeakClasses type) const; + + AccountType_t getRequiredAccountType() const { return requiredAccountType; } + + void setRequiredAccountType(AccountType_t reqAccType) { requiredAccountType = reqAccType; } + + bool getNeedAccess() const { return needAccess; } + + void setNeedAccess(bool b) { needAccess = b; } + +private: + std::string getScriptEventName() const override; + + std::string words; + std::vector wordsMap; + std::string separator = "\""; + bool needAccess = false; + AccountType_t requiredAccountType = ACCOUNT_TYPE_NORMAL; }; class TalkActions final : public BaseEvents { - public: - TalkActions(); - ~TalkActions(); +public: + TalkActions(); + ~TalkActions(); - // non-copyable - TalkActions(const TalkActions&) = delete; - TalkActions& operator=(const TalkActions&) = delete; + // non-copyable + TalkActions(const TalkActions&) = delete; + TalkActions& operator=(const TalkActions&) = delete; - TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; - bool registerLuaEvent(TalkAction* event); - void clear(bool fromLua) override final; + bool registerLuaEvent(TalkAction* event); + void clear(bool fromLua) override final; - private: - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; +private: + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - std::map talkActions; + std::map talkActions; - LuaScriptInterface scriptInterface; + LuaScriptInterface scriptInterface; }; -#endif +#endif // FS_TALKACTION_H diff --git a/src/tasks.cpp b/src/tasks.cpp index b792aeb81f..a5528fb6ee 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -1,38 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "tasks.h" + +#include "enums.h" #include "game.h" extern Game g_game; -Task* createTask(TaskFunc&& f) -{ - return new Task(std::move(f)); -} +Task* createTask(TaskFunc&& f) { return new Task(std::move(f)); } -Task* createTask(uint32_t expiration, TaskFunc&& f) -{ - return new Task(expiration, std::move(f)); -} +Task* createTask(uint32_t expiration, TaskFunc&& f) { return new Task(expiration, std::move(f)); } void Dispatcher::threadMain() { @@ -44,7 +24,7 @@ void Dispatcher::threadMain() // check if there are tasks waiting taskLockUnique.lock(); if (taskList.empty()) { - //if the list is empty wait for signal + // if the list is empty wait for signal taskSignal.wait(taskLockUnique); } tmpTaskList.swap(taskList); diff --git a/src/tasks.h b/src/tasks.h index d0443f9d8a..50fc8546e0 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -1,28 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_TASKS_H_A66AC384766041E59DCA059DAB6E1976 -#define FS_TASKS_H_A66AC384766041E59DCA059DAB6E1976 - -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_TASKS_H +#define FS_TASKS_H + #include "thread_holder_base.h" -#include "enums.h" using TaskFunc = std::function; const int DISPATCHER_TASK_EXPIRATION = 2000; @@ -30,61 +12,57 @@ const auto SYSTEM_TIME_ZERO = std::chrono::system_clock::time_point(std::chrono: class Task { - public: - // DO NOT allocate this class on the stack - explicit Task(TaskFunc&& f) : func(std::move(f)) {} - Task(uint32_t ms, TaskFunc&& f) : - expiration(std::chrono::system_clock::now() + std::chrono::milliseconds(ms)), func(std::move(f)) {} - - virtual ~Task() = default; - void operator()() { - func(); - } - - void setDontExpire() { - expiration = SYSTEM_TIME_ZERO; +public: + // DO NOT allocate this class on the stack + explicit Task(TaskFunc&& f) : func(std::move(f)) {} + Task(uint32_t ms, TaskFunc&& f) : + expiration(std::chrono::system_clock::now() + std::chrono::milliseconds(ms)), func(std::move(f)) + {} + + virtual ~Task() = default; + void operator()() { func(); } + + void setDontExpire() { expiration = SYSTEM_TIME_ZERO; } + + bool hasExpired() const + { + if (expiration == SYSTEM_TIME_ZERO) { + return false; } + return expiration < std::chrono::system_clock::now(); + } - bool hasExpired() const { - if (expiration == SYSTEM_TIME_ZERO) { - return false; - } - return expiration < std::chrono::system_clock::now(); - } - - protected: - std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; +protected: + std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; - private: - // Expiration has another meaning for scheduler tasks, - // then it is the time the task should be added to the - // dispatcher - TaskFunc func; +private: + // Expiration has another meaning for scheduler tasks, then it is the time the task should be added to the + // dispatcher + TaskFunc func; }; Task* createTask(TaskFunc&& f); Task* createTask(uint32_t expiration, TaskFunc&& f); -class Dispatcher : public ThreadHolder { - public: - void addTask(Task* task); +class Dispatcher : public ThreadHolder +{ +public: + void addTask(Task* task); - void shutdown(); + void shutdown(); - uint64_t getDispatcherCycle() const { - return dispatcherCycle; - } + uint64_t getDispatcherCycle() const { return dispatcherCycle; } - void threadMain(); + void threadMain(); - private: - std::mutex taskLock; - std::condition_variable taskSignal; +private: + std::mutex taskLock; + std::condition_variable taskSignal; - std::vector taskList; - uint64_t dispatcherCycle = 0; + std::vector taskList; + uint64_t dispatcherCycle = 0; }; extern Dispatcher g_dispatcher; -#endif +#endif // FS_TASKS_H diff --git a/src/teleport.cpp b/src/teleport.cpp index a1d132cada..f6dee1510b 100644 --- a/src/teleport.cpp +++ b/src/teleport.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "teleport.h" + #include "game.h" extern Game g_game; @@ -27,7 +12,8 @@ extern Game g_game; Attr_ReadValue Teleport::readAttr(AttrTypes_t attr, PropStream& propStream) { if (attr == ATTR_TELE_DEST) { - if (!propStream.read(destPos.x) || !propStream.read(destPos.y) || !propStream.read(destPos.z)) { + if (!propStream.read(destPos.x) || !propStream.read(destPos.y) || + !propStream.read(destPos.z)) { return ATTR_READ_ERROR; } return ATTR_READ_CONTINUE; @@ -60,31 +46,9 @@ ReturnValue Teleport::queryRemove(const Thing&, uint32_t, uint32_t, Creature* /* return RETURNVALUE_NOERROR; } -Cylinder* Teleport::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) -{ - return this; -} +Cylinder* Teleport::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) { return this; } -void Teleport::addThing(Thing* thing) -{ - return addThing(0, thing); -} - -bool Teleport::checkInfinityLoop(Tile* destTile) -{ - if (!destTile) { - return false; - } - - if (Teleport* teleport = destTile->getTeleportItem()) { - const Position& nextDestPos = teleport->getDestPos(); - if (getPosition() == nextDestPos) { - return true; - } - return checkInfinityLoop(g_game.map.getTile(nextDestPos)); - } - return false; -} +void Teleport::addThing(Thing* thing) { return addThing(0, thing); } void Teleport::addThing(int32_t, Thing* thing) { @@ -93,11 +57,30 @@ void Teleport::addThing(int32_t, Thing* thing) return; } - // Prevent infinity loop - if (checkInfinityLoop(destTile)) { - const Position& pos = getPosition(); - std::cout << "Warning: infinity loop teleport. " << pos << std::endl; - return; + // Prevent infinite loop + Teleport* destTeleport = destTile->getTeleportItem(); + if (destTeleport) { + std::vector lastPositions = {getPosition()}; + + while (true) { + const Position& nextPos = destTeleport->getDestPos(); + if (std::find(lastPositions.begin(), lastPositions.end(), nextPos) != lastPositions.end()) { + std::cout << "Warning: possible infinite loop teleport. " << nextPos << std::endl; + return; + } + + const Tile* tile = g_game.map.getTile(nextPos); + if (!tile) { + break; + } + + destTeleport = tile->getTeleportItem(); + if (!destTeleport) { + break; + } + + lastPositions.push_back(nextPos); + } } const MagicEffectClasses effect = Item::items[id].magicEffect; @@ -115,7 +98,8 @@ void Teleport::addThing(int32_t, Thing* thing) g_game.addMagicEffect(destTile->getPosition(), effect); g_game.addMagicEffect(item->getPosition(), effect); } - g_game.internalMoveItem(getTile(), destTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + g_game.internalMoveItem(getTile(), destTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, + FLAG_NOLIMIT); } } diff --git a/src/teleport.h b/src/teleport.h index 6c8a478f6d..4b73d3d277 100644 --- a/src/teleport.h +++ b/src/teleport.h @@ -1,74 +1,50 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_TELEPORT_H_873B7F7F1DB24101A7ACFB54B25E0ABC -#define FS_TELEPORT_H_873B7F7F1DB24101A7ACFB54B25E0ABC +#ifndef FS_TELEPORT_H +#define FS_TELEPORT_H -#include "tile.h" +#include "item.h" class Teleport final : public Item, public Cylinder { - public: - explicit Teleport(uint16_t type) : Item(type) {}; +public: + explicit Teleport(uint16_t type) : Item(type){}; - Teleport* getTeleport() override { - return this; - } - const Teleport* getTeleport() const override { - return this; - } + Teleport* getTeleport() override { return this; } + const Teleport* getTeleport() const override { return this; } - //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - void serializeAttr(PropWriteStream& propWriteStream) const override; + // serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream& propWriteStream) const override; - const Position& getDestPos() const { - return destPos; - } - void setDestPos(Position pos) { - destPos = std::move(pos); - } + const Position& getDestPos() const { return destPos; } + void setDestPos(const Position& pos) { destPos = pos; } - bool checkInfinityLoop(Tile* destTile); + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, - uint32_t& maxQueryCount, uint32_t flags) const override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; - Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) override; + void addThing(Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; - void addThing(Thing* thing) override; - void addThing(int32_t index, Thing* thing) override; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; + void removeThing(Thing* thing, uint32_t count) override; - void removeThing(Thing* thing, uint32_t count) override; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - - private: - Position destPos; +private: + Position destPos; }; -#endif +#endif // FS_TELEPORT_H diff --git a/src/thing.cpp b/src/thing.cpp index abda31fb54..6468c60ec6 100644 --- a/src/thing.cpp +++ b/src/thing.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "thing.h" + #include "tile.h" const Position& Thing::getPosition() const @@ -31,12 +16,6 @@ const Position& Thing::getPosition() const return tile->getPosition(); } -Tile* Thing::getTile() -{ - return dynamic_cast(this); -} +Tile* Thing::getTile() { return dynamic_cast(this); } -const Tile* Thing::getTile() const -{ - return dynamic_cast(this); -} +const Tile* Thing::getTile() const { return dynamic_cast(this); } diff --git a/src/thing.h b/src/thing.h index 3fee9d2b88..f6ad95453b 100644 --- a/src/thing.h +++ b/src/thing.h @@ -1,85 +1,51 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_THING_H_6F16A8E566AF4ACEAE02CF32A7246144 -#define FS_THING_H_6F16A8E566AF4ACEAE02CF32A7246144 +#ifndef FS_THING_H +#define FS_THING_H -#include "position.h" - -class Tile; +class Container; +class Creature; class Cylinder; class Item; -class Creature; -class Container; +class Tile; +struct Position; class Thing { - public: - constexpr Thing() = default; - virtual ~Thing() = default; +public: + constexpr Thing() = default; + virtual ~Thing() = default; - // non-copyable - Thing(const Thing&) = delete; - Thing& operator=(const Thing&) = delete; + // non-copyable + Thing(const Thing&) = delete; + Thing& operator=(const Thing&) = delete; - virtual std::string getDescription(int32_t lookDistance) const = 0; + virtual std::string getDescription(int32_t lookDistance) const = 0; - virtual Cylinder* getParent() const { - return nullptr; - } - virtual Cylinder* getRealParent() const { - return getParent(); - } + virtual Cylinder* getParent() const { return nullptr; } + virtual Cylinder* getRealParent() const { return getParent(); } - virtual void setParent(Cylinder*) { - // - } + virtual void setParent(Cylinder*) + { + // + } - virtual Tile* getTile(); - virtual const Tile* getTile() const; + virtual Tile* getTile(); + virtual const Tile* getTile() const; - virtual const Position& getPosition() const; - virtual int32_t getThrowRange() const = 0; - virtual bool isPushable() const = 0; + virtual const Position& getPosition() const; + virtual int32_t getThrowRange() const = 0; + virtual bool isPushable() const = 0; - virtual Container* getContainer() { - return nullptr; - } - virtual const Container* getContainer() const { - return nullptr; - } - virtual Item* getItem() { - return nullptr; - } - virtual const Item* getItem() const { - return nullptr; - } - virtual Creature* getCreature() { - return nullptr; - } - virtual const Creature* getCreature() const { - return nullptr; - } + virtual Container* getContainer() { return nullptr; } + virtual const Container* getContainer() const { return nullptr; } + virtual Item* getItem() { return nullptr; } + virtual const Item* getItem() const { return nullptr; } + virtual Creature* getCreature() { return nullptr; } + virtual const Creature* getCreature() const { return nullptr; } - virtual bool isRemoved() const { - return true; - } + virtual bool isRemoved() const { return true; } }; -#endif +#endif // FS_THING_H diff --git a/src/thread_holder_base.h b/src/thread_holder_base.h index 1ab206bdaa..9b51007d34 100644 --- a/src/thread_holder_base.h +++ b/src/thread_holder_base.h @@ -1,59 +1,39 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_THREAD_HOLDER_H_BEB56FC46748E71D15A5BF0773ED2E67 -#define FS_THREAD_HOLDER_H_BEB56FC46748E71D15A5BF0773ED2E67 - -#include -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_THREAD_HOLDER_BASE_H +#define FS_THREAD_HOLDER_BASE_H + #include "enums.h" template class ThreadHolder { - public: - ThreadHolder() {} - void start() { - setState(THREAD_STATE_RUNNING); - thread = std::thread(&Derived::threadMain, static_cast(this)); - } +public: + ThreadHolder() {} + void start() + { + setState(THREAD_STATE_RUNNING); + thread = std::thread(&Derived::threadMain, static_cast(this)); + } - void stop() { - setState(THREAD_STATE_CLOSING); - } + void stop() { setState(THREAD_STATE_CLOSING); } - void join() { - if (thread.joinable()) { - thread.join(); - } - } - protected: - void setState(ThreadState newState) { - threadState.store(newState, std::memory_order_relaxed); + void join() + { + if (thread.joinable()) { + thread.join(); } + } - ThreadState getState() const { - return threadState.load(std::memory_order_relaxed); - } - private: - std::atomic threadState{THREAD_STATE_TERMINATED}; - std::thread thread; +protected: + void setState(ThreadState newState) { threadState.store(newState, std::memory_order_relaxed); } + + ThreadState getState() const { return threadState.load(std::memory_order_relaxed); } + +private: + std::atomic threadState{THREAD_STATE_TERMINATED}; + std::thread thread; }; -#endif +#endif // FS_THREAD_HOLDER_BASE_H diff --git a/src/tile.cpp b/src/tile.cpp index 670b0421f6..f36a458a73 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,37 +1,21 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include - #include "tile.h" -#include "creature.h" #include "combat.h" +#include "configmanager.h" +#include "creature.h" #include "game.h" +#include "housetile.h" #include "mailbox.h" #include "monster.h" #include "movement.h" +#include "spectators.h" #include "teleport.h" #include "trashholder.h" -#include "configmanager.h" extern Game g_game; extern MoveEvents* g_moveEvents; @@ -135,10 +119,7 @@ uint32_t Tile::getDownItemCount() const return 0; } -std::string Tile::getDescription(int32_t) const -{ - return "You dont know why, but you cant see anything!"; -} +std::string Tile::getDescription(int32_t) const { return "You dont know why, but you cant see anything!"; } Teleport* Tile::getTeleportItem() const { @@ -260,11 +241,6 @@ Creature* Tile::getTopVisibleCreature(const Creature* creature) const { if (const CreatureVector* creatures = getCreatures()) { if (creature) { - const Player* player = creature->getPlayer(); - if (player && player->isAccessPlayer()) { - return getTopCreature(); - } - for (Creature* tileCreature : *creatures) { if (creature->canSeeCreature(tileCreature)) { return tileCreature; @@ -288,11 +264,6 @@ const Creature* Tile::getBottomVisibleCreature(const Creature* creature) const { if (const CreatureVector* creatures = getCreatures()) { if (creature) { - const Player* player = creature->getPlayer(); - if (player && player->isAccessPlayer()) { - return getBottomCreature(); - } - for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { if (creature->canSeeCreature(*it)) { return *it; @@ -330,13 +301,15 @@ Item* Tile::getTopTopItem() const Item* Tile::getItemByTopOrder(int32_t topOrder) { - //topOrder: - //1: borders - //2: ladders, signs, splashes - //3: doors etc - //4: creatures + // topOrder: + // 1: borders + // 2: ladders, signs, splashes + // 3: doors etc + // 4: creatures if (TileItemVector* items = getItemList()) { - for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { + for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), + end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); + it != end; ++it) { if (Item::items[(*it)->getID()].alwaysOnTopOrder == topOrder) { return (*it); } @@ -354,14 +327,17 @@ Thing* Tile::getTopVisibleThing(const Creature* creature) TileItemVector* items = getItemList(); if (items) { - for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; + ++it) { const ItemType& iit = Item::items[(*it)->getID()]; if (!iit.lookThrough) { return (*it); } } - for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { + for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), + end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); + it != end; ++it) { const ItemType& iit = Item::items[(*it)->getID()]; if (!iit.lookThrough) { return (*it); @@ -378,7 +354,7 @@ void Tile::onAddTileItem(Item* item) auto it = g_game.browseFields.find(this); if (it != g_game.browseFields.end()) { it->second->addItemBack(item); - item->setParent(this); + item->setParent(it->second); } } @@ -389,19 +365,20 @@ void Tile::onAddTileItem(Item* item) SpectatorVec spectators; g_game.map.getSpectators(spectators, cylinderMapPos, true); - //send to client + // send to client for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendAddTileItem(this, cylinderMapPos, item); } } - //event methods + // event methods for (Creature* spectator : spectators) { spectator->onAddTileItem(this, cylinderMapPos); } - if ((!hasFlag(TILESTATE_PROTECTIONZONE) || g_config.getBoolean(ConfigManager::CLEAN_PROTECTION_ZONES)) && item->isCleanable()) { + if ((!hasFlag(TILESTATE_PROTECTIONZONE) || g_config.getBoolean(ConfigManager::CLEAN_PROTECTION_ZONES)) && + item->isCleanable()) { if (!dynamic_cast(this)) { g_game.addTileToClean(this); } @@ -416,7 +393,7 @@ void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newIte int32_t index = it->second->getThingIndex(oldItem); if (index != -1) { it->second->replaceThing(index, newItem); - newItem->setParent(this); + newItem->setParent(it->second); } } } else if (oldItem->hasProperty(CONST_PROP_MOVEABLE) || oldItem->getContainer()) { @@ -433,14 +410,14 @@ void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newIte SpectatorVec spectators; g_game.map.getSpectators(spectators, cylinderMapPos, true); - //send to client + // send to client for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, newItem); } } - //event methods + // event methods for (Creature* spectator : spectators) { spectator->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType); } @@ -460,7 +437,7 @@ void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vectorgetID()]; - //send to client + // send to client size_t i = 0; for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { @@ -468,7 +445,7 @@ void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vectoronRemoveTileItem(this, cylinderMapPos, iType, item); } @@ -498,7 +475,7 @@ void Tile::onUpdateTile(const SpectatorVec& spectators) { const Position& cylinderMapPos = getPosition(); - //send to clients + // send to clients for (Creature* spectator : spectators) { spectator->getPlayer()->sendUpdateTile(this, cylinderMapPos); } @@ -515,7 +492,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags return RETURNVALUE_NOTPOSSIBLE; } - if (ground == nullptr) { + if (!ground) { return RETURNVALUE_NOTPOSSIBLE; } @@ -534,7 +511,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags const Monster* creatureMonster = tileCreature->getMonster(); if (!creatureMonster || !tileCreature->isPushable() || - (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { + (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -555,7 +532,8 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags return RETURNVALUE_NOTPOSSIBLE; } - if (hasFlag(TILESTATE_BLOCKSOLID) || (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) { + if (hasFlag(TILESTATE_BLOCKSOLID) || + (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) { if (!(monster->canPushItems() || hasBitSet(FLAG_IGNOREBLOCKITEM, flags))) { return RETURNVALUE_NOTPOSSIBLE; } @@ -568,11 +546,12 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags CombatType_t combatType = field->getCombatType(); - //There is 3 options for a monster to enter a magic field - //1) Monster is immune + // There is 3 options for a monster to enter a magic field + // 1) Monster is immune if (!monster->isImmune(combatType)) { - //1) Monster is able to walk over field type - //2) Being attacked while random stepping will make it ignore field damages + // 1) Monster is able to walk over field type + // 2) Being attacked while random stepping will make it ignore + // field damages if (hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { if (!(monster->canWalkOnFieldType(combatType) || monster->isIgnoringFieldDamage())) { return RETURNVALUE_NOTPOSSIBLE; @@ -587,7 +566,8 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags const CreatureVector* creatures = getCreatures(); if (const Player* player = creature->getPlayer()) { - if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) { + if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && + !player->isAccessPlayer()) { for (const Creature* tileCreature : *creatures) { if (!player->canWalkthrough(tileCreature)) { return RETURNVALUE_NOTPOSSIBLE; @@ -595,15 +575,15 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } } - if (player->getParent() == nullptr && hasFlag(TILESTATE_NOLOGOUT)) { - //player is trying to login to a "no logout" tile + if (!player->getParent() && hasFlag(TILESTATE_NOLOGOUT)) { + // player is trying to login to a "no logout" tile return RETURNVALUE_NOTPOSSIBLE; } const Tile* playerTile = player->getTile(); if (playerTile && player->isPzLocked()) { if (!playerTile->hasFlag(TILESTATE_PVPZONE)) { - //player is trying to enter a pvp zone while being pz-locked + // player is trying to enter a pvp zone while being pz-locked if (hasFlag(TILESTATE_PVPZONE)) { return RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE; } @@ -613,7 +593,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } if ((!playerTile->hasFlag(TILESTATE_NOPVPZONE) && hasFlag(TILESTATE_NOPVPZONE)) || - (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) { + (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) { // player is trying to enter a non-pvp/protection zone while being pz-locked return RETURNVALUE_PLAYERISPZLOCKED; } @@ -627,12 +607,12 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } if (!hasBitSet(FLAG_IGNOREBLOCKITEM, flags)) { - //If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item + // If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item if (hasFlag(TILESTATE_BLOCKSOLID)) { return RETURNVALUE_NOTENOUGHROOM; } } else { - //FLAG_IGNOREBLOCKITEM is set + // FLAG_IGNOREBLOCKITEM is set if (ground) { const ItemType& iiType = Item::items[ground->getID()]; if (iiType.blockSolid && (!iiType.moveable || ground->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) { @@ -664,7 +644,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } bool itemIsHangable = item->isHangable(); - if (ground == nullptr && !itemIsHangable) { + if (!ground && !itemIsHangable) { return RETURNVALUE_NOTPOSSIBLE; } @@ -740,7 +720,7 @@ ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags } const Item* item = thing.getItem(); - if (item == nullptr) { + if (!item) { return RETURNVALUE_NOTPOSSIBLE; } @@ -837,10 +817,10 @@ Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t& destTile = g_game.map.getTile(dx, dy, dz); } - if (destTile == nullptr) { + if (!destTile) { destTile = this; } else { - flags |= FLAG_NOLIMIT; //Will ignore that there is blocking items/creatures + flags |= FLAG_NOLIMIT; // Will ignore that there is blocking items/creatures } if (destTile) { @@ -852,10 +832,7 @@ Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t& return destTile; } -void Tile::addThing(Thing* thing) -{ - addThing(0, thing); -} +void Tile::addThing(Thing* thing) { addThing(0, thing); } void Tile::addThing(int32_t, Thing* thing) { @@ -871,7 +848,7 @@ void Tile::addThing(int32_t, Thing* thing) creatures->insert(creatures->begin(), creature); } else { Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } @@ -884,7 +861,7 @@ void Tile::addThing(int32_t, Thing* thing) const ItemType& itemType = Item::items[item->getID()]; if (itemType.isGroundTile()) { - if (ground == nullptr) { + if (!ground) { ground = item; onAddTileItem(item); } else { @@ -901,8 +878,9 @@ void Tile::addThing(int32_t, Thing* thing) } } else if (itemType.alwaysOnTop) { if (itemType.isSplash() && items) { - //remove old splash if exists - for (ItemVector::const_iterator it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + // remove old splash if exists + for (ItemVector::const_iterator it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; + ++it) { Item* oldSplash = *it; if (!Item::items[oldSplash->getID()].isSplash()) { continue; @@ -920,7 +898,7 @@ void Tile::addThing(int32_t, Thing* thing) if (items) { for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { - //Note: this is different from internalAddThing + // Note: this is different from internalAddThing if (itemType.alwaysOnTopOrder <= Item::items[(*it)->getID()].alwaysOnTopOrder) { items->insert(it, item); isInserted = true; @@ -938,9 +916,10 @@ void Tile::addThing(int32_t, Thing* thing) onAddTileItem(item); } else { if (itemType.isMagicField()) { - //remove old field item if exists + // remove old field item if exists if (items) { - for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); + it != end; ++it) { MagicField* oldField = (*it)->getMagicField(); if (oldField) { if (oldField->isReplaceable()) { @@ -951,7 +930,7 @@ void Tile::addThing(int32_t, Thing* thing) postRemoveNotification(oldField, nullptr, 0); break; } else { - //This magic field cannot be replaced. + // This magic field cannot be replaced. item->setParent(nullptr); g_game.ReleaseItem(item); return; @@ -977,7 +956,7 @@ void Tile::updateThing(Thing* thing, uint16_t itemId, uint32_t count) } Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } @@ -995,7 +974,7 @@ void Tile::replaceThing(uint32_t index, Thing* thing) int32_t pos = index; Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return /*RETURNVALUE_NOTPOSSIBLE*/; } @@ -1133,7 +1112,8 @@ void Tile::removeThing(Thing* thing, uint32_t count) } if (itemType.stackable && count != item->getItemCount()) { - uint8_t newCount = static_cast(std::max(0, static_cast(item->getItemCount() - count))); + uint8_t newCount = + static_cast(std::max(0, static_cast(item->getItemCount() - count))); item->setItemCount(newCount); onUpdateTileItem(item, itemType, item, itemType); } else { @@ -1228,7 +1208,8 @@ int32_t Tile::getClientIndexOfCreature(const Player* player, const Creature* cre } if (const CreatureVector* creatures = getCreatures()) { - for (const Creature* c : boost::adaptors::reverse(*creatures)) { + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + const Creature* c = (*it); if (c == creature) { return n; } else if (player->canSeeCreature(c)) { @@ -1289,15 +1270,9 @@ int32_t Tile::getStackposOfItem(const Player* player, const Item* item) const return -1; } -size_t Tile::getFirstIndex() const -{ - return 0; -} +size_t Tile::getFirstIndex() const { return 0; } -size_t Tile::getLastIndex() const -{ - return getThingCount(); -} +size_t Tile::getLastIndex() const { return getThingCount(); } uint32_t Tile::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const { @@ -1349,7 +1324,8 @@ Thing* Tile::getThing(size_t index) const return nullptr; } -void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link /*= LINK_OWNER*/) { SpectatorVec spectators; g_game.map.getSpectators(spectators, getPosition(), true, true); @@ -1357,7 +1333,7 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); } - //add a reference to this item, it may be deleted after being added (mailbox for example) + // add a reference to this item, it may be deleted after being added (mailbox for example) Creature* creature = thing->getCreature(); Item* item; if (creature) { @@ -1388,7 +1364,7 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t } } - //calling movement scripts + // calling movement scripts if (creature) { g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_IN); } else if (item) { @@ -1396,7 +1372,7 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t } } - //release the reference to this item onces we are finished + // release the reference to this item onces we are finished if (creature) { g_game.ReleaseCreature(creature); } else if (item) { @@ -1417,7 +1393,7 @@ void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32 spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR); } - //calling movement scripts + // calling movement scripts Creature* creature = thing->getCreature(); if (creature) { g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_OUT); @@ -1429,10 +1405,7 @@ void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32 } } -void Tile::internalAddThing(Thing* thing) -{ - internalAddThing(0, thing); -} +void Tile::internalAddThing(Thing* thing) { internalAddThing(0, thing); } void Tile::internalAddThing(uint32_t, Thing* thing) { @@ -1449,13 +1422,13 @@ void Tile::internalAddThing(uint32_t, Thing* thing) creatures->insert(creatures->begin(), creature); } else { Item* item = thing->getItem(); - if (item == nullptr) { + if (!item) { return; } const ItemType& itemType = Item::items[item->getID()]; if (itemType.isGroundTile()) { - if (ground == nullptr) { + if (!ground) { ground = item; setTileFlags(item); } @@ -1575,7 +1548,8 @@ void Tile::resetTileFlags(const Item* item) resetFlag(TILESTATE_IMMOVABLEBLOCKPATH); } - if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) && + !hasProperty(item, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); } @@ -1609,10 +1583,7 @@ void Tile::resetTileFlags(const Item* item) } } -bool Tile::isMoveableBlocking() const -{ - return !ground || hasFlag(TILESTATE_BLOCKSOLID); -} +bool Tile::isMoveableBlocking() const { return !ground || hasFlag(TILESTATE_BLOCKSOLID); } Item* Tile::getUseItem(int32_t index) const { diff --git a/src/tile.h b/src/tile.h index ec394303fb..6f483a360d 100644 --- a/src/tile.h +++ b/src/tile.h @@ -1,42 +1,26 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 -#define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_TILE_H +#define FS_TILE_H #include "cylinder.h" #include "item.h" #include "tools.h" -#include "spectators.h" +class BedItem; class Creature; +class MagicField; +class Mailbox; +class SpectatorVec; class Teleport; class TrashHolder; -class Mailbox; -class MagicField; -class QTreeLeafNode; -class BedItem; using CreatureVector = std::vector; using ItemVector = std::vector; -enum tileflags_t : uint32_t { +enum tileflags_t : uint32_t +{ TILESTATE_NONE = 0, TILESTATE_FLOORCHANGE_DOWN = 1 << 0, @@ -64,10 +48,13 @@ enum tileflags_t : uint32_t { TILESTATE_NOFIELDBLOCKPATH = 1 << 22, TILESTATE_SUPPORTS_HANGABLE = 1 << 23, - TILESTATE_FLOORCHANGE = TILESTATE_FLOORCHANGE_DOWN | TILESTATE_FLOORCHANGE_NORTH | TILESTATE_FLOORCHANGE_SOUTH | TILESTATE_FLOORCHANGE_EAST | TILESTATE_FLOORCHANGE_WEST | TILESTATE_FLOORCHANGE_SOUTH_ALT | TILESTATE_FLOORCHANGE_EAST_ALT, + TILESTATE_FLOORCHANGE = TILESTATE_FLOORCHANGE_DOWN | TILESTATE_FLOORCHANGE_NORTH | TILESTATE_FLOORCHANGE_SOUTH | + TILESTATE_FLOORCHANGE_EAST | TILESTATE_FLOORCHANGE_WEST | TILESTATE_FLOORCHANGE_SOUTH_ALT | + TILESTATE_FLOORCHANGE_EAST_ALT, }; -enum ZoneType_t { +enum ZoneType_t +{ ZONE_PROTECTION, ZONE_NOPVP, ZONE_PVP, @@ -77,264 +64,217 @@ enum ZoneType_t { class TileItemVector : private ItemVector { - public: - using ItemVector::begin; - using ItemVector::end; - using ItemVector::rbegin; - using ItemVector::rend; - using ItemVector::size; - using ItemVector::clear; - using ItemVector::at; - using ItemVector::insert; - using ItemVector::erase; - using ItemVector::push_back; - using ItemVector::value_type; - using ItemVector::iterator; - using ItemVector::const_iterator; - using ItemVector::reverse_iterator; - using ItemVector::const_reverse_iterator; - using ItemVector::empty; - - iterator getBeginDownItem() { - return begin(); - } - const_iterator getBeginDownItem() const { - return begin(); - } - iterator getEndDownItem() { - return begin() + downItemCount; - } - const_iterator getEndDownItem() const { - return begin() + downItemCount; - } - iterator getBeginTopItem() { - return getEndDownItem(); - } - const_iterator getBeginTopItem() const { - return getEndDownItem(); - } - iterator getEndTopItem() { - return end(); - } - const_iterator getEndTopItem() const { - return end(); - } - - uint32_t getTopItemCount() const { - return size() - downItemCount; - } - uint32_t getDownItemCount() const { - return downItemCount; - } - inline Item* getTopTopItem() const { - if (getTopItemCount() == 0) { - return nullptr; - } - return *(getEndTopItem() - 1); - } - inline Item* getTopDownItem() const { - if (downItemCount == 0) { - return nullptr; - } - return *getBeginDownItem(); - } - void addDownItemCount(int32_t increment) { - downItemCount += increment; - } - - private: - uint16_t downItemCount = 0; +public: + using ItemVector::at; + using ItemVector::begin; + using ItemVector::clear; + using ItemVector::const_iterator; + using ItemVector::const_reverse_iterator; + using ItemVector::empty; + using ItemVector::end; + using ItemVector::erase; + using ItemVector::insert; + using ItemVector::iterator; + using ItemVector::push_back; + using ItemVector::rbegin; + using ItemVector::rend; + using ItemVector::reverse_iterator; + using ItemVector::size; + using ItemVector::value_type; + + iterator getBeginDownItem() { return begin(); } + const_iterator getBeginDownItem() const { return begin(); } + iterator getEndDownItem() { return begin() + downItemCount; } + const_iterator getEndDownItem() const { return begin() + downItemCount; } + iterator getBeginTopItem() { return getEndDownItem(); } + const_iterator getBeginTopItem() const { return getEndDownItem(); } + iterator getEndTopItem() { return end(); } + const_iterator getEndTopItem() const { return end(); } + + uint32_t getTopItemCount() const { return size() - downItemCount; } + uint32_t getDownItemCount() const { return downItemCount; } + inline Item* getTopTopItem() const + { + if (getTopItemCount() == 0) { + return nullptr; + } + return *(getEndTopItem() - 1); + } + inline Item* getTopDownItem() const + { + if (downItemCount == 0) { + return nullptr; + } + return *getBeginDownItem(); + } + void addDownItemCount(uint16_t increment) { downItemCount += increment; } + +private: + uint16_t downItemCount = 0; }; class Tile : public Cylinder { - public: - static Tile& nullptr_tile; - Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} - virtual ~Tile() { - delete ground; - }; - - // non-copyable - Tile(const Tile&) = delete; - Tile& operator=(const Tile&) = delete; - - virtual TileItemVector* getItemList() = 0; - virtual const TileItemVector* getItemList() const = 0; - virtual TileItemVector* makeItemList() = 0; - - virtual CreatureVector* getCreatures() = 0; - virtual const CreatureVector* getCreatures() const = 0; - virtual CreatureVector* makeCreatures() = 0; - - int32_t getThrowRange() const override final { - return 0; - } - bool isPushable() const override final { - return false; - } - - MagicField* getFieldItem() const; - Teleport* getTeleportItem() const; - TrashHolder* getTrashHolder() const; - Mailbox* getMailbox() const; - BedItem* getBedItem() const; - - Creature* getTopCreature() const; - const Creature* getBottomCreature() const; - Creature* getTopVisibleCreature(const Creature* creature) const; - const Creature* getBottomVisibleCreature(const Creature* creature) const; - Item* getTopTopItem() const; - Item* getTopDownItem() const; - bool isMoveableBlocking() const; - Thing* getTopVisibleThing(const Creature* creature); - Item* getItemByTopOrder(int32_t topOrder); - - size_t getThingCount() const { - size_t thingCount = getCreatureCount() + getItemCount(); - if (ground) { - thingCount++; - } - return thingCount; - } - // If these return != 0 the associated vectors are guaranteed to exists - size_t getCreatureCount() const; - size_t getItemCount() const; - uint32_t getTopItemCount() const; - uint32_t getDownItemCount() const; - - bool hasProperty(ITEMPROPERTY prop) const; - bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; - - bool hasFlag(uint32_t flag) const { - return hasBitSet(flag, this->flags); - } - void setFlag(uint32_t flag) { - this->flags |= flag; - } - void resetFlag(uint32_t flag) { - this->flags &= ~flag; - } - - ZoneType_t getZone() const { - if (hasFlag(TILESTATE_PROTECTIONZONE)) { - return ZONE_PROTECTION; - } else if (hasFlag(TILESTATE_NOPVPZONE)) { - return ZONE_NOPVP; - } else if (hasFlag(TILESTATE_PVPZONE)) { - return ZONE_PVP; - } else { - return ZONE_NORMAL; - } - } - - bool hasHeight(uint32_t n) const; - - std::string getDescription(int32_t lookDistance) const override final; - - int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; - int32_t getStackposOfItem(const Player* player, const Item* item) const; - - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, - uint32_t& maxQueryCount, uint32_t flags) const override final; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; - Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - - void addThing(Thing* thing) override final; - void addThing(int32_t index, Thing* thing) override; - - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; - void replaceThing(uint32_t index, Thing* thing) override final; - - void removeThing(Thing* thing, uint32_t count) override final; - - void removeCreature(Creature* creature); - - int32_t getThingIndex(const Thing* thing) const override final; - size_t getFirstIndex() const override final; - size_t getLastIndex() const override final; - uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; - Thing* getThing(size_t index) const override final; - - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; - - void internalAddThing(Thing* thing) override final; - void internalAddThing(uint32_t index, Thing* thing) override; - - const Position& getPosition() const override final { - return tilePos; - } - - bool isRemoved() const override final { - return false; - } - - Item* getUseItem(int32_t index) const; - - Item* getGround() const { - return ground; - } - void setGround(Item* item) { - ground = item; - } - - private: - void onAddTileItem(Item* item); - void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); - void onRemoveTileItem(const SpectatorVec& spectators, const std::vector& oldStackPosVector, Item* item); - void onUpdateTile(const SpectatorVec& spectators); - - void setTileFlags(const Item* item); - void resetTileFlags(const Item* item); - - Item* ground = nullptr; - Position tilePos; - uint32_t flags = 0; +public: + static Tile& nullptr_tile; + Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} + virtual ~Tile() { delete ground; }; + + // non-copyable + Tile(const Tile&) = delete; + Tile& operator=(const Tile&) = delete; + + virtual TileItemVector* getItemList() = 0; + virtual const TileItemVector* getItemList() const = 0; + virtual TileItemVector* makeItemList() = 0; + + virtual CreatureVector* getCreatures() = 0; + virtual const CreatureVector* getCreatures() const = 0; + virtual CreatureVector* makeCreatures() = 0; + + int32_t getThrowRange() const override final { return 0; } + bool isPushable() const override final { return false; } + + MagicField* getFieldItem() const; + Teleport* getTeleportItem() const; + TrashHolder* getTrashHolder() const; + Mailbox* getMailbox() const; + BedItem* getBedItem() const; + + Creature* getTopCreature() const; + const Creature* getBottomCreature() const; + Creature* getTopVisibleCreature(const Creature* creature) const; + const Creature* getBottomVisibleCreature(const Creature* creature) const; + Item* getTopTopItem() const; + Item* getTopDownItem() const; + bool isMoveableBlocking() const; + Thing* getTopVisibleThing(const Creature* creature); + Item* getItemByTopOrder(int32_t topOrder); + + size_t getThingCount() const + { + size_t thingCount = getCreatureCount() + getItemCount(); + if (ground) { + thingCount++; + } + return thingCount; + } + // If these return != 0 the associated vectors are guaranteed to exists + size_t getCreatureCount() const; + size_t getItemCount() const; + uint32_t getTopItemCount() const; + uint32_t getDownItemCount() const; + + bool hasProperty(ITEMPROPERTY prop) const; + bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; + + bool hasFlag(uint32_t flag) const { return hasBitSet(flag, this->flags); } + void setFlag(uint32_t flag) { this->flags |= flag; } + void resetFlag(uint32_t flag) { this->flags &= ~flag; } + + ZoneType_t getZone() const + { + if (hasFlag(TILESTATE_PROTECTIONZONE)) { + return ZONE_PROTECTION; + } else if (hasFlag(TILESTATE_NOPVPZONE)) { + return ZONE_NOPVP; + } else if (hasFlag(TILESTATE_PVPZONE)) { + return ZONE_PVP; + } + return ZONE_NORMAL; + } + + bool hasHeight(uint32_t n) const; + + std::string getDescription(int32_t lookDistance) const override final; + + int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; + int32_t getStackposOfItem(const Player* player, const Item* item) const; + + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const override final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; + + void addThing(Thing* thing) override final; + void addThing(int32_t index, Thing* thing) override; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; + void replaceThing(uint32_t index, Thing* thing) override final; + + void removeThing(Thing* thing, uint32_t count) override final; + + void removeCreature(Creature* creature); + + int32_t getThingIndex(const Thing* thing) const override final; + size_t getFirstIndex() const override final; + size_t getLastIndex() const override final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; + Thing* getThing(size_t index) const override final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override final; + + void internalAddThing(Thing* thing) override final; + void internalAddThing(uint32_t index, Thing* thing) override; + + const Position& getPosition() const override final { return tilePos; } + + bool isRemoved() const override final { return false; } + + Item* getUseItem(int32_t index) const; + + Item* getGround() const { return ground; } + void setGround(Item* item) { ground = item; } + +private: + void onAddTileItem(Item* item); + void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); + void onRemoveTileItem(const SpectatorVec& spectators, const std::vector& oldStackPosVector, Item* item); + void onUpdateTile(const SpectatorVec& spectators); + + void setTileFlags(const Item* item); + void resetTileFlags(const Item* item); + + Item* ground = nullptr; + Position tilePos; + uint32_t flags = 0; }; // Used for walkable tiles, where there is high likeliness of // items being added/removed class DynamicTile : public Tile { - // By allocating the vectors in-house, we avoid some memory fragmentation - TileItemVector items; - CreatureVector creatures; - - public: - DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} - ~DynamicTile() { - for (Item* item : items) { - item->decrementReferenceCounter(); - } - } - - // non-copyable - DynamicTile(const DynamicTile&) = delete; - DynamicTile& operator=(const DynamicTile&) = delete; - - TileItemVector* getItemList() override { - return &items; - } - const TileItemVector* getItemList() const override { - return &items; - } - TileItemVector* makeItemList() override { - return &items; - } - - CreatureVector* getCreatures() override { - return &creatures; - } - const CreatureVector* getCreatures() const override { - return &creatures; - } - CreatureVector* makeCreatures() override { - return &creatures; - } + // By allocating the vectors in-house, we avoid some memory fragmentation + TileItemVector items; + CreatureVector creatures; + +public: + DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} + ~DynamicTile() + { + for (Item* item : items) { + item->decrementReferenceCounter(); + } + } + + // non-copyable + DynamicTile(const DynamicTile&) = delete; + DynamicTile& operator=(const DynamicTile&) = delete; + + TileItemVector* getItemList() override { return &items; } + const TileItemVector* getItemList() const override { return &items; } + TileItemVector* makeItemList() override { return &items; } + + CreatureVector* getCreatures() override { return &creatures; } + const CreatureVector* getCreatures() const override { return &creatures; } + CreatureVector* makeCreatures() override { return &creatures; } }; // For blocking tiles, where we very rarely actually have items @@ -344,45 +284,40 @@ class StaticTile final : public Tile std::unique_ptr items; std::unique_ptr creatures; - public: - StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} - ~StaticTile() { - if (items) { - for (Item* item : *items) { - item->decrementReferenceCounter(); - } +public: + StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} + ~StaticTile() + { + if (items) { + for (Item* item : *items) { + item->decrementReferenceCounter(); } } + } - // non-copyable - StaticTile(const StaticTile&) = delete; - StaticTile& operator=(const StaticTile&) = delete; + // non-copyable + StaticTile(const StaticTile&) = delete; + StaticTile& operator=(const StaticTile&) = delete; - TileItemVector* getItemList() override { - return items.get(); - } - const TileItemVector* getItemList() const override { - return items.get(); - } - TileItemVector* makeItemList() override { - if (!items) { - items.reset(new TileItemVector); - } - return items.get(); + TileItemVector* getItemList() override { return items.get(); } + const TileItemVector* getItemList() const override { return items.get(); } + TileItemVector* makeItemList() override + { + if (!items) { + items.reset(new TileItemVector); } + return items.get(); + } - CreatureVector* getCreatures() override { - return creatures.get(); - } - const CreatureVector* getCreatures() const override { - return creatures.get(); - } - CreatureVector* makeCreatures() override { - if (!creatures) { - creatures.reset(new CreatureVector); - } - return creatures.get(); + CreatureVector* getCreatures() override { return creatures.get(); } + const CreatureVector* getCreatures() const override { return creatures.get(); } + CreatureVector* makeCreatures() override + { + if (!creatures) { + creatures.reset(new CreatureVector); } + return creatures.get(); + } }; -#endif +#endif // FS_TILE_H diff --git a/src/tools.cpp b/src/tools.cpp index a95f18862e..f17254095c 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1,27 +1,14 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "tools.h" + #include "configmanager.h" +#include + extern ConfigManager g_config; void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result) @@ -73,17 +60,15 @@ void printXMLError(const std::string& where, const std::string& fileName, const std::cout << '^' << std::endl; } -static uint32_t circularShift(int bits, uint32_t value) -{ - return (value << bits) | (value >> (32 - bits)); -} +static uint32_t circularShift(int bits, uint32_t value) { return (value << bits) | (value >> (32 - bits)); } static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H) { uint32_t W[80]; for (int i = 0; i < 16; ++i) { const size_t offset = i << 2; - W[i] = messageBlock[offset] << 24 | messageBlock[offset + 1] << 16 | messageBlock[offset + 2] << 8 | messageBlock[offset + 3]; + W[i] = messageBlock[offset] << 24 | messageBlock[offset + 1] << 16 | messageBlock[offset + 2] << 8 | + messageBlock[offset + 3]; } for (int i = 16; i < 80; ++i) { @@ -94,22 +79,38 @@ static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H) for (int i = 0; i < 20; ++i) { const uint32_t tmp = circularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5A827999; - E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + E = D; + D = C; + C = circularShift(30, B); + B = A; + A = tmp; } for (int i = 20; i < 40; ++i) { const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1; - E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + E = D; + D = C; + C = circularShift(30, B); + B = A; + A = tmp; } for (int i = 40; i < 60; ++i) { const uint32_t tmp = circularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC; - E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + E = D; + D = C; + C = circularShift(30, B); + B = A; + A = tmp; } for (int i = 60; i < 80; ++i) { const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6; - E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + E = D; + D = C; + C = circularShift(30, B); + B = A; + A = tmp; } H[0] += A; @@ -121,13 +122,7 @@ static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H) std::string transformToSHA1(const std::string& input) { - uint32_t H[] = { - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0 - }; + uint32_t H[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}; uint8_t messageBlock[64]; size_t index = 0; @@ -221,7 +216,8 @@ std::string generateToken(const std::string& key, uint32_t ticks) uint32_t offset = static_cast(std::strtoul(message.substr(39, 1).c_str(), nullptr, 16) & 0xF); // get truncated hash - uint32_t truncHash = static_cast(std::strtoul(message.substr(2 * offset, 8).c_str(), nullptr, 16)) & 0x7FFFFFFF; + uint32_t truncHash = + static_cast(std::strtoul(message.substr(2 * offset, 8).c_str(), nullptr, 16)) & 0x7FFFFFFF; message.assign(std::to_string(truncHash)); // return only last AUTHENTICATOR_DIGITS (default 6) digits, also asserts exactly 6 digits @@ -231,47 +227,19 @@ std::string generateToken(const std::string& key, uint32_t ticks) return message; } -void replaceString(std::string& str, const std::string& sought, const std::string& replacement) -{ - size_t pos = 0; - size_t start = 0; - size_t soughtLen = sought.length(); - size_t replaceLen = replacement.length(); - - while ((pos = str.find(sought, start)) != std::string::npos) { - str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen); - start = pos + replaceLen; - } -} - -void trim_right(std::string& source, char t) -{ - source.erase(source.find_last_not_of(t) + 1); -} - -void trim_left(std::string& source, char t) -{ - source.erase(0, source.find_first_not_of(t)); -} - -void toLowerCaseString(std::string& source) +bool caseInsensitiveEqual(std::string_view str1, std::string_view str2) { - std::transform(source.begin(), source.end(), source.begin(), tolower); + return str1.size() == str2.size() && + std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { return tolower(a) == tolower(b); }); } -std::string asLowerCaseString(std::string source) +bool caseInsensitiveStartsWith(std::string_view str, std::string_view prefix) { - toLowerCaseString(source); - return source; + return str.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), str.begin(), + [](char a, char b) { return tolower(a) == tolower(b); }); } -std::string asUpperCaseString(std::string source) -{ - std::transform(source.begin(), source.end(), source.begin(), toupper); - return source; -} - -StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) +StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit /* = -1*/) { StringVector returnVector; std::string::size_type start = 0, end = 0; @@ -334,18 +302,12 @@ int32_t normal_random(int32_t minNumber, int32_t maxNumber) return minNumber + increment; } -bool boolean_random(double probability/* = 0.5*/) +bool boolean_random(double probability /* = 0.5*/) { static std::bernoulli_distribution booleanRand; return booleanRand(getRandomGenerator(), std::bernoulli_distribution::param_type(probability)); } -void trimString(std::string& str) -{ - str.erase(str.find_last_not_of(' ') + 1); - str.erase(0, str.find_first_not_of(' ')); -} - std::string convertIPToString(uint32_t ip) { char buffer[17]; @@ -366,7 +328,8 @@ std::string formatDate(time_t time) } char buffer[20]; - int res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec); + int res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, + tms->tm_hour, tms->tm_min, tms->tm_sec); if (res < 0) { return {}; } @@ -400,13 +363,17 @@ Direction getDirection(const std::string& string) direction = DIRECTION_SOUTH; } else if (string == "west" || string == "w" || string == "3") { direction = DIRECTION_WEST; - } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || string == "4") { + } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || + string == "4") { direction = DIRECTION_SOUTHWEST; - } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || string == "5") { + } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || + string == "5") { direction = DIRECTION_SOUTHEAST; - } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || string == "6") { + } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || + string == "6") { direction = DIRECTION_NORTHWEST; - } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || string == "7") { + } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || + string == "7") { direction = DIRECTION_NORTHEAST; } @@ -461,6 +428,10 @@ Position getNextPosition(Direction direction, Position pos) Direction getDirectionTo(const Position& from, const Position& to) { + if (from == to) { + return DIRECTION_NONE; + } + Direction dir; int32_t x_offset = Position::getOffsetX(from, to); @@ -505,199 +476,273 @@ using WeaponActionNames = std::unordered_map; using SkullNames = std::unordered_map; MagicEffectNames magicEffectNames = { - {"redspark", CONST_ME_DRAWBLOOD}, - {"bluebubble", CONST_ME_LOSEENERGY}, - {"poff", CONST_ME_POFF}, - {"yellowspark", CONST_ME_BLOCKHIT}, - {"explosionarea", CONST_ME_EXPLOSIONAREA}, - {"explosion", CONST_ME_EXPLOSIONHIT}, - {"firearea", CONST_ME_FIREAREA}, - {"yellowbubble", CONST_ME_YELLOW_RINGS}, - {"greenbubble", CONST_ME_GREEN_RINGS}, - {"blackspark", CONST_ME_HITAREA}, - {"teleport", CONST_ME_TELEPORT}, - {"energy", CONST_ME_ENERGYHIT}, - {"blueshimmer", CONST_ME_MAGIC_BLUE}, - {"redshimmer", CONST_ME_MAGIC_RED}, - {"greenshimmer", CONST_ME_MAGIC_GREEN}, - {"fire", CONST_ME_HITBYFIRE}, - {"greenspark", CONST_ME_HITBYPOISON}, - {"mortarea", CONST_ME_MORTAREA}, - {"greennote", CONST_ME_SOUND_GREEN}, - {"rednote", CONST_ME_SOUND_RED}, - {"poison", CONST_ME_POISONAREA}, - {"yellownote", CONST_ME_SOUND_YELLOW}, - {"purplenote", CONST_ME_SOUND_PURPLE}, - {"bluenote", CONST_ME_SOUND_BLUE}, - {"whitenote", CONST_ME_SOUND_WHITE}, - {"bubbles", CONST_ME_BUBBLES}, - {"dice", CONST_ME_CRAPS}, - {"giftwraps", CONST_ME_GIFT_WRAPS}, - {"yellowfirework", CONST_ME_FIREWORK_YELLOW}, - {"redfirework", CONST_ME_FIREWORK_RED}, - {"bluefirework", CONST_ME_FIREWORK_BLUE}, - {"stun", CONST_ME_STUN}, - {"sleep", CONST_ME_SLEEP}, - {"watercreature", CONST_ME_WATERCREATURE}, - {"groundshaker", CONST_ME_GROUNDSHAKER}, - {"hearts", CONST_ME_HEARTS}, - {"fireattack", CONST_ME_FIREATTACK}, - {"energyarea", CONST_ME_ENERGYAREA}, - {"smallclouds", CONST_ME_SMALLCLOUDS}, - {"holydamage", CONST_ME_HOLYDAMAGE}, - {"bigclouds", CONST_ME_BIGCLOUDS}, - {"icearea", CONST_ME_ICEAREA}, - {"icetornado", CONST_ME_ICETORNADO}, - {"iceattack", CONST_ME_ICEATTACK}, - {"stones", CONST_ME_STONES}, - {"smallplants", CONST_ME_SMALLPLANTS}, - {"carniphila", CONST_ME_CARNIPHILA}, - {"purpleenergy", CONST_ME_PURPLEENERGY}, - {"yellowenergy", CONST_ME_YELLOWENERGY}, - {"holyarea", CONST_ME_HOLYAREA}, - {"bigplants", CONST_ME_BIGPLANTS}, - {"cake", CONST_ME_CAKE}, - {"giantice", CONST_ME_GIANTICE}, - {"watersplash", CONST_ME_WATERSPLASH}, - {"plantattack", CONST_ME_PLANTATTACK}, - {"tutorialarrow", CONST_ME_TUTORIALARROW}, - {"tutorialsquare", CONST_ME_TUTORIALSQUARE}, - {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL}, - {"mirrorvertical", CONST_ME_MIRRORVERTICAL}, - {"skullhorizontal", CONST_ME_SKULLHORIZONTAL}, - {"skullvertical", CONST_ME_SKULLVERTICAL}, - {"assassin", CONST_ME_ASSASSIN}, - {"stepshorizontal", CONST_ME_STEPSHORIZONTAL}, - {"bloodysteps", CONST_ME_BLOODYSTEPS}, - {"stepsvertical", CONST_ME_STEPSVERTICAL}, - {"yalaharighost", CONST_ME_YALAHARIGHOST}, - {"bats", CONST_ME_BATS}, - {"smoke", CONST_ME_SMOKE}, - {"insects", CONST_ME_INSECTS}, - {"dragonhead", CONST_ME_DRAGONHEAD}, - {"orcshaman", CONST_ME_ORCSHAMAN}, - {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE}, - {"thunder", CONST_ME_THUNDER}, - {"ferumbras", CONST_ME_FERUMBRAS}, - {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL}, - {"confettivertical", CONST_ME_CONFETTI_VERTICAL}, - {"blacksmoke", CONST_ME_BLACKSMOKE}, - {"redsmoke", CONST_ME_REDSMOKE}, - {"yellowsmoke", CONST_ME_YELLOWSMOKE}, - {"greensmoke", CONST_ME_GREENSMOKE}, - {"purplesmoke", CONST_ME_PURPLESMOKE}, + {"redspark", CONST_ME_DRAWBLOOD}, + {"bluebubble", CONST_ME_LOSEENERGY}, + {"poff", CONST_ME_POFF}, + {"yellowspark", CONST_ME_BLOCKHIT}, + {"explosionarea", CONST_ME_EXPLOSIONAREA}, + {"explosion", CONST_ME_EXPLOSIONHIT}, + {"firearea", CONST_ME_FIREAREA}, + {"yellowbubble", CONST_ME_YELLOW_RINGS}, + {"greenbubble", CONST_ME_GREEN_RINGS}, + {"blackspark", CONST_ME_HITAREA}, + {"teleport", CONST_ME_TELEPORT}, + {"energy", CONST_ME_ENERGYHIT}, + {"blueshimmer", CONST_ME_MAGIC_BLUE}, + {"redshimmer", CONST_ME_MAGIC_RED}, + {"greenshimmer", CONST_ME_MAGIC_GREEN}, + {"fire", CONST_ME_HITBYFIRE}, + {"greenspark", CONST_ME_HITBYPOISON}, + {"mortarea", CONST_ME_MORTAREA}, + {"greennote", CONST_ME_SOUND_GREEN}, + {"rednote", CONST_ME_SOUND_RED}, + {"poison", CONST_ME_POISONAREA}, + {"yellownote", CONST_ME_SOUND_YELLOW}, + {"purplenote", CONST_ME_SOUND_PURPLE}, + {"bluenote", CONST_ME_SOUND_BLUE}, + {"whitenote", CONST_ME_SOUND_WHITE}, + {"bubbles", CONST_ME_BUBBLES}, + {"dice", CONST_ME_CRAPS}, + {"giftwraps", CONST_ME_GIFT_WRAPS}, + {"yellowfirework", CONST_ME_FIREWORK_YELLOW}, + {"redfirework", CONST_ME_FIREWORK_RED}, + {"bluefirework", CONST_ME_FIREWORK_BLUE}, + {"stun", CONST_ME_STUN}, + {"sleep", CONST_ME_SLEEP}, + {"watercreature", CONST_ME_WATERCREATURE}, + {"groundshaker", CONST_ME_GROUNDSHAKER}, + {"hearts", CONST_ME_HEARTS}, + {"fireattack", CONST_ME_FIREATTACK}, + {"energyarea", CONST_ME_ENERGYAREA}, + {"smallclouds", CONST_ME_SMALLCLOUDS}, + {"holydamage", CONST_ME_HOLYDAMAGE}, + {"bigclouds", CONST_ME_BIGCLOUDS}, + {"icearea", CONST_ME_ICEAREA}, + {"icetornado", CONST_ME_ICETORNADO}, + {"iceattack", CONST_ME_ICEATTACK}, + {"stones", CONST_ME_STONES}, + {"smallplants", CONST_ME_SMALLPLANTS}, + {"carniphila", CONST_ME_CARNIPHILA}, + {"purpleenergy", CONST_ME_PURPLEENERGY}, + {"yellowenergy", CONST_ME_YELLOWENERGY}, + {"holyarea", CONST_ME_HOLYAREA}, + {"bigplants", CONST_ME_BIGPLANTS}, + {"cake", CONST_ME_CAKE}, + {"giantice", CONST_ME_GIANTICE}, + {"watersplash", CONST_ME_WATERSPLASH}, + {"plantattack", CONST_ME_PLANTATTACK}, + {"tutorialarrow", CONST_ME_TUTORIALARROW}, + {"tutorialsquare", CONST_ME_TUTORIALSQUARE}, + {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL}, + {"mirrorvertical", CONST_ME_MIRRORVERTICAL}, + {"skullhorizontal", CONST_ME_SKULLHORIZONTAL}, + {"skullvertical", CONST_ME_SKULLVERTICAL}, + {"assassin", CONST_ME_ASSASSIN}, + {"stepshorizontal", CONST_ME_STEPSHORIZONTAL}, + {"bloodysteps", CONST_ME_BLOODYSTEPS}, + {"stepsvertical", CONST_ME_STEPSVERTICAL}, + {"yalaharighost", CONST_ME_YALAHARIGHOST}, + {"bats", CONST_ME_BATS}, + {"smoke", CONST_ME_SMOKE}, + {"insects", CONST_ME_INSECTS}, + {"dragonhead", CONST_ME_DRAGONHEAD}, + {"orcshaman", CONST_ME_ORCSHAMAN}, + {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE}, + {"thunder", CONST_ME_THUNDER}, + {"ferumbras", CONST_ME_FERUMBRAS}, + {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL}, + {"confettivertical", CONST_ME_CONFETTI_VERTICAL}, + {"blacksmoke", CONST_ME_BLACKSMOKE}, + {"redsmoke", CONST_ME_REDSMOKE}, + {"yellowsmoke", CONST_ME_YELLOWSMOKE}, + {"greensmoke", CONST_ME_GREENSMOKE}, + {"purplesmoke", CONST_ME_PURPLESMOKE}, + {"earlythunder", CONST_ME_EARLY_THUNDER}, + {"bonecapsule", CONST_ME_RAGIAZ_BONECAPSULE}, + {"criticaldamage", CONST_ME_CRITICAL_DAMAGE}, + {"plungingfish", CONST_ME_PLUNGING_FISH}, + {"bluechain", CONST_ME_BLUECHAIN}, + {"orangechain", CONST_ME_ORANGECHAIN}, + {"greenchain", CONST_ME_GREENCHAIN}, + {"purplechain", CONST_ME_PURPLECHAIN}, + {"greychain", CONST_ME_GREYCHAIN}, + {"yellowchain", CONST_ME_YELLOWCHAIN}, + {"yellowsparkles", CONST_ME_YELLOWSPARKLES}, + {"faeexplosion", CONST_ME_FAEEXPLOSION}, + {"faecoming", CONST_ME_FAECOMING}, + {"faegoing", CONST_ME_FAEGOING}, + {"bigcloudssinglespace", CONST_ME_BIGCLOUDSSINGLESPACE}, + {"stonessinglespace", CONST_ME_STONESSINGLESPACE}, + {"blueghost", CONST_ME_BLUEGHOST}, + {"pointofinterest", CONST_ME_POINTOFINTEREST}, + {"mapeffect", CONST_ME_MAPEFFECT}, + {"pinkspark", CONST_ME_PINKSPARK}, + {"greenfirework", CONST_ME_FIREWORK_GREEN}, + {"orangefirework", CONST_ME_FIREWORK_ORANGE}, + {"purplefirework", CONST_ME_FIREWORK_PURPLE}, + {"turquoisefirework", CONST_ME_FIREWORK_TURQUOISE}, + {"thecube", CONST_ME_THECUBE}, + {"drawink", CONST_ME_DRAWINK}, + {"prismaticsparkles", CONST_ME_PRISMATICSPARKLES}, + {"thaian", CONST_ME_THAIAN}, + {"thaianghost", CONST_ME_THAIANGHOST}, + {"ghostsmoke", CONST_ME_GHOSTSMOKE}, + {"floatingblock", CONST_ME_FLOATINGBLOCK}, + {"block", CONST_ME_BLOCK}, + {"rooting", CONST_ME_ROOTING}, + {"ghostlyscratch", CONST_ME_GHOSTLYSCRATCH}, + {"ghostlybite", CONST_ME_GHOSTLYBITE}, + {"bigscratching", CONST_ME_BIGSCRATCHING}, + {"slash", CONST_ME_SLASH}, + {"bite", CONST_ME_BITE}, + {"chivalriouschallenge", CONST_ME_CHIVALRIOUSCHALLENGE}, + {"divinedazzle", CONST_ME_DIVINEDAZZLE}, + {"electricalspark", CONST_ME_ELECTRICALSPARK}, + {"purpleteleport", CONST_ME_PURPLETELEPORT}, + {"redteleport", CONST_ME_REDTELEPORT}, + {"orangeteleport", CONST_ME_ORANGETELEPORT}, + {"greyteleport", CONST_ME_GREYTELEPORT}, + {"lightblueteleport", CONST_ME_LIGHTBLUETELEPORT}, + {"fatal", CONST_ME_FATAL}, + {"dodge", CONST_ME_DODGE}, + {"hourglass", CONST_ME_HOURGLASS}, + {"ferumbras1", CONST_ME_FERUMBRAS_1}, + {"gazharagoth", CONST_ME_GAZHARAGOTH}, + {"madmage", CONST_ME_MAD_MAGE}, + {"horestis", CONST_ME_HORESTIS}, + {"devovorga", CONST_ME_DEVOVORGA}, + {"ferumbras2", CONST_ME_FERUMBRAS_2}, }; ShootTypeNames shootTypeNames = { - {"spear", CONST_ANI_SPEAR}, - {"bolt", CONST_ANI_BOLT}, - {"arrow", CONST_ANI_ARROW}, - {"fire", CONST_ANI_FIRE}, - {"energy", CONST_ANI_ENERGY}, - {"poisonarrow", CONST_ANI_POISONARROW}, - {"burstarrow", CONST_ANI_BURSTARROW}, - {"throwingstar", CONST_ANI_THROWINGSTAR}, - {"throwingknife", CONST_ANI_THROWINGKNIFE}, - {"smallstone", CONST_ANI_SMALLSTONE}, - {"death", CONST_ANI_DEATH}, - {"largerock", CONST_ANI_LARGEROCK}, - {"snowball", CONST_ANI_SNOWBALL}, - {"powerbolt", CONST_ANI_POWERBOLT}, - {"poison", CONST_ANI_POISON}, - {"infernalbolt", CONST_ANI_INFERNALBOLT}, - {"huntingspear", CONST_ANI_HUNTINGSPEAR}, - {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR}, - {"redstar", CONST_ANI_REDSTAR}, - {"greenstar", CONST_ANI_GREENSTAR}, - {"royalspear", CONST_ANI_ROYALSPEAR}, - {"sniperarrow", CONST_ANI_SNIPERARROW}, - {"onyxarrow", CONST_ANI_ONYXARROW}, - {"piercingbolt", CONST_ANI_PIERCINGBOLT}, - {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD}, - {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE}, - {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB}, - {"etherealspear", CONST_ANI_ETHEREALSPEAR}, - {"ice", CONST_ANI_ICE}, - {"earth", CONST_ANI_EARTH}, - {"holy", CONST_ANI_HOLY}, - {"suddendeath", CONST_ANI_SUDDENDEATH}, - {"flasharrow", CONST_ANI_FLASHARROW}, - {"flammingarrow", CONST_ANI_FLAMMINGARROW}, - {"shiverarrow", CONST_ANI_SHIVERARROW}, - {"energyball", CONST_ANI_ENERGYBALL}, - {"smallice", CONST_ANI_SMALLICE}, - {"smallholy", CONST_ANI_SMALLHOLY}, - {"smallearth", CONST_ANI_SMALLEARTH}, - {"eartharrow", CONST_ANI_EARTHARROW}, - {"explosion", CONST_ANI_EXPLOSION}, - {"cake", CONST_ANI_CAKE}, - {"tarsalarrow", CONST_ANI_TARSALARROW}, - {"vortexbolt", CONST_ANI_VORTEXBOLT}, - {"prismaticbolt", CONST_ANI_PRISMATICBOLT}, - {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW}, - {"drillbolt", CONST_ANI_DRILLBOLT}, - {"envenomedarrow", CONST_ANI_ENVENOMEDARROW}, - {"gloothspear", CONST_ANI_GLOOTHSPEAR}, - {"simplearrow", CONST_ANI_SIMPLEARROW}, + {"spear", CONST_ANI_SPEAR}, + {"bolt", CONST_ANI_BOLT}, + {"arrow", CONST_ANI_ARROW}, + {"fire", CONST_ANI_FIRE}, + {"energy", CONST_ANI_ENERGY}, + {"poisonarrow", CONST_ANI_POISONARROW}, + {"burstarrow", CONST_ANI_BURSTARROW}, + {"throwingstar", CONST_ANI_THROWINGSTAR}, + {"throwingknife", CONST_ANI_THROWINGKNIFE}, + {"smallstone", CONST_ANI_SMALLSTONE}, + {"death", CONST_ANI_DEATH}, + {"largerock", CONST_ANI_LARGEROCK}, + {"snowball", CONST_ANI_SNOWBALL}, + {"powerbolt", CONST_ANI_POWERBOLT}, + {"poison", CONST_ANI_POISON}, + {"infernalbolt", CONST_ANI_INFERNALBOLT}, + {"huntingspear", CONST_ANI_HUNTINGSPEAR}, + {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR}, + {"redstar", CONST_ANI_REDSTAR}, + {"greenstar", CONST_ANI_GREENSTAR}, + {"royalspear", CONST_ANI_ROYALSPEAR}, + {"sniperarrow", CONST_ANI_SNIPERARROW}, + {"onyxarrow", CONST_ANI_ONYXARROW}, + {"piercingbolt", CONST_ANI_PIERCINGBOLT}, + {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD}, + {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE}, + {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB}, + {"etherealspear", CONST_ANI_ETHEREALSPEAR}, + {"ice", CONST_ANI_ICE}, + {"earth", CONST_ANI_EARTH}, + {"holy", CONST_ANI_HOLY}, + {"suddendeath", CONST_ANI_SUDDENDEATH}, + {"flasharrow", CONST_ANI_FLASHARROW}, + {"flammingarrow", CONST_ANI_FLAMMINGARROW}, + {"shiverarrow", CONST_ANI_SHIVERARROW}, + {"energyball", CONST_ANI_ENERGYBALL}, + {"smallice", CONST_ANI_SMALLICE}, + {"smallholy", CONST_ANI_SMALLHOLY}, + {"smallearth", CONST_ANI_SMALLEARTH}, + {"eartharrow", CONST_ANI_EARTHARROW}, + {"explosion", CONST_ANI_EXPLOSION}, + {"cake", CONST_ANI_CAKE}, + {"tarsalarrow", CONST_ANI_TARSALARROW}, + {"vortexbolt", CONST_ANI_VORTEXBOLT}, + {"prismaticbolt", CONST_ANI_PRISMATICBOLT}, + {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW}, + {"drillbolt", CONST_ANI_DRILLBOLT}, + {"envenomedarrow", CONST_ANI_ENVENOMEDARROW}, + {"gloothspear", CONST_ANI_GLOOTHSPEAR}, + {"simplearrow", CONST_ANI_SIMPLEARROW}, + {"leafstar", CONST_ANI_LEAFSTAR}, + {"diamondarrow", CONST_ANI_DIAMONDARROW}, + {"spectralbolt", CONST_ANI_SPECTRALBOLT}, + {"royalstar", CONST_ANI_ROYALSTAR}, }; CombatTypeNames combatTypeNames = { - {COMBAT_PHYSICALDAMAGE, "physical"}, - {COMBAT_ENERGYDAMAGE, "energy"}, - {COMBAT_EARTHDAMAGE, "earth"}, - {COMBAT_FIREDAMAGE, "fire"}, - {COMBAT_UNDEFINEDDAMAGE, "undefined"}, - {COMBAT_LIFEDRAIN, "lifedrain"}, - {COMBAT_MANADRAIN, "manadrain"}, - {COMBAT_HEALING, "healing"}, - {COMBAT_DROWNDAMAGE, "drown"}, - {COMBAT_ICEDAMAGE, "ice"}, - {COMBAT_HOLYDAMAGE, "holy"}, - {COMBAT_DEATHDAMAGE, "death"}, + {COMBAT_PHYSICALDAMAGE, "physical"}, {COMBAT_ENERGYDAMAGE, "energy"}, {COMBAT_EARTHDAMAGE, "earth"}, + {COMBAT_FIREDAMAGE, "fire"}, {COMBAT_UNDEFINEDDAMAGE, "undefined"}, {COMBAT_LIFEDRAIN, "lifedrain"}, + {COMBAT_MANADRAIN, "manadrain"}, {COMBAT_HEALING, "healing"}, {COMBAT_DROWNDAMAGE, "drown"}, + {COMBAT_ICEDAMAGE, "ice"}, {COMBAT_HOLYDAMAGE, "holy"}, {COMBAT_DEATHDAMAGE, "death"}, }; AmmoTypeNames ammoTypeNames = { - {"spear", AMMO_SPEAR}, - {"bolt", AMMO_BOLT}, - {"arrow", AMMO_ARROW}, - {"poisonarrow", AMMO_ARROW}, - {"burstarrow", AMMO_ARROW}, - {"throwingstar", AMMO_THROWINGSTAR}, - {"throwingknife", AMMO_THROWINGKNIFE}, - {"smallstone", AMMO_STONE}, - {"largerock", AMMO_STONE}, - {"snowball", AMMO_SNOWBALL}, - {"powerbolt", AMMO_BOLT}, - {"infernalbolt", AMMO_BOLT}, - {"huntingspear", AMMO_SPEAR}, - {"enchantedspear", AMMO_SPEAR}, - {"royalspear", AMMO_SPEAR}, - {"sniperarrow", AMMO_ARROW}, - {"onyxarrow", AMMO_ARROW}, - {"piercingbolt", AMMO_BOLT}, - {"etherealspear", AMMO_SPEAR}, - {"flasharrow", AMMO_ARROW}, - {"flammingarrow", AMMO_ARROW}, - {"shiverarrow", AMMO_ARROW}, - {"eartharrow", AMMO_ARROW}, + {"spear", AMMO_SPEAR}, + {"bolt", AMMO_BOLT}, + {"arrow", AMMO_ARROW}, + {"poisonarrow", AMMO_ARROW}, + {"burstarrow", AMMO_ARROW}, + {"throwingstar", AMMO_THROWINGSTAR}, + {"throwingknife", AMMO_THROWINGKNIFE}, + {"smallstone", AMMO_STONE}, + {"largerock", AMMO_STONE}, + {"snowball", AMMO_SNOWBALL}, + {"powerbolt", AMMO_BOLT}, + {"infernalbolt", AMMO_BOLT}, + {"huntingspear", AMMO_SPEAR}, + {"enchantedspear", AMMO_SPEAR}, + {"royalspear", AMMO_SPEAR}, + {"sniperarrow", AMMO_ARROW}, + {"onyxarrow", AMMO_ARROW}, + {"piercingbolt", AMMO_BOLT}, + {"etherealspear", AMMO_SPEAR}, + {"flasharrow", AMMO_ARROW}, + {"flammingarrow", AMMO_ARROW}, + {"shiverarrow", AMMO_ARROW}, + {"eartharrow", AMMO_ARROW}, + {"tarsalarrow", AMMO_ARROW}, + {"vortexbolt", AMMO_BOLT}, + {"prismaticbolt", AMMO_BOLT}, + {"crystallinearrow", AMMO_ARROW}, + {"drillbolt", AMMO_BOLT}, + {"envenomedarrow", AMMO_ARROW}, + {"gloothspear", AMMO_SPEAR}, + {"simplearrow", AMMO_ARROW}, + {"redstar", AMMO_THROWINGSTAR}, + {"greenstar", AMMO_THROWINGSTAR}, + {"leafstar", AMMO_THROWINGSTAR}, + {"diamondarrow", AMMO_ARROW}, + {"spectralbolt", AMMO_BOLT}, + {"royalstar", AMMO_THROWINGSTAR}, }; WeaponActionNames weaponActionNames = { - {"move", WEAPONACTION_MOVE}, - {"removecharge", WEAPONACTION_REMOVECHARGE}, - {"removecount", WEAPONACTION_REMOVECOUNT}, + {"move", WEAPONACTION_MOVE}, + {"removecharge", WEAPONACTION_REMOVECHARGE}, + {"removecount", WEAPONACTION_REMOVECOUNT}, }; SkullNames skullNames = { - {"none", SKULL_NONE}, - {"yellow", SKULL_YELLOW}, - {"green", SKULL_GREEN}, - {"white", SKULL_WHITE}, - {"red", SKULL_RED}, - {"black", SKULL_BLACK}, - {"orange", SKULL_ORANGE}, + {"none", SKULL_NONE}, {"yellow", SKULL_YELLOW}, {"green", SKULL_GREEN}, {"white", SKULL_WHITE}, + {"red", SKULL_RED}, {"black", SKULL_BLACK}, {"orange", SKULL_ORANGE}, }; +std::vector depotBoxes = {ITEM_DEPOT_BOX_I, ITEM_DEPOT_BOX_II, ITEM_DEPOT_BOX_III, ITEM_DEPOT_BOX_IV, + ITEM_DEPOT_BOX_V, ITEM_DEPOT_BOX_VI, ITEM_DEPOT_BOX_VII, ITEM_DEPOT_BOX_VIII, + ITEM_DEPOT_BOX_IX, ITEM_DEPOT_BOX_X, ITEM_DEPOT_BOX_XI, ITEM_DEPOT_BOX_XII, + ITEM_DEPOT_BOX_XIII, ITEM_DEPOT_BOX_XIV, ITEM_DEPOT_BOX_XV, ITEM_DEPOT_BOX_XVI, + ITEM_DEPOT_BOX_XVII, ITEM_DEPOT_BOX_XVIII}; + +uint16_t getDepotBoxId(uint16_t index) +{ + if (index >= depotBoxes.size()) { + return 0; + } + return depotBoxes[index]; +} + MagicEffectClasses getMagicEffect(const std::string& strValue) { auto magicEffect = magicEffectNames.find(strValue); @@ -877,19 +922,6 @@ bool booleanString(const std::string& str) return ch != 'f' && ch != 'n' && ch != '0'; } -std::string getWeaponName(WeaponType_t weaponType) -{ - switch (weaponType) { - case WEAPON_SWORD: return "sword"; - case WEAPON_CLUB: return "club"; - case WEAPON_AXE: return "axe"; - case WEAPON_DISTANCE: return "distance"; - case WEAPON_WAND: return "wand"; - case WEAPON_AMMO: return "ammunition"; - default: return std::string(); - } -} - size_t combatTypeToIndex(CombatType_t combatType) { switch (combatType) { @@ -922,10 +954,7 @@ size_t combatTypeToIndex(CombatType_t combatType) } } -CombatType_t indexToCombatType(size_t v) -{ - return static_cast(1 << v); -} +CombatType_t indexToCombatType(size_t v) { return static_cast(1 << v); } uint8_t serverFluidToClient(uint8_t serverFluid) { @@ -995,8 +1024,14 @@ itemAttrTypes stringToItemAttribute(const std::string& str) return ITEM_ATTRIBUTE_FLUIDTYPE; } else if (str == "doorid") { return ITEM_ATTRIBUTE_DOORID; + } else if (str == "decayto") { + return ITEM_ATTRIBUTE_DECAYTO; } else if (str == "wrapid") { return ITEM_ATTRIBUTE_WRAPID; + } else if (str == "storeitem") { + return ITEM_ATTRIBUTE_STOREITEM; + } else if (str == "attackspeed") { + return ITEM_ATTRIBUTE_ATTACK_SPEED; } return ITEM_ATTRIBUTE_NONE; } @@ -1094,7 +1129,7 @@ const char* getReturnMessage(ReturnValue value) return "You do not have the required magic level to use this rune."; case RETURNVALUE_YOUAREALREADYTRADING: - return "You are already trading."; + return "You are already trading. Finish this trade first."; case RETURNVALUE_THISPLAYERISALREADYTRADING: return "This player is already trading."; @@ -1228,6 +1263,9 @@ const char* getReturnMessage(ReturnValue value) case RETURNVALUE_ITEMCANNOTBEMOVEDTHERE: return "This item cannot be moved there."; + case RETURNVALUE_YOUCANNOTUSETHISBED: + return "This bed can't be used, but Premium Account players can rent houses and sleep in beds there to regain health and mana."; + default: // RETURNVALUE_NOTPOSSIBLE, etc return "Sorry, not possible."; } @@ -1235,12 +1273,13 @@ const char* getReturnMessage(ReturnValue value) int64_t OTSYS_TIME() { - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); } SpellGroup_t stringToSpellGroup(const std::string& value) { - std::string tmpStr = asLowerCaseString(value); + std::string tmpStr = boost::algorithm::to_lower_copy(value); if (tmpStr == "attack" || tmpStr == "1") { return SPELLGROUP_ATTACK; } else if (tmpStr == "healing" || tmpStr == "2") { diff --git a/src/tools.h b/src/tools.h index 042bb01c43..7be4f817b8 100644 --- a/src/tools.h +++ b/src/tools.h @@ -1,51 +1,30 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 -#define FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 - -#include +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_TOOLS_H +#define FS_TOOLS_H -#include "position.h" #include "const.h" #include "enums.h" +#include "position.h" void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result); std::string transformToSHA1(const std::string& input); std::string generateToken(const std::string& key, uint32_t ticks); -void replaceString(std::string& str, const std::string& sought, const std::string& replacement); -void trim_right(std::string& source, char t); -void trim_left(std::string& source, char t); -void toLowerCaseString(std::string& source); -std::string asLowerCaseString(std::string source); -std::string asUpperCaseString(std::string source); +// checks that str1 is equivalent to str2 ignoring letter case +bool caseInsensitiveEqual(std::string_view str1, std::string_view str2); + +// checks that str1 starts with str2 ignoring letter case +bool caseInsensitiveStartsWith(std::string_view str, std::string_view prefix); using StringVector = std::vector; using IntegerVector = std::vector; StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); IntegerVector vectorAtoi(const StringVector& stringVector); -constexpr bool hasBitSet(uint32_t flag, uint32_t flags) { - return (flags & flag) != 0; -} +constexpr bool hasBitSet(uint32_t flag, uint32_t flags) { return (flags & flag) != 0; } std::mt19937& getRandomGenerator(); int32_t uniform_random(int32_t minNumber, int32_t maxNumber); @@ -62,8 +41,7 @@ std::string formatDate(time_t time); std::string formatDateShort(time_t time); std::string convertIPToString(uint32_t ip); -void trimString(std::string& str); - +uint16_t getDepotBoxId(uint16_t index); MagicEffectClasses getMagicEffect(const std::string& strValue); ShootType_t getShootType(const std::string& strValue); Ammo_t getAmmoType(const std::string& strValue); @@ -80,8 +58,6 @@ std::string ucfirst(std::string str); std::string ucwords(std::string str); bool booleanString(const std::string& str); -std::string getWeaponName(WeaponType_t weaponType); - size_t combatTypeToIndex(CombatType_t combatType); CombatType_t indexToCombatType(size_t v); @@ -96,4 +72,4 @@ int64_t OTSYS_TIME(); SpellGroup_t stringToSpellGroup(const std::string& value); -#endif +#endif // FS_TOOLS_H diff --git a/src/town.h b/src/town.h index be3c9e9523..8984df4981 100644 --- a/src/town.h +++ b/src/town.h @@ -1,98 +1,71 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 -#define FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 +#ifndef FS_TOWN_H +#define FS_TOWN_H #include "position.h" class Town { - public: - explicit Town(uint32_t id) : id(id) {} +public: + explicit Town(uint32_t id) : id(id) {} - const Position& getTemplePosition() const { - return templePosition; - } - const std::string& getName() const { - return name; - } + const Position& getTemplePosition() const { return templePosition; } + const std::string& getName() const { return name; } - void setTemplePos(Position pos) { - templePosition = pos; - } - void setName(std::string name) { - this->name = std::move(name); - } - uint32_t getID() const { - return id; - } + void setTemplePos(Position pos) { templePosition = pos; } + void setName(std::string name) { this->name = std::move(name); } + uint32_t getID() const { return id; } - private: - uint32_t id; - std::string name; - Position templePosition; +private: + uint32_t id; + std::string name; + Position templePosition; }; using TownMap = std::map; class Towns { - public: - Towns() = default; - ~Towns() { - for (const auto& it : townMap) { - delete it.second; - } +public: + Towns() = default; + ~Towns() + { + for (const auto& it : townMap) { + delete it.second; } + } - // non-copyable - Towns(const Towns&) = delete; - Towns& operator=(const Towns&) = delete; + // non-copyable + Towns(const Towns&) = delete; + Towns& operator=(const Towns&) = delete; - bool addTown(uint32_t townId, Town* town) { - return townMap.emplace(townId, town).second; - } + bool addTown(uint32_t townId, Town* town) { return townMap.emplace(townId, town).second; } - Town* getTown(const std::string& townName) const { - for (const auto& it : townMap) { - if (strcasecmp(townName.c_str(), it.second->getName().c_str()) == 0) { - return it.second; - } + Town* getTown(const std::string& townName) const + { + for (const auto& it : townMap) { + if (caseInsensitiveEqual(townName, it.second->getName())) { + return it.second; } - return nullptr; } + return nullptr; + } - Town* getTown(uint32_t townId) const { - auto it = townMap.find(townId); - if (it == townMap.end()) { - return nullptr; - } - return it->second; + Town* getTown(uint32_t townId) const + { + auto it = townMap.find(townId); + if (it == townMap.end()) { + return nullptr; } + return it->second; + } - const TownMap& getTowns() const { - return townMap; - } + const TownMap& getTowns() const { return townMap; } - private: - TownMap townMap; +private: + TownMap townMap; }; -#endif +#endif // FS_TOWN_H diff --git a/src/trashholder.cpp b/src/trashholder.cpp index e05f27b8b9..df841ee3f0 100644 --- a/src/trashholder.cpp +++ b/src/trashholder.cpp @@ -1,25 +1,10 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "trashholder.h" + #include "game.h" extern Game g_game; @@ -40,15 +25,9 @@ ReturnValue TrashHolder::queryRemove(const Thing&, uint32_t, uint32_t, Creature* return RETURNVALUE_NOTPOSSIBLE; } -Cylinder* TrashHolder::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) -{ - return this; -} +Cylinder* TrashHolder::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) { return this; } -void TrashHolder::addThing(Thing* thing) -{ - return addThing(0, thing); -} +void TrashHolder::addThing(Thing* thing) { return addThing(0, thing); } void TrashHolder::addThing(int32_t, Thing* thing) { diff --git a/src/trashholder.h b/src/trashholder.h index 6695ec29d1..0c76ac0072 100644 --- a/src/trashholder.h +++ b/src/trashholder.h @@ -1,57 +1,40 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 -#define FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_TRASHHOLDER_H +#define FS_TRASHHOLDER_H #include "item.h" -#include "cylinder.h" -#include "const.h" class TrashHolder final : public Item, public Cylinder { - public: - explicit TrashHolder(uint16_t itemId) : Item(itemId) {} +public: + explicit TrashHolder(uint16_t itemId) : Item(itemId) {} - TrashHolder* getTrashHolder() override { - return this; - } - const TrashHolder* getTrashHolder() const override { - return this; - } + TrashHolder* getTrashHolder() override { return this; } + const TrashHolder* getTrashHolder() const override { return this; } - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; - ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, uint32_t flags) const override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, Creature* actor = nullptr) const override; - Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; + // cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags, + Creature* actor = nullptr) const override; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - void addThing(Thing* thing) override; - void addThing(int32_t index, Thing* thing) override; + void addThing(Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void removeThing(Thing* thing, uint32_t count) override; + void removeThing(Thing* thing, uint32_t count) override; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, + cylinderlink_t link = LINK_OWNER) override; }; -#endif +#endif // FS_TRASHHOLDER_H diff --git a/src/vocation.cpp b/src/vocation.cpp index 7c1e4f8db3..0be58ba130 100644 --- a/src/vocation.cpp +++ b/src/vocation.cpp @@ -1,26 +1,11 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "vocation.h" +#include "player.h" #include "pugicast.h" #include "tools.h" @@ -34,114 +19,90 @@ bool Vocations::loadFromXml() } for (auto vocationNode : doc.child("vocations").children()) { - pugi::xml_attribute attr; - if (!(attr = vocationNode.attribute("id"))) { + pugi::xml_attribute attr = vocationNode.attribute("id"); + if (!attr) { std::cout << "[Warning - Vocations::loadFromXml] Missing vocation id" << std::endl; continue; } uint16_t id = pugi::cast(attr.value()); - - auto res = vocationsMap.emplace(std::piecewise_construct, - std::forward_as_tuple(id), std::forward_as_tuple(id)); + auto res = vocationsMap.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id)); Vocation& voc = res.first->second; - if ((attr = vocationNode.attribute("name"))) { - voc.name = attr.as_string(); - } - - if ((attr = vocationNode.attribute("clientid"))) { - voc.clientId = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("description"))) { - voc.description = attr.as_string(); - } - - if ((attr = vocationNode.attribute("gaincap"))) { - voc.gainCap = pugi::cast(attr.value()) * 100; - } - - if ((attr = vocationNode.attribute("gainhp"))) { - voc.gainHP = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("gainmana"))) { - voc.gainMana = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("gainhpticks"))) { - voc.gainHealthTicks = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("gainhpamount"))) { - voc.gainHealthAmount = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("gainmanaticks"))) { - voc.gainManaTicks = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("gainmanaamount"))) { - voc.gainManaAmount = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("manamultiplier"))) { - voc.manaMultiplier = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("attackspeed"))) { - voc.attackSpeed = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("basespeed"))) { - voc.baseSpeed = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("soulmax"))) { - voc.soulMax = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("gainsoulticks"))) { - voc.gainSoulTicks = pugi::cast(attr.value()); - } - - if ((attr = vocationNode.attribute("fromvoc"))) { - voc.fromVocation = pugi::cast(attr.value()); + vocationNode.remove_attribute("id"); + for (auto attrNode : vocationNode.attributes()) { + const char* attrName = attrNode.name(); + if (caseInsensitiveEqual(attrName, "name")) { + voc.name = attrNode.as_string(); + } else if (caseInsensitiveEqual(attrName, "allowpvp")) { + voc.allowPvp = attrNode.as_bool(); + } else if (caseInsensitiveEqual(attrName, "clientid")) { + voc.clientId = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "description")) { + voc.description = attrNode.as_string(); + } else if (caseInsensitiveEqual(attrName, "gaincap")) { + voc.gainCap = pugi::cast(attrNode.value()) * 100; + } else if (caseInsensitiveEqual(attrName, "gainhp")) { + voc.gainHP = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "gainmana")) { + voc.gainMana = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "gainhpticks")) { + voc.gainHealthTicks = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "gainhpamount")) { + voc.gainHealthAmount = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "gainmanaticks")) { + voc.gainManaTicks = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "gainmanaamount")) { + voc.gainManaAmount = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "manamultiplier")) { + voc.manaMultiplier = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "attackspeed")) { + voc.attackSpeed = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "basespeed")) { + voc.baseSpeed = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "soulmax")) { + voc.soulMax = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "gainsoulticks")) { + voc.gainSoulTicks = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "fromvoc")) { + voc.fromVocation = pugi::cast(attrNode.value()); + } else if (caseInsensitiveEqual(attrName, "nopongkicktime")) { + voc.noPongKickTime = pugi::cast(attrNode.value()) * 1000; + } else { + std::cout << "[Notice - Vocations::loadFromXml] Unknown attribute: \"" << attrName + << "\" for vocation: " << voc.id << std::endl; + } } for (auto childNode : vocationNode.children()) { - if (strcasecmp(childNode.name(), "skill") == 0) { - pugi::xml_attribute skillIdAttribute = childNode.attribute("id"); - if (skillIdAttribute) { - uint16_t skill_id = pugi::cast(skillIdAttribute.value()); - if (skill_id <= SKILL_LAST) { - voc.skillMultipliers[skill_id] = pugi::cast(childNode.attribute("multiplier").value()); + if (caseInsensitiveEqual(childNode.name(), "skill")) { + if ((attr = childNode.attribute("id"))) { + uint16_t skillId = pugi::cast(attr.value()); + if (skillId <= SKILL_LAST) { + voc.skillMultipliers[skillId] = pugi::cast(childNode.attribute("multiplier").value()); } else { - std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skill_id << " for vocation: " << voc.id << std::endl; + std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skillId + << " for vocation: " << voc.id << std::endl; } } else { - std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl; + std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id + << std::endl; } - } else if (strcasecmp(childNode.name(), "formula") == 0) { - pugi::xml_attribute meleeDamageAttribute = childNode.attribute("meleeDamage"); - if (meleeDamageAttribute) { - voc.meleeDamageMultiplier = pugi::cast(meleeDamageAttribute.value()); + } else if (caseInsensitiveEqual(childNode.name(), "formula")) { + if ((attr = childNode.attribute("meleeDamage"))) { + voc.meleeDamageMultiplier = pugi::cast(attr.value()); } - pugi::xml_attribute distDamageAttribute = childNode.attribute("distDamage"); - if (distDamageAttribute) { - voc.distDamageMultiplier = pugi::cast(distDamageAttribute.value()); + if ((attr = childNode.attribute("distDamage"))) { + voc.distDamageMultiplier = pugi::cast(attr.value()); } - pugi::xml_attribute defenseAttribute = childNode.attribute("defense"); - if (defenseAttribute) { - voc.defenseMultiplier = pugi::cast(defenseAttribute.value()); + if ((attr = childNode.attribute("defense"))) { + voc.defenseMultiplier = pugi::cast(attr.value()); } - pugi::xml_attribute armorAttribute = childNode.attribute("armor"); - if (armorAttribute) { - voc.armorMultiplier = pugi::cast(armorAttribute.value()); + if ((attr = childNode.attribute("armor"))) { + voc.armorMultiplier = pugi::cast(attr.value()); } } } @@ -161,17 +122,18 @@ Vocation* Vocations::getVocation(uint16_t id) int32_t Vocations::getVocationId(const std::string& name) const { - auto it = std::find_if(vocationsMap.begin(), vocationsMap.end(), [&name](decltype(vocationsMap)::value_type it) { - return it.second.name == name; + auto it = std::find_if(vocationsMap.begin(), vocationsMap.end(), [&name](auto it) { + return name.size() == it.second.name.size() && + std::equal(name.begin(), name.end(), it.second.name.begin(), + [](char a, char b) { return std::tolower(a) == std::tolower(b); }); }); return it != vocationsMap.end() ? it->first : -1; } uint16_t Vocations::getPromotedVocation(uint16_t id) const { - auto it = std::find_if(vocationsMap.begin(), vocationsMap.end(), [id](decltype(vocationsMap)::value_type it) { - return it.second.fromVocation == id && it.first != id; - }); + auto it = std::find_if(vocationsMap.begin(), vocationsMap.end(), + [id](auto it) { return it.second.fromVocation == id && it.first != id; }); return it != vocationsMap.end() ? it->first : VOCATION_NONE; } @@ -182,7 +144,8 @@ uint64_t Vocation::getReqSkillTries(uint8_t skill, uint16_t level) if (skill > SKILL_LAST) { return 0; } - return skillBase[skill] * std::pow(skillMultipliers[skill], static_cast(level - 11)); + return skillBase[skill] * + std::pow(skillMultipliers[skill], static_cast(level - (MINIMUM_SKILL_LEVEL + 1))); } uint64_t Vocation::getReqMana(uint32_t magLevel) diff --git a/src/vocation.h b/src/vocation.h index 47e3891227..cf4f5f63f9 100644 --- a/src/vocation.h +++ b/src/vocation.h @@ -1,134 +1,92 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D -#define FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_VOCATION_H +#define FS_VOCATION_H #include "enums.h" -#include "item.h" class Vocation { - public: - explicit Vocation(uint16_t id) : id(id) {} - - const std::string& getVocName() const { - return name; - } - const std::string& getVocDescription() const { - return description; - } - uint64_t getReqSkillTries(uint8_t skill, uint16_t level); - uint64_t getReqMana(uint32_t magLevel); - - uint16_t getId() const { - return id; - } - - uint8_t getClientId() const { - return clientId; - } - - uint32_t getHPGain() const { - return gainHP; - } - uint32_t getManaGain() const { - return gainMana; - } - uint32_t getCapGain() const { - return gainCap; - } - - uint32_t getManaGainTicks() const { - return gainManaTicks; - } - uint32_t getManaGainAmount() const { - return gainManaAmount; - } - uint32_t getHealthGainTicks() const { - return gainHealthTicks; - } - uint32_t getHealthGainAmount() const { - return gainHealthAmount; - } - - uint8_t getSoulMax() const { - return soulMax; - } - uint16_t getSoulGainTicks() const { - return gainSoulTicks; - } - - uint32_t getAttackSpeed() const { - return attackSpeed; - } - uint32_t getBaseSpeed() const { - return baseSpeed; - } - - uint32_t getFromVocation() const { - return fromVocation; - } - - float meleeDamageMultiplier = 1.0f; - float distDamageMultiplier = 1.0f; - float defenseMultiplier = 1.0f; - float armorMultiplier = 1.0f; - - private: - friend class Vocations; - - std::string name = "none"; - std::string description; - - double skillMultipliers[SKILL_LAST + 1] = {1.5, 2.0, 2.0, 2.0, 2.0, 1.5, 1.1}; - float manaMultiplier = 4.0f; - - uint32_t gainHealthTicks = 6; - uint32_t gainHealthAmount = 1; - uint32_t gainManaTicks = 6; - uint32_t gainManaAmount = 1; - uint32_t gainCap = 500; - uint32_t gainMana = 5; - uint32_t gainHP = 5; - uint32_t fromVocation = VOCATION_NONE; - uint32_t attackSpeed = 1500; - uint32_t baseSpeed = 220; - uint16_t id; - - uint16_t gainSoulTicks = 120; - - uint8_t soulMax = 100; - uint8_t clientId = 0; +public: + explicit Vocation(uint16_t id) : id(id) {} + + const std::string& getVocName() const { return name; } + const std::string& getVocDescription() const { return description; } + uint64_t getReqSkillTries(uint8_t skill, uint16_t level); + uint64_t getReqMana(uint32_t magLevel); + + uint16_t getId() const { return id; } + + uint8_t getClientId() const { return clientId; } + + uint32_t getHPGain() const { return gainHP; } + uint32_t getManaGain() const { return gainMana; } + uint32_t getCapGain() const { return gainCap; } + + uint32_t getManaGainTicks() const { return gainManaTicks; } + uint32_t getManaGainAmount() const { return gainManaAmount; } + uint32_t getHealthGainTicks() const { return gainHealthTicks; } + uint32_t getHealthGainAmount() const { return gainHealthAmount; } + + uint8_t getSoulMax() const { return soulMax; } + uint16_t getSoulGainTicks() const { return gainSoulTicks; } + + uint32_t getAttackSpeed() const { return attackSpeed; } + uint32_t getBaseSpeed() const { return baseSpeed; } + + uint32_t getFromVocation() const { return fromVocation; } + + uint32_t getNoPongKickTime() const { return noPongKickTime; } + + bool allowsPvp() const { return allowPvp; } + + float meleeDamageMultiplier = 1.0f; + float distDamageMultiplier = 1.0f; + float defenseMultiplier = 1.0f; + float armorMultiplier = 1.0f; + +private: + friend class Vocations; + + std::string name = "none"; + std::string description; + + double skillMultipliers[SKILL_LAST + 1] = {1.5, 2.0, 2.0, 2.0, 2.0, 1.5, 1.1}; + float manaMultiplier = 4.0f; + + uint32_t gainHealthTicks = 6; + uint32_t gainHealthAmount = 1; + uint32_t gainManaTicks = 6; + uint32_t gainManaAmount = 1; + uint32_t gainCap = 500; + uint32_t gainMana = 5; + uint32_t gainHP = 5; + uint32_t fromVocation = VOCATION_NONE; + uint32_t attackSpeed = 1500; + uint32_t baseSpeed = 220; + uint32_t noPongKickTime = 60000; + + uint16_t id; + uint16_t gainSoulTicks = 120; + + uint8_t soulMax = 100; + uint8_t clientId = 0; + + bool allowPvp = true; }; class Vocations { - public: - bool loadFromXml(); +public: + bool loadFromXml(); - Vocation* getVocation(uint16_t id); - int32_t getVocationId(const std::string& name) const; - uint16_t getPromotedVocation(uint16_t vocationId) const; + Vocation* getVocation(uint16_t id); + int32_t getVocationId(const std::string& name) const; + uint16_t getPromotedVocation(uint16_t vocationId) const; - private: - std::map vocationsMap; +private: + std::map vocationsMap; }; -#endif +#endif // FS_VOCATION_H diff --git a/src/weapons.cpp b/src/weapons.cpp index 15418bca74..d2881c3169 100644 --- a/src/weapons.cpp +++ b/src/weapons.cpp @@ -1,44 +1,24 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" +#include "weapons.h" + #include "combat.h" #include "configmanager.h" #include "game.h" +#include "luavariant.h" #include "pugicast.h" -#include "weapons.h" extern Game g_game; extern Vocations g_vocations; extern ConfigManager g_config; extern Weapons* g_weapons; -Weapons::Weapons() -{ - scriptInterface.initState(); -} +Weapons::Weapons() { scriptInterface.initState(); } -Weapons::~Weapons() -{ - clear(false); -} +Weapons::~Weapons() { clear(false); } const Weapon* Weapons::getWeapon(const Item* item) const { @@ -55,7 +35,7 @@ const Weapon* Weapons::getWeapon(const Item* item) const void Weapons::clear(bool fromLua) { - for (auto it = weapons.begin(); it != weapons.end(); ) { + for (auto it = weapons.begin(); it != weapons.end();) { if (fromLua == it->second->fromLua) { it = weapons.erase(it); } else { @@ -66,15 +46,9 @@ void Weapons::clear(bool fromLua) reInitState(fromLua); } -LuaScriptInterface& Weapons::getScriptInterface() -{ - return scriptInterface; -} +LuaScriptInterface& Weapons::getScriptInterface() { return scriptInterface; } -std::string Weapons::getScriptBaseName() const -{ - return "weapons"; -} +std::string Weapons::getScriptBaseName() const { return "weapons"; } void Weapons::loadDefaults() { @@ -114,11 +88,11 @@ void Weapons::loadDefaults() Event_ptr Weapons::getEvent(const std::string& nodeName) { - if (strcasecmp(nodeName.c_str(), "melee") == 0) { + if (caseInsensitiveEqual(nodeName, "melee")) { return Event_ptr(new WeaponMelee(&scriptInterface)); - } else if (strcasecmp(nodeName.c_str(), "distance") == 0) { + } else if (caseInsensitiveEqual(nodeName, "distance")) { return Event_ptr(new WeaponDistance(&scriptInterface)); - } else if (strcasecmp(nodeName.c_str(), "wand") == 0) { + } else if (caseInsensitiveEqual(nodeName, "wand")) { return Event_ptr(new WeaponWand(&scriptInterface)); } return nullptr; @@ -126,33 +100,33 @@ Event_ptr Weapons::getEvent(const std::string& nodeName) bool Weapons::registerEvent(Event_ptr event, const pugi::xml_node&) { - Weapon* weapon = static_cast(event.release()); //event is guaranteed to be a Weapon + Weapon* weapon = static_cast(event.release()); // event is guaranteed to be a Weapon auto result = weapons.emplace(weapon->getID(), weapon); if (!result.second) { - std::cout << "[Warning - Weapons::registerEvent] Duplicate registered item with id: " << weapon->getID() << std::endl; + std::cout << "[Warning - Weapons::registerEvent] Duplicate registered item with id: " << weapon->getID() + << std::endl; } return result.second; } -bool Weapons::registerLuaEvent(Weapon* event) +bool Weapons::registerLuaEvent(Weapon* weapon) { - Weapon_ptr weapon{ event }; - weapons[weapon->getID()] = weapon.release(); - + weapons[weapon->getID()] = weapon; return true; } -//monsters +// monsters int32_t Weapons::getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue) { return static_cast(std::ceil((attackSkill * (attackValue * 0.05)) + (attackValue * 0.5))); } -//players +// players int32_t Weapons::getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor) { - return static_cast(std::round((level / 5) + (((((attackSkill / 4.) + 1) * (attackValue / 3.)) * 1.03) / attackFactor))); + return static_cast( + std::round((level / 5) + (((((attackSkill / 4.) + 1) * (attackValue / 3.)) * 1.03) / attackFactor))); } bool Weapon::configureEvent(const pugi::xml_node& node) @@ -193,7 +167,7 @@ bool Weapon::configureEvent(const pugi::xml_node& node) } if ((attr = node.attribute("action"))) { - action = getWeaponAction(asLowerCaseString(attr.as_string())); + action = getWeaponAction(boost::algorithm::to_lower_copy(attr.as_string())); if (action == WEAPONACTION_NONE) { std::cout << "[Warning - Weapon::configureEvent] Unknown action " << attr.as_string() << std::endl; } @@ -222,7 +196,7 @@ bool Weapon::configureEvent(const pugi::xml_node& node) } if (vocationNode.attribute("showInDescription").as_bool(true)) { - vocStringList.push_back(asLowerCaseString(attr.as_string())); + vocStringList.push_back(boost::algorithm::to_lower_copy(attr.as_string())); } } } @@ -271,15 +245,9 @@ bool Weapon::configureEvent(const pugi::xml_node& node) return true; } -void Weapon::configureWeapon(const ItemType& it) -{ - id = it.id; -} +void Weapon::configureWeapon(const ItemType& it) { id = it.id; } -std::string Weapon::getScriptEventName() const -{ - return "onUseWeapon"; -} +std::string Weapon::getScriptEventName() const { return "onUseWeapon"; } int32_t Weapon::playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const { @@ -289,7 +257,8 @@ int32_t Weapon::playerWeaponCheck(Player* player, Creature* target, uint8_t shoo return 0; } - if (std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)) > shootRange) { + if (std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)) > + shootRange) { return 0; } @@ -379,14 +348,15 @@ void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int { if (scripted) { LuaVariant var; - var.type = VARIANT_NUMBER; - var.number = target->getID(); + var.setNumber(target->getID()); executeUseWeapon(player, var); } else { CombatDamage damage; WeaponType_t weaponType = item->getWeaponType(); if (weaponType == WEAPON_AMMO || weaponType == WEAPON_DISTANCE) { damage.origin = ORIGIN_RANGED; + } else if (weaponType == WEAPON_WAND) { + damage.origin = ORIGIN_WAND; } else { damage.origin = ORIGIN_MELEE; } @@ -404,8 +374,7 @@ void Weapon::internalUseWeapon(Player* player, Item* item, Tile* tile) const { if (scripted) { LuaVariant var; - var.type = VARIANT_TARGETPOSITION; - var.pos = tile->getPosition(); + var.setTargetPosition(tile->getPosition()); executeUseWeapon(player, var); } else { Combat::postCombatEffects(player, tile->getPosition(), params); @@ -441,6 +410,7 @@ void Weapon::onUsedWeapon(Player* player, Item* item, Tile* destTile) const } if (breakChance != 0 && uniform_random(1, 100) <= breakChance) { + player->sendSupplyUsed(item->getClientID()); Weapon::decrementItemCount(item); return; } @@ -448,6 +418,7 @@ void Weapon::onUsedWeapon(Player* player, Item* item, Tile* destTile) const switch (action) { case WEAPONACTION_REMOVECOUNT: if (g_config.getBoolean(ConfigManager::REMOVE_WEAPON_AMMO)) { + player->sendSupplyUsed(item->getClientID()); Weapon::decrementItemCount(item); } break; @@ -497,7 +468,7 @@ int32_t Weapon::getHealthCost(const Player* player) const bool Weapon::executeUseWeapon(Player* player, const LuaVariant& var) const { - //onUseWeapon(player, var) + // onUseWeapon(player, var) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Weapon::executeUseWeapon] Call stack overflow" << std::endl; return false; @@ -526,8 +497,7 @@ void Weapon::decrementItemCount(Item* item) } } -WeaponMelee::WeaponMelee(LuaScriptInterface* interface) : - Weapon(interface) +WeaponMelee::WeaponMelee(LuaScriptInterface* interface) : Weapon(interface) { params.blockedByArmor = true; params.blockedByShield = true; @@ -559,8 +529,7 @@ bool WeaponMelee::useWeapon(Player* player, Item* item, Creature* target) const return true; } -bool WeaponMelee::getSkillType(const Player* player, const Item* item, - skills_t& skill, uint32_t& skillpoint) const +bool WeaponMelee::getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const { if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { skillpoint = 1; @@ -605,13 +574,16 @@ int32_t WeaponMelee::getElementDamage(const Player* player, const Creature*, con return -normal_random(0, static_cast(maxValue * player->getVocation()->meleeDamageMultiplier)); } -int32_t WeaponMelee::getWeaponDamage(const Player* player, const Creature*, const Item* item, bool maxDamage /*= false*/) const +int32_t WeaponMelee::getWeaponDamage(const Player* player, const Creature*, const Item* item, + bool maxDamage /*= false*/) const { int32_t attackSkill = player->getWeaponSkill(item); int32_t attackValue = std::max(0, item->getAttack()); float attackFactor = player->getAttackFactor(); - int32_t maxValue = static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * player->getVocation()->meleeDamageMultiplier); + int32_t maxValue = + static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * + player->getVocation()->meleeDamageMultiplier); if (maxDamage) { return -maxValue; } @@ -619,8 +591,7 @@ int32_t WeaponMelee::getWeaponDamage(const Player* player, const Creature*, cons return -normal_random(0, maxValue); } -WeaponDistance::WeaponDistance(LuaScriptInterface* interface) : - Weapon(interface) +WeaponDistance::WeaponDistance(LuaScriptInterface* interface) : Weapon(interface) { params.blockedByArmor = true; params.combatType = COMBAT_PHYSICALDAMAGE; @@ -665,25 +636,26 @@ bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) con int32_t chance; if (it.hitChance == 0) { - //hit chance is based on distance to target and distance skill + // hit chance is based on distance to target and distance skill uint32_t skill = player->getSkillLevel(SKILL_DISTANCE); const Position& playerPos = player->getPosition(); const Position& targetPos = target->getPosition(); - uint32_t distance = std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)); + uint32_t distance = std::max(Position::getDistanceX(playerPos, targetPos), + Position::getDistanceY(playerPos, targetPos)); uint32_t maxHitChance; if (it.maxHitChance != -1) { maxHitChance = it.maxHitChance; } else if (it.ammoType != AMMO_NONE) { - //hit chance on two-handed weapons is limited to 90% + // hit chance on two-handed weapons is limited to 90% maxHitChance = 90; } else { - //one-handed is set to 75% + // one-handed is set to 75% maxHitChance = 75; } if (maxHitChance == 75) { - //chance for one-handed weapons + // chance for one-handed weapons switch (distance) { case 1: case 5: @@ -709,7 +681,7 @@ bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) con break; } } else if (maxHitChance == 90) { - //formula for two-handed weapons + // formula for two-handed weapons switch (distance) { case 1: case 5: @@ -774,15 +746,12 @@ bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) con if (chance >= uniform_random(1, 100)) { Weapon::internalUseWeapon(player, item, target, damageModifier); } else { - //miss target + // miss target Tile* destTile = target->getTile(); if (!Position::areInRange<1, 1, 0>(player->getPosition(), target->getPosition())) { - static std::vector> destList { - {-1, -1}, {0, -1}, {1, -1}, - {-1, 0}, {0, 0}, {1, 0}, - {-1, 1}, {0, 1}, {1, 1} - }; + static std::vector> destList{{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {0, 0}, + {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); Position destPos = target->getPosition(); @@ -790,7 +759,7 @@ bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) con for (const auto& dir : destList) { // Blocking tiles or tiles without ground ain't valid targets for spears Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); - if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { + if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround()) { destTile = tmpTile; break; } @@ -832,7 +801,8 @@ int32_t WeaponDistance::getElementDamage(const Player* player, const Creature* t return -normal_random(minValue, static_cast(maxValue * player->getVocation()->distDamageMultiplier)); } -int32_t WeaponDistance::getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage /*= false*/) const +int32_t WeaponDistance::getWeaponDamage(const Player* player, const Creature* target, const Item* item, + bool maxDamage /*= false*/) const { int32_t attackValue = item->getAttack(); @@ -846,7 +816,9 @@ int32_t WeaponDistance::getWeaponDamage(const Player* player, const Creature* ta int32_t attackSkill = player->getSkillLevel(SKILL_DISTANCE); float attackFactor = player->getAttackFactor(); - int32_t maxValue = static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * player->getVocation()->distDamageMultiplier); + int32_t maxValue = + static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * + player->getVocation()->distDamageMultiplier); if (maxDamage) { return -maxValue; } @@ -911,7 +883,7 @@ bool WeaponWand::configureEvent(const pugi::xml_node& node) return true; } - std::string tmpStrValue = asLowerCaseString(attr.as_string()); + std::string tmpStrValue = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStrValue == "earth") { params.combatType = COMBAT_EARTHDAMAGE; } else if (tmpStrValue == "ice") { @@ -925,7 +897,8 @@ bool WeaponWand::configureEvent(const pugi::xml_node& node) } else if (tmpStrValue == "holy") { params.combatType = COMBAT_HOLYDAMAGE; } else { - std::cout << "[Warning - WeaponWand::configureEvent] Type \"" << attr.as_string() << "\" does not exist." << std::endl; + std::cout << "[Warning - WeaponWand::configureEvent] Type \"" << attr.as_string() << "\" does not exist." + << std::endl; } return true; } diff --git a/src/weapons.h b/src/weapons.h index e889784678..c4c8ab3b4f 100644 --- a/src/weapons.h +++ b/src/weapons.h @@ -1,311 +1,226 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_WEAPONS_H_69D1993478AA42948E24C0B90B8F5BF5 -#define FS_WEAPONS_H_69D1993478AA42948E24C0B90B8F5BF5 +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_WEAPONS_H +#define FS_WEAPONS_H -#include "luascript.h" -#include "player.h" #include "baseevents.h" #include "combat.h" #include "const.h" +#include "luascript.h" #include "vocation.h" -extern Vocations g_vocations; - class Weapon; -class WeaponMelee; -class WeaponDistance; -class WeaponWand; using Weapon_ptr = std::unique_ptr; +extern Vocations g_vocations; + class Weapons final : public BaseEvents { - public: - Weapons(); - ~Weapons(); +public: + Weapons(); + ~Weapons(); - // non-copyable - Weapons(const Weapons&) = delete; - Weapons& operator=(const Weapons&) = delete; + // non-copyable + Weapons(const Weapons&) = delete; + Weapons& operator=(const Weapons&) = delete; - void loadDefaults(); - const Weapon* getWeapon(const Item* item) const; + void loadDefaults(); + const Weapon* getWeapon(const Item* item) const; - static int32_t getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue); - static int32_t getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor); + static int32_t getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue); + static int32_t getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor); - bool registerLuaEvent(Weapon* event); - void clear(bool fromLua) override final; + bool registerLuaEvent(Weapon* event); + void clear(bool fromLua) override final; - private: - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; +private: + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - std::map weapons; + std::map weapons; - LuaScriptInterface scriptInterface { "Weapon Interface" }; + LuaScriptInterface scriptInterface{"Weapon Interface"}; }; class Weapon : public Event { - public: - explicit Weapon(LuaScriptInterface* interface) : Event(interface) {} +public: + explicit Weapon(LuaScriptInterface* interface) : Event(interface) {} - bool configureEvent(const pugi::xml_node& node) override; - bool loadFunction(const pugi::xml_attribute&, bool) final { - return true; - } - virtual void configureWeapon(const ItemType& it); - virtual bool interruptSwing() const { - return false; - } + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute&, bool) final { return true; } + virtual void configureWeapon(const ItemType& it); + virtual bool interruptSwing() const { return false; } - int32_t playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const; - static bool useFist(Player* player, Creature* target); - virtual bool useWeapon(Player* player, Item* item, Creature* target) const; + int32_t playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const; + static bool useFist(Player* player, Creature* target); + virtual bool useWeapon(Player* player, Item* item, Creature* target) const; - virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const = 0; - virtual int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const = 0; - virtual CombatType_t getElementType() const = 0; + virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, + bool maxDamage = false) const = 0; + virtual int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const = 0; + virtual CombatType_t getElementType() const = 0; - uint16_t getID() const { - return id; - } - void setID(uint16_t newId) { - id = newId; - } + uint16_t getID() const { return id; } + void setID(uint16_t newId) { id = newId; } - uint32_t getReqLevel() const { - return level; - } - void setRequiredLevel(uint32_t reqlvl) { - level = reqlvl; - } + uint32_t getReqLevel() const { return level; } + void setRequiredLevel(uint32_t reqlvl) { level = reqlvl; } - uint32_t getReqMagLv() const { - return magLevel; - } - void setRequiredMagLevel(uint32_t reqlvl) { - magLevel = reqlvl; - } + uint32_t getReqMagLv() const { return magLevel; } + void setRequiredMagLevel(uint32_t reqlvl) { magLevel = reqlvl; } - bool isPremium() const { - return premium; - } - void setNeedPremium(bool prem) { - premium = prem; - } + bool isPremium() const { return premium; } + void setNeedPremium(bool prem) { premium = prem; } - bool isWieldedUnproperly() const { - return wieldUnproperly; - } - void setWieldUnproperly(bool unproperly) { - wieldUnproperly = unproperly; - } + bool isWieldedUnproperly() const { return wieldUnproperly; } + void setWieldUnproperly(bool unproperly) { wieldUnproperly = unproperly; } - uint32_t getMana() const { - return mana; - } - void setMana(uint32_t m) { - mana = m; - } + uint32_t getMana() const { return mana; } + void setMana(uint32_t m) { mana = m; } - uint32_t getManaPercent() const { - return manaPercent; - } - void setManaPercent(uint32_t m) { - manaPercent = m; - } + uint32_t getManaPercent() const { return manaPercent; } + void setManaPercent(uint32_t m) { manaPercent = m; } - int32_t getHealth() const { - return health; - } - void setHealth(int32_t h) { - health = h; - } + int32_t getHealth() const { return health; } + void setHealth(int32_t h) { health = h; } - uint32_t getHealthPercent() const { - return healthPercent; - } - void setHealthPercent(uint32_t m) { - healthPercent = m; - } + uint32_t getHealthPercent() const { return healthPercent; } + void setHealthPercent(uint32_t m) { healthPercent = m; } - uint32_t getSoul() const { - return soul; - } - void setSoul(uint32_t s) { - soul = s; - } + uint32_t getSoul() const { return soul; } + void setSoul(uint32_t s) { soul = s; } - uint8_t getBreakChance() const { - return breakChance; - } - void setBreakChance(uint8_t b) { - breakChance = b; - } + uint8_t getBreakChance() const { return breakChance; } + void setBreakChance(uint8_t b) { breakChance = b; } - bool isEnabled() const { - return enabled; - } - void setIsEnabled(bool e) { - enabled = e; - } + bool isEnabled() const { return enabled; } + void setIsEnabled(bool e) { enabled = e; } - uint32_t getWieldInfo() const { - return wieldInfo; - } - void setWieldInfo(uint32_t info) { - wieldInfo |= info; - } + uint32_t getWieldInfo() const { return wieldInfo; } + void setWieldInfo(uint32_t info) { wieldInfo |= info; } - void addVocWeaponMap(std::string vocName) { - int32_t vocationId = g_vocations.getVocationId(vocName); - if (vocationId != -1) { - vocWeaponMap[vocationId] = true; - } + void addVocWeaponMap(std::string vocName) + { + int32_t vocationId = g_vocations.getVocationId(vocName); + if (vocationId != -1) { + vocWeaponMap[vocationId] = true; } + } - const std::string& getVocationString() const { - return vocationString; - } - void setVocationString(const std::string& str) { - vocationString = str; - } + const std::string& getVocationString() const { return vocationString; } + void setVocationString(const std::string& str) { vocationString = str; } - WeaponAction_t action = WEAPONACTION_NONE; - CombatParams params; - WeaponType_t weaponType; - std::map vocWeaponMap; + WeaponAction_t action = WEAPONACTION_NONE; + CombatParams params; + WeaponType_t weaponType; + std::map vocWeaponMap; - protected: - void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const; - void internalUseWeapon(Player* player, Item* item, Tile* tile) const; +protected: + void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const; + void internalUseWeapon(Player* player, Item* item, Tile* tile) const; - uint16_t id = 0; + uint16_t id = 0; - private: - virtual bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const { - return false; - } +private: + virtual bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const { return false; } - uint32_t getManaCost(const Player* player) const; - int32_t getHealthCost(const Player* player) const; + uint32_t getManaCost(const Player* player) const; + int32_t getHealthCost(const Player* player) const; - uint32_t level = 0; - uint32_t magLevel = 0; - uint32_t mana = 0; - uint32_t manaPercent = 0; - uint32_t health = 0; - uint32_t healthPercent = 0; - uint32_t soul = 0; - uint32_t wieldInfo = WIELDINFO_NONE; - uint8_t breakChance = 0; - bool enabled = true; - bool premium = false; - bool wieldUnproperly = false; - std::string vocationString = ""; + uint32_t level = 0; + uint32_t magLevel = 0; + uint32_t mana = 0; + uint32_t manaPercent = 0; + uint32_t health = 0; + uint32_t healthPercent = 0; + uint32_t soul = 0; + uint32_t wieldInfo = WIELDINFO_NONE; + uint8_t breakChance = 0; + bool enabled = true; + bool premium = false; + bool wieldUnproperly = false; + std::string vocationString = ""; - std::string getScriptEventName() const override final; + std::string getScriptEventName() const override final; - bool executeUseWeapon(Player* player, const LuaVariant& var) const; - void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; + bool executeUseWeapon(Player* player, const LuaVariant& var) const; + void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; - static void decrementItemCount(Item* item); + static void decrementItemCount(Item* item); - friend class Combat; + friend class Combat; }; class WeaponMelee final : public Weapon { - public: - explicit WeaponMelee(LuaScriptInterface* interface); +public: + explicit WeaponMelee(LuaScriptInterface* interface); - void configureWeapon(const ItemType& it) override; + void configureWeapon(const ItemType& it) override; - bool useWeapon(Player* player, Item* item, Creature* target) const override; + bool useWeapon(Player* player, Item* item, Creature* target) const override; - int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; - int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; - CombatType_t getElementType() const override { return elementType; } + int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, + bool maxDamage = false) const override; + int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; + CombatType_t getElementType() const override { return elementType; } - private: - bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; +private: + bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; - CombatType_t elementType = COMBAT_NONE; - uint16_t elementDamage = 0; + CombatType_t elementType = COMBAT_NONE; + uint16_t elementDamage = 0; }; class WeaponDistance final : public Weapon { - public: - explicit WeaponDistance(LuaScriptInterface* interface); +public: + explicit WeaponDistance(LuaScriptInterface* interface); - void configureWeapon(const ItemType& it) override; - bool interruptSwing() const override { - return true; - } + void configureWeapon(const ItemType& it) override; + bool interruptSwing() const override { return true; } - bool useWeapon(Player* player, Item* item, Creature* target) const override; + bool useWeapon(Player* player, Item* item, Creature* target) const override; - int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; - int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; - CombatType_t getElementType() const override { return elementType; } + int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, + bool maxDamage = false) const override; + int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; + CombatType_t getElementType() const override { return elementType; } - private: - bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; +private: + bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; - CombatType_t elementType = COMBAT_NONE; - uint16_t elementDamage = 0; + CombatType_t elementType = COMBAT_NONE; + uint16_t elementDamage = 0; }; class WeaponWand final : public Weapon { - public: - explicit WeaponWand(LuaScriptInterface* interface) : Weapon(interface) {} +public: + explicit WeaponWand(LuaScriptInterface* interface) : Weapon(interface) {} - bool configureEvent(const pugi::xml_node& node) override; - void configureWeapon(const ItemType& it) override; + bool configureEvent(const pugi::xml_node& node) override; + void configureWeapon(const ItemType& it) override; - int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; - int32_t getElementDamage(const Player*, const Creature*, const Item*) const override { return 0; } - CombatType_t getElementType() const override { return COMBAT_NONE; } + int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, + bool maxDamage = false) const override; + int32_t getElementDamage(const Player*, const Creature*, const Item*) const override { return 0; } + CombatType_t getElementType() const override { return COMBAT_NONE; } - void setMinChange(int32_t change) { - minChange = change; - } + void setMinChange(int32_t change) { minChange = change; } - void setMaxChange(int32_t change) { - maxChange = change; - } + void setMaxChange(int32_t change) { maxChange = change; } - private: - bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const override { - return false; - } +private: + bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const override { return false; } - int32_t minChange = 0; - int32_t maxChange = 0; + int32_t minChange = 0; + int32_t maxChange = 0; }; -#endif +#endif // FS_WEAPONS_H diff --git a/src/wildcardtree.cpp b/src/wildcardtree.cpp index ec182b6b8b..7ac2c4b0c4 100644 --- a/src/wildcardtree.cpp +++ b/src/wildcardtree.cpp @@ -1,28 +1,13 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" -#include - #include "wildcardtree.h" +#include +#include + WildcardTreeNode* WildcardTreeNode::getChild(char ch) { auto it = children.find(ch); @@ -49,8 +34,8 @@ WildcardTreeNode* WildcardTreeNode::addChild(char ch, bool breakpoint) child->breakpoint = true; } } else { - auto pair = children.emplace(std::piecewise_construct, - std::forward_as_tuple(ch), std::forward_as_tuple(breakpoint)); + auto pair = + children.emplace(std::piecewise_construct, std::forward_as_tuple(ch), std::forward_as_tuple(breakpoint)); child = &pair.first->second; } return child; diff --git a/src/wildcardtree.h b/src/wildcardtree.h index 38aa7ed20c..8272f19873 100644 --- a/src/wildcardtree.h +++ b/src/wildcardtree.h @@ -1,49 +1,33 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_WILDCARDTREE_H_054C9BA46A1D4EA4B7C77ECE60ED4DEB -#define FS_WILDCARDTREE_H_054C9BA46A1D4EA4B7C77ECE60ED4DEB +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. + +#ifndef FS_WILDCARDTREE_H +#define FS_WILDCARDTREE_H #include "enums.h" class WildcardTreeNode { - public: - explicit WildcardTreeNode(bool breakpoint) : breakpoint(breakpoint) {} - WildcardTreeNode(WildcardTreeNode&& other) = default; +public: + explicit WildcardTreeNode(bool breakpoint) : breakpoint(breakpoint) {} + WildcardTreeNode(WildcardTreeNode&& other) = default; - // non-copyable - WildcardTreeNode(const WildcardTreeNode&) = delete; - WildcardTreeNode& operator=(const WildcardTreeNode&) = delete; + // non-copyable + WildcardTreeNode(const WildcardTreeNode&) = delete; + WildcardTreeNode& operator=(const WildcardTreeNode&) = delete; - WildcardTreeNode* getChild(char ch); - const WildcardTreeNode* getChild(char ch) const; - WildcardTreeNode* addChild(char ch, bool breakpoint); + WildcardTreeNode* getChild(char ch); + const WildcardTreeNode* getChild(char ch) const; + WildcardTreeNode* addChild(char ch, bool breakpoint); - void insert(const std::string& str); - void remove(const std::string& str); + void insert(const std::string& str); + void remove(const std::string& str); - ReturnValue findOne(const std::string& query, std::string& result) const; + ReturnValue findOne(const std::string& query, std::string& result) const; - private: - std::map children; - bool breakpoint; +private: + std::map children; + bool breakpoint; }; -#endif +#endif // FS_WILDCARDTREE_H diff --git a/src/xtea.cpp b/src/xtea.cpp index 34af5313df..12fd32d3f1 100644 --- a/src/xtea.cpp +++ b/src/xtea.cpp @@ -1,75 +1,60 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2020 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. #include "otpch.h" #include "xtea.h" -#include -#include +#include namespace xtea { -namespace { - -constexpr uint32_t delta = 0x9E3779B9; - -template -void apply_rounds(uint8_t* data, size_t length, Round round) +round_keys expand_key(const key& k) { - for (auto j = 0u; j < length; j += 8) { - uint32_t left = data[j+0] | data[j+1] << 8u | data[j+2] << 16u | data[j+3] << 24u, - right = data[j+4] | data[j+5] << 8u | data[j+6] << 16u | data[j+7] << 24u; - - round(left, right); + constexpr uint32_t delta = 0x9E3779B9; + round_keys expanded; - data[j] = static_cast(left); - data[j+1] = static_cast(left >> 8u); - data[j+2] = static_cast(left >> 16u); - data[j+3] = static_cast(left >> 24u); - data[j+4] = static_cast(right); - data[j+5] = static_cast(right >> 8u); - data[j+6] = static_cast(right >> 16u); - data[j+7] = static_cast(right >> 24u); + for (uint32_t i = 0, sum = 0, next_sum = sum + delta; i < expanded.size(); + i += 2, sum = next_sum, next_sum += delta) { + expanded[i] = sum + k[sum & 3]; + expanded[i + 1] = next_sum + k[(next_sum >> 11) & 3]; } -} + return expanded; } -void encrypt(uint8_t* data, size_t length, const key& k) +void encrypt(uint8_t* data, size_t length, const round_keys& k) { - for (uint32_t i = 0, sum = 0, next_sum = sum + delta; i < 32; ++i, sum = next_sum, next_sum += delta) { - apply_rounds(data, length, [&](uint32_t& left, uint32_t& right) { - left += ((right << 4 ^ right >> 5) + right) ^ (sum + k[sum & 3]); - right += ((left << 4 ^ left >> 5) + left) ^ (next_sum + k[(next_sum >> 11) & 3]); - }); - }; + for (int32_t i = 0; i < k.size(); i += 2) { + for (auto it = data, last = data + length; it < last; it += 8) { + uint32_t left, right; + std::memcpy(&left, it, 4); + std::memcpy(&right, it + 4, 4); + + left += ((right << 4 ^ right >> 5) + right) ^ k[i]; + right += ((left << 4 ^ left >> 5) + left) ^ k[i + 1]; + + std::memcpy(it, &left, 4); + std::memcpy(it + 4, &right, 4); + } + } } -void decrypt(uint8_t* data, size_t length, const key& k) +void decrypt(uint8_t* data, size_t length, const round_keys& k) { - for (uint32_t i = 0, sum = delta << 5, next_sum = sum - delta; i < 32; ++i, sum = next_sum, next_sum -= delta) { - apply_rounds(data, length, [&](uint32_t& left, uint32_t& right) { - right -= ((left << 4 ^ left >> 5) + left) ^ (sum + k[(sum >> 11) & 3]); - left -= ((right << 4 ^ right >> 5) + right) ^ (next_sum + k[next_sum & 3]); - }); - }; + for (int32_t i = k.size() - 1; i > 0; i -= 2) { + for (auto it = data, last = data + length; it < last; it += 8) { + uint32_t left, right; + std::memcpy(&left, it, 4); + std::memcpy(&right, it + 4, 4); + + right -= ((left << 4 ^ left >> 5) + left) ^ k[i]; + left -= ((right << 4 ^ right >> 5) + right) ^ k[i - 1]; + + std::memcpy(it, &left, 4); + std::memcpy(it + 4, &right, 4); + } + } } } // namespace xtea diff --git a/src/xtea.h b/src/xtea.h index 6f8f555d5f..91b0c3fec1 100644 --- a/src/xtea.h +++ b/src/xtea.h @@ -1,32 +1,18 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +// Copyright 2022 The Forgotten Server Authors. All rights reserved. +// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. -#ifndef TFS_XTEA_H -#define TFS_XTEA_H +#ifndef FS_XTEA_H +#define FS_XTEA_H namespace xtea { using key = std::array; +using round_keys = std::array; -void encrypt(uint8_t* data, size_t length, const key& k); -void decrypt(uint8_t* data, size_t length, const key& k); +round_keys expand_key(const key& k); +void encrypt(uint8_t* data, size_t length, const round_keys& k); +void decrypt(uint8_t* data, size_t length, const round_keys& k); } // namespace xtea -#endif // TFS_XTEA_H +#endif // FS_XTEA_H diff --git a/vc14/arch32.props b/vc17/arch32.props similarity index 100% rename from vc14/arch32.props rename to vc17/arch32.props diff --git a/vc14/arch64.props b/vc17/arch64.props similarity index 100% rename from vc14/arch64.props rename to vc17/arch64.props diff --git a/vc14/debug.props b/vc17/debug.props similarity index 100% rename from vc14/debug.props rename to vc17/debug.props diff --git a/vc14/release.props b/vc17/release.props similarity index 100% rename from vc14/release.props rename to vc17/release.props diff --git a/vc14/settings.props b/vc17/settings.props similarity index 100% rename from vc14/settings.props rename to vc17/settings.props diff --git a/vc17/theforgottenserver.ico b/vc17/theforgottenserver.ico new file mode 100644 index 0000000000..354b930280 Binary files /dev/null and b/vc17/theforgottenserver.ico differ diff --git a/vc17/theforgottenserver.rc b/vc17/theforgottenserver.rc new file mode 100644 index 0000000000..96b0dd2caa --- /dev/null +++ b/vc17/theforgottenserver.rc @@ -0,0 +1 @@ +MAINICON ICON "theforgottenserver.ico" diff --git a/vc14/theforgottenserver.sln b/vc17/theforgottenserver.sln similarity index 95% rename from vc14/theforgottenserver.sln rename to vc17/theforgottenserver.sln index d8c243d4fd..2ab700c7af 100644 --- a/vc14/theforgottenserver.sln +++ b/vc17/theforgottenserver.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2035 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "theforgottenserver", "theforgottenserver.vcxproj", "{A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}" EndProject diff --git a/vc14/theforgottenserver.vcxproj b/vc17/theforgottenserver.vcxproj similarity index 97% rename from vc14/theforgottenserver.vcxproj rename to vc17/theforgottenserver.vcxproj index cc690d2b81..2150b7c141 100644 --- a/vc14/theforgottenserver.vcxproj +++ b/vc17/theforgottenserver.vcxproj @@ -1,5 +1,5 @@ - + Debug @@ -19,6 +19,7 @@ + 16.0 Win32Proj {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E} 10.0 @@ -27,22 +28,22 @@ Application true - v142 + v143 Application true - v142 + v143 Application false - v142 + v143 Application false - v142 + v143 @@ -185,7 +186,6 @@ - @@ -213,6 +213,7 @@ + @@ -274,7 +275,6 @@ - @@ -297,6 +297,7 @@ + @@ -330,6 +331,9 @@ + + +