diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d159169d..00000000 --- a/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/bootstrap.bat b/bootstrap.bat new file mode 100644 index 00000000..dbb188f2 --- /dev/null +++ b/bootstrap.bat @@ -0,0 +1,178 @@ +@echo off +setlocal enabledelayedexpansion + +set projectDir=%CD% + +echo :::::::::::::::::::::::::::::::::::::: +echo :::::::::::::::::::::::::::::::::::::: +echo ::::: Black Tek Server Bootstrap ::::: +echo ::::: (for windows) ::::: +echo :::::::::::::::::::::::::::::::::::::: +echo :::::::::::::::::::::::::::::::::::::: + + +:: Check if vcpkg is installed first. +where vcpkg >nul 2>&1 +if %ERRORLEVEL%==0 ( + echo Vcpkg installation has been found! + echo skipping vcpkg installation.. + goto premake_input +) else ( + echo Vcpkg installation has not been detected! + echo moving forward with vpckg installation... +) + +:vcpkg_input +echo Please provide the path to the directory you wish to store your vcpgk installation! +echo Warning!! This folder MUST ALREADY EXIST, or it won't be accepted. +echo Example input: C:\Packages\vcpkg +set /p VCPKG_INSTALL_PATH=Path: + +:: Remove quotes if user included them +set VCPKG_INSTALL_PATH=!VCPKG_INSTALL_PATH:"=! + +:: Check if path exists +if not exist "!VCPKG_INSTALL_PATH!" ( + echo Error: The specified path does not exist. + echo Please enter a valid path. + goto vcpkg_input +) + +cd "!VCPKG_INSTALL_PATH!" + +:: Download and extract vcpkg +echo Downloading vcpkg... +curl -L "https://github.com/microsoft/vcpkg/archive/refs/heads/master.zip" -o vcpkg.zip || ( + echo Failed to download vcpkg. + exit /b +) +tar -xf vcpkg.zip +del vcpkg.zip +ren "vcpkg-master" "vcpkg" + +:: Setup vcpkg +echo Setting up vcpkg... +cd "!VCPKG_INSTALL_PATH!\vcpkg" +call bootstrap-vcpkg.bat || ( + echo Failed to bootstrap vcpkg. + exit /b +) +vcpkg integrate install || ( + echo Failed to integrate vcpkg. + exit /b +) +cd "!projectDir!" + +:premake_input +echo Please enter a path to a folder for storing premake +echo Warning: This folder must already exist, or it won't be accepted. +echo Example: C:\premake +set /p PREMAKE_PATH=Path: + +set PREMAKE_PATH=!PREMAKE_PATH:"=! + +:: Check if path exists +if not exist "!PREMAKE_PATH!" ( + echo Error: The specified path does not exist. + echo Please enter a valid path. + goto premake_input +) + +cd "!PREMAKE_PATH!" + +:: Download and extract premake. +:: We are currently locked into a version of premake +:: which does not fail to build using its bootstrap.bat +:: but also supports our needed features. +echo Downloading premake... +curl -L "https://github.com/premake/premake-core/archive/490686ceb24b29f16c1ec817ed05c07c5cce89c6.zip" -o premake.zip || ( + echo Failed to download premake. + exit /b +) +tar -xf premake.zip +del premake.zip +ren "premake-core-490686ceb24b29f16c1ec817ed05c07c5cce89c6" "premake-core" +set premakeBin=!PREMAKE_PATH!\premake-core\bin\release\premake5.exe + +:: Build premake +echo Building premake... +cd premake-core +cmd /c Bootstrap.bat || ( + echo Premake build failed. + exit /b +) +cls + +:: Copy premake5.exe to BlackTek-Server +echo Copying premake5.exe... +if not exist "!premakeBin!" ( + echo Source file does not exist: "!premakeBin!" + exit /b +) +if not exist "!projectDir!" ( + echo Destination directory does not exist: "!projectDir!" + exit /b +) +copy "!premakeBin!" "!projectDir!" +if errorlevel 1 ( + echo Failed to copy premake5.exe. + echo Error level: %errorlevel% + echo From: "!premakeBin!" + echo To: "!projectDir!" + exit /b +) + +:select_vs_version +cls +echo Select Visual Studio version: +echo [1] Visual Studio 2022 +echo [2] Visual Studio 2019 +echo [3] Visual Studio 2015 +echo [4] Visual Studio 2013 +echo [5] Visual Studio 2012 +echo. + +set /p choice="Enter number (1-5): " + +:: Set the vs_version variable based on user choice +if "%choice%"=="1" ( + set "vs_version=vs2022" +) else if "%choice%"=="2" ( + set "vs_version=vs2019" +) else if "%choice%"=="3" ( + set "vs_version=vs2015" +) else if "%choice%"=="4" ( + set "vs_version=vs2013" +) else if "%choice%"=="5" ( + set "vs_version=vs2012" +) else ( + echo Invalid selection. Please try again. + timeout /t 2 >nul + goto select_vs_version +) + +:: Confirm selection +echo. +echo You selected Visual Studio %vs_version% +echo. +set /p confirm="Is this correct? (Y/N): " +if /i not "%confirm%"=="Y" goto select_vs_version + +:: Run premake with selected version +echo. +echo Generating solution files for %vs_version%... +cd !projectDir! +cmd /c premake5.exe %vs_version% + +if errorlevel 1 ( + echo Failed to generate solution files. + echo Error level: %errorlevel% + pause + exit /b 1 +) + +echo Solution files generated successfully! +echo Setup complete. BlackTek has been successfully installed! +pause +exit +endlocal \ No newline at end of file diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 00000000..59f1282d --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# Echo tags +RED="\033[0;31m" +GREEN="\033[0;32m" +ORANGE="\033[31;33m" +END="\033[0;0m" + +premake_cmd="${HOME}/.local/bin/premake5" +premake_args="" + +# Prerequisite packages +debian_install="apt-get -y install git zip unzip tar curl build-essential uuid-dev pkg-config" +arch_install="pacman -Syu git zip" +fedora_install="dnf -y install git make gcc-c++ libuuid-devel perl-IPC-Cmd" +suse_install="zypper -y install git-core make gcc-c++ libuuid-devel" + +# https://stackoverflow.com/questions/45125516/possible-values-for-uname-m +arch=$(uname -m) + +build_arch="64" +make_debug="make -j \`nproc\` config=debug_${build_arch}" +make_release="make -j \`nproc\` config=release_${build_arch}" + +case "$arch" in + aarch64 | armv8* | armv9* | arm64) + build_arch="arm64" + # https://github.com/Black-Tek/BlackTek-Server/pull/5 + export VCPKG_FORCE_SYSTEM_BINARIES=1 + debian_install="${debian_install} cmake ninja-build" + arch_install="${arch_install} cmake ninja" + fedora_install="${fedora_install} cmake ninja-build" + suse_install="${suse_install} cmake ninja-build" + ;; + arm | armv7*) + build_arch="arm" + export VCPKG_FORCE_SYSTEM_BINARIES=1 + premake_args="${premake_args} --lua=lua5.4" + # Only works on Debian 12 and Ubuntu 24.04, older versions have to git clone tomlplusplus + debian_install="${debian_install} liblua5.4-dev libmysqlclient-dev libboost-system-dev libboost-iostreams-dev libpugixml-dev libcrypto++-dev libfmt-dev libcurl4-openssl-dev libtomlplusplus-dev" + skip_vcpkg=true + echo -e "${ORANGE}Arm support is experimental and will likely require some manual setup.${END}" + read -p "Press [Enter] to continue or [Ctrl + C] to cancel." _ + ;; + armv6* | i386 | i686) + echo -e "${RED}=== 32 bit systems are not supported ===${END}" + exit 1 + ;; + x86_64) + build_arch="64" + ;; + *) + echo -e "${ORANGE}Unknown processor architecture ${arch}. If your OS is 64 bit it may still work." + echo -e "32 bit systems are not supported.${END}" + read -p "Press [Enter] to continue or [Ctrl + C] to cancel." _ + ;; +esac + +check_deps () { + if ! command -v "git" &> /dev/null \ + || ! command -v "make" &> /dev/null \ + || ! command -v "zip" &> /dev/null \ + || ! command -v "curl" &> /dev/null \ + || ( command -v "pkg-config" &> /dev/null && ! pkg-config --exists uuid &> /dev/null ); then + return 1 + fi + + if [ "$build_arch" == "ARM64" ] && ( ! command -v "cmake" &> /dev/null || ! command -v "ninja" &> /dev/null ); then + return 1 + fi + + return 0 +} + +gcc_version=`gcc --version | awk 'NR==1 {print $3}' | cut -f 1 -d "."` + +if (( gcc_version < 10 )); then + echo -e "${RED}=== GCC version 10 or above is required. ===${END}" + exit 1 +fi + +if ! check_deps; then + echo "Some required software could not be found." + read -p "Attempt automatic install? (y/n): " do_install + if [ ! $do_install ] || [ $do_install == "n" ]; then + echo "Please install the prerequisite software using your package manager." + echo + echo "Debian/Ubuntu: ${debian_install}" + echo "Arch: ${arch_install}" + echo "Fedora: ${fedora_install}" + echo "OpenSUSE: ${suse_install}" + exit 1 + fi + + if command -v "apt" &> /dev/null; then + echo -e ${GREEN}Installing required software${END} + sudo apt-get -y -q update + if ! sudo $debian_install; then + echo "Missing dependencies were not installed." + echo -e "${RED}=== Configuration is not finished ===${END}" + exit 1 + fi + elif command -v "pacman" &> /dev/null; then + echo -e ${GREEN}Installing required software${END} + if ! sudo $arch_install; then + echo "Missing dependencies were not installed." + echo -e "${RED}=== Configuration is not finished ===${END}" + exit 1 + fi + elif command -v "dnf" &> /dev/null; then + echo -e ${GREEN}Installing required software${END} + if ! sudo $fedora_install; then + echo "Missing dependencies were not installed." + echo -e "${RED}=== Configuration is not finished ===${END}" + exit 1 + fi + elif command -v "zypper" &> /dev/null; then + echo -e ${GREEN}Installing required software${END} + if ! sudo $suse_install; then + echo "Missing dependencies were not installed." + echo -e "${RED}=== Configuration is not finished ===${END}" + exit 1 + fi + else + echo "Automatic install is not supported on your distribution." + echo "Please install the prerequisite software using your package manager." + echo + echo "Debian/Ubuntu: ${debian_install}" + echo "Arch: ${arch_install}" + echo "Fedora: ${fedora_install}" + echo "OpenSUSE: ${suse_install}" + echo + echo -e "${RED}=== Configuration is not finished ===${END}" + exit 1 + fi +fi + +dir=`pwd` + +if [ ! -d ~/.local/bin ]; then + mkdir -p ~/.local/bin +fi + +if ! command -v "${premake_cmd}" &> /dev/null; then + echo -e "${GREEN}Installing premake${END}" + already_uptodate=false + if [ ! -d ../premake-core ]; then + git clone https://github.com/premake/premake-core ../premake-core + cd ../premake-core + else + cd ../premake-core + if [ "$(git pull --ff-only)" == "Already up to date." ]; then + already_uptodate=true + fi + fi + + if [ ! $already_uptodate ] || [ ! -f bin/release/premake5 ]; then + if make -f Bootstrap.mak linux; then + cp -f bin/release/premake5 ~/.local/bin/premake5 + else + echo -e "${RED}=== An error occured while compiling premake. Configuration is not complete. ===${END}" + exit 1 + fi + fi +fi + +if [ ! $skip_vcpkg ]; then + # Check for vcpkg in env + if ! printenv VCPKG_ROOT &> /dev/null; then + if [ -d $HOME/vcpkg ]; then + export VCPKG_ROOT=$HOME/vcpkg + else + export VCPKG_ROOT=../vcpkg + fi + fi + + if [ ! -d $VCPKG_ROOT ] || [ ! -f $VCPKG_ROOT/vcpkg ]; then + echo -e "${GREEN}Installing vcpkg${END}" + if [ ! -d $VCPKG_ROOT ]; then + git clone https://github.com/microsoft/vcpkg ../vcpkg + fi + cd $VCPKG_ROOT + if ! ./bootstrap-vcpkg.sh -disableMetrics; then + echo -e "${RED}=== An error occured while installing vcpkg. Configuration is not complete. ===${END}" + exit 1 + fi + cp -f ./vcpkg ~/.local/bin/vcpkg + fi +fi + +cd $dir + +# In case .local/bin is not in path we use the path where we installed it instead +if ! ${premake_cmd} gmake2 ${premake_args}; then + echo -e "${RED}=== An error occured while executing premake. Configuration is not complete. ===${END}" + exit 1 +fi + +if [ ! $skip_vcpkg ]; then + if ! $VCPKG_ROOT/vcpkg install; then + echo -e "${RED}=== An error occured while executing vcpkg. Configuration is not complete. ===${END}" + exit 1 + fi +fi + +echo -e "${GREEN}=== Configuration Finished ===${END}" +echo +read -p "Would you like to compile the server now? (n: No, d: Debug, r: Release) " compile + +if [ ! $compile ] || [ $compile == "n" ]; then + echo "To compile the server run:" + echo "Debug: ${make_debug}" + echo "Release: ${make_release}" + exit 0 +elif [ $compile == "d" ]; then + if make -j `nproc` "config=debug_${build_arch}"; then + echo + echo -e "${GREEN}=== Compilation Successful ===${END}" + echo + echo "To start the server: ./Black-Tek-Server" + echo "Remember to configure MySQL details in config.lua!" + echo + echo "To re-compile:" + echo "Debug: ${make_debug}" + echo "Release: ${make_release}" + echo "Alternatively re-run bootstrap.sh" + exit 0 + else + echo + echo -e "${RED}=== Compilation Failed ===${END}" + exit 1 + fi +elif [ $compile == "r" ]; then + if make -j `nproc` "config=release_${build_arch}"; then + echo + echo -e "${GREEN}=== Compilation Successful ===${END}" + echo + echo "To start the server: ./Black-Tek-Server" + echo "Remember to configure MySQL details in config.lua!" + echo + echo "To re-compile:" + echo "Debug: ${make_debug}" + echo "Release: ${make_release}" + echo "Alternatively re-run bootstrap.sh" + exit 0 + else + echo + echo -e "${RED}=== Compilation Failed ===${END}" + exit 1 + fi +fi diff --git a/data/actions/scripts/doors.lua b/data/actions/scripts/doors.lua new file mode 100644 index 00000000..34da4f92 --- /dev/null +++ b/data/actions/scripts/doors.lua @@ -0,0 +1,116 @@ +local 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, 10775, 10784, 12092, 12099, 12188, 12197, 19840, 19849, 19980, 19989, 20273, + 20282, 22814, 22823, 25283, 25290 +} + +local closedNormalDoors = { + [1210] = 1211, [1232] = 1233, [1250] = 1251, [3545] = 3546, [4914] = 4915, [5108] = 5109, + [5126] = 5127, [5141] = 5142, [5144] = 5145, [5282] = 5283, [5736] = 5737, [6193] = 6194, + [6250] = 6251, [6901] = 6902, [7043] = 7044, [1213] = 1214, [1235] = 1236, [1253] = 1254, + [3536] = 3537, [4917] = 4918, [5099] = 5100, [5117] = 5118, [5135] = 5136, [5138] = 5139, + [5279] = 5280, [5733] = 5734, [6196] = 6197, [6253] = 6254, [6892] = 6893, [7034] = 7035, + [8542] = 8543, [9166] = 9167, [9268] = 9269, [10269] = 10270, [10469] = 10470, [10766] = 10777, + [12093] = 12094, [12100] = 12101, [12189] = 12190, [19841] = 19842, [19981] = 19982, [20274] = 20275, + [22815] = 22816, [25284] = 25285 +} + +local openVerticalDoors = { + [1211] = 1210, [1233] = 1232, [1251] = 1250, [3546] = 3545, [4915] = 4914, [5109] = 5108, + [5127] = 5126, [5142] = 5141, [5145] = 5144, [5283] = 5282, [5737] = 5736, [6194] = 6193, + [6251] = 6250, [6902] = 6901, [7044] = 7043, [8543] = 8542, [9167] = 9166, [9269] = 9268, + [10270] = 10269, [10470] = 10469, [10777] = 10766, [12094] = 12093, [12101] = 12100, [12190] = 12189, + [19842] = 19841, [19982] = 19981, [20275] = 20274, [22816] = 22815, [25285] = 25284 +} + +local openHorizontalDoors = { + [1214] = 1213, [1236] = 1235, [1254] = 1253, [3537] = 3536, [4918] = 4917, [5100] = 5099, + [5118] = 5117, [5136] = 5135, [5139] = 5138, [5280] = 5279, [5734] = 5733, [6197] = 6196, + [6254] = 6253, [6893] = 6892, [7035] = 7034, [8546] = 8545, [9169] = 9168, [9272] = 9271, + [10273] = 10272, [10479] = 10478, [10786] = 10785, [12101] = 12100, [12199] = 12198, [19851] = 19850, + [19991] = 19990, [20284] = 20283, [22825] = 22824, [25292] = 25291 +} + +local levelDoors = { + [1227] = 1228, [1229] = 1230, [1245] = 1246, [1247] = 1248, [1259] = 1260, [1261] = 1262, + [3540] = 3541, [3549] = 3550, [5103] = 5104, [5112] = 5113, [5121] = 5122, [5130] = 5131, + [5292] = 5293, [5294] = 5295, [6206] = 6207, [6208] = 6209, [6263] = 6264, [6265] = 6266, + [6896] = 6897, [6905] = 6906, [7038] = 7039, [7047] = 7048, [9653] = 9654, [9655] = 9656 +} + +local questDoors = { + [1224] = 1223, [1226] = 1225, [1242] = 1241, [1244] = 1243, [1256] = 1255, [1258] = 1257, + [3543] = 3542, [3552] = 3551, [5106] = 5105, [5115] = 5114, [5124] = 5123, [5133] = 5132, + [5289] = 5288, [5291] = 5290, [5746] = 5745, [5749] = 5748, [6203] = 6202, [6205] = 6204, + [6260] = 6259, [6262] = 6261, [6899] = 6898, [6908] = 6907, [7041] = 7040, [7050] = 7049, + [9650] = 9649, [9652] = 9651 +} + +local passthrough = { + [1634] = 1635, [1635] = 1634, [1636] = 1637, [1637] = 1636, [1638] = 1639, [1639] = 1638, + [1640] = 1641, [1641] = 1640 +} + +function onUse(player, item, fromPosition, target, toPosition) + if table.contains(lockedDoors, item:getId()) then + player:sendTextMessage(MESSAGE_INFO_DESCR, "It is locked.") + return true + end + + local closedDoor = closedNormalDoors[item:getId()] + if closedDoor then + item:transform(closedDoor, 1) + return true + end + + local verticalDoor = openVerticalDoors[item:getId()] + if verticalDoor then + local doorCreature = Tile(item:getPosition()):getTopCreature() + if doorCreature then + doorCreature:teleportTo(item:getPosition():moveRel(1, 0, 0), true) + end + item:transform(verticalDoor, 1) + return true + end + + local horizontalDoor = openHorizontalDoors[item:getId()] + if horizontalDoor then + local doorCreature = Tile(item:getPosition()):getTopCreature() + if doorCreature then + doorCreature:teleportTo(item:getPosition():moveRel(0, 1, 0), true) + end + item:transform(horizontalDoor, 1) + return true + end + + local levelDoor = levelDoors[item:getId()] + if levelDoor then + if item.actionid > 0 and player:getLevel() >= item.actionid - 1000 or player:getGroup():getAccess() then + player:teleportTo(item:getPosition(), true) + item:transform(levelDoor, 1) + else + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are not worthy.") + end + return true + end + + local questDoor = questDoors[item:getId()] + if questDoor then + if player:getStorageValue(item.actionid) ~= -1 or player:getGroup():getAccess() then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The door seems to be sealed against unwanted intruders.") + return true + end + + player:teleportTo(item:getPosition(), true) + item:transform(questDoor, 1) + return true + end + + local door = passthrough[item:getId()] + if door then + item:transform(door, 1) + return true + end + return true +end diff --git a/data/augments/druid.toml b/data/augments/druid.toml new file mode 100644 index 00000000..80265801 --- /dev/null +++ b/data/augments/druid.toml @@ -0,0 +1,36 @@ +[DruidicBlessing] +name = "Druidic Blessing" +description = "A vital blessing from the Great Meridan to only those worthy of such favor!" +modifiers = [ + # Mana gained from poison (earth) spell damage + { mod = "manasteal", value = 20, damage = "poison", origin = "spell" }, + # Stamina gain from earth (poison) conditions + { mod = "staminasteal", value = 1, chance = 5, factor = "flat", damage = "earth", origin = "condition" }, + # Soul gain from ice spells + { mod = "soulsteal", chance = 10, damage = "ice", origin = "spell" }, +] + +[LightsEmbrace] +name = "Light's Embrace" +description = "The great cleansing power of the Light" +modifiers = [ + # Undead Killer + { mod = "piercing", value = 100, damage = "holy", origin = "all", race = "undead" }, + { mod = "critical", value = 50, damage = "holy", origin = "all", race = "undead" }, +] + +[WisdomOfAges] +name = "Wisdom of Ages" +description = "The knowledge to embrace holy power's abundance of life!" +modifiers = [ + # Absorb holy damage + { mod = "absorb", value = 100, damage = "holy"}, + # Convert Damages to Holy + { mod = "reform", value = 100, chance = 100, damage = "physical", toDamage = "holy"}, + { mod = "reform", value = 30, chance = 25, damage = "death", toDamage = "holy"}, + { mod = "reform", value = 25, chance = 25, damage = "fire", toDamage = "holy"}, + { mod = "reform", value = 20, chance = 25, damage = "energy", toDamage = "holy"}, + { mod = "reform", value = 10, chance = 25, damage = "earth", toDamage = "holy"}, + { mod = "reform", value = 10, chance = 25, damage = "ice", toDamage = "holy"}, + +] \ No newline at end of file diff --git a/data/augments/knight.toml b/data/augments/knight.toml new file mode 100644 index 00000000..29ab5252 --- /dev/null +++ b/data/augments/knight.toml @@ -0,0 +1,33 @@ +[LancelotsLance] +name = "Lancelot's Lance" +description = "a powerful ability whose origins trace back to the White Knight Lancelot" +modifiers = [ + # Lifesteal + { mod = "lifesteal", value = 35, chance = 85, damage = "physical", origin = "melee"}, + # Melee Crits + { mod = "critical", value = 35, chance = 98, damage = "physical", origin = "melee"}, +] + +[GuardiansShield] +name = "Guardian's Shield" +description = "This enhancement comes from years of skilled craftsmanship, and just a touch of magic" +modifiers = [ + # Deflection + { mod = "deflect", value = 100, chance = 10 }, + { mod = "ricochet", value = 100, chance = 30 }, +] + +[WardensCalling] +name = "Warden's Calling" +description = "Great power bestowed to the protectors of the meak!" +modifiers = [ + # Absorb physical damage + { mod = "absorb", value = 100, damage = "physical"}, + # High Defenses + { mod = "resist", value = 30, chance = "25", damage = "death"}, + { mod = "resist", value = 25, chance = "25", damage = "fire"}, + { mod = "reflect", value = 20, chance = "10", damage = "energy"}, + { mod = "deflect", value = 10, chance = "10", damage = "earth"}, + { mod = "ricochet", value = 10, chance = "10", damage = "ice"}, + +] \ No newline at end of file diff --git a/data/augments/paladin.toml b/data/augments/paladin.toml new file mode 100644 index 00000000..5364929a --- /dev/null +++ b/data/augments/paladin.toml @@ -0,0 +1,47 @@ +[VitalShot] +name = "Vital Shot" +description = "An experienced marksman's ability to hit vital locations on a target" +modifiers = [ + # Piercing Shot + { mod = "piercing", value = 20, factor = "flat", origin = "ranged"} +] + +[HeavyShot] +name = "Heavy Shot" +description = "The secret to making it hurt... bigger munitions!" +modifiers = [ + # Heavy Shot + { mod = "critical", value = 50, factor = "flat", chance = 50, origin = "ranged"} +] + +[LightningShot] +name = "Lightning Shot" +description = "This special ability allows your shots such speed your ammo sometimes summons lightning itself" +modifiers = [ + # Electric Shot + { damage = "physical", mod = "conversion", value = 20, chance = 30, toDamage = "energy", origin = "ranged"} +] + +[RighteousRejuvination] +name = "Righteous Rejuvination" +description = "The devout warriors rite achieved through pain and suffering" +modifiers = [ + # Soul restore from all damage + { mod = "revive", value = 1, chance = 80} +] + +[DragonHunter] +name = "Dragon Hunter" +description = "The secret of slaying dragons passed down from the ancient hunters" +modifiers = [ + # All Fire Resistance + { damage = "fire", mod = "resist", value = 45, chance = 60}, + # Dragon Piercing shots + { monster = "Dragon", mod = "piercing", value = 100, chance = 50, origin = "ranged"}, + { monster = "DragonLord", mod = "piercing", value = 75, chance = 30, origin = "ranged"}, + # Dragon shots extra damage + { monster = "Dragon", mod = "critical", value = 65, factor = "flat", chance = 60, origin = "ranged"}, + # Ricochet some damage from Dragon Fire attacks + { monster = "Dragon", damage = "fire", mod = "ricochet", value = 95, chance = 25}, + { monster = "DragonLord", damage = "fire", mod = "ricochet", value = 35, chance = 10}, +] \ No newline at end of file diff --git a/data/augments/sorceror.toml b/data/augments/sorceror.toml new file mode 100644 index 00000000..d1f2c7b2 --- /dev/null +++ b/data/augments/sorceror.toml @@ -0,0 +1,40 @@ +[MerlinsRage] +name = "Merlins Rage" +description = "A portion of The Great Mage Merlin's power! It takes great power just to wield this power!" +modifiers = [ + # Piercing curse damage + { mod = "piercing", value = 100, damage = "death", origin="condition"}, + # Spell Crits + { mod = "critical", value = 30, chance = 25, origin = "spell"}, + # Spell Death Conversion + { mod = "conversion", value = 10, toDamage = "death", origin = "spell" }, +] + +[NecromancersDelight] +name = "Necromancer's Delight" +description = "The darkest souls find comfort in deathly pain" +modifiers = [ + # Spell Death Conversion + { mod = "absorb", value = 15, damage = "death"} +] + +[MorganasGift] +name = "Morgana's Gift" +description = "A small taste of Morgana's frightening power!" +modifiers = [ + # Spell Vamp + { mod = "lifesteal", value = 15, origin = "spell"} +] + +[GiantsDemise] +name = "Giant's Demise" +decription = "The dark knowledge to a bring down all those who are truly giant" +modifiers = [ + # Boss Piercing + { mod = "piercing", target = "boss", value = 20, origin = "spell"}, + # Boss Crits + { mod = "critical", target = "boss", value = 30, chance = 25}, + # Behemoth Piercing + { mod = "piercing", monster = "Behemoth", value = 60, origin = "spell"}, + { mod = "piercing", monster = "Behemoth", value = 60, origin = "condition"}, +] \ No newline at end of file diff --git a/data/events/events.xml b/data/events/events.xml index 836e6f64..47b9d70f 100644 --- a/data/events/events.xml +++ b/data/events/events.xml @@ -39,6 +39,8 @@ + + @@ -47,7 +49,9 @@ - + + + diff --git a/data/events/scripts/item.lua b/data/events/scripts/item.lua index d0fc2c9a..fa6693af 100644 --- a/data/events/scripts/item.lua +++ b/data/events/scripts/item.lua @@ -9,21 +9,35 @@ end function Item:onRemoveImbue(imbueType, decayRemoved) local onRemoveImbue = EventCallback.onRemoveImbue - if EventCallback.onRemoveImbue then + if onRemoveImbue then onRemoveImbue(self, imbueType, decayRemoved) end end function Item:onAttack(player, creature, blockType, combatType) local onAttack = EventCallback.onAttack - if EventCallback.onAttack then + if onAttack then onAttack(self, player, creature, blockType, combatType) end end function Item:onDefend(player, creature, blockType, combatType) local onDefend = EventCallback.onDefend - if EventCallback.onDefend then + if onDefend then onDefend(self, player, creature, blockType, combatType) end +end + +function Item:onAugment(augment) + local onAugment = EventCallback.onItemAugment + if onAugment then + onAugment(self, augment) + end +end + +function Item:onRemoveAugment(augment) + local onRemoveAugment = EventCallback.onRemoveItemAugment + if onRemoveAugment then + onRemoveAugment(self, augment) + end end \ No newline at end of file diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index f9aaaa87..afd58f65 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -135,41 +135,6 @@ function Player:onTradeCompleted(target, item, targetItem, isSuccess) end end -local soulCondition = Condition(CONDITION_SOUL, CONDITIONID_DEFAULT) -soulCondition:setTicks(4 * 60 * 1000) -soulCondition:setParameter(CONDITION_PARAM_SOULGAIN, 1) - -local function useStamina(player) - local staminaMinutes = player:getStamina() - if staminaMinutes == 0 then - return - 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 - return - end - - if timePassed > 60 then - if staminaMinutes > 2 then - staminaMinutes = staminaMinutes - 2 - else - staminaMinutes = 0 - end - nextUseStaminaTime[playerId] = currentTime + 120 - else - staminaMinutes = staminaMinutes - 1 - nextUseStaminaTime[playerId] = currentTime + 60 - end - player:setStamina(staminaMinutes) -end - function Player:onGainExperience(source, exp, rawExp, sendText) local onGainExperience = EventCallback.onGainExperience return onGainExperience and onGainExperience(self, source, exp, rawExp, sendText) or exp @@ -253,4 +218,18 @@ function Player:onSpellTry(spell, spellType) return onSpellTry(self, spell, spellType) end return true +end + +function Player:onAugment(augment) + local onAugment = EventCallback.onPlayerAugment + if onAugment then + onAugment(self, augment) + end +end + +function Player:onRemoveAugment(augment) + local onRemoveAugment = EventCallback.onRemovePlayerAugment + if onRemoveAugment then + onRemoveAugment(self, augment) + end end \ No newline at end of file diff --git a/data/migrations/29.lua b/data/migrations/29.lua index 8edcb5a8..d0ffd9c0 100644 --- a/data/migrations/29.lua +++ b/data/migrations/29.lua @@ -1,13 +1,3 @@ function onUpdateDatabase() - 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 + return false end diff --git a/data/migrations/30.lua b/data/migrations/30.lua index 81827ded..d0ffd9c0 100644 --- a/data/migrations/30.lua +++ b/data/migrations/30.lua @@ -1,16 +1,3 @@ function onUpdateDatabase() - print(">> Updating database to version 30 (reward boss system)") - db.query([[ - CREATE TABLE IF NOT EXISTS `player_rewarditems` ( - `player_id` int NOT NULL, - `sid` int NOT NULL COMMENT 'range 0-100 will be reserved for adding items to player who are offline and all > 100 is for items saved from reward chest', - `pid` int NOT NULL DEFAULT '0', - `itemtype` smallint unsigned NOT NULL, - `count` smallint NOT NULL DEFAULT '0', - `attributes` blob NOT NULL, - UNIQUE KEY `player_id_2` (`player_id`, `sid`), - FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE - ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; - ]]) - return true -end \ No newline at end of file + return false +end diff --git a/data/migrations/32.lua b/data/migrations/32.lua new file mode 100644 index 00000000..d0ffd9c0 --- /dev/null +++ b/data/migrations/32.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false +end diff --git a/data/scripts/#discord.lua b/data/scripts/#discord.lua deleted file mode 100644 index 4f3faa18..00000000 --- a/data/scripts/#discord.lua +++ /dev/null @@ -1,20 +0,0 @@ -local LoginEvent = CreatureEvent("DiscordHook") - ---Discord webhook enums ---MESSAGE_NORMAL ---MESSAGE_ERROR; ---MESSAGE_LOG; ---MESSAGE_INFO; - -local webhookLink = "TOKEN HERE" - -function LoginEvent.onLogin(player) - Game.sendDiscordMessage(webhookLink, MESSAGE_INFO, "Player: " .. player:getName() .. " has logged in") - Game.sendDiscordMessage(webhookLink, MESSAGE_ERROR, "Player: " .. player:getName() .. " has logged in") - Game.sendDiscordMessage(webhookLink, MESSAGE_NORMAL, "Player: " .. player:getName() .. " has logged in") - Game.sendDiscordMessage(webhookLink, MESSAGE_LOG, "Player: " .. player:getName() .. " has logged in") - return true -end - -LoginEvent:type("login") -LoginEvent:register() diff --git a/data/scripts/actions/others/doors.lua b/data/scripts/actions/others/doors.lua index d9d234a8..6254734e 100644 --- a/data/scripts/actions/others/doors.lua +++ b/data/scripts/actions/others/doors.lua @@ -143,7 +143,7 @@ function door.onUse(player, item, fromPosition, target, toPosition, isHotkey) local itemId = item:getId() local transformTo = 0 if table.contains(closedQuestDoors, itemId) then - if player:getStorageValue(item.actionid) ~= -1 or player:getGroup():getAccess() then + if player:getStorageValue(item.actionid) ~= -1 then item:transform(itemId + 1) player:teleportTo(toPosition, true) else @@ -151,7 +151,7 @@ function door.onUse(player, item, fromPosition, target, toPosition, isHotkey) end return true elseif table.contains(closedLevelDoors, itemId) then - if item.actionid > 0 and player:getLevel() >= item.actionid - actionIds.levelDoor or player:getGroup():getAccess() then + if item.actionid > 0 and player:getLevel() >= item.actionid - actionIds.levelDoor then item:transform(itemId + 1) player:teleportTo(toPosition, true) else diff --git a/data/scripts/actions/others/spellbook.lua b/data/scripts/actions/others/spellbook.lua index d82067d1..4642365f 100644 --- a/data/scripts/actions/others/spellbook.lua +++ b/data/scripts/actions/others/spellbook.lua @@ -8,7 +8,7 @@ function spellbook.onUse(player, item, fromPosition, target, toPosition, isHotke if spell.manapercent > 0 then spell.mana = spell.manapercent .. "%" end - if spell.params > 0 then + if spell.params then spell.words = spell.words .. " para" end spells[#spells + 1] = spell diff --git a/data/scripts/eventcallbacks/item/default_onAugment.lua b/data/scripts/eventcallbacks/item/default_onAugment.lua new file mode 100644 index 00000000..8ae90616 --- /dev/null +++ b/data/scripts/eventcallbacks/item/default_onAugment.lua @@ -0,0 +1,8 @@ +local ec = EventCallback + +ec.onItemAugment = function(self, augment) + -- default + return +end + +ec:register() diff --git a/data/scripts/eventcallbacks/item/default_onRemoveAugment.lua b/data/scripts/eventcallbacks/item/default_onRemoveAugment.lua new file mode 100644 index 00000000..338b40bc --- /dev/null +++ b/data/scripts/eventcallbacks/item/default_onRemoveAugment.lua @@ -0,0 +1,8 @@ +local ec = EventCallback + +ec.onRemoveItemAugment = function(self, augment) + -- default + return +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onAugment.lua b/data/scripts/eventcallbacks/player/default_onAugment.lua new file mode 100644 index 00000000..b04c46d4 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onAugment.lua @@ -0,0 +1,8 @@ +local ec = EventCallback + +ec.onPlayerAugment = function(self, augment) + -- default + return +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onGainExperience.lua b/data/scripts/eventcallbacks/player/default_onGainExperience.lua new file mode 100644 index 00000000..4b608512 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onGainExperience.lua @@ -0,0 +1,92 @@ +local soulCondition = Condition(CONDITION_SOUL, CONDITIONID_DEFAULT) +soulCondition:setTicks(4 * 60 * 1000) +soulCondition:setParameter(CONDITION_PARAM_SOULGAIN, 1) + +local function useStamina(player) + local staminaMinutes = player:getStamina() + if staminaMinutes == 0 then + return + 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 + return + end + + if timePassed > 60 then + if staminaMinutes > 2 then + staminaMinutes = staminaMinutes - 2 + else + staminaMinutes = 0 + end + nextUseStaminaTime[playerId] = currentTime + 120 + else + staminaMinutes = staminaMinutes - 1 + nextUseStaminaTime[playerId] = currentTime + 60 + end + player:setStamina(staminaMinutes) +end + +local default = EventCallback + +function default.onGainExperience(player, source, exp, rawExp, sendText) + if not source or source:isPlayer() then + return exp + end + + local level = player:getLevel() + + -- Soul regeneration + local vocation = player:getVocation() + if player:getSoul() < vocation:getMaxSoul() and exp >= level then + soulCondition:setParameter(CONDITION_PARAM_SOULTICKS, vocation:getSoulGainTicks() * 1000) + player:addCondition(soulCondition) + end + + -- Apply experience stage multiplier + exp = exp * Game.getExperienceStage(level) + + -- Stamina modifier + if configManager.getBoolean(configKeys.STAMINA_SYSTEM) then + useStamina(player) + + local staminaMinutes = player:getStamina() + if staminaMinutes > 2340 and player:isPremium() then + exp = exp * 1.5 + elseif staminaMinutes <= 840 then + exp = exp * 0.5 + end + end + return exp +end + +default:register() + +-- For this event, we use the trigger index math.huge so that this event is called last, thus ensuring that the +-- experience message is sent to the client with the correct value. + +local message = EventCallback + +function message.onGainExperience(player, source, exp, rawExp, sendText) + if sendText and exp ~= 0 then + local pos = player:getPosition() + local expString = exp .. (exp ~= 1 and " experience points." or " experience point.") + player:sendTextMessage(MESSAGE_EXPERIENCE, "You gained " .. expString, pos, exp, TEXTCOLOR_WHITE_EXP) + + local spectators = Game.getSpectators(pos, false, true) + for _, spectator in ipairs(spectators) do + if spectator ~= player then + spectator:sendTextMessage(MESSAGE_EXPERIENCE_OTHERS, player:getName() .. " gained " .. expString) + end + end + end + return exp +end + +message:register(math.huge) diff --git a/data/scripts/eventcallbacks/player/default_onLook.lua b/data/scripts/eventcallbacks/player/default_onLook.lua index 5b4bec0f..2ede7701 100644 --- a/data/scripts/eventcallbacks/player/default_onLook.lua +++ b/data/scripts/eventcallbacks/player/default_onLook.lua @@ -124,6 +124,14 @@ ec.onLook = function(self, thing, position, distance, description) description = description and description .. "\n" .. imbuementsDescription or imbuementsDescription end end + + if thing:isAugmented() then + for index, augment in pairs(thing:getAugments()) do + augDesc = augment:getDescription() + description = description.. " \n Augment : " .. augment:getName() .. " \n " .. augDesc + end + end + end return description diff --git a/data/scripts/eventcallbacks/player/default_onLookInMarket.lua b/data/scripts/eventcallbacks/player/default_onLookInMarket.lua new file mode 100644 index 00000000..5d4eadb7 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onLookInMarket.lua @@ -0,0 +1,311 @@ +local showAtkWeaponTypes = {WEAPON_CLUB, WEAPON_SWORD, WEAPON_AXE, WEAPON_DISTANCE} +local showDefWeaponTypes = {WEAPON_CLUB, WEAPON_SWORD, WEAPON_AXE, WEAPON_DISTANCE, WEAPON_SHIELD} + +local event = EventCallback + +event.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 + local ammoType = itemType:getAmmoType() + 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() + local skillBoosts = {} + if atkSpeed ~= 0 then + skillBoosts[#skillBoosts + 1] = string.format("attack speed %0.2f/turn", 2000 / atkSpeed) + end + + -- skill boost + 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 + +event:register() diff --git a/data/scripts/eventcallbacks/player/default_onLookInShop.lua b/data/scripts/eventcallbacks/player/default_onLookInShop.lua new file mode 100644 index 00000000..49150b4c --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onLookInShop.lua @@ -0,0 +1,25 @@ +local event = EventCallback + +event.onLookInShop = function(self, itemType, count, description) + local description = "You see " .. itemType:getItemDescription() + 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 + +event:register() diff --git a/data/scripts/eventcallbacks/player/default_onRemoveAugment.lua b/data/scripts/eventcallbacks/player/default_onRemoveAugment.lua new file mode 100644 index 00000000..466117e4 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onRemoveAugment.lua @@ -0,0 +1,8 @@ +local ec = EventCallback + +ec.onRemovePlayerAugment = function(self, augment) + -- default + return +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onRotateItem.lua b/data/scripts/eventcallbacks/player/default_onRotateItem.lua new file mode 100644 index 00000000..fe7696bc --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onRotateItem.lua @@ -0,0 +1,10 @@ +local ec = EventCallback + +ec.onRotateItem = function(self, item) + local newId = item:getType():getRotateTo() + if newId ~= 0 then + item:transform(newId) + end +end + +ec:register() diff --git a/data/scripts/eventcallbacks/player/default_onSpellTry.lua b/data/scripts/eventcallbacks/player/default_onSpellTry.lua new file mode 100644 index 00000000..4ac69a05 --- /dev/null +++ b/data/scripts/eventcallbacks/player/default_onSpellTry.lua @@ -0,0 +1,7 @@ +local ec = EventCallback + +ec.onSpellTry = function(self, spell, spellType) + return true +end + +ec:register() diff --git a/data/scripts/lib/event_callbacks.lua b/data/scripts/lib/event_callbacks.lua index aa9f81ee..7f75b54d 100644 --- a/data/scripts/lib/event_callbacks.lua +++ b/data/scripts/lib/event_callbacks.lua @@ -62,15 +62,18 @@ ec.onWrapItem = {} ec.onInventoryUpdate = {} ec.onRotateItem = {} ec.onSpellTry = {} +ec.onPlayerAugment = {} +ec.onRemovePlayerAugment = {} -- Monster ec.onDropLoot = {} ec.onSpawn = {} -- Item ec.onImbue = {} ec.onRemoveImbue = {} --- Offensive ec.onAttack = {} ec.onDefend = {} +ec.onItemAugment = {} +ec.onRemoveItemAugment = {} EventCallback = { register = function(self, triggerIndex) diff --git a/data/scripts/talkactions/storages.lua b/data/scripts/talkactions/storages.lua new file mode 100644 index 00000000..0a0c1691 --- /dev/null +++ b/data/scripts/talkactions/storages.lua @@ -0,0 +1,38 @@ +local talk = TalkAction("/storage") + +function talk.onSay(player, words, param) + if not player or not player:getGroup():getAccess() or player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local parameters = param:split(",") + local playerName = (parameters[1] or ""):trim() + local storageKey = tonumber(parameters[2]) or 0 + local storageValue = tonumber(parameters[3]) + + if not playerName or not storageKey or #parameters <= 1 then + player:sendTextMessage(MESSAGE_EVENT_ORANGE, "Insufficient parameters, usage: /storage playerName, key [, value]") + return false + end + + local checkedPlayer = Player(playerName) + if not checkedPlayer then + player:sendTextMessage(MESSAGE_EVENT_ORANGE, (string.format("Could not find player '%s'. Maybe is not online.", playerName))) + player:getPosition():sendMagicEffect(CONST_ME_BUBBLES) + return false + end + + local msg + if storageValue then + checkedPlayer:setStorageValue(storageKey, storageValue) + msg = string.format("Storage key '%s' is now set to '%d' for player '%s'.", storageKey, storageValue, checkedPlayer:getName()) + else + storageValue = checkedPlayer:getStorageValue(storageKey) + msg = string.format("Storage key '%s' is currently '%d' for player '%s'.", storageKey, storageValue, checkedPlayer:getName()) + end + + player:sendTextMessage(MESSAGE_EVENT_ORANGE, msg) +end + +talk:separator(" ") +talk:register() diff --git a/data/talkactions/scripts/server_save.lua b/data/talkactions/scripts/server_save.lua new file mode 100644 index 00000000..ce6741a8 --- /dev/null +++ b/data/talkactions/scripts/server_save.lua @@ -0,0 +1,13 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + saveData() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server Saved.") + return false +end diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml index 760dcb65..1661abb7 100644 --- a/data/talkactions/talkactions.xml +++ b/data/talkactions/talkactions.xml @@ -1,6 +1,7 @@ + diff --git a/premake5.lua b/premake5.lua index 896184b5..8962423d 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,6 +1,6 @@ workspace "Black-Tek-Server" configurations { "Debug", "Release"} - platforms { "64", "ARM64" } + platforms { "64", "ARM64", "ARM" } location "" editorintegration "On" @@ -12,62 +12,134 @@ workspace "Black-Tek-Server" objdir "build/%{cfg.buildcfg}/obj" location "" files { "src/**.cpp", "src/**.h" } - flags {"LinkTimeOptimization", "MultiProcessorCompile"} + flags {"MultiProcessorCompile"} enableunitybuild "On" intrinsics "On" + editandcontinue "Off" + + newoption { + trigger = "lua", + description = "Specific Lua library to use. For example lua5.4. Useful if the packaged lua does not provide a symbolic liblua.so", + value = "libname", + category = "BlackTek", -- Group options together + default = "lua", + allowed = { + {"lua", "Default"}, + {"lua5.4", "Use Lua 5.4"}, + {"lua5.3", "Use Lua 5.3"}, + } + } + + newoption { + trigger = "custom-includes", + description = "A comma separated list of custom include paths.", + value = "include paths", + category = "BlackTek", -- Group options together + } + + newoption { + trigger = "custom-libs", + description = "A comma separated list of custom library paths.", + value = "library paths", + category = "BlackTek", -- Group options together + } + + newoption { + trigger = "verbose", + description = "Show warnings during compilation.", + category = "BlackTek" -- Group options together + } + + if _OPTIONS["custom-includes"] then + includedirs { string.explode(_OPTIONS["custom-includes"], ",") } + end + + if _OPTIONS["custom-libs"] then + libdirs { string.explode(_OPTIONS["custom-libs"], ",") } + end filter "configurations:Debug" defines { "DEBUG" } + runtime "Debug" symbols "On" optimize "Debug" + flags {"NoIncrementalLink"} filter {} filter "configurations:Release" defines { "NDEBUG" } - symbols "On" - optimize "Speed" + runtime "Release" + symbols "Off" + optimize "Full" filter {} - + filter "platforms:64" architecture "x86_64" + filter {} filter "platforms:ARM64" architecture "ARM64" + filter {} + + filter "platforms:ARM" + architecture "ARM" + filter {} filter "system:not windows" buildoptions { "-Wall", "-Wextra", "-pedantic", "-pipe", "-fvisibility=hidden", "-Wno-unused-local-typedefs" } linkoptions{"-flto=auto"} + flags {} filter {} filter "system:windows" openmp "On" characterset "MBCS" - debugformat "c7" linkoptions {"/IGNORE:4099"} + buildoptions {"/bigobj"} vsprops { VcpkgEnableManifest = "true" } + symbolspath '$(OutDir)$(TargetName).pdb' filter {} filter "architecture:amd64" vectorextensions "AVX" filter{} + filter {"system:linux", "options:verbose"} + linkoptions { "-v" } + warnings "Extra" + filter {} + + filter { "system:linux", "not options:verbose" } + warnings "Off" + filter {} + + filter { "system:linux", "architecture:ARM" } + -- Paths to vcpkg installed dependencies + libdirs { "vcpkg_installed/arm-linux/lib", "/usr/arm-linux-gnueabihf" } + includedirs { "vcpkg_installed/arm-linux/include", "/usr/arm-linux-gnueabihf" } + filter{} + filter { "system:linux", "architecture:ARM64" } -- Paths to vcpkg installed dependencies - libdirs { "vcpkg_installed/arm64-linux/lib" } - includedirs { "vcpkg_installed/arm64-linux/include" } - links { "pugixml", "lua", "fmt", "ssl", "mariadb", "cryptopp", "crypto", "boost_iostreams", "zstd", "z", "curl" } + libdirs { "vcpkg_installed/arm64-linux/lib", "/usr/arm-linux-gnueabi" } + includedirs { "vcpkg_installed/arm64-linux/include", "/usr/arm-linux-gnueabi" } filter{} filter { "system:linux", "architecture:amd64" } -- Paths to vcpkg installed dependencies libdirs { "vcpkg_installed/x64-linux/lib" } includedirs { "vcpkg_installed/x64-linux/include" } - links { "pugixml", "lua", "fmt", "ssl", "mariadb", "cryptopp", "crypto", "boost_iostreams", "zstd", "z", "curl" } + filter{} + + filter "system:linux" + -- Common Linux paths + libdirs { "/usr/lib" } + includedirs { "/usr/include", "/usr/include/lua5.*" } + links { "pugixml", _OPTIONS["lua"], "fmt", "mariadb", "cryptopp", "boost_iostreams", "zstd", "z", "curl", "ssl", "crypto" } filter{} filter "toolset:gcc" buildoptions { "-fno-strict-aliasing" } - buildoptions {"-std=c++20"} filter {} filter "toolset:clang" diff --git a/src/actions.cpp b/src/actions.cpp index 0129be36..f65dd475 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -191,27 +191,27 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) bool Actions::registerLuaEvent(Action* event) { Action_ptr action{ event }; - if (isValid(ids, event)) { - const auto& range = getItemIdRange(event); - for (auto& id : range) { + 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 << " in range from id: " << range.front() << ", to id: " << range.back() << std::endl; } } return true; - } else if (isValid(uids, event)) { - const auto& range = getUniqueIdRange(event); - for (auto& id : range) { + } 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: " << id << " in range from uid: " << range.front() << ", to uid: " << range.back() << std::endl; } } return true; - } else if (isValid(aids, event)) { - const auto& range = getActionIdRange(event); - for (auto& id : range) { + } 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: " << id << " in range from aid: " << range.front() << ", to aid: " << range.back() << std::endl; diff --git a/src/actions.h b/src/actions.h index 1cbe4ca0..7cd99796 100644 --- a/src/actions.h +++ b/src/actions.h @@ -45,6 +45,35 @@ class Action : public Event 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() { @@ -60,6 +89,9 @@ class Action : public Event bool allowFarUse = false; bool checkFloor = true; bool checkLineOfSight = true; + std::vector ids; + std::vector uids; + std::vector aids; }; class Actions final : public BaseEvents @@ -79,23 +111,9 @@ class Actions final : public BaseEvents ReturnValue canUse(const Player* player, const Position& pos, const Item* item); ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor); - void clearItemIdRange(Action* action) { ids.erase(action); } - const std::vector& getItemIdRange(Action* action) const { return ids.at(action); } - void addItemId(Action* action, uint16_t id) { ids[action].emplace_back(id); } - - void clearUniqueIdRange(Action* action) { uids.erase(action); } - const std::vector& getUniqueIdRange(Action* action) const { return uids.at(action); } - void addUniqueId(Action* action, uint16_t id) { uids[action].emplace_back(id); } - - void clearActionIdRange(Action* action) { aids.erase(action); } - const std::vector& getActionIdRange(Action* action) const { return aids.at(action); } - void addActionId(Action* action, uint16_t id) { aids[action].emplace_back(id); } - bool registerLuaEvent(Action* event); void clear(bool fromLua) override final; - bool isValid(std::map> map, Action* action) { return map.find(action) != map.end(); } - private: ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey); @@ -109,10 +127,6 @@ class Actions final : public BaseEvents ActionUseMap uniqueItemMap; ActionUseMap actionItemMap; - std::map> ids; - std::map> uids; - std::map> aids; - Action* getAction(const Item* item); void clearMap(ActionUseMap& map, bool fromLua); diff --git a/src/augment.cpp b/src/augment.cpp new file mode 100644 index 00000000..b3c09f6b --- /dev/null +++ b/src/augment.cpp @@ -0,0 +1,49 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + + +#include "augment.h" + +Augment::Augment(std::string name, std::string description) : m_name(name), m_description(description) { + +} + +Augment::Augment(std::shared_ptr& original) : m_name(original->m_name), m_description(original->m_description) { + + for (const auto& mod : original->m_attack_modifiers) { + auto copiedMod = std::make_shared(*mod); + m_attack_modifiers.push_back(copiedMod); + } + + for (const auto& mod : original->m_defense_modifiers) { + auto copiedMod = std::make_shared(*mod); + m_defense_modifiers.push_back(copiedMod); + } +} + + +std::vector> Augment::getAttackModifiers(uint8_t modType) { + std::vector> modifiers; + for (auto& mod : m_attack_modifiers) { + + if (mod->getType() == modType) { + modifiers.emplace_back(mod); + } + } + return modifiers; +} + +std::vector> Augment::getDefenseModifiers(uint8_t modType) { + std::vector> modifiers; + for (auto& mod : m_defense_modifiers) { + + if (mod->getType() == modType) { + modifiers.emplace_back(mod); + } + } + return modifiers; +} \ No newline at end of file diff --git a/src/augment.h b/src/augment.h new file mode 100644 index 00000000..821807f1 --- /dev/null +++ b/src/augment.h @@ -0,0 +1,184 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + + +#ifndef FS_AUGMENT_H +#define FS_AUGMENT_H + +#include "damagemodifier.h" + +class Augment : public std::enable_shared_from_this { + +public: + Augment() = default; + Augment(std::string name, std::string description = ""); + Augment(std::shared_ptr& original); + + ~Augment() = default; + + // allow copying + explicit Augment(const Augment&) = default; + Augment& operator=(const Augment&) = default; + + // comparison operator + std::strong_ordering operator<=>(const Augment& other) const = default; + + const std::string getName() const; + const std::string getDescription() const; + + void setName(std::string name); + void setDescription(std::string description); + + static std::shared_ptr MakeAugment(std::string augmentName, std::string description = ""); + static std::shared_ptr MakeAugment(std::shared_ptr& originalPointer); + + void addModifier(std::shared_ptr& modifier); + void removeModifier(std::shared_ptr& modifier); + + std::vector>& getAttackModifiers(); + std::vector>& getDefenseModifiers(); + + std::vector> getAttackModifiers(uint8_t modType); + std::vector> getDefenseModifiers(uint8_t modType); + + void serialize(PropWriteStream& propWriteStream) const { + // Serialize m_name and m_description + propWriteStream.writeString(m_name); + propWriteStream.writeString(m_description); + + // Serialize m_attack_modifiers + propWriteStream.write(m_attack_modifiers.size()); // Write the number of attack modifiers + for (const auto& modifier : m_attack_modifiers) { + modifier->serialize(propWriteStream); // Call serialize on each DamageModifier + } + + // Serialize m_defense_modifiers + propWriteStream.write(m_defense_modifiers.size()); // Write the number of defense modifiers + for (const auto& modifier : m_defense_modifiers) { + modifier->serialize(propWriteStream); // Call serialize on each DamageModifier + } + } + + bool unserialize(PropStream& propReadStream) { + // Deserialize m_name and m_description + auto [name, successName] = propReadStream.readString(); + if (!successName) { + std::cout << "WARNING: Failed to deserialize augment name" << std::endl; + return false; + } + m_name = std::string(name); + + auto [description, successDesc] = propReadStream.readString(); + if (!successDesc) { + std::cout << "WARNING: Failed to deserialize augment description" << std::endl; + return false; + } + m_description = std::string(description); + + // Deserialize m_attack_modifiers + uint32_t attackModifierCount; + if (!propReadStream.read(attackModifierCount)) { + std::cout << "WARNING: Failed to deserialize attack modifier count" << std::endl; + return false; + } + + m_attack_modifiers.clear(); + for (uint32_t i = 0; i < attackModifierCount; ++i) { + auto modifier = std::make_shared(); + if (!modifier->unserialize(propReadStream)) { + std::cout << "WARNING: Failed to deserialize attack modifier " << i << std::endl; + return false; + } + m_attack_modifiers.push_back(modifier); + } + + // Deserialize m_defense_modifiers + uint32_t defenseModifierCount; + if (!propReadStream.read(defenseModifierCount)) { + std::cout << "WARNING: Failed to deserialize defense modifier count" << std::endl; + return false; + } + + m_defense_modifiers.clear(); + for (uint32_t i = 0; i < defenseModifierCount; ++i) { + auto modifier = std::make_shared(); + if (!modifier->unserialize(propReadStream)) { + std::cout << "WARNING: Failed to deserialize defense modifier " << i << std::endl; + return false; + } + m_defense_modifiers.push_back(modifier); + } + + return true; + } + + +private: + + std::vector> m_attack_modifiers; + std::vector> m_defense_modifiers; + std::string m_name; + std::string m_description; +}; + + +inline std::shared_ptr Augment::MakeAugment(std::string augmentName, std::string description) { + auto augment = std::make_shared(augmentName); + return augment; +} + +inline std::shared_ptr Augment::MakeAugment(std::shared_ptr& originalRef) +{ + auto augmentClone = std::make_shared(originalRef); + return augmentClone; +} + +inline const std::string Augment::getName() const { + return m_name; +} + +inline const std::string Augment::getDescription() const +{ + return m_description; +} + +inline void Augment::setName(std::string name) { + m_name = name; +} + +inline void Augment::setDescription(std::string description) { + m_description = description; +} + +inline void Augment::addModifier(std::shared_ptr& mod) { + if (mod->getStance() == ATTACK_MOD) { + m_attack_modifiers.push_back(mod); + } else if (mod->getStance() == DEFENSE_MOD) { + m_defense_modifiers.push_back(mod); + } +} + +inline void Augment::removeModifier(std::shared_ptr& mod) { + if (mod->getStance() == ATTACK_MOD) { + m_attack_modifiers.erase(std::remove(m_attack_modifiers.begin(), m_attack_modifiers.end(), mod), m_attack_modifiers.end()); + } + else if (mod->getStance() == DEFENSE_MOD) { + m_defense_modifiers.erase(std::remove(m_defense_modifiers.begin(), m_defense_modifiers.end(), mod), m_defense_modifiers.end()); + } +} + + +inline std::vector>& Augment::getAttackModifiers() +{ + return m_attack_modifiers; +} + +inline std::vector>& Augment::getDefenseModifiers() +{ + return m_defense_modifiers; +} +#endif \ No newline at end of file diff --git a/src/augments.cpp b/src/augments.cpp new file mode 100644 index 00000000..9ca2cc11 --- /dev/null +++ b/src/augments.cpp @@ -0,0 +1,363 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + +#include +#include +#include +#include + +#include "augments.h" + +static std::unordered_map> global_augments {}; + +std::shared_ptr Augments::MakeAugment(std::string_view augmentName) +{ + auto it = global_augments.find(augmentName.data()); + + if (it != global_augments.end()) { + auto augmentClone = Augment::MakeAugment(it->second); + return augmentClone; + } + std::cout << "Failed to find augment named : " << augmentName; + return nullptr; +} + +void Augments::loadAll() { + for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) { + if (entry.is_regular_file() && entry.path().extension() == ".toml") { + + try { + + auto file = toml::parse_file(entry.path().string()); + + for (const auto& [index, entry] : file) { + + toml::table augment_info = *entry.as_table(); + auto modifier_data = augment_info["modifiers"]; + std::string name = augment_info["name"].value_or("unknown"); + std::string description = augment_info["description"].value_or("unknown"); + + if (name == "unknown") { + std::cout << "Error: All augments require a name \n"; + break; + } + std::shared_ptr augment = Augment::MakeAugment(name); + augment->setDescription(description); + if (auto mod_list = modifier_data.as_array()) { + mod_list->for_each([augment, name](auto&& prop) { + if (prop.is_table()) { + auto& table = *prop.as_table(); + std::string_view modType = table["mod"].value_or("none"); + uint16_t amount = table["value"].value_or(0); + std::string_view factor = table["factor"].value_or("none"); + uint8_t chance = table["chance"].value_or(100); + std::string_view damageType = table["damage"].value_or("none"); + std::string_view originType = table["origin"].value_or("none"); + std::string_view creatureType = table["target"].value_or("none"); + std::string_view race = table["race"].value_or("none"); + std::string_view creatureName = table["monster"].value_or("none"); + + // To-do: Change all static methods used below to accept const values and use const variables above. + // also change the 'Get' methods into 'parse' methods for clarity + if (ParseStance(modType) == ATTACK_MOD) { + + std::shared_ptr damage_modifier = DamageModifier::makeModifier( + ParseStance(modType), + ParseAttackModifier(modType), + amount, + ParseFactor(factor), + chance, + ParseDamage(damageType), + ParseOrigin(originType), + ParseCreatureType(creatureType), + ParseRaceType(race)); + + // To-do : create a new variable for storing monster names to not conflict with other aux variables + if (modType == "conversion") { + auto convertedType = ParseDamage(table["toDamage"].value_or("none")); + damage_modifier->setTransformDamageType(convertedType); + } + + if (creatureName != "none") { + damage_modifier->setCreatureName(creatureName); + } + + augment->addModifier(damage_modifier); + + } else if (ParseStance(modType) == DEFENSE_MOD) { + + std::shared_ptr damage_modifier = DamageModifier::makeModifier( + ParseStance(modType), + ParseDefenseModifier(modType), + amount, + ParseFactor(factor), + chance, + ParseDamage(damageType), + ParseOrigin(originType), + ParseCreatureType(creatureType), + ParseRaceType(race)); + + if (modType == "reform") { + auto reformType = ParseDamage(table["toDamage"].value_or("none")); + damage_modifier->setTransformDamageType(reformType); + } + + if (creatureName != "none") { + damage_modifier->setCreatureName(creatureName); + } + + augment->addModifier(damage_modifier); + + } else { + + std::cout << "Modifier has unknown stance " << table["stance"] << "\n"; + } + } + }); + } + AddAugment(augment); + } + } catch (const toml::parse_error& err) { + std::cerr << "Error parsing file " << entry.path() << ": " << err << "\n"; + } + } + } +} + +void Augments::clearAll() +{ + global_augments.clear(); +} + +void Augments::reload() +{ + clearAll(); + loadAll(); + // if (config::deleteOldAugments) { CleanPlayerAugments(); } +} + +const ModifierStance Augments::ParseStance(std::string_view modName) noexcept +{ + if (ParseAttackModifier(modName) != ATTACK_MODIFIER_NONE) { + return ATTACK_MOD; + } else if (ParseDefenseModifier(modName) != DEFENSE_MODIFIER_NONE) { + return DEFENSE_MOD; + } + std::cout << "[::Augment Error::] no such mod by type name : " << std::string{ modName } << " /n"; + return NO_MOD; +} + +const ModFactor Augments::ParseFactor(std::string_view factor) noexcept +{ + std::string f_type = std::string{ factor }; + if (f_type == "flat") { + return FLAT_MODIFIER; + } + return PERCENT_MODIFIER; +} + +const CombatType_t Augments::ParseDamage(std::string_view damageName) noexcept +{ // Note : If you add values to the list you must increase the size manually + // current size is : 21 + const std::array, 21> static_map{ { + {"none", COMBAT_NONE}, + {"all", COMBAT_NONE}, + {"physical", COMBAT_PHYSICALDAMAGE}, + {"melee", COMBAT_PHYSICALDAMAGE}, + {"energy", COMBAT_ENERGYDAMAGE}, + {"electric", COMBAT_ENERGYDAMAGE}, + {"earth", COMBAT_EARTHDAMAGE}, + {"poison", COMBAT_EARTHDAMAGE}, + {"fire", COMBAT_FIREDAMAGE}, + {"lifedrain", COMBAT_LIFEDRAIN}, + {"lifesteal", COMBAT_LIFEDRAIN}, + {"lifeleech", COMBAT_LIFEDRAIN}, + {"manadrain", COMBAT_MANADRAIN}, + {"manasteal", COMBAT_MANADRAIN}, + {"manaleech", COMBAT_MANADRAIN}, + {"drown", COMBAT_DROWNDAMAGE}, + {"water", COMBAT_DROWNDAMAGE}, + {"ice", COMBAT_ICEDAMAGE}, + {"holy", COMBAT_HOLYDAMAGE}, + {"death", COMBAT_DEATHDAMAGE}, + {"curse", COMBAT_DEATHDAMAGE}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == damageName) { + return value; + } + } + + return COMBAT_NONE; +} + +const CombatOrigin Augments::ParseOrigin(std::string_view originName) noexcept +{ // Note : If you add values to the list you must increase the size manually + // current size is : 14 + const std::array, 14> static_map{ { + {"none", ORIGIN_NONE}, + {"all", ORIGIN_NONE}, + {"condition", ORIGIN_CONDITION}, + {"spell", ORIGIN_SPELL}, + {"melee", ORIGIN_MELEE}, + {"ranged", ORIGIN_RANGED}, + {"absorb", ORIGIN_ABSORB}, + {"restore", ORIGIN_RESTORE}, + {"reflect", ORIGIN_REFLECT}, + {"deflect", ORIGIN_DEFLECT}, + {"ricochet", ORIGIN_RICOCHET}, + {"piercing", ORIGIN_PIERCING}, + {"augment", ORIGIN_AUGMENT}, + {"imbuement", ORIGIN_IMBUEMENT}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == originName) { + return value; + } + } + + return ORIGIN_NONE; +} + +const ModifierAttackType Augments::ParseAttackModifier(std::string_view modName) noexcept { + // Note : If you add values to the list you must increase the size manually + // current size is : 8 + const std::array, 8> static_map{ { + {"none", ATTACK_MODIFIER_NONE}, + {"lifesteal", ATTACK_MODIFIER_LIFESTEAL}, + {"manasteal", ATTACK_MODIFIER_MANASTEAL}, + {"staminasteal", ATTACK_MODIFIER_STAMINASTEAL}, + {"soulsteal", ATTACK_MODIFIER_SOULSTEAL}, + {"critical", ATTACK_MODIFIER_CRITICAL}, + {"piercing", ATTACK_MODIFIER_PIERCING}, + {"conversion", ATTACK_MODIFIER_CONVERSION}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == modName) { + return value; + } + } + + return ATTACK_MODIFIER_NONE; +} + +const ModifierDefenseType Augments::ParseDefenseModifier(std::string_view modName) noexcept +{ // Note : If you add values to the list you must increase the size manually + // current size is : 10 + const std::array, 10> static_map{ { + {"none", DEFENSE_MODIFIER_NONE}, + {"absorb", DEFENSE_MODIFIER_ABSORB}, + {"restore", DEFENSE_MODIFIER_RESTORE}, + {"replenish", DEFENSE_MODIFIER_REPLENISH}, + {"revive", DEFENSE_MODIFIER_REVIVE}, + {"reflect", DEFENSE_MODIFIER_REFLECT}, + {"deflect", DEFENSE_MODIFIER_DEFLECT}, + {"ricochet", DEFENSE_MODIFIER_RICOCHET}, + {"resist", DEFENSE_MODIFIER_RESIST}, + {"reform", DEFENSE_MODIFIER_REFORM}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == modName) { + return value; + } + } + + return DEFENSE_MODIFIER_NONE; +} + + +const RaceType_t Augments::ParseRaceType(std::string_view raceType) noexcept { + // Note : If you add values to the list you must increase the size manually + // current size is : 6 + const std::array, 6> static_map{ { + {"none", RACE_NONE}, + {"venom", RACE_VENOM}, + {"blood", RACE_BLOOD}, + {"undead", RACE_UNDEAD}, + {"fire", RACE_FIRE}, + {"energy", RACE_ENERGY}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == raceType) { + return value; + } + } + + return RACE_NONE; +} + +const CreatureType_t Augments::ParseCreatureType(std::string_view creatureType) noexcept { + // Note : If you add values to the list you must increase the size manually + // current size is : 14 + const std::array, 14> static_map{ { + {"player", CREATURETYPE_PLAYER}, + {"monster", CREATURETYPE_MONSTER}, + {"npc", CREATURETYPE_NPC}, + {"allsummon", CREATURETYPE_SUMMON_ALL}, + {"summons", CREATURETYPE_SUMMON_ALL}, + {"ownedsummon", CREATURETYPE_SUMMON_OWN}, + {"mysummon", CREATURETYPE_SUMMON_OWN}, + {"hostilesummon", CREATURETYPE_SUMMON_HOSTILE}, + {"enemysummon", CREATURETYPE_SUMMON_HOSTILE}, + {"guildsummon", CREATURETYPE_SUMMON_GUILD}, + {"partysummon", CREATURETYPE_SUMMON_PARTY}, + {"boss", CREATURETYPE_BOSS}, + {"none", CREATURETYPE_ATTACKABLE}, + {"all", CREATURETYPE_ATTACKABLE}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == creatureType) { + return value; + } + } + + return CREATURETYPE_ATTACKABLE; +} + +void Augments::AddAugment(std::shared_ptr augment) { + auto [it, inserted] = global_augments.try_emplace(augment->getName().data(), augment); + if (!inserted) { + std::cout << "[Warning][Augments] " << augment->getName() << " already exists! \n"; + } +} + +void Augments::RemoveAugment(std::shared_ptr augment) { + auto it = global_augments.find(augment->getName().data()); + if (it != global_augments.end()) { + global_augments.erase(it); + } +} + +void Augments::RemoveAugment(std::string_view augName) { + auto it = global_augments.find(std::string(augName)); + if (it != global_augments.end()) { + global_augments.erase(it); + } +} + +void Augments::RemoveAugment(std::string augName) { + auto it = global_augments.find(augName); + if (it != global_augments.end()) { + global_augments.erase(it); + } +} + +std::shared_ptr Augments::GetAugment(std::string_view augName) +{ + auto it = global_augments.find(augName.data()); + if (it != global_augments.end()) { + auto augment = Augment::MakeAugment(it->second); + return augment; + } + return nullptr; +} diff --git a/src/augments.h b/src/augments.h new file mode 100644 index 00000000..81f06760 --- /dev/null +++ b/src/augments.h @@ -0,0 +1,48 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + + +#ifndef FS_AUGMENTS_H +#define FS_AUGMENTS_H + +#include "augment.h" + +class Augments { + +public: + // No Constructors! Purely static class. + Augments() = delete; + ~Augments() = delete; + Augments(const Augments&) = delete; + Augments& operator=(const Augments&) = delete; + Augments(Augments&&) = delete; + Augments& operator=(Augments&&) = delete; + + static constexpr auto path = "data/augments/"; + static const ModifierStance ParseStance(std::string_view stanceName) noexcept; + static const ModFactor ParseFactor(std::string_view factor) noexcept; + static const ModifierAttackType ParseAttackModifier(std::string_view modName) noexcept; + static const ModifierDefenseType ParseDefenseModifier(std::string_view modName) noexcept; + static const CombatType_t ParseDamage(std::string_view damageName) noexcept; + static const CombatOrigin ParseOrigin(std::string_view originName) noexcept; + static const RaceType_t ParseRaceType(std::string_view raceType) noexcept; + static const CreatureType_t ParseCreatureType(std::string_view creatureType) noexcept; + + static std::shared_ptr MakeAugment(std::string_view augmentName); + + static void loadAll(); + static void clearAll(); + static void reload(); + static void AddAugment(std::shared_ptr augment); + static void RemoveAugment(std::shared_ptr augment); + static void RemoveAugment(std::string_view augName); + static void RemoveAugment(std::string augName); + static std::shared_ptr GetAugment(std::string_view augName); +}; + + +#endif \ No newline at end of file diff --git a/src/chat.cpp b/src/chat.cpp index f3c04178..2db44c92 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -233,7 +233,7 @@ bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, LuaScriptInterface::pushUserdata(L, &player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, type); + lua_pushinteger(L, type); LuaScriptInterface::pushString(L, message); bool result = false; diff --git a/src/combat.cpp b/src/combat.cpp index e91d4595..57077a3e 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -8,6 +8,9 @@ #include "weapons.h" #include "configmanager.h" #include "events.h" +#include "monster.h" + +#include extern Game g_game; extern Weapons* g_weapons; @@ -676,7 +679,7 @@ void Combat::doCombat(Creature* caster, Creature* target) const } } - if (params.dispelType == CONDITION_PARALYZE) { + if (params.dispelType & CONDITION_PARALYZE) { target->removeCondition(CONDITION_PARALYZE); } else { target->removeCombatCondition(params.dispelType); @@ -769,7 +772,7 @@ void Combat::doCombat(Creature* caster, const Position& position) const } } - if (params.dispelType == CONDITION_PARALYZE) { + if (params.dispelType & CONDITION_PARALYZE) { creature->removeCondition(CONDITION_PARALYZE); } else { creature->removeCombatCondition(params.dispelType); @@ -788,91 +791,466 @@ void Combat::doCombat(Creature* caster, const Position& position) const } } -void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) -{ - if (caster && target && params.distanceEffect != CONST_ANI_NONE) { +void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params, bool sendDistanceEffect) +{ + // To-do : I need to properly handle augment based damage which requires entire reworking of this method. + // The thing that needs to happen is for augment based damage should not interact again with other aumgent + // based damage. Instead of using origin for this, would possibly be better as fields on the combat or combat params. + + if (params.distanceEffect != CONST_ANI_NONE && sendDistanceEffect) { addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } + // To-do : Get rid of these optionals (unsure, but I think they are a leak) + // by adding isPlayer and isMonster member methods to all the Creature classes. + std::optional casterPlayer = caster && caster->getPlayer() ? std::optional(caster->getPlayer()) : std::nullopt; + std::optional targetPlayer = target && target->getPlayer() ? std::optional(target->getPlayer()) : std::nullopt; + std::optional casterMonster = caster && caster->getMonster() ? std::optional(caster->getMonster()) : std::nullopt; + + std::unordered_map attackModData; + + if (casterPlayer.has_value() && target) { + attackModData.reserve(ATTACK_MODIFIER_LAST); + auto targetType = CREATURETYPE_ATTACKABLE; + // to-do: this is ugly, lets make it a function and assign its return to the variable above instead. + if (target->getMonster()) { + targetType = casterPlayer.value()->getCreatureType(*target->getMonster()); + } else if (target->getPlayer()) { + targetType = CREATURETYPE_PLAYER; + } + attackModData = casterPlayer.value()->getAttackModifierTotals(damage.primary.type, damage.origin, targetType, target->getRace(), target->getName()); + /// we do conversion here incase someone wants to convert say healing to mana or mana to death. + + const auto& conversionTotals = casterPlayer.value()->getConvertedTotals(ATTACK_MODIFIER_CONVERSION, damage.primary.type, damage.origin, targetType, target->getRace(), target->getName()) ; + if (!conversionTotals.empty() && params.origin != ORIGIN_AUGMENT) { + casterPlayer.value()->convertDamage(target->getCreature(), damage, conversionTotals); + if (damage.primary.value == 0) { + return; + } + } - Player* casterPlayer = caster ? caster->getPlayer() : nullptr; + if (damage.primary.type != COMBAT_MANADRAIN && damage.primary.type != COMBAT_HEALING) { + // to-do: checking against origin for augment is too limiting.. Lets make piercing like crit and leech, ect. + if (!attackModData.empty() && params.origin != ORIGIN_PIERCING) { + const auto& [piercingFlatTotal, piercingPercentTotal] = attackModData[ATTACK_MODIFIER_PIERCING]; + + int32_t piercingDamage = 0; + const auto& originalDamage = std::abs(damage.primary.value); + if (piercingPercentTotal) { + const auto& piercePercent = static_cast(piercingPercentTotal); + + piercingDamage = (piercingPercentTotal <= 100) + ? (originalDamage * piercePercent / 100) + : damage.primary.value; + + } + + if (piercingFlatTotal) { + piercingDamage += static_cast(piercingFlatTotal); + } + + if (piercingDamage) { + piercingDamage = std::min(piercingDamage, originalDamage); + damage.primary.value += piercingDamage; + + CombatDamage piercing; + piercing.origin = ORIGIN_AUGMENT; + piercing.primary.value = (0 - piercingDamage); + piercing.primary.type = COMBAT_UNDEFINEDDAMAGE; + + CombatParams piercingParams; + piercingParams.origin = ORIGIN_AUGMENT; + piercingParams.combatType = COMBAT_UNDEFINEDDAMAGE; + piercingParams.impactEffect = CONST_ME_SKULLHORIZONTAL; + + const auto message = "You pierced " + target->getName() + " for " + std::to_string(piercingDamage) + " damage!" ; + casterPlayer.value()->sendTextMessage(MESSAGE_EVENT_DEFAULT, message); + g_game.combatChangeHealth(caster, target, piercing); + + if (damage.primary.value == 0) { + return; + } + } + } + + if (params.origin != ORIGIN_PIERCING) { + auto blocked =g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0, params.ignoreResistances); + if (blocked) { + return; + } + } + + if (!damage.critical) { + int32_t percentTotal = 0; + int32_t flatTotal = 0; + if (!attackModData.empty()) { + percentTotal = attackModData[ATTACK_MODIFIER_CRITICAL].percentTotal; + flatTotal = attackModData[ATTACK_MODIFIER_CRITICAL].flatTotal; + } + + // normal crits are the old ones and are percent based + const auto& normalCritChance = static_cast(casterPlayer.value()->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE)); + const auto& normalCritDamage = static_cast(casterPlayer.value()->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT)); + + // note : the way this works, its own damage increase is independent allowing for more than 100 + // and also at the same time, its chance is independent, so it doesn't add to augmented crit's chance. + if ((normalCritChance && normalCritDamage) && (normal_random(1, 100) <= normalCritChance)) { + percentTotal += normalCritDamage; + } + + // we do percent based crits first, so that the flat damage doesn't add to the percent increase. + if (percentTotal) { + auto damageIncrease = std::abs(damage.primary.value * percentTotal / 100); + damage.primary.value -= damageIncrease; + damage.critical = true; + } + + if (flatTotal) { + damage.primary.value -= flatTotal; + damage.critical = true; + } + } + + if (targetPlayer.has_value() && (casterPlayer.value() != targetPlayer.value()) && params.origin != ORIGIN_AUGMENT) { + const auto& reformTotals = targetPlayer.value()->getConvertedTotals(DEFENSE_MODIFIER_REFORM, damage.primary.type, damage.origin, CREATURETYPE_PLAYER, caster->getRace(), caster->getName()); + if (!reformTotals.empty()) { + targetPlayer.value()->reformDamage(*casterPlayer.value()->getCreature(), damage, reformTotals); + if (damage.primary.value == 0) { + return; + } + } - bool success = false; - if (damage.primary.type != COMBAT_MANADRAIN) { + const auto& defenseModData = targetPlayer.value()->getDefenseModifierTotals(damage.primary.type, damage.origin, CREATURETYPE_PLAYER, caster->getRace(), caster->getName()); + if (!defenseModData.empty()) { + for (const auto& [modkind, modTotals] : defenseModData) { + if (modTotals.percentTotal || modTotals.flatTotal) { + applyDamageReductionModifier(modkind, damage, *targetPlayer.value()->getPlayer(), *caster->getCreature(), static_cast(modTotals.percentTotal), static_cast(modTotals.flatTotal), params.origin, params.impactEffect, params.distanceEffect); + if (damage.primary.value == 0) { + return; + } + } + } + } + } + + } + } else if (casterMonster.has_value()) { 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 && casterPlayer != targetPlayer && targetPlayer->getSkull() != SKULL_BLACK && damage.primary.type != COMBAT_HEALING) { - damage.primary.value /= 2; - damage.secondary.value /= 2; + if (targetPlayer.has_value()) { + const auto& attackerType = targetPlayer.value()->getCreatureType(*casterMonster.value()->getMonster()); + const auto& defenseModData = targetPlayer.value()->getDefenseModifierTotals(damage.primary.type, damage.origin, attackerType, casterMonster.value()->getRace(), casterMonster.value()->getName()); + auto reformTotals = targetPlayer.value()->getConvertedTotals(DEFENSE_MODIFIER_REFORM, damage.primary.type, damage.origin, attackerType, casterMonster.value()->getRace(), casterMonster.value()->getName()); + if (!reformTotals.empty() && params.origin != ORIGIN_AUGMENT) { + targetPlayer.value()->reformDamage(*casterMonster.value()->getCreature(), damage, reformTotals); + if (damage.primary.value == 0) { + return; + } } - if (!damage.critical && damage.primary.type != COMBAT_HEALING && damage.origin != ORIGIN_CONDITION) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT); - if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - damage.primary.value += std::round(damage.primary.value * (skill / 100.)); - damage.secondary.value += std::round(damage.secondary.value * (skill / 100.)); - damage.critical = true; + if (!defenseModData.empty() && params.origin != ORIGIN_AUGMENT) { + for (const auto& [modkind, modTotals] : defenseModData) { + if (modTotals.percentTotal || modTotals.flatTotal) { + applyDamageReductionModifier(modkind, damage, *targetPlayer.value()->getPlayer(), *caster->getCreature(), static_cast(modTotals.percentTotal), static_cast(modTotals.flatTotal), params.origin, params.impactEffect, params.distanceEffect); + if (damage.primary.value == 0) { + return; + } + } } } } - - success = g_game.combatChangeHealth(caster, target, damage); - } else { - success = g_game.combatChangeMana(caster, target, damage); + } else [[unlikely]] { + std::cout << "Getting calls to doTargetCombat without a target. \n"; } + auto success = (damage.primary.type == COMBAT_MANADRAIN) ? + g_game.combatChangeMana(caster, target, damage) : + g_game.combatChangeHealth(caster, target, damage); + if (success) { - if (damage.blockType == BLOCK_NONE || damage.blockType == BLOCK_ARMOR) { - for (const auto& condition : params.conditionList) { - if (caster == target || !target->isImmune(condition->getType())) { + + if (target && caster && target != caster) { + if (damage.critical) { + g_game.addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); + } + + for (const auto& condition : params.conditionList) + { + if (!target->isImmune(condition->getType())) { Condition* conditionCopy = condition->clone(); if (caster) { conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); } - //TODO: infight condition until all aggressive conditions has ended target->addCombatCondition(conditionCopy); } } - } + // hopefully runes are counted as spells and not ranged + if (casterPlayer.has_value() + && (damage.origin == ORIGIN_MELEE || damage.origin == ORIGIN_RANGED) + && (damage.primary.type != COMBAT_HEALING && damage.primary.type != COMBAT_MANADRAIN )) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + + Item* item = casterPlayer.value()->getInventoryItem(static_cast(slot)); + if (!item) { + continue; + } + if (item->hasImbuements()) { + + for (auto& imbuement : item->getImbuements()) { + if (!imbuement->value) { + continue; + } + const auto& originalDamage = abs(damage.primary.value); + const auto& conversionAmount = (originalDamage * imbuement->value) / 100; + const int32_t& difference = (originalDamage - conversionAmount); + + CombatDamage imbueDamage; + imbueDamage.blockType = BLOCK_NONE; + imbueDamage.origin = ORIGIN_IMBUEMENT; + + switch (imbuement->imbuetype) { + case IMBUEMENT_TYPE_FIRE_DAMAGE: + imbueDamage.primary.type = COMBAT_FIREDAMAGE; + break; + case IMBUEMENT_TYPE_ENERGY_DAMAGE: + imbueDamage.primary.type = COMBAT_ENERGYDAMAGE; + break; + case IMBUEMENT_TYPE_EARTH_DAMAGE: + imbueDamage.primary.type = COMBAT_EARTHDAMAGE; + break; + case IMBUEMENT_TYPE_ICE_DAMAGE: + imbueDamage.primary.type = COMBAT_ICEDAMAGE; + break; + case IMBUEMENT_TYPE_HOLY_DAMAGE: + imbueDamage.primary.type = COMBAT_HOLYDAMAGE; + break; + case IMBUEMENT_TYPE_DEATH_DAMAGE: + imbueDamage.primary.type = COMBAT_DEATHDAMAGE; + break; + default: [[unlikely]] + break; + } - if (damage.critical) { - g_game.addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); - } + if (difference < 0) { + imbueDamage.primary.value -= originalDamage; + g_game.combatChangeHealth(caster, target, imbueDamage); + break; + } // else + imbueDamage.primary.value -= conversionAmount; + g_game.combatChangeHealth(caster, target, imbueDamage); + } + } + } + } + + if (targetPlayer.has_value() && damage.primary.type != COMBAT_HEALING && damage.primary.type != COMBAT_MANADRAIN ) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) + { + Item* item = targetPlayer.value()->getInventoryItem(static_cast(slot)); + if (!item) { + continue; + } + + if (item->hasImbuements()) { + for (const auto& imbuement : item->getImbuements()) { + const auto combatType = damage.primary.type; + const auto& originalDamage = abs(damage.primary.value); + const auto& resistance = (originalDamage * imbuement->value) / 100; + const int32_t& difference = (originalDamage - resistance); + switch (imbuement->imbuetype) { + case ImbuementType::IMBUEMENT_TYPE_FIRE_RESIST: + if (combatType == COMBAT_FIREDAMAGE) { + if (difference < 0) { + damage.primary.value = 0; + return; + } + damage.primary.value += difference; + } + break; + case ImbuementType::IMBUEMENT_TYPE_EARTH_RESIST: + if (combatType == COMBAT_EARTHDAMAGE) { + if (difference < 0) { + damage.primary.value = 0; + return; + } + damage.primary.value += difference; + } + break; + case ImbuementType::IMBUEMENT_TYPE_ICE_RESIST: + if (combatType == COMBAT_ICEDAMAGE) { + if (difference < 0) { + damage.primary.value = 0; + return; + } + damage.primary.value += difference; + } + break; + case ImbuementType::IMBUEMENT_TYPE_ENERGY_RESIST: + if (combatType == COMBAT_ENERGYDAMAGE) { + if (difference < 0) { + damage.primary.value = 0; + return; + } + damage.primary.value += difference; + } + break; + case ImbuementType::IMBUEMENT_TYPE_DEATH_RESIST: + if (combatType == COMBAT_DEATHDAMAGE) { + if (difference < 0) { + damage.primary.value = 0; + return; + } + damage.primary.value += difference; + } + break; + case ImbuementType::IMBUEMENT_TYPE_HOLY_RESIST: + if (combatType == COMBAT_HOLYDAMAGE) { + if (difference < 0) { + damage.primary.value = 0; + return; + } + damage.primary.value += difference; + } + break; + default: [[unlikely]] + break; + } + } + } + } + } - if (!damage.leeched && damage.primary.type != COMBAT_HEALING && casterPlayer && damage.origin != ORIGIN_CONDITION) { - CombatDamage leechCombat; - leechCombat.origin = ORIGIN_NONE; - leechCombat.leeched = true; + if (!damage.leeched && damage.primary.type != COMBAT_HEALING + && casterPlayer + && damage.origin != ORIGIN_CONDITION) { + const auto totalDamage = std::abs(damage.primary.value + damage.secondary.value); + int32_t lifeStealPercentTotal = 0, manaStealPercentTotal = 0, staminaStealPercentTotal = 0, soulStealPercentTotal = 0; + int32_t lifeStealFlatTotal = 0, manaStealFlatTotal = 0, staminaStealFlatTotal = 0, soulStealFlatTotal = 0; + int32_t lifeStealGain = 0, manaStealGain = 0, soulGain = 0, staminaGain = 0; + + // Static cast everything to int32_t to ensure consistency + if (!attackModData.empty() && params.origin != ORIGIN_AUGMENT) { + // Percents + lifeStealPercentTotal = static_cast(attackModData[ATTACK_MODIFIER_LIFESTEAL].percentTotal); + manaStealPercentTotal = static_cast(attackModData[ATTACK_MODIFIER_MANASTEAL].percentTotal); + staminaStealPercentTotal = static_cast(attackModData[ATTACK_MODIFIER_STAMINASTEAL].percentTotal); + soulStealPercentTotal = static_cast(attackModData[ATTACK_MODIFIER_SOULSTEAL].percentTotal); + + // Flats + lifeStealFlatTotal = static_cast(attackModData[ATTACK_MODIFIER_LIFESTEAL].flatTotal); + manaStealFlatTotal = static_cast(attackModData[ATTACK_MODIFIER_MANASTEAL].flatTotal); + staminaStealFlatTotal = static_cast(attackModData[ATTACK_MODIFIER_STAMINASTEAL].flatTotal); + soulStealFlatTotal = static_cast(attackModData[ATTACK_MODIFIER_SOULSTEAL].flatTotal); + } - int32_t totalDamage = std::abs(damage.primary.value + damage.secondary.value); + const auto lifeLeechChance = static_cast(casterPlayer.value()->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE)); + const auto lifeLeechAmount = static_cast(casterPlayer.value()->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT)); + const auto manaLeechChance = static_cast(casterPlayer.value()->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE)); + const auto manaLeechAmount = static_cast(casterPlayer.value()->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT)); - 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::round(totalDamage * (skill / 100.)); - g_game.combatChangeHealth(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_RED); + // Lifesteal + if ((lifeLeechChance && lifeLeechAmount) && (normal_random(1, 100) <= lifeLeechChance)) { + lifeStealGain += totalDamage * lifeLeechAmount / 100; + } + + if (lifeStealPercentTotal) { + lifeStealGain += totalDamage * lifeStealPercentTotal / 100; } - } - if (casterPlayer->getMana() < casterPlayer->getMaxMana()) { - 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::round(totalDamage * (skill / 100.)); - g_game.combatChangeMana(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_BLUE); + if (lifeStealFlatTotal) { + lifeStealGain += (lifeStealFlatTotal); + } + + if (lifeStealGain) { + CombatDamage lifeStealCombat; + lifeStealCombat.origin = ORIGIN_AUGMENT; + lifeStealCombat.leeched = true; + lifeStealCombat.primary.type = COMBAT_LIFEDRAIN; + lifeStealCombat.primary.value = lifeStealGain; + g_game.combatChangeHealth(target, caster, lifeStealCombat); + } + + /// Manasteal + if ((manaLeechChance && manaLeechAmount) && (normal_random(1, 100) <= manaLeechChance)) { + manaStealGain += totalDamage * manaLeechAmount / 100; + } + + if (manaStealPercentTotal) { + manaStealGain += totalDamage * manaStealPercentTotal / 100; + } + + if (manaStealFlatTotal) { + manaStealGain += manaStealFlatTotal; + } + + if (manaStealGain) { + CombatDamage manaStealCombat; + manaStealCombat.origin = ORIGIN_AUGMENT; + manaStealCombat.leeched = true; + manaStealCombat.primary.type = COMBAT_MANADRAIN; + manaStealCombat.primary.value = manaStealGain; + g_game.combatChangeMana(target, caster, manaStealCombat); + } + + /// Staminasteal + if (staminaStealPercentTotal) { + staminaGain += totalDamage * staminaStealPercentTotal / 100; + } + + if (staminaStealFlatTotal) { + staminaGain += staminaStealFlatTotal; + } + + if (staminaGain) { + if (staminaGain <= std::numeric_limits::max()) { + const uint16_t trueStaminaGain = g_config.getBoolean(ConfigManager::AUGMENT_STAMINA_RULE) ? + static_cast(staminaGain) : + static_cast(staminaGain / 60); + + const uint16_t currentStamina = casterPlayer.value()->getStaminaMinutes(); + const uint16_t missingStamina = (MaximumStamina - currentStamina); + if ((trueStaminaGain + currentStamina) >= missingStamina) { + casterPlayer.value()->addStamina(missingStamina); + } else { + casterPlayer.value()->addStamina(trueStaminaGain); + } + } else { + casterPlayer.value()->addStamina(MaximumStamina - casterPlayer.value()->getStaminaMinutes()); + } + g_game.addMagicEffect(casterPlayer.value()->getPosition(), CONST_ME_YELLOWENERGY); + } + + // Soulsteal + if (soulStealPercentTotal) { + soulGain += totalDamage * soulStealPercentTotal / 100 ; + } + + if (soulStealFlatTotal) { + soulGain += soulStealFlatTotal; + } + + if (soulGain) { + if (soulGain <= std::numeric_limits::max()) { + const uint8_t trueSoulGain = static_cast(soulGain); + const uint8_t currentSoul = casterPlayer.value()->getSoul(); + const uint8_t maxSoul = casterPlayer.value()->getVocation()->getSoulMax(); + const uint8_t missingSoul = (maxSoul - currentSoul); + if ((trueSoulGain + currentSoul) >= maxSoul) { + casterPlayer.value()->addSoul(missingSoul); + } else { + casterPlayer.value()->addSoul(trueSoulGain); + } + } else { + casterPlayer.value()->addSoul(casterPlayer.value()->getVocation()->getSoulMax() - casterPlayer.value()->getSoul()); + } + + g_game.addMagicEffect(casterPlayer.value()->getPosition(), CONST_ME_MAGIC_GREEN); } } } - - if (params.dispelType == CONDITION_PARALYZE) { + if (params.dispelType & CONDITION_PARALYZE) { target->removeCondition(CONDITION_PARALYZE); } else { target->removeCombatCondition(params.dispelType); @@ -889,17 +1267,6 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const 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) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT); - if (chance > 0 && skill > 0 && uniform_random(1, 100) <= chance) { - criticalPrimary = std::round(damage.primary.value * (skill / 100.)); - criticalSecondary = std::round(damage.secondary.value * (skill / 100.)); - damage.critical = true; - } - } uint32_t maxX = 0; uint32_t maxY = 0; @@ -961,89 +1328,64 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area } } - CombatDamage leechCombat; - leechCombat.origin = ORIGIN_NONE; - leechCombat.leeched = true; - - for (Creature* creature : toDamageCreatures) { + for (Creature* target : 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. - bool playerCombatReduced = false; - if ((damageCopy.primary.value < 0 || damageCopy.secondary.value < 0) && caster) { - Player* targetPlayer = creature->getPlayer(); - if (casterPlayer && targetPlayer && casterPlayer != targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) { - damageCopy.primary.value /= 2; - damageCopy.secondary.value /= 2; - playerCombatReduced = true; - } - } + Combat::doTargetCombat(caster, target, damageCopy, params, false); + } +} - if (damageCopy.critical) { - damageCopy.primary.value += playerCombatReduced ? criticalPrimary / 2 : criticalPrimary; - damageCopy.secondary.value += playerCombatReduced ? criticalSecondary / 2 : criticalSecondary; - g_game.addMagicEffect(creature->getPosition(), CONST_ME_CRITICAL_DAMAGE); - } +void Combat::applyDamageIncreaseModifier(uint8_t modifierType, CombatDamage& damage, int32_t percentValue, int32_t flatValue) { - bool success = false; - if (damageCopy.primary.type != COMBAT_MANADRAIN) { - if (g_game.combatBlockHit(damageCopy, caster, creature, params.blockedByShield, params.blockedByArmor, params.itemId != 0, params.ignoreResistances)) { - continue; - } - success = g_game.combatChangeHealth(caster, creature, damageCopy); + if (percentValue) { + if (percentValue <= 100) { + damage.primary.value += damage.primary.value * (percentValue / 100.0); } else { - success = g_game.combatChangeMana(caster, creature, damageCopy); + damage.primary.value *= 2; } + } + if (percentValue) { + damage.primary.value += percentValue; + } - if (success) { - if (damage.blockType == BLOCK_NONE || damage.blockType == BLOCK_ARMOR) { - for (const auto& condition : params.conditionList) { - if (caster == creature || !creature->isImmune(condition->getType())) { - Condition* conditionCopy = condition->clone(); - if (caster) { - conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); - } +} - //TODO: infight condition until all aggressive conditions has ended - creature->addCombatCondition(conditionCopy); - } - } - } +void Combat::applyDamageReductionModifier(uint8_t modifierType, CombatDamage& damage, Player& damageTarget, std::optional> attacker, int32_t percent, int32_t flat, CombatOrigin paramOrigin, uint8_t areaEffect, uint8_t distanceEffect) { - int32_t totalDamage = std::abs(damageCopy.primary.value + damageCopy.secondary.value); + switch (modifierType) { + case DEFENSE_MODIFIER_ABSORB: + damageTarget.absorbDamage(attacker, damage, percent, flat); + return; - if (casterPlayer && !damage.leeched && damage.primary.type != COMBAT_HEALING && damage.origin != ORIGIN_CONDITION) { - int32_t targetsCount = toDamageCreatures.size(); + case DEFENSE_MODIFIER_RESTORE: + damageTarget.restoreManaFromDamage(attacker, damage, percent, flat); + return; - 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); - g_game.combatChangeHealth(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_RED); - } - } + case DEFENSE_MODIFIER_REPLENISH: + damageTarget.replenishStaminaFromDamage(attacker, damage, percent, flat); + return; - if (casterPlayer->getMana() < casterPlayer->getMaxMana()) { - 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); - g_game.combatChangeMana(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_BLUE); - } - } - } + case DEFENSE_MODIFIER_RESIST: + damageTarget.resistDamage(attacker, damage, percent, flat); + return; - if (params.dispelType == CONDITION_PARALYZE) { - creature->removeCondition(CONDITION_PARALYZE); - } else { - creature->removeCombatCondition(params.dispelType); - } - } + case DEFENSE_MODIFIER_REVIVE: + damageTarget.reviveSoulFromDamage(attacker, damage, percent, flat); + return; - if (params.targetCallback) { - params.targetCallback->onTargetCombat(caster, creature); - } + case DEFENSE_MODIFIER_REFLECT: + damageTarget.reflectDamage(attacker, damage, percent, flat, areaEffect, distanceEffect); + return; + + case DEFENSE_MODIFIER_DEFLECT: + damageTarget.deflectDamage(attacker, damage, percent, flat, paramOrigin, areaEffect, distanceEffect); + return; + + case DEFENSE_MODIFIER_RICOCHET: + damageTarget.ricochetDamage(damage, percent, flat, areaEffect, distanceEffect); + return; + + default: + return; } } @@ -1074,8 +1416,8 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const switch (type) { case COMBAT_FORMULA_LEVELMAGIC: { //onGetPlayerMinMaxValues(player, level, maglevel) - lua_pushnumber(L, player->getLevel()); - lua_pushnumber(L, player->getMagicLevel()); + lua_pushinteger(L, player->getLevel()); + lua_pushinteger(L, player->getMagicLevel()); parameters += 2; break; } @@ -1100,8 +1442,8 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage) const damage.secondary.value = weapon->getElementDamage(player, nullptr, tool); } - lua_pushnumber(L, player->getWeaponSkill(item ? item : tool)); - lua_pushnumber(L, attackValue); + lua_pushinteger(L, player->getWeaponSkill(item ? item : tool)); + lua_pushinteger(L, attackValue); lua_pushnumber(L, player->getAttackFactor()); parameters += 3; break; diff --git a/src/combat.h b/src/combat.h index aab6b735..b1f319b1 100644 --- a/src/combat.h +++ b/src/combat.h @@ -12,6 +12,8 @@ #include "matrixarea.h" #include +#include +#include class Condition; class Creature; @@ -73,6 +75,7 @@ class AreaCombat 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; @@ -93,6 +96,11 @@ class Combat static bool isPlayerCombat(const Creature* target); static CombatType_t ConditionToDamageType(ConditionType_t type); static ConditionType_t DamageToConditionType(CombatType_t type); + // To-do : follow this call stack and improve it. + // Here we have a method that is under-utilized. It should be used in combats + // to remove those same checks out of game::combatHealthChange + // with the breaking down of the smaller parts of combatHealthChange as well + // we can eliminate the need in it all together and provide cleaner code. static ReturnValue canTargetCreature(Player* attacker, Creature* target); static ReturnValue canDoCombat(Creature* caster, Tile* tile, bool aggressive); static ReturnValue canDoCombat(Creature* attacker, Creature* target); @@ -103,9 +111,12 @@ class Combat 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 doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params, bool sendDistanceEffect = true); static void doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); + static void applyDamageIncreaseModifier(uint8_t modifierType, CombatDamage& damage, int32_t percentValue, int32_t flatValue); + static void applyDamageReductionModifier(uint8_t modifierType, CombatDamage& damage, Player& damageTarget, std::optional> attacker, int32_t percentValue, int32_t flatValue, CombatOrigin paramOrigin, uint8_t areaEffect = CONST_ME_NONE, uint8_t distanceEffect = CONST_ANI_NONE); + bool setCallback(CallBackParam_t key); CallBack* getCallback(CallBackParam_t key); @@ -181,4 +192,5 @@ class MagicField final : public Item int64_t createTime; }; + #endif diff --git a/src/condition.cpp b/src/condition.cpp index f42113cf..08a190f6 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -5,6 +5,7 @@ #include "condition.h" #include "game.h" +#include "monster.h" extern Game g_game; @@ -1377,6 +1378,61 @@ bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) return false; } + if (Player* player = creature->getPlayer()) { + + if (attacker) { + auto creatureType = CREATURETYPE_ATTACKABLE; + + if (Player* attackPlayer = attacker->getPlayer()) { + creatureType = CREATURETYPE_PLAYER; + } else if (Monster* attackMonster = attacker->getMonster()) { + creatureType = player->getCreatureType(*attackMonster); + } + auto reformTotals = player->getConvertedTotals(DEFENSE_MODIFIER_REFORM, damage.primary.type, ORIGIN_CONDITION, creatureType, attacker->getRace(), attacker->getName()); + if (!reformTotals.empty()) { + std::cout << "Reform Modifier Activated on " << damage.primary.value << " damage \n"; + player->reformDamage(*attacker, damage, reformTotals); + if (damage.primary.value == 0) { + return true; + } + } + + auto defenseModData = player->getDefenseModifierTotals(damage.primary.type, ORIGIN_CONDITION, creatureType, attacker->getRace(), attacker->getName()); + if (!defenseModData.empty()) { + for (const auto& [modkind, modTotals] : defenseModData) { + if (modTotals.percentTotal || modTotals.flatTotal) { + Combat::applyDamageReductionModifier(modkind, damage, *player, *attacker, static_cast(modTotals.percentTotal), static_cast(modTotals.flatTotal), ORIGIN_CONDITION); + } + if (damage.primary.value == 0) { + return true; + } + } + } + } else { // no attacker + + auto reformTotals = player->getConvertedTotals(DEFENSE_MODIFIER_REFORM, damage.primary.type, ORIGIN_CONDITION, CREATURETYPE_ATTACKABLE, RACE_NONE, "none"); + if (!reformTotals.empty()) { + std::cout << "Reform Modifier Activated on " << damage.primary.value << " damage \n"; + player->reformDamage(std::nullopt, damage, reformTotals); + if (damage.primary.value == 0) { + return true; + } + } + + auto defenseModData = player->getDefenseModifierTotals(damage.primary.type, ORIGIN_CONDITION, CREATURETYPE_ATTACKABLE, RACE_NONE, "none"); + if (!defenseModData.empty()) { + for (const auto& [modkind, modTotals] : defenseModData) { + if (modTotals.percentTotal || modTotals.flatTotal) { + Combat::applyDamageReductionModifier(modkind, damage, *player, std::nullopt, static_cast(modTotals.percentTotal), static_cast(modTotals.flatTotal), ORIGIN_CONDITION); + } + if (damage.primary.value == 0) { + return true; + } + } + } + } + } + return g_game.combatChangeHealth(attacker, creature, damage); } diff --git a/src/configmanager.cpp b/src/configmanager.cpp index a5bdefcc..99a4262a 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -243,6 +243,8 @@ bool ConfigManager::load() boolean[PLAYER_CONSOLE_LOGS] = getGlobalBoolean(L, "showPlayerLogInConsole", true); boolean[CHECK_DUPLICATE_STORAGE_KEYS] = getGlobalBoolean(L, "checkDuplicateStorageKeys", false); boolean[BED_OFFLINE_TRAINING] = getGlobalBoolean(L, "bedOfflineTraining", true); + boolean[AUGMENT_SLOT_PROTECTION] = getGlobalBoolean(L, "augmentSlotProtection", true); + boolean[AUGMENT_STAMINA_RULE] = getGlobalBoolean(L, "augmentStaminInMinutes", false); string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); string[SERVER_NAME] = getGlobalString(L, "serverName", ""); diff --git a/src/configmanager.h b/src/configmanager.h index 3490f5db..51eb2a63 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -55,6 +55,8 @@ class ConfigManager PLAYER_CONSOLE_LOGS, CHECK_DUPLICATE_STORAGE_KEYS, BED_OFFLINE_TRAINING, + AUGMENT_SLOT_PROTECTION, + AUGMENT_STAMINA_RULE, LAST_BOOLEAN_CONFIG /* this must be the last one */ }; diff --git a/src/creature.cpp b/src/creature.cpp index 16dfd327..92868291 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -605,7 +605,12 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos if (creature == followCreature || (creature == this && followCreature)) { if (hasFollowPath) { - isUpdatingPath = true; + if ((creature == followCreature) && listWalkDir.empty()) { + isUpdatingPath = false; + goToFollowCreature(); + } else { + isUpdatingPath = true; + } } if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { @@ -870,83 +875,6 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 } } - if (attacker) - { - Player* attackerPlayer = attacker->getPlayer(); - if (attackerPlayer) { - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { - - 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 (item->hasImbuements() && blockType == BLOCK_NONE) { - - for (auto& imbuement : item->getImbuements()) { - - auto conversionAmount = std::abs(std::round(damage * (imbuement->value / 100.))); - - if (conversionAmount > 0) { - auto trueDamage = std::abs(damage); - auto difference = (trueDamage - conversionAmount); - - CombatDamage imbueDamage; - imbueDamage.blockType = BLOCK_NONE; - imbueDamage.origin = ORIGIN_IMBUEMENT; - - switch (imbuement->imbuetype) { - case IMBUEMENT_TYPE_FIRE_DAMAGE: - imbueDamage.primary.type = COMBAT_FIREDAMAGE; - break; - case IMBUEMENT_TYPE_ENERGY_DAMAGE: - imbueDamage.primary.type = COMBAT_ENERGYDAMAGE; - break; - case IMBUEMENT_TYPE_EARTH_DAMAGE: - imbueDamage.primary.type = COMBAT_EARTHDAMAGE; - break; - case IMBUEMENT_TYPE_ICE_DAMAGE: - imbueDamage.primary.type = COMBAT_ICEDAMAGE; - break; - case IMBUEMENT_TYPE_HOLY_DAMAGE: - imbueDamage.primary.type = COMBAT_HOLYDAMAGE; - break; - case IMBUEMENT_TYPE_DEATH_DAMAGE: - imbueDamage.primary.type = COMBAT_DEATHDAMAGE; - break; - default: - break; - } - - // Here we keep the damage from being reduced so much it becomes healing, - // while also considering the damage the imbuement does, and limiting it - // to only do as much as actually convertable. - if (difference <= 0) { - if (difference < 0) { - imbueDamage.primary.value = (0 - trueDamage); - } else { - imbueDamage.primary.value = (0 - conversionAmount); - } - damage = 0; - } else { - imbueDamage.primary.value = (0 - conversionAmount); - // this might be confusing, but since we know damage is a negative number - // and we know that the conversion is positive, we can add them to reduce the damage. - damage += conversionAmount; - } - g_game.combatChangeHealth(attacker, this, imbueDamage); - } - } - } - } - } - } - if (damage <= 0) { damage = 0; blockType = BLOCK_ARMOR; @@ -1060,7 +988,12 @@ bool Creature::setFollowCreature(Creature* creature) hasFollowPath = false; forceUpdateFollowPath = false; followCreature = creature; - isUpdatingPath = true; + if (getMonster()) { + isUpdatingPath = false; + goToFollowCreature(); + } else { + isUpdatingPath = true; + } } else { isUpdatingPath = false; followCreature = nullptr; diff --git a/src/creature.h b/src/creature.h index 0d64385a..5a16e114 100644 --- a/src/creature.h +++ b/src/creature.h @@ -157,6 +157,10 @@ class Creature : virtual public Thing hiddenHealth = b; } + bool isBoss() const { + return false; + } + int32_t getThrowRange() const override final { return 1; } diff --git a/src/creatureevent.cpp b/src/creatureevent.cpp index 843538dc..1caf1b0c 100644 --- a/src/creatureevent.cpp +++ b/src/creatureevent.cpp @@ -319,7 +319,7 @@ bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) scriptInterface->pushFunction(scriptId); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - lua_pushnumber(L, interval); + lua_pushinteger(L, interval); return scriptInterface->callFunction(2); } @@ -408,9 +408,9 @@ bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldL scriptInterface->pushFunction(scriptId); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, static_cast(skill)); - lua_pushnumber(L, oldLevel); - lua_pushnumber(L, newLevel); + lua_pushinteger(L, static_cast(skill)); + lua_pushinteger(L, oldLevel); + lua_pushinteger(L, newLevel); return scriptInterface->callFunction(4); } @@ -453,9 +453,9 @@ void CreatureEvent::executeModalWindow(Player* player, uint32_t modalWindowId, u LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, modalWindowId); - lua_pushnumber(L, buttonId); - lua_pushnumber(L, choiceId); + lua_pushinteger(L, modalWindowId); + lua_pushinteger(L, buttonId); + lua_pushinteger(L, choiceId); scriptInterface->callVoidFunction(4); } @@ -581,7 +581,7 @@ void CreatureEvent::executeExtendedOpcode(Player* player, uint8_t opcode, const LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, opcode); + lua_pushinteger(L, opcode); LuaScriptInterface::pushString(L, buffer); scriptInterface->callVoidFunction(3); diff --git a/src/damagemodifier.cpp b/src/damagemodifier.cpp new file mode 100644 index 00000000..d8006301 --- /dev/null +++ b/src/damagemodifier.cpp @@ -0,0 +1,28 @@ +#include "damagemodifier.h" + +std::shared_ptr DamageModifier::makeModifier(uint8_t stance, uint8_t modType, uint16_t amount, ModFactor factor, uint8_t chance, CombatType_t combatType, CombatOrigin source, CreatureType_t creatureType, RaceType_t race, std::string_view creatureName) { + auto mod = std::make_shared(stance, modType, amount, factor, chance, combatType, source, creatureType, race, creatureName.data()); + return mod; +} + +void DamageModifier::setTransformDamageType(CombatType_t damageType) { + m_to_damage_type = damageType; +} + +void DamageModifier::increaseValue(uint16_t amount) { + if ((m_value + amount) <= std::numeric_limits::max()) { + m_value += amount; + } else { + m_value = std::numeric_limits::max(); + std::cout << "[WARNING] Amount exceded numeric limits for uint16_t. m_value set to limit." << "\n"; + } +} + +void DamageModifier::decreaseValue(uint16_t amount) { + if (m_value >= amount) { + m_value -= amount; + }else { + m_value = 0; + std::cout << "[WARNING] Amount is greater than m_value. m_value set to zero. " << "\n"; + } +} diff --git a/src/damagemodifier.h b/src/damagemodifier.h new file mode 100644 index 00000000..6c8c5b7f --- /dev/null +++ b/src/damagemodifier.h @@ -0,0 +1,358 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + +#ifndef FS_DAMAGEMODIFIER_H +#define FS_DAMAGEMODIFIER_H + +#include "otpch.h" +#include "tools.h" +#include "fileloader.h" + +struct ModifierTotals { + ModifierTotals() = default; + ModifierTotals(uint16_t flat, uint16_t percent) : flatTotal(flat), percentTotal(percent) {} + uint16_t flatTotal = 0; + uint16_t percentTotal = 0; + + ModifierTotals operator+(const ModifierTotals& other) const { + return ModifierTotals(flatTotal + other.flatTotal, percentTotal + other.percentTotal); + } + + ModifierTotals& operator+=(const ModifierTotals& other) { + flatTotal += other.flatTotal; + percentTotal = std::min(percentTotal + other.percentTotal, 100); + return *this; + } +}; + +struct WeildModifier { + uint8_t main = 0; // stat / skill + uint8_t sub = 0; // health / sword + uint8_t value = 0; + // Percent & Flat can be out-of-banded into being each their own containers; +}; + +enum StatModifier : uint8_t { + MAX_HEALTH, + HEALTH_REGEN, + MAX_MANA, + MANA_REGEN, + MAX_SOUL, + SOUL_REGEN, + MAX_STAMINA, + STAMINA_REGEN, + /// Entries below this line are to be added as future features. + MOVEMENT_SPEED, + CASTING_SPEED, + ATTACK_SPEED, +}; + +enum SkillModifier : uint8_t { + MELEE_SKILL, + FIST_SKILL, + SWORD_SKILL, + AXE_SKILL, + CLUB_SKILL, + WAND_SKILL, + ROD_SKILL, + MAGIC_SKILL, + MAGIC_WEAPON_SKILL, + DISTANCE_SKILL, + SHIELD_SKILL, + FISHING_SKILL, +}; + +enum KillModifier : uint8_t { + AOE_DAMAGE, + BONUS_LOOT, // flat + BONUS_EXP, // flat + LOOT_GAIN, // percent + EXP_GAIN, // percent +}; + +enum PassiveEffect : uint8_t { + // all these applicable to specific creature. + SCAVENGE, // increase chance for skinning + GUTTING, // increased creature products w/ chance + VOIDCALL, // increase mana leech if applicable + VAMPIRIC, // increase life leech if applicable + BLESS, // reduced death penalty + CLEANSE, // removes one random active negative status effect and temporarily makes you immune against it + ADDRENALINE, // chance to get temporary boosted movement speed after being attacked + NUMB, // chance to paralyze attacker after an attack + LOWBLOW, // increases crit hit chance if chance is already above 0 + DODGE, // chance to dodge an attack +}; + + + + + +enum ModifierAttackType : uint8_t { + ATTACK_MODIFIER_NONE, // default + ATTACK_MODIFIER_LIFESTEAL, // damage is converted to health + ATTACK_MODIFIER_MANASTEAL, // damage is converted to mana + ATTACK_MODIFIER_STAMINASTEAL, // damage is converted stamina + ATTACK_MODIFIER_SOULSTEAL, // damage is converted soul + ATTACK_MODIFIER_CRITICAL, // damage can critcally hit + ATTACK_MODIFIER_PIERCING, // damage ignores defenses + ATTACK_MODIFIER_CONVERSION, // damage is converted to different type + // ATTACK_MODIFIER_CRIPPLE, // new modifier for paralyzing target + + ATTACK_MODIFIER_FIRST = ATTACK_MODIFIER_LIFESTEAL, + ATTACK_MODIFIER_LAST = ATTACK_MODIFIER_CONVERSION, +}; + +enum ModifierDefenseType : uint8_t { + DEFENSE_MODIFIER_NONE, // default + DEFENSE_MODIFIER_ABSORB, // damage is converted to health + DEFENSE_MODIFIER_RESTORE, // damage is converted to mana + DEFENSE_MODIFIER_REPLENISH, // damage is converted to stamina + DEFENSE_MODIFIER_REVIVE, // damage is converted to soul + DEFENSE_MODIFIER_REFLECT, // damage is reduced on defender and returns to attacker + DEFENSE_MODIFIER_DEFLECT, // damage is negated on defender but hits all nearby enemies + DEFENSE_MODIFIER_RICOCHET, // damage is negated on defender but hits one random enemy + DEFENSE_MODIFIER_RESIST, // damage reduction + DEFENSE_MODIFIER_REFORM, // convert damage to another type + + DEFENSE_MODIFIER_FIRST = DEFENSE_MODIFIER_NONE, + DEFENSE_MODIFIER_LAST = DEFENSE_MODIFIER_REFORM +}; + +enum ModFactor : uint8_t { + PERCENT_MODIFIER, + FLAT_MODIFIER +}; + +enum ModifierStance : uint8_t { + NO_MOD, + ATTACK_MOD, + DEFENSE_MOD +}; + +class DamageModifier : public std::enable_shared_from_this { + +public: + DamageModifier() = default; + ~DamageModifier() = default; + + // allow copying + DamageModifier(const DamageModifier&) = default; + DamageModifier& operator=(const DamageModifier&) = default; + + DamageModifier(uint8_t stance, uint8_t modType, uint16_t amount, ModFactor factorType, uint8_t chance, CombatType_t combatType = COMBAT_NONE , CombatOrigin source = ORIGIN_NONE, CreatureType_t creatureType = CREATURETYPE_ATTACKABLE, RaceType_t race = RACE_NONE, std::string creatureName = "none") : + m_mod_stance(stance), // attack / defense + m_mod_type(modType), // the enum specific type + m_value(amount), // value to modify; default = percent + m_factor(factorType), // flat or percent based? defaults to percent. + m_chance(chance), // chance; if chance is 0, chance is not used. + m_damage_type(combatType), // if none, defaults to all damage types + m_origin_type(source), // if none, is used on all origin types + m_creature_type(creatureType), // defaults to all creatures if not set + m_race_type(race), // if none, all races. + m_creature_name(creatureName) // if none, all creatures. + {} + + static std::shared_ptr makeModifier(uint8_t stance, uint8_t modType, uint16_t amount, ModFactor factorType, uint8_t chance, CombatType_t combatType = COMBAT_NONE, CombatOrigin source = ORIGIN_NONE, CreatureType_t creatureType = CREATURETYPE_ATTACKABLE, RaceType_t race = RACE_NONE, std::string_view creatureName = "none"); + + const uint8_t& getStance() const; + const uint8_t& getType() const; + const uint16_t& getValue() const; + const uint8_t& getChance() const; + + const CombatType_t& getDamageType() const; + const CombatOrigin& getOriginType() const; + + const bool isPercent() const; + const bool isFlatValue() const; + const bool appliesToDamage(const CombatType_t damageType) const; + const bool appliesToOrigin(const CombatOrigin origin) const; + const bool appliesToTarget(const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) const; + const bool isAttackStance() const; + const bool isDefenseStance() const; + const std::string& getMonsterName() const; + const CombatType_t& getConversionType() const; + + void setValue(uint16_t amount); + void setFactor(uint8_t factor); + void setCombatType(CombatType_t combatType); + void setOriginType(CombatOrigin origin); + void increaseValue(uint16_t amount); + void decreaseValue(uint16_t amount); + void setTransformDamageType(CombatType_t damageType); + void setCreatureName(std::string_view creatureName); + + void serialize(PropWriteStream& propWriteStream) const { + // Serialize regular fields + propWriteStream.write(m_mod_stance); + propWriteStream.write(m_mod_type); + propWriteStream.write(m_value); + propWriteStream.write(m_factor); + propWriteStream.write(m_chance); + propWriteStream.write(m_damage_type); + propWriteStream.write(m_to_damage_type); + propWriteStream.write(m_origin_type); + propWriteStream.write(m_creature_type); + propWriteStream.write(m_race_type); + propWriteStream.writeString(m_creature_name); + + } + + bool unserialize(PropStream& propReadStream) { + if (!propReadStream.read(m_mod_stance)) return false; + if (!propReadStream.read(m_mod_type)) return false; + if (!propReadStream.read(m_value)) return false; + if (!propReadStream.read(m_factor)) return false; + if (!propReadStream.read(m_chance)) return false; + + if (!propReadStream.read(m_damage_type)) return false; + if (!propReadStream.read(m_to_damage_type)) return false; + if (!propReadStream.read(m_origin_type)) return false; + if (!propReadStream.read(m_creature_type)) return false; + if (!propReadStream.read(m_race_type)) return false; + + auto [creatureName, success] = propReadStream.readString(); + if (!success) return false; + m_creature_name = std::string(creatureName); + + return true; + } + + +private: + uint8_t m_mod_stance = 0; // 0 = none, 1 = attack, 2 = defense; + uint8_t m_mod_type = 0; + uint16_t m_value = 0; + uint8_t m_factor = 0; + uint8_t m_chance = 0; + CombatType_t m_damage_type = COMBAT_NONE; + CombatType_t m_to_damage_type = COMBAT_NONE; + CombatOrigin m_origin_type = ORIGIN_NONE; + CreatureType_t m_creature_type = CREATURETYPE_ATTACKABLE; + RaceType_t m_race_type = RACE_NONE; + std::string m_creature_name = "none"; +}; + +/// Inline Methods' Definitions + +inline void DamageModifier::setValue(uint16_t amount) { + m_value = amount; +} + +inline void DamageModifier::setFactor(uint8_t factor) +{ + m_factor = static_cast(factor); +} + +inline void DamageModifier::setCombatType(CombatType_t combatType) { + m_damage_type = combatType; +} + +inline void DamageModifier::setOriginType(CombatOrigin origin) { + m_origin_type = origin; +} + +inline void DamageModifier::setCreatureName(std::string_view creatureName) { + m_creature_name = creatureName.data(); +} + +inline const bool DamageModifier::isPercent() const { + return m_factor == PERCENT_MODIFIER; +} + +inline const bool DamageModifier::isFlatValue() const { + return m_factor == FLAT_MODIFIER; +} + +inline const bool DamageModifier::appliesToDamage(const CombatType_t damageType) const { + return m_damage_type == COMBAT_NONE || m_damage_type == damageType; +} + +inline const bool DamageModifier::appliesToOrigin(const CombatOrigin origin) const { + bool matches = (m_origin_type == ORIGIN_NONE || m_origin_type == origin); + bool applies = (m_origin_type == ORIGIN_AUGMENT + && (origin == ORIGIN_ABSORB + || origin == ORIGIN_RESTORE + || origin == ORIGIN_REFLECT + || origin == ORIGIN_DEFLECT + || origin == ORIGIN_RICOCHET + || origin == ORIGIN_PIERCING)); + + return matches || applies; +} + +inline const bool DamageModifier::appliesToTarget(const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) const { + bool matchesType = (m_creature_type == CREATURETYPE_ATTACKABLE || m_creature_type == creatureType); + bool isValidTarget = + ((m_creature_type == CREATURETYPE_MONSTER || m_creature_type == CREATURETYPE_SUMMON_ALL) && ( + creatureType == CREATURETYPE_MONSTER || + creatureType == CREATURETYPE_SUMMON_ALL || + creatureType == CREATURETYPE_SUMMON_OWN || + creatureType == CREATURETYPE_SUMMON_GUILD || + creatureType == CREATURETYPE_SUMMON_HOSTILE || + creatureType == CREATURETYPE_SUMMON_PARTY + )); + bool matchesRace = (m_race_type == RACE_NONE || m_race_type == race); + + bool attackableTarget = false; + + if ((matchesType || isValidTarget) && matchesRace) { + + if (m_creature_name.empty() || m_creature_name == "none") { + attackableTarget = true; + } else { + attackableTarget = (m_creature_name == std::string(creatureName.data())); + } + } + return attackableTarget; +} + +inline const uint8_t& DamageModifier::getStance() const { + return m_mod_stance; +} + +inline const uint8_t& DamageModifier::getType() const { + return m_mod_type; +} + +inline const uint16_t& DamageModifier::getValue() const { + return m_value; +} + +inline const uint8_t& DamageModifier::getChance() const { + return m_chance; +} + +inline const CombatType_t& DamageModifier::getDamageType() const { + return m_damage_type; +} + +inline const CombatOrigin& DamageModifier::getOriginType() const { + return m_origin_type; +} + +inline const bool DamageModifier::isAttackStance() const +{ + return m_mod_stance == ATTACK_MOD; +} + +inline const bool DamageModifier::isDefenseStance() const +{ + return m_mod_stance == DEFENSE_MOD; +} + +inline const std::string& DamageModifier::getMonsterName() const +{ + return m_creature_name; +} + +inline const CombatType_t& DamageModifier::getConversionType() const +{ + return m_to_damage_type; +} + +#endif \ No newline at end of file diff --git a/src/definitions.h b/src/definitions.h index 3cf60b7a..f546f3d1 100644 --- a/src/definitions.h +++ b/src/definitions.h @@ -5,8 +5,10 @@ #define FS_DEFINITIONS_H static constexpr auto STATUS_SERVER_NAME = "Black Tek Server"; -static constexpr auto STATUS_SERVER_VERSION = "Pre-Alpha 0.0.1"; -static constexpr auto STATUS_SERVER_DEVELOPERS = "Black Tek Server Team"; +static constexpr auto STATUS_SERVER_VERSION = "1.0"; +static constexpr auto STATUS_SERVER_DEVELOPERS = "BlackTek"; +static constexpr auto STATUS_SERVER_MAINTAINER = "Codinablack@Github.com"; +static constexpr auto STATUS_SERVER_COMMUNITY_LINK = "https://discord.gg/Jgs5czzC"; static constexpr auto CLIENT_VERSION_MIN = 1097; static constexpr auto CLIENT_VERSION_MAX = 1098; diff --git a/src/enums.h b/src/enums.h index 7afc0f05..dde173b2 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,5 +1,8 @@ -// Copyright 2024 Black Tek 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. +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. #ifndef FS_ENUMS_H #define FS_ENUMS_H @@ -135,7 +138,12 @@ enum CreatureType_t : uint8_t { CREATURETYPE_MONSTER = 1, CREATURETYPE_NPC = 2, CREATURETYPE_SUMMON_OWN = 3, - CREATURETYPE_SUMMON_OTHERS = 4, + CREATURETYPE_SUMMON_HOSTILE = 4, + CREATURETYPE_SUMMON_GUILD = 5, + CREATURETYPE_SUMMON_PARTY = 6, + CREATURETYPE_BOSS = 7, + CREATURETYPE_ATTACKABLE = 8, + CREATURETYPE_SUMMON_ALL, }; enum OperatingSystem_t : uint8_t { @@ -156,6 +164,8 @@ enum SpellGroup_t : uint8_t { SPELLGROUP_HEALING = 2, SPELLGROUP_SUPPORT = 3, SPELLGROUP_SPECIAL = 4, + + SPELLGROUP_UNKNOWN = 255 // when no group set in revscript }; enum SpellType_t : uint8_t { @@ -593,8 +603,14 @@ enum CombatOrigin ORIGIN_SPELL, ORIGIN_MELEE, ORIGIN_RANGED, + ORIGIN_ABSORB, + ORIGIN_RESTORE, ORIGIN_REFLECT, + ORIGIN_DEFLECT, + ORIGIN_RICOCHET, + ORIGIN_PIERCING, ORIGIN_IMBUEMENT, + ORIGIN_AUGMENT, }; struct CombatDamage @@ -623,18 +639,4 @@ enum MonstersEvent_t : uint8_t { MONSTERS_EVENT_SAY = 5, }; -struct Reflect { - Reflect() = default; - Reflect(uint16_t percent, uint16_t chance) : percent(percent), chance(chance) {}; - - Reflect& operator+=(const Reflect& other) { - percent += other.percent; - chance = static_cast(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 9323e551..f1979d84 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -122,6 +122,10 @@ bool Events::load() info.playerOnRotateItem = event; } else if (methodName == "onSpellTry") { info.playerOnSpellTry = event; + } else if (methodName == "onAugment") { + info.playerOnAugment = event; + } else if (methodName == "onRemoveAugment") { + info.playerOnRemoveAugment = event; } else { std::cout << "[Warning - Events::load] Unknown player method: " << methodName << std::endl; } @@ -142,7 +146,11 @@ bool Events::load() info.itemOnAttack = event; } else if (methodName == "onDefend") { info.itemOnDefend = event; - } + } else if (methodName == "onAugment") { + info.itemOnAugment = event; + } else if (methodName == "onRemoveAugment") { + info.itemOnRemoveAugment = event; + } } else { std::cout << "[Warning - Events::load] Unknown class: " << className << std::endl; } @@ -314,7 +322,7 @@ void Events::eventCreatureOnHear(Creature* creature, Creature* speaker, const st LuaScriptInterface::setCreatureMetatable(L, -1, speaker); LuaScriptInterface::pushString(L, words); - lua_pushnumber(L, type); + lua_pushinteger(L, type); scriptInterface.callVoidFunction(4); } @@ -343,9 +351,9 @@ void Events::eventCreatureOnAttack(Creature* attacker, Creature* target, BlockTy LuaScriptInterface::pushUserdata(L, target); LuaScriptInterface::setCreatureMetatable(L, -1, target); - lua_pushnumber(L, static_cast(blockType)); - lua_pushnumber(L, static_cast(combatType)); - lua_pushnumber(L, static_cast(origin)); + lua_pushinteger(L, static_cast(blockType)); + lua_pushinteger(L, static_cast(combatType)); + lua_pushinteger(L, static_cast(origin)); lua_pushboolean(L, criticalDamage); lua_pushboolean(L, leechedDamage); @@ -377,9 +385,9 @@ void Events::eventCreatureOnDefend(Creature* defender, Creature* attacker, Block LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); - lua_pushnumber(L, static_cast(blockType)); - lua_pushnumber(L, static_cast(combatType)); - lua_pushnumber(L, static_cast(origin)); + lua_pushinteger(L, static_cast(blockType)); + lua_pushinteger(L, static_cast(combatType)); + lua_pushinteger(L, static_cast(origin)); lua_pushboolean(L, criticalDamage); lua_pushboolean(L, leechedDamage); @@ -487,7 +495,7 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); - lua_pushnumber(L, exp); + lua_pushinteger(L, exp); if (scriptInterface.protectedCall(L, 2, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); @@ -639,7 +647,7 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* } LuaScriptInterface::pushPosition(L, position, stackpos); - lua_pushnumber(L, lookDistance); + lua_pushinteger(L, lookDistance); scriptInterface.callVoidFunction(4); } @@ -668,7 +676,7 @@ void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, i LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - lua_pushnumber(L, lookDistance); + lua_pushinteger(L, lookDistance); scriptInterface.callVoidFunction(3); } @@ -700,7 +708,7 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - lua_pushnumber(L, lookDistance); + lua_pushinteger(L, lookDistance); scriptInterface.callVoidFunction(4); } @@ -729,7 +737,7 @@ bool Events::eventPlayerOnLookInShop(Player* player, const ItemType* itemType, u LuaScriptInterface::pushUserdata(L, itemType); LuaScriptInterface::setMetatable(L, -1, "ItemType"); - lua_pushnumber(L, count); + lua_pushinteger(L, count); lua_pushstring(L, description.c_str()); return scriptInterface.callFunction(4); @@ -759,7 +767,7 @@ ReturnValue Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t c LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - lua_pushnumber(L, count); + lua_pushinteger(L, count); LuaScriptInterface::pushPosition(L, fromPosition); LuaScriptInterface::pushPosition(L, toPosition); @@ -803,7 +811,7 @@ void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - lua_pushnumber(L, count); + lua_pushinteger(L, count); LuaScriptInterface::pushPosition(L, fromPosition); LuaScriptInterface::pushPosition(L, toPosition); @@ -866,8 +874,8 @@ void Events::eventPlayerOnReportRuleViolation(Player* player, const std::string& LuaScriptInterface::pushString(L, targetName); - lua_pushnumber(L, reportType); - lua_pushnumber(L, reportReason); + lua_pushinteger(L, reportType); + lua_pushinteger(L, reportReason); LuaScriptInterface::pushString(L, comment); LuaScriptInterface::pushString(L, translation); @@ -898,7 +906,7 @@ bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, LuaScriptInterface::pushString(L, message); LuaScriptInterface::pushPosition(L, position); - lua_pushnumber(L, category); + lua_pushinteger(L, category); return scriptInterface.callFunction(4); } @@ -924,7 +932,7 @@ bool Events::eventPlayerOnTurn(Player* player, Direction direction) LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, direction); + lua_pushinteger(L, direction); return scriptInterface.callFunction(2); } @@ -1056,8 +1064,8 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 lua_pushnil(L); } - lua_pushnumber(L, exp); - lua_pushnumber(L, rawExp); + lua_pushinteger(L, exp); + lua_pushinteger(L, rawExp); if (scriptInterface.protectedCall(L, 4, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); @@ -1090,7 +1098,7 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, exp); + lua_pushinteger(L, exp); if (scriptInterface.protectedCall(L, 2, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); @@ -1123,8 +1131,8 @@ void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_ LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, skill); - lua_pushnumber(L, tries); + lua_pushinteger(L, skill); + lua_pushinteger(L, tries); if (scriptInterface.protectedCall(L, 3, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); @@ -1187,7 +1195,7 @@ void Events::eventPlayerOnInventoryUpdate(Player* player, Item* item, slots_t sl LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - lua_pushnumber(L, slot); + lua_pushinteger(L, slot); LuaScriptInterface::pushBoolean(L, equip); scriptInterface.callVoidFunction(4); @@ -1217,7 +1225,7 @@ void Events::eventPlayerOnRotateItem(Player* player, Item* item) LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - scriptInterface.callFunction(2); + scriptInterface.callVoidFunction(2); } bool Events::eventPlayerOnSpellTry(Player* player, const Spell* spell, SpellType_t spellType) @@ -1243,9 +1251,62 @@ bool Events::eventPlayerOnSpellTry(Player* player, const Spell* spell, SpellType LuaScriptInterface::pushSpell(L, *spell); - return scriptInterface.callFunction(2); + return scriptInterface.callFunction(3); +} + +void Events::eventPlayerOnAugment(Player* player, std::shared_ptr augment) +{ + // Player:onAugment(augment) + if (info.playerOnAugment == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnAugment] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnAugment, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnAugment); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushSharedPtr(L, augment); + LuaScriptInterface::setMetatable(L, -1, "Augment"); + + scriptInterface.callVoidFunction(2); } +void Events::eventPlayerOnRemoveAugment(Player* player, std::shared_ptr augment) +{ + // Player:onRemoveAugment(augment) + if (info.playerOnRemoveAugment == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnRemoveAugment] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnRemoveAugment, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnRemoveAugment); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushSharedPtr(L, augment); + LuaScriptInterface::setMetatable(L, -1, "Augment"); + + scriptInterface.callVoidFunction(2); +} void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse) { @@ -1271,7 +1332,7 @@ void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse) LuaScriptInterface::pushUserdata(L, corpse); LuaScriptInterface::setMetatable(L, -1, "Container"); - return scriptInterface.callVoidFunction(2); + scriptInterface.callVoidFunction(2); } bool Events::eventItemOnImbue(Item* item, std::shared_ptr imbuement, bool created) @@ -1324,10 +1385,10 @@ void Events::eventItemOnRemoveImbue(Item* item, ImbuementType imbueType, bool de LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - lua_pushnumber(L, static_cast(imbueType)); + lua_pushinteger(L, static_cast(imbueType)); LuaScriptInterface::pushBoolean(L, decayed); - return scriptInterface.callVoidFunction(3); + scriptInterface.callVoidFunction(3); } void Events::eventItemOnAttack(Item* item, Player* itemHolder, Creature* defender, BlockType_t blockType, CombatType_t combatType, CombatOrigin origin, bool criticalDamage, bool leechedDamage) @@ -1357,14 +1418,14 @@ void Events::eventItemOnAttack(Item* item, Player* itemHolder, Creature* defende LuaScriptInterface::pushUserdata(L, defender); LuaScriptInterface::setCreatureMetatable(L, -1, defender); - lua_pushnumber(L, static_cast(blockType)); - lua_pushnumber(L, static_cast(combatType)); - lua_pushnumber(L, static_cast(origin)); + lua_pushinteger(L, static_cast(blockType)); + lua_pushinteger(L, static_cast(combatType)); + lua_pushinteger(L, static_cast(origin)); - lua_pushboolean(L, criticalDamage); - lua_pushboolean(L, leechedDamage); + lua_pushinteger(L, criticalDamage); + lua_pushinteger(L, leechedDamage); - return scriptInterface.callVoidFunction(8); + scriptInterface.callVoidFunction(8); } void Events::eventItemOnDefend(Item* item, Player* itemHolder, Creature* attacker, BlockType_t blockType, CombatType_t combatType, CombatOrigin origin, bool criticalDamage, bool leechedDamage) @@ -1394,12 +1455,66 @@ void Events::eventItemOnDefend(Item* item, Player* itemHolder, Creature* attacke LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); - lua_pushnumber(L, static_cast(blockType)); - lua_pushnumber(L, static_cast(combatType)); - lua_pushnumber(L, static_cast(origin)); + lua_pushinteger(L, blockType); + lua_pushinteger(L, static_cast(combatType)); + lua_pushinteger(L, static_cast(origin)); - lua_pushboolean(L, criticalDamage); - lua_pushboolean(L, leechedDamage); + lua_pushinteger(L, criticalDamage); + lua_pushinteger(L, leechedDamage); + + scriptInterface.callVoidFunction(8); +} + +void Events::eventItemOnAugment(Item* item, std::shared_ptr augment) +{ + // Item:onAugment(augment) + if (info.itemOnAugment == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventItemOnAugment] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.itemOnAugment, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.itemOnAugment); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setMetatable(L, -1, "Item"); - return scriptInterface.callVoidFunction(8); -} \ No newline at end of file + LuaScriptInterface::pushSharedPtr(L, augment); + LuaScriptInterface::setMetatable(L, -1, "Augment"); + + scriptInterface.callVoidFunction(2); +} + +void Events::eventItemOnRemoveAugment(Item* item, std::shared_ptr augment) +{ + // Item:onRemoveAugment(augment) + if (info.itemOnRemoveAugment == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventItemOnRemoveAugment] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.itemOnRemoveAugment, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.itemOnRemoveAugment); + + LuaScriptInterface::pushUserdata(L, item); + LuaScriptInterface::setMetatable(L, -1, "Item"); + + LuaScriptInterface::pushSharedPtr(L, augment); + LuaScriptInterface::setMetatable(L, -1, "Augment"); + + scriptInterface.callVoidFunction(2); +} diff --git a/src/events.h b/src/events.h index e8e1d984..555d84f2 100644 --- a/src/events.h +++ b/src/events.h @@ -63,6 +63,8 @@ class Events int32_t playerOnInventoryUpdate = -1; int32_t playerOnRotateItem = -1; int32_t playerOnSpellTry = -1; + int32_t playerOnAugment = -1; + int32_t playerOnRemoveAugment = -1; // Monster int32_t monsterOnDropLoot = -1; @@ -73,6 +75,8 @@ class Events int32_t itemOnRemoveImbue = -1; int32_t itemOnAttack = -1; int32_t itemOnDefend = -1; + int32_t itemOnAugment = -1; + int32_t itemOnRemoveAugment = -1; }; @@ -120,6 +124,8 @@ class Events void eventPlayerOnInventoryUpdate(Player* player, Item* item, slots_t slot, bool equip); void eventPlayerOnRotateItem(Player* player, Item* item); bool eventPlayerOnSpellTry(Player* player, const Spell* spell, SpellType_t spellType); + void eventPlayerOnAugment(Player* player, std::shared_ptr augment); + void eventPlayerOnRemoveAugment(Player* player, std::shared_ptr augment); // Monster void eventMonsterOnDropLoot(Monster* monster, Container* corpse); @@ -131,6 +137,8 @@ class Events void eventItemOnAttack(Item* item, Player* itemHolder, Creature* defender, BlockType_t blockType, CombatType_t combatType, CombatOrigin origin, bool criticalDamage = false, bool leechedDamage = false); void eventItemOnDefend(Item* item, Player* itemHolder, Creature* attacker, BlockType_t blockType, CombatType_t combatType, CombatOrigin origin, bool criticalDamage = false, bool leechedDamage = false); + void eventItemOnAugment(Item* item, std::shared_ptr augment); + void eventItemOnRemoveAugment(Item* item, std::shared_ptr augment); int32_t getScriptId(EventInfoId eventInfoId) { diff --git a/src/game.cpp b/src/game.cpp index a167f08a..2c3783c1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1623,6 +1623,11 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) return item; } + if (item->isAugmented() || item->hasImbuements()) { + std::cout << "Warning! Attempted to transform imbued/augmented item : " << item->getName() << " \n"; + return item; + } + Cylinder* cylinder = item->getParent(); if (cylinder == nullptr) { return nullptr; @@ -4064,12 +4069,17 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) { + // Get the position of the target for later use in displaying messages and effects const Position& targetPos = target->getPosition(); + + // If the damage value is positive (indicating healing or positive health change) if (damage.primary.value > 0) { + // Early exit if the target is already dead if (target->getHealth() <= 0) { return false; } + // Determine if the attacker is a player, if not, set to nullptr Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); @@ -4077,22 +4087,31 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage attackerPlayer = nullptr; } + // Check if the target is a player Player* targetPlayer = target->getPlayer(); + + // If the attacker is a player with a black skull and the target is not marked with a skull, + // the attack should not proceed. if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } + // Handle health change events if any exist if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { + // Execute each health change event for (CreatureEvent* creatureEvent : events) { creatureEvent->executeHealthChange(target, attacker, damage); } + // Reset the damage origin to prevent recursive calls from re-triggering the event damage.origin = ORIGIN_NONE; + // Recursively call the function to process the final health change return combatChangeHealth(attacker, target, damage); } } + // Calculate the actual health change and apply it to the target int32_t realHealthChange = target->getHealth(); target->gainHealth(attacker, damage.primary.value); realHealthChange = target->getHealth() - realHealthChange; @@ -4113,16 +4132,19 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // If health was gained and the target is not in ghost mode, send notifications to spectators if (realHealthChange > 0 && !target->isInGhostMode()) { auto damageString = fmt::format("{:d} hitpoint{:s}", realHealthChange, realHealthChange != 1 ? "s" : ""); std::string spectatorMessage; + // Prepare the message for spectators about the healing TextMessage message; message.position = targetPos; message.primary.value = realHealthChange; message.primary.color = TEXTCOLOR_PASTELRED; + // Get all spectators around the target SpectatorVec spectators; map.getSpectators(spectators, targetPos, false, true); for (Creature* spectator : spectators) { @@ -4159,6 +4181,9 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } } else { + // If the damage value is non-positive (indicating damage or negative health change) + + // Check if the target is attackable; if not, create a visual effect and return if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); @@ -4166,6 +4191,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } + // Similar logic for attacker and target player checks as before Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); @@ -4178,6 +4204,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return false; } + // Convert the damage values to positive for health reduction damage.primary.value = std::abs(damage.primary.value); damage.secondary.value = std::abs(damage.secondary.value); @@ -4186,10 +4213,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } + // Prepare message for spectators about the damage TextMessage message; message.position = targetPos; SpectatorVec spectators; + + // Check if the target has a mana shield, which can absorb some or all of the damage if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { @@ -4207,6 +4237,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Drain mana from the target and create a visual effect targetPlayer->drainMana(attacker, manaDamage); map.getSpectators(spectators, targetPos, true, true); addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); @@ -4216,6 +4247,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage message.primary.value = manaDamage; message.primary.color = TEXTCOLOR_BLUE; + // Notify spectators about the mana drain for (Creature* spectator : spectators) { assert(dynamic_cast(spectator) != nullptr); @@ -4254,6 +4286,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage spectatorPlayer->sendTextMessage(message); } + // Adjust the damage values after mana absorption damage.primary.value -= manaDamage; if (damage.primary.value < 0) { damage.secondary.value = std::max(0, damage.secondary.value + damage.primary.value); @@ -4262,11 +4295,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Calculate the total damage remaining after any mana absorption int32_t realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } + // Handle health change events again if necessary if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { @@ -4278,6 +4313,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Cap the primary damage at the target's current health int32_t targetHealth = target->getHealth(); if (damage.primary.value >= targetHealth) { damage.primary.value = targetHealth; @@ -4286,11 +4322,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage damage.secondary.value = std::min(damage.secondary.value, targetHealth - damage.primary.value); } + // Recalculate total damage after adjustments realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } + // Get spectators if not already done if (spectators.empty()) { map.getSpectators(spectators, targetPos, true, true); } @@ -4354,8 +4392,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage tmpPlayer->sendTextMessage(message); } } - - // rewardboss player attacking boss +// rewardboss player attacking boss if (target && target->getMonster() && target->getMonster()->isRewardBoss()) { uint32_t monsterId = target->getMonster()->getID(); @@ -4382,7 +4419,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage rewardBossTracking[monsterId].playerScoreTable[playerGuid].damageTaken += realDamage * g_config.getFloat(ConfigManager::REWARD_RATE_DAMAGE_TAKEN); } } - + // If the damage is enough to kill the target, execute death preparation events if (realDamage >= targetHealth) { for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { @@ -4391,6 +4428,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Apply the damage to the target's health and update spectators target->drainHealth(attacker, realDamage); addCreatureHealth(spectators, target); } @@ -4398,6 +4436,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } + bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage) { Player* targetPlayer = target->getPlayer(); @@ -4893,7 +4932,7 @@ void Game::updateCreatureType(Creature* creature) if (master) { masterPlayer = master->getPlayer(); if (masterPlayer) { - creatureType = CREATURETYPE_SUMMON_OTHERS; + creatureType = CREATURETYPE_SUMMON_HOSTILE; } } } @@ -4902,7 +4941,7 @@ void Game::updateCreatureType(Creature* creature) SpectatorVec spectators; map.getSpectators(spectators, creature->getPosition(), true, true); - if (creatureType == CREATURETYPE_SUMMON_OTHERS) { + if (creatureType == CREATURETYPE_SUMMON_HOSTILE) { for (Creature* spectator : spectators) { Player* player = spectator->getPlayer(); if (masterPlayer == player) { @@ -6078,3 +6117,4 @@ bool Game::playerUpdateAutoLoot(uint32_t playerId, uint16_t clientId, const std: player->updateAutoLoot(clientId, name, remove); return true; } + diff --git a/src/globalevent.cpp b/src/globalevent.cpp index c99fb1aa..3e4e5246 100644 --- a/src/globalevent.cpp +++ b/src/globalevent.cpp @@ -341,8 +341,8 @@ bool GlobalEvent::executeRecord(uint32_t current, uint32_t old) lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(scriptId); - lua_pushnumber(L, current); - lua_pushnumber(L, old); + lua_pushinteger(L, current); + lua_pushinteger(L, old); return scriptInterface->callFunction(2); } @@ -360,7 +360,7 @@ bool GlobalEvent::executeEvent() const int32_t params = 0; if (eventType == GLOBALEVENT_NONE || eventType == GLOBALEVENT_TIMER) { - lua_pushnumber(L, interval); + lua_pushinteger(L, interval); params = 1; } diff --git a/src/imbuement.h b/src/imbuement.h index fc37174b..c658ca35 100644 --- a/src/imbuement.h +++ b/src/imbuement.h @@ -65,19 +65,19 @@ struct Imbuement : std::enable_shared_from_this { bool isInfightDecay() const; void serialize(PropWriteStream& propWriteStream) const { - propWriteStream.write(static_cast(imbuetype)); + propWriteStream.write(static_cast(imbuetype)); propWriteStream.write(value); propWriteStream.write(duration); - propWriteStream.write(static_cast(decaytype)); + propWriteStream.write(static_cast(decaytype)); } - bool unserialize(PropStream& propReadStream) { - uint32_t type, val, dur, decay; - if (!propReadStream.read(type) || + uint32_t val, dur; + uint8_t type, decay; + if (!propReadStream.read(type) || !propReadStream.read(val) || !propReadStream.read(dur) || - !propReadStream.read(decay)) { + !propReadStream.read(decay)) { return false; } diff --git a/src/iologindata.cpp b/src/iologindata.cpp index c18811ee..0603c4fe 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -12,6 +12,13 @@ extern ConfigManager g_config; extern Game g_game; +#include +#include + +const size_t MAX_AUGMENT_DATA_SIZE = 1024 * 64; // 64 KB is the limit for BLOB +const uint32_t MAX_AUGMENT_COUNT = 100; // Augments should not break size limit if we limit how many can go on a single player or item + + Account IOLoginData::loadAccount(uint32_t accno) { Account account; @@ -443,7 +450,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) //load inventory items ItemMap itemMap; - 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())))) { + if ((result = db.storeQuery(fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments` 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) { @@ -470,7 +477,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) //load depot items itemMap.clear(); - 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())))) { + if ((result = db.storeQuery(fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments` 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) { @@ -500,53 +507,35 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) // Load reward items itemMap.clear(); - if ((result = db.storeQuery(fmt::format("SELECT `sid`, `pid`, `itemtype`, `count`, `attributes` FROM `player_rewarditems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", player->getGUID())))) { + if ((result = db.storeQuery(fmt::format("SELECT `sid`, `pid`, `itemtype`, `count`, `attributes`, `augments` FROM `player_rewarditems` WHERE `player_id` = {:d} ORDER BY `sid` DESC", player->getGUID())))) { loadItems(itemMap, result); - // Map to store containers (bags) for each unique DATE attribute - std::unordered_map dateContainers; - - // Get the current time and calculate the time 7 days ago - time_t now = std::time(nullptr); - time_t seven_days_ago = now - (7 * 24 * 60 * 60); // 7 days in seconds - - 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; + + if (pid == 0) { + auto& rewardChest = player->getRewardChest(); + rewardChest.internalAddThing(item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } - int64_t rewardDate = item->getIntAttr(ITEM_ATTRIBUTE_DATE); - - // Skip items older than 7 days - if (rewardDate < static_cast(seven_days_ago)) { - continue; - } - - // Create or get existing container for the given DATE attribute - Container* container = nullptr; - auto containerIt = dateContainers.find(rewardDate); - if (containerIt != dateContainers.end()) { - container = containerIt->second; - } - else { - container = new Container(ITEM_REWARD_CONTAINER); - container->setIntAttr(ITEM_ATTRIBUTE_DATE, rewardDate); // Set the DATE attribute on the container - container->setIntAttr(ITEM_ATTRIBUTE_REWARDID, item->getIntAttr(ITEM_ATTRIBUTE_REWARDID)); - dateContainers[rewardDate] = container; + Container* container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } } - - container->internalAddThing(item); - } - - for (auto& pair : dateContainers) { - player->getRewardChest().internalAddThing(pair.second); } } //load inbox items itemMap.clear(); - 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())))) { + if ((result = db.storeQuery(fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments` 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) { @@ -574,7 +563,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) //load store inbox items itemMap.clear(); - 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())))) { + if ((result = db.storeQuery(fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments` 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) { @@ -606,6 +595,24 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } while (result->next()); } + if ((result = db.storeQuery(fmt::format("SELECT `player_id`, `augments` FROM `player_augments` WHERE `player_id` = {:d}", player->getGUID())))) { + try { + std::vector> augments; + loadPlayerAugments(augments, result); + + if (!augments.empty()) { + for (auto& augment : augments) { + if (augment) { + player->addAugment(augment); + } + } + } + } + catch (const std::exception& e) { + std::cout << "ERROR: Failed to process loaded augments: " << e.what() << std::endl; + } + } + //load vip list if ((result = db.storeQuery(fmt::format("SELECT `player_id` FROM `account_viplist` WHERE `account_id` = {:d}", player->getAccount())))) { do { @@ -627,6 +634,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, int32_t runningId = 100; Database& db = Database::getInstance(); + for (const auto& it : itemList) { int32_t pid = it.first; Item* item = it.second; @@ -638,8 +646,22 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, propWriteStream.clear(); item->serializeAttr(propWriteStream); + auto attributesData = propWriteStream.getStream(); + + auto augmentStream = PropWriteStream(); + const auto& augments = item->getAugments(); + augmentStream.clear(); + augmentStream.write(augments.size()); + + for (const auto& augment : augments) { + augment->serialize(augmentStream); + } + auto augmentsData = augmentStream.getStream(); - if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", player->getGUID(), pid, runningId, item->getID(), item->getSubType(), db.escapeString(propWriteStream.getStream())))) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}, {:s}", + player->getGUID(), pid, runningId, item->getID(), item->getSubType(), + db.escapeString(attributesData), + db.escapeString(augmentsData)))) { return false; } } @@ -653,59 +675,105 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, for (Item* item : container->getItemList()) { ++runningId; - Container* subContainer = item->getContainer(); - if (subContainer) { - queue.emplace_back(subContainer, runningId); - } - propWriteStream.clear(); item->serializeAttr(propWriteStream); + auto attributesData = propWriteStream.getStream(); + + auto augmentStream = PropWriteStream(); + const auto& augments = item->getAugments(); + augmentStream.clear(); + augmentStream.write(augments.size()); - if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", player->getGUID(), parentId, runningId, item->getID(), item->getSubType(), db.escapeString(propWriteStream.getStream())))) { + for (const auto& augment : augments) { + augment->serialize(augmentStream); + } + + auto augmentsData = augmentStream.getStream(); + + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}, {:s}", + player->getGUID(), parentId, runningId, item->getID(), item->getSubType(), + db.escapeString(attributesData), + db.escapeString(augmentsData)))) { return false; } + + if (Container* subContainer = item->getContainer()) { + queue.emplace_back(subContainer, runningId); + } } } + return query_insert.execute(); } - -bool IOLoginData::addRewardItems(uint32_t playerId, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) -{ - using ContainerBlock = std::pair; - std::list queue; - +bool IOLoginData::saveAugments(const Player* player, DBInsert& query_insert, PropWriteStream& augmentStream) { Database& db = Database::getInstance(); + auto& augments = player->getPlayerAugments(); + uint32_t augmentCount = augments.size(); + augmentStream.clear(); + augmentStream.write(augmentCount); + + // Cap the max augments at a reasonable limit + if (augmentCount > MAX_AUGMENT_COUNT) { + // to-do : handle this better, and let player know in case this happens, what is happening. + std::cout << "ERROR: Too many augments to save (" << augmentCount << ") for player " << player->getGUID() << std::endl; + return false; + } - DBResult_ptr result = db.storeQuery(fmt::format("SELECT MAX(pid) as max_pid FROM `player_rewarditems` WHERE `player_id` = {:d}", playerId)); - int32_t runningId = 1; - int32_t pidCounter = 1; + for (auto& augment : augments) { + augment->serialize(augmentStream); + } - if (result) { - int32_t maxPid = result->getNumber("max_pid"); + auto augmentsData = augmentStream.getStream(); - if (maxPid > 0) { - pidCounter = maxPid + 1; - } + // Blobs can only hold 64 kb's + if (augmentsData.size() > MAX_AUGMENT_DATA_SIZE) { + // to-do : handle this better, and let player know in case this happens, what is happening. + std::cout << "ERROR: Augment data size exceeds the limit during save for player " << player->getGUID() << std::endl; + return false; + } + + if (!query_insert.addRow(fmt::format("{:d}, {:s}", player->getGUID(), db.escapeString(augmentsData)))) { + return false; } - int32_t parentPid = pidCounter; + return query_insert.execute(); +} + +bool IOLoginData::addRewardItems(uint32_t playerID, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) +{ + using ContainerBlock = std::pair; + std::list queue; + int32_t runningId = 100; + Database& db = Database::getInstance(); for (const auto& it : itemList) { + int32_t pid = it.first; Item* item = it.second; - + ++runningId; propWriteStream.clear(); item->serializeAttr(propWriteStream); + auto attributesData = propWriteStream.getStream(); + + auto augmentStream = PropWriteStream(); + const auto& augments = item->getAugments(); + augmentStream.clear(); + augmentStream.write(augments.size()); + for (const auto& augment : augments) { + augment->serialize(augmentStream); + } + auto augmentsData = augmentStream.getStream(); - if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", playerId, parentPid, runningId, item->getID(), item->getSubType(), db.escapeString(propWriteStream.getStream())))) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}, {:s}", + playerID, pid, runningId, item->getID(), item->getSubType(), + db.escapeString(attributesData), + db.escapeString(augmentsData)))) { return false; } if (Container* container = item->getContainer()) { queue.emplace_back(container, runningId); } - - ++runningId; // Always increment SID upwards } while (!queue.empty()) { @@ -715,21 +783,33 @@ bool IOLoginData::addRewardItems(uint32_t playerId, const ItemBlockList& itemLis queue.pop_front(); for (Item* item : container->getItemList()) { + ++runningId; propWriteStream.clear(); item->serializeAttr(propWriteStream); + auto attributesData = propWriteStream.getStream(); + + auto augmentStream = PropWriteStream(); + const auto& augments = item->getAugments(); + augmentStream.clear(); + augmentStream.write(augments.size()); + for (const auto& augment : augments) { + augment->serialize(augmentStream); + } + auto augmentsData = augmentStream.getStream(); - if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", playerId, parentId, runningId, item->getID(), item->getSubType(), db.escapeString(propWriteStream.getStream())))) { + if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}, {:s}", + playerID, parentId, runningId, item->getID(), item->getSubType(), + db.escapeString(attributesData), + db.escapeString(augmentsData)))) { return false; } - Container* subContainer = item->getContainer(); - if (subContainer) { + if (Container* subContainer = item->getContainer()) { queue.emplace_back(subContainer, runningId); } - - ++runningId; // Always increment SID upwards } } + return query_insert.execute(); } @@ -890,7 +970,7 @@ bool IOLoginData::savePlayer(Player* player) 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`, `augments` ) VALUES "); ItemBlockList itemList; for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { @@ -909,7 +989,7 @@ bool IOLoginData::savePlayer(Player* player) return false; } - DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments`) VALUES "); itemList.clear(); for (const auto& it : player->depotChests) { @@ -927,21 +1007,11 @@ bool IOLoginData::savePlayer(Player* player) return false; } - DBInsert rewardQuery("INSERT INTO `player_rewarditems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + DBInsert rewardQuery("INSERT INTO `player_rewarditems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments`) VALUES "); itemList.clear(); - int32_t pidCounter = 1; - for (Item* item : player->getRewardChest().getItemList()) { - if (Container* container = item->getContainer()) { - int32_t currentPid = pidCounter++; - for (Item* subItem : container->getItemList()) { - itemList.emplace_back(currentPid, subItem); - } - } - else { - itemList.emplace_back(0, item); - } + itemList.emplace_back(0, item); } if (!saveItems(player, itemList, rewardQuery, propWriteStream, openContainers)) { @@ -954,7 +1024,7 @@ bool IOLoginData::savePlayer(Player* player) 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`, `augments`) VALUES "); itemList.clear(); for (Item* item : player->getInbox()->getItemList()) { @@ -970,7 +1040,7 @@ bool IOLoginData::savePlayer(Player* player) 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`, `augments`) VALUES "); itemList.clear(); for (Item* item : player->getStoreInbox()->getItemList()) { @@ -998,6 +1068,19 @@ bool IOLoginData::savePlayer(Player* player) return false; } + if (!db.executeQuery(fmt::format("DELETE FROM `player_augments` WHERE `player_id` = {:d}", player->getGUID()))) { + return false; + } + + DBInsert augmentQuery("INSERT INTO `player_augments` (`player_id`, `augments`) VALUES "); + PropWriteStream augmentStream; + + // Size check before proceeding + if (!saveAugments(player, augmentQuery, augmentStream)) { + return false; + } + + //End the transaction return transaction.commit(); } @@ -1060,30 +1143,100 @@ bool IOLoginData::formatPlayerName(std::string& name) return true; } +void IOLoginData::loadPlayerAugments(std::vector>& augmentList, DBResult_ptr result) { + try { + if (!result) { + std::cout << "ERROR: Null result in loadPlayerAugments" << std::endl; + return; + } + + uint32_t playerID = result->getNumber("player_id"); + auto augmentData = result->getString("augments"); + + if (augmentData.empty()) { + std::cout << "INFO: Empty augment data for player " << playerID << std::endl; + return; + } + + PropStream augmentStream; + augmentStream.init(augmentData.data(), augmentData.size()); + + uint32_t augmentCount = 0; + + if (!augmentStream.read(augmentCount)) { + std::cout << "WARNING: Failed to read augment count for player " << playerID << std::endl; + return; + } + + // Additional validation on augmentCount + if (augmentCount > MAX_AUGMENT_COUNT) { + std::cout << "ERROR: Augment count too high for player " << playerID << ": " << augmentCount << std::endl; + return; + } + + augmentList.reserve(augmentCount); + + for (uint32_t i = 0; i < augmentCount; ++i) { + auto augment = std::make_shared(); + + try { + if (!augment->unserialize(augmentStream)) { + std::cout << "WARNING: Failed to unserialize augment " << i + << " for player " << playerID << std::endl; + return; + } + augmentList.emplace_back(augment); + } + catch (const std::exception& e) { + std::cout << "ERROR: Exception while unserializing augment " << i + << " for player " << playerID << ": " << e.what() << std::endl; + return; + } + } + } + catch (const std::exception& e) { + std::cout << "ERROR: Exception in loadPlayerAugments: " << e.what() << std::endl; + augmentList.clear(); + } +} + void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result) { + Database& db = Database::getInstance(); + do { uint32_t sid = result->getNumber("sid"); uint32_t pid = result->getNumber("pid"); uint16_t type = result->getNumber("itemtype"); uint16_t count = result->getNumber("count"); + // Load the attributes field auto attr = result->getString("attributes"); PropStream propStream; propStream.init(attr.data(), attr.size()); + auto augmentData = result->getString("augments"); + PropStream augmentStream; + augmentStream.init(augmentData.data(), augmentData.size()); + + Item* item = Item::CreateItem(type, count); if (item) { + // Deserialize the item's attributes if (!item->unserializeAttr(propStream)) { - std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; } - std::pair pair(item, pid); - itemMap[sid] = pair; + if (!item->unserializeAugments(augmentStream)) { + // todo: handle this + } + + // Add item to the itemMap + itemMap[sid] = std::make_pair(item, pid); } } while (result->next()); } + void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) { Database::getInstance().executeQuery(fmt::format("UPDATE `players` SET `balance` = `balance` + {:d} WHERE `id` = {:d}", bankBalance, guid)); diff --git a/src/iologindata.h b/src/iologindata.h index 61f5d258..3848cf15 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -50,6 +50,8 @@ class IOLoginData static void loadItems(ItemMap& itemMap, DBResult_ptr result); static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream, std::map& openContainers); + static bool saveAugments(const Player* player, DBInsert& query_insert, PropWriteStream& augmentStream); + static void loadPlayerAugments(std::vector>& augmentList, DBResult_ptr result); }; #endif diff --git a/src/item.cpp b/src/item.cpp index ea11ada4..a464558e 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -32,9 +32,6 @@ void handleWeaponMeleeDescription(std::ostringstream& s, const ItemType& it, con void handleSkillsDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleStatsDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleStatsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); -void handleAbsorbsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); -void handleReflectPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); -void handleAbsorbsFieldsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleAbilitiesDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleMiscDescription(std::ostringstream& s, const ItemType& it, bool& begin); @@ -392,6 +389,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_RUNE_CHARGES: { uint8_t count; if (!propStream.read(count)) { + std::cout << "Failed to read : Rune Charges \n"; return ATTR_READ_ERROR; } @@ -402,6 +400,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_ACTION_ID: { uint16_t actionId; if (!propStream.read(actionId)) { + std::cout << "Failed to read : Action ID \n"; return ATTR_READ_ERROR; } @@ -412,6 +411,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_UNIQUE_ID: { uint16_t uniqueId; if (!propStream.read(uniqueId)) { + std::cout << "Failed to read : Unique ID \n"; return ATTR_READ_ERROR; } @@ -422,6 +422,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_TEXT: { auto [text, ok] = propStream.readString(); if (!ok) { + std::cout << "Failed to read : Text Attribute String \n"; return ATTR_READ_ERROR; } @@ -432,6 +433,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_WRITTENDATE: { uint32_t writtenDate; if (!propStream.read(writtenDate)) { + std::cout << "Failed to read : Written Date \n"; return ATTR_READ_ERROR; } @@ -442,6 +444,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_WRITTENBY: { auto [writer, ok] = propStream.readString(); if (!ok) { + std::cout << "Failed to read : Written By \n"; return ATTR_READ_ERROR; } @@ -452,6 +455,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_DESC: { auto [text, ok] = propStream.readString(); if (!ok) { + std::cout << "Failed to read : Description Attribute \n"; return ATTR_READ_ERROR; } @@ -462,6 +466,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_CHARGES: { uint16_t charges; if (!propStream.read(charges)) { + std::cout << "Failed to read : Charges Attribute \n"; return ATTR_READ_ERROR; } @@ -472,6 +477,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_DURATION: { int32_t duration; if (!propStream.read(duration)) { + std::cout << "Failed to read : Duration \n"; return ATTR_READ_ERROR; } @@ -482,6 +488,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_DECAYING_STATE: { uint8_t state; if (!propStream.read(state)) { + std::cout << "Failed to read : Decay State \n"; return ATTR_READ_ERROR; } @@ -494,6 +501,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_NAME: { auto [name, ok] = propStream.readString(); if (!ok) { + std::cout << "Failed to read : Name Attribute \n"; return ATTR_READ_ERROR; } @@ -503,6 +511,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_ARTICLE: { auto [article, ok] = propStream.readString(); + std::cout << "Failed to read : Article Attribute \n"; if (!ok) { return ATTR_READ_ERROR; } @@ -514,6 +523,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_PLURALNAME: { auto [pluralName, ok] = propStream.readString(); if (!ok) { + std::cout << "Failed to read : PluralName \n"; return ATTR_READ_ERROR; } @@ -524,6 +534,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_WEIGHT: { uint32_t weight; if (!propStream.read(weight)) { + std::cout << "Failed to read : Weight \n"; return ATTR_READ_ERROR; } @@ -534,6 +545,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_ATTACK: { int32_t attack; if (!propStream.read(attack)) { + std::cout << "Failed to read : Attack \n"; return ATTR_READ_ERROR; } @@ -544,6 +556,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_ATTACK_SPEED: { uint32_t attackSpeed; if (!propStream.read(attackSpeed)) { + std::cout << "Failed to read : Attack Speed \n"; return ATTR_READ_ERROR; } @@ -551,51 +564,10 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_CLASSIFICATION: { - uint32_t classification; - if (!propStream.read(classification)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_CLASSIFICATION, classification); - break; - } - - case ATTR_TIER: { - uint32_t tier; - if (!propStream.read(tier)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_TIER, tier); - break; - } - - - case ATTR_REWARDID: { - uint32_t rewardid; - if (!propStream.read(rewardid)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_REWARDID, rewardid); - break; - } - - - case ATTR_IMBUESLOTS: { - uint32_t slots; - if (!propStream.read(slots)) { - return ATTR_READ_ERROR; - } - - imbuementSlots = slots; - break; - } - case ATTR_DEFENSE: { int32_t defense; if (!propStream.read(defense)) { + std::cout << "Failed to read : Defense \n"; return ATTR_READ_ERROR; } @@ -606,6 +578,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_EXTRADEFENSE: { int32_t extraDefense; if (!propStream.read(extraDefense)) { + std::cout << "Failed to read : Extra Defense \n"; return ATTR_READ_ERROR; } @@ -616,6 +589,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_ARMOR: { int32_t armor; if (!propStream.read(armor)) { + std::cout << "Failed to read : Armor \n"; return ATTR_READ_ERROR; } @@ -626,6 +600,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_HITCHANCE: { int8_t hitChance; if (!propStream.read(hitChance)) { + std::cout << "Failed to read : HitChance \n"; return ATTR_READ_ERROR; } @@ -636,6 +611,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_SHOOTRANGE: { uint8_t shootRange; if (!propStream.read(shootRange)) { + std::cout << "Failed to read : ShootRange \n"; return ATTR_READ_ERROR; } @@ -646,6 +622,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_DECAYTO: { int32_t decayTo; if (!propStream.read(decayTo)) { + std::cout << "Failed to read : Decay To \n"; return ATTR_READ_ERROR; } @@ -656,6 +633,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_WRAPID: { uint16_t wrapId; if (!propStream.read(wrapId)) { + std::cout << "Failed to read : Wrap ID \n"; return ATTR_READ_ERROR; } @@ -666,6 +644,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_STOREITEM: { uint8_t storeItem; if (!propStream.read(storeItem)) { + std::cout << "Failed to read : Store Item \n"; return ATTR_READ_ERROR; } @@ -683,61 +662,6 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) 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; - } - - case ATTR_IMBUEMENTS: { - uint16_t size; - if (!propStream.read(size)) { - return ATTR_READ_ERROR; - } - - for (uint16_t i = 0; i < size; ++i) { - std::shared_ptr imb = std::make_shared(); - if (!imb->unserialize(propStream)) { - return ATTR_READ_ERROR; - } - - addImbuement(imb, false); - } - break; - } - //12+ compatibility case ATTR_OPENCONTAINER: case ATTR_PODIUMOUTFIT: { @@ -754,6 +678,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) //Depot class case ATTR_DEPOT_ID: { if (!propStream.skip(2)) { + std::cout << "Failed to read : Depot ID \n"; return ATTR_READ_ERROR; } break; @@ -762,6 +687,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) //Door class case ATTR_HOUSEDOORID: { if (!propStream.skip(1)) { + std::cout << "Failed to read : HouseDoor ID \n"; return ATTR_READ_ERROR; } break; @@ -770,6 +696,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) //Bed class case ATTR_SLEEPERGUID: { if (!propStream.skip(4)) { + std::cout << "Failed to read : Sleeper GUID \n"; return ATTR_READ_ERROR; } break; @@ -777,6 +704,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) case ATTR_SLEEPSTART: { if (!propStream.skip(4)) { + std::cout << "Failed to read : Sleep Start \n"; return ATTR_READ_ERROR; } break; @@ -785,6 +713,7 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) //Teleport class case ATTR_TELE_DEST: { if (!propStream.skip(5)) { + std::cout << "Failed to read : Teleport Destination \n"; return ATTR_READ_ERROR; } break; @@ -792,12 +721,14 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) //Container class case ATTR_CONTAINER_ITEMS: { + std::cout << "Failed to read : Container Items \n"; return ATTR_READ_ERROR; } case ATTR_CUSTOM_ATTRIBUTES: { uint64_t size; if (!propStream.read(size)) { + std::cout << "Failed to read : Custom Attribute Size \n"; return ATTR_READ_ERROR; } @@ -805,12 +736,14 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) // Unserialize key type and value auto [key, ok] = propStream.readString(); if (!ok) { + std::cout << "Failed to read : Custom Attribute Key \n"; return ATTR_READ_ERROR; }; // Unserialize value type and value ItemAttributes::CustomAttribute val; if (!val.unserialize(propStream)) { + std::cout << "Failed to read : Custom Attribute Value \n"; return ATTR_READ_ERROR; } @@ -819,8 +752,71 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } + case ATTR_CLASSIFICATION: { + auto [classification, ok] = propStream.readString(); + if (!ok) { + std::cout << "Failed to read : Classification \n"; + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_CLASSIFICATION, classification); + break; + } + + case ATTR_TIER: { + auto [tier, ok] = propStream.readString(); + if (!ok) { + std::cout << "Failed to read : Tier \n"; + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_TIER, tier); + break; + } + + case ATTR_IMBUESLOTS: { + uint16_t slots; + if (!propStream.read(slots)) { + std::cout << "Failed to read : Imbuement Slots \n"; + return ATTR_READ_ERROR; + } + + imbuementSlots = slots; + break; + } + + case ATTR_IMBUEMENTS: { + uint32_t size; + if (!propStream.read(size)) { + std::cout << "Failed to read : Imbuement's Size \n"; + return ATTR_READ_ERROR; + } + + for (uint32_t i = 0; i < size; ++i) { + std::shared_ptr imb = std::make_shared(); + if (!imb->unserialize(propStream)) { + std::cout << "Failed to read : Imbuement Data \n"; + return ATTR_READ_ERROR; + } + + addImbuement(imb, false); + } + break; + } + + case ATTR_REWARDID: { + uint32_t rewardid; + if (!propStream.read(rewardid)) { + std::cout << "Failed to read : Reward ID \n"; + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_REWARDID, rewardid); + break; + } + default: - return ATTR_READ_ERROR; + return ATTR_READ_CONTINUE; } return ATTR_READ_CONTINUE; @@ -840,6 +836,27 @@ bool Item::unserializeAttr(PropStream& propStream) return true; } +bool Item::unserializeAugments(PropStream& propStream) +{ + uint32_t augmentCount = 0; + + if (!propStream.read(augmentCount)) { + std::cout << "WARNING: Failed to read augment count in IOLoginData::loadItems" << std::endl; + return false; + } + + for (uint32_t i = 0; i < augmentCount; ++i) { + auto augment = std::make_shared(); + bool result = augment->unserialize(propStream); + if (!result) { + return result; + } else { + this->addAugment(augment); + } + } + return true; +} + bool Item::unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream) { return unserializeAttr(propStream); @@ -932,26 +949,6 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ATTACK_SPEED)); } - if (hasAttribute(ITEM_ATTRIBUTE_CLASSIFICATION)) { - propWriteStream.write(ATTR_CLASSIFICATION); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_CLASSIFICATION)); - } - - if (hasAttribute(ITEM_ATTRIBUTE_TIER)) { - propWriteStream.write(ATTR_TIER); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_TIER)); - } - - if (hasAttribute(ITEM_ATTRIBUTE_REWARDID)) { - propWriteStream.write(ATTR_REWARDID); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_REWARDID)); - } - - if (getImbuementSlots() > 0) { - propWriteStream.write(ATTR_IMBUESLOTS); - propWriteStream.write(imbuementSlots); - } - if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { propWriteStream.write(ATTR_DEFENSE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); @@ -1001,49 +998,44 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const const ItemAttributes::CustomAttributeMap* customAttrMap = attributes->getCustomAttributeMap(); propWriteStream.write(ATTR_CUSTOM_ATTRIBUTES); propWriteStream.write(static_cast(customAttrMap->size())); - for (const auto &entry : *customAttrMap) { - // Serializing key type and value + for (const auto& entry : *customAttrMap) { propWriteStream.writeString(entry.first); - - // Serializing value type and value 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); - } - } + if (hasAttribute(ITEM_ATTRIBUTE_CLASSIFICATION)) { + propWriteStream.write(ATTR_CLASSIFICATION); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_CLASSIFICATION)); + } - const auto& boosts = attributes->boostPercent; - if (!boosts.empty()) { - propWriteStream.write(ATTR_BOOST); - propWriteStream.write(boosts.size()); + if (hasAttribute(ITEM_ATTRIBUTE_TIER)) { + propWriteStream.write(ATTR_TIER); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_TIER)); + } - for (const auto& boost : boosts) { - propWriteStream.write(boost.first); - propWriteStream.write(boost.second); - } - } + if (getImbuementSlots() > 0) { + propWriteStream.write(ATTR_IMBUESLOTS); + propWriteStream.write(imbuementSlots); } if (hasImbuements()) { + const auto& imbues = getImbuements(); propWriteStream.write(ATTR_IMBUEMENTS); - propWriteStream.write(imbuements.size()); - for (auto entry : imbuements) { + propWriteStream.write(imbues.size()); + for (const auto& entry : imbues) { entry->serialize(propWriteStream); } } + + if (hasAttribute(ITEM_ATTRIBUTE_REWARDID)) { + propWriteStream.write(ATTR_REWARDID); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_REWARDID)); + } } + + bool Item::hasProperty(ITEMPROPERTY prop) const { const ItemType& it = items[id]; @@ -1476,7 +1468,7 @@ bool Item::canDecay() const if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { return false; } - + // to-do: Should probably block imbued and augmented items here. return true; } @@ -1491,43 +1483,10 @@ LightInfo Item::getLightInfo() const return {it.lightLevel, it.lightColor}; } -Reflect Item::getReflect(CombatType_t combatType, bool total /* = true */) const -{ - const ItemType& it = Item::items[id]; - - Reflect reflect; - if (attributes) { - reflect += attributes->getReflect(combatType); - } - - if (total && it.abilities) { - reflect += it.abilities->reflect[combatTypeToIndex(combatType)]; - } - - return reflect; -} - -uint16_t Item::getBoostPercent(CombatType_t combatType, bool total /* = true */) const -{ - 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 { @@ -1648,18 +1607,6 @@ bool Item::hasMarketAttributes() const 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) { @@ -1741,7 +1688,14 @@ uint16_t Item::getFreeImbuementSlots() const bool Item::canImbue() const { - // item:canImbue() -- returns true if item has slots that are free + // todo: known bug, stackables are imbueable, but no protection is offered for someone copying items with imbuements, + // unless the stackables are items created a new, in that case, the imbuements don't carry over when broken down, + // what needs to be in place is a change of itemID and name when imbuing a stack of stackables, and apply these changes, + // to every single item in the stack, keeping them from being stacked with other items, or being cloned, all while + // still allowing the item to benefit from the imbuement. + if (canDecay() || !canTransform() || getCharges() || !hasProperty(CONST_PROP_MOVEABLE)) { + return false; + } return (imbuementSlots > 0 && imbuementSlots > imbuements.size()) ? true : false; } @@ -1824,7 +1778,7 @@ bool Item::hasImbuement(const std::shared_ptr& imbuement) const bool Item::hasImbuements() const { // item:hasImbuements() -- returns true if item has any imbuements - return imbuements.size() > 0; + return !imbuements.empty(); } bool Item::addImbuement(std::shared_ptr imbuement, bool created) @@ -1872,6 +1826,90 @@ std::vector>& Item::getImbuements() { return imbuements; } +const std::vector>& Item::getImbuements() const +{ + return imbuements; +} + +const bool Item::addAugment(std::shared_ptr& augment) +{ + if (std::find(augments.begin(), augments.end(), augment) != augments.end()) { + return false; + } + + augments.push_back(augment); + g_events->eventItemOnAugment(this, augment); + return true; +} + +const bool Item::addAugment(std::string_view augmentName) +{ + if (auto augment = Augments::GetAugment(augmentName)) { + augments.emplace_back(augment); + g_events->eventItemOnAugment(this, augment); + return true; + } + return false; +} + +const bool Item::removeAugment(std::shared_ptr& augment) +{ + auto originalSize = augments.size(); + augments.erase(std::remove(augments.begin(), augments.end(), augment), augments.end()); + auto removed = (augments.size() - originalSize) > 0 ? true : false; + if (removed) { + g_events->eventItemOnRemoveAugment(this, augment); + } + return removed; +} + +const bool Item::removeAugment(std::string_view name) { + auto originalSize = augments.size(); + + augments.erase(std::remove_if(augments.begin(), augments.end(), + [this, &name](const std::shared_ptr& augment) { + auto match = augment->getName() == name; + if (match) { + g_events->eventItemOnRemoveAugment(this, augment); + } + return match; + }), augments.end()); + + return augments.size() < originalSize; +} + +// To-do: Move to const inline next three methods at least. +bool Item::isAugmented() const +{ + return augments.size() > 0; +} + +bool Item::hasAugment(std::string_view name) const +{ + for (const auto& aug : augments) { + if (aug->getName() == name) { + return true; + } + } + return false; +} + +bool Item::hasAugment(const std::shared_ptr& augment) const +{ + for (const auto& aug : augments) { + if (aug == augment) { + return true; + } + } + return false; +} + + +const std::vector>& Item::getAugments() +{ + return augments; +} + void Item::decayImbuements(bool infight) { for (auto& imbue : imbuements) { if (imbue->isEquipDecay()) { @@ -1891,7 +1929,6 @@ void Item::decayImbuements(bool infight) { } } - void handleRuneDescription(std::ostringstream& s, const ItemType& it, const Item* item, int32_t& subType) { if (RuneSpell* rune = g_spells->getRuneSpell(it.id)) { int32_t tmpSubType = subType; @@ -2078,103 +2115,7 @@ void handleStatsPercentDescription(std::ostringstream& s, const ItemType& it, bo s << ", "; } - s << getStatName(i) << ' ' << std::showpos << it.abilities->statsPercent[i] << std::noshowpos; - } -} - -void handleAbsorbsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin) { - 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 << "absorb "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "absorb all " << std::showpos << show << std::noshowpos << '%'; - } -} - -void handleAbsorbsFieldsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin) { - int16_t 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 << "absorb "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "absorb all fields " << std::showpos << show << std::noshowpos << '%'; + s << getStatName(i) << ' ' << std::showpos << (it.abilities->statsPercent[i] - 100) << "%" << std::noshowpos; } } @@ -2191,61 +2132,10 @@ void handleMiscDescription(std::ostringstream& s, const ItemType& it, bool& begi } } -void handleReflectPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin) { - int16_t show = it.abilities->reflect[0].percent; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->reflect[i].percent != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->reflect[i].percent == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "reflect "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->reflect[i].percent << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "reflect all " << std::showpos << show << std::noshowpos << '%'; - } -} - void handleAbilitiesDescription(std::ostringstream& s, const ItemType& it, bool& begin) { handleSkillsDescription(s, it, begin); handleStatsDescription(s, it, begin); handleStatsPercentDescription(s, it, begin); - handleReflectPercentDescription(s, it, begin); - handleAbsorbsPercentDescription(s, it, begin); - handleAbsorbsFieldsPercentDescription(s, it, begin); handleMiscDescription(s, it, begin); } diff --git a/src/item.h b/src/item.h index 66eb1708..04311f37 100644 --- a/src/item.h +++ b/src/item.h @@ -10,8 +10,8 @@ #include "luascript.h" #include "tools.h" #include "imbuement.h" +#include "augments.h" #include - #include #include @@ -91,8 +91,8 @@ enum AttrTypes_t { ATTR_WRAPID = 36, ATTR_STOREITEM = 37, ATTR_ATTACK_SPEED = 38, - ATTR_REFLECT = 39, - ATTR_BOOST = 40, + ATTR_PLACE_HOLDER = 39, + ATTR_PLACE_HOLDERTOO = 40, ATTR_CLASSIFICATION = 41, ATTR_TIER = 42, ATTR_IMBUESLOTS = 43, @@ -100,7 +100,7 @@ enum AttrTypes_t { ATTR_LOOT_CATEGORY = 45, ATTR_OPENCONTAINER = 46, ATTR_PODIUMOUTFIT = 47, - ATTR_IMBUEMENTS, + ATTR_IMBUEMENTS = 48, }; enum Attr_ReadValue { @@ -255,7 +255,7 @@ class ItemAttributes } void operator()(const int64_t& v) const { - lua_pushnumber(L, v); + lua_pushinteger(L, v); } void operator()(const double& v) const { @@ -353,7 +353,6 @@ class ItemAttributes static int64_t emptyInt; static double emptyDouble; static bool emptyBool; - static Reflect emptyReflect; typedef std::unordered_map CustomAttributeMap; @@ -422,18 +421,6 @@ class ItemAttributes std::vector attributes; uint32_t attributeBits = 0; - std::map reflect; - std::map boostPercent; - - 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 std::string& getStrAttr(itemAttrTypes type) const; void setStrAttr(itemAttrTypes type, std::string_view value); @@ -524,9 +511,9 @@ class ItemAttributes | 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_LOOTCATEGORY | ITEM_ATTRIBUTE_CLASSIFICATION | ITEM_ATTRIBUTE_TIER | ITEM_ATTRIBUTE_REWARDID; + | ITEM_ATTRIBUTE_ATTACK_SPEED | ITEM_ATTRIBUTE_LOOTCATEGORY | ITEM_ATTRIBUTE_REWARDID; const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | ITEM_ATTRIBUTE_WRITER - | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME; + | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME | ITEM_ATTRIBUTE_CLASSIFICATION | ITEM_ATTRIBUTE_TIER; public: static bool isIntAttrType(itemAttrTypes type) { @@ -839,6 +826,7 @@ class Item : virtual public Thing //serialization virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); bool unserializeAttr(PropStream& propStream); + bool unserializeAugments(PropStream& propStream); virtual bool unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream); virtual void serializeAttr(PropWriteStream& propWriteStream) const; @@ -921,6 +909,10 @@ class Item : virtual public Thing return items[id].hitChance; } + uint16_t getEquipSlot() const { + return items[id].equipSlot; + } + LootCategory_t getLootCategoryId() const; void resetLootCategoryId() { removeAttribute(ITEM_ATTRIBUTE_LOOTCATEGORY); } @@ -936,16 +928,6 @@ class Item : virtual public Thing 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; @@ -1119,6 +1101,20 @@ class Item : virtual public Thing bool addImbuement(std::shared_ptr imbuement, bool created = true); bool removeImbuement(std::shared_ptr imbuement, bool decayed = false); std::vector>& getImbuements(); + const std::vector>& getImbuements() const; + + + const bool addAugment(std::string_view augmentName); + const bool addAugment(std::shared_ptr& augment); + + const bool removeAugment(std::string_view name); + const bool removeAugment(std::shared_ptr& augment); + + bool isAugmented() const; + bool hasAugment(std::string_view name) const; + bool hasAugment(const std::shared_ptr& augment) const; + + const std::vector>& getAugments(); protected: Cylinder* parent = nullptr; @@ -1131,8 +1127,8 @@ class Item : virtual public Thing std::unique_ptr attributes; uint16_t imbuementSlots = 0; - std::vector> imbuements; - + std::vector> imbuements{}; + std::vector> augments{}; uint32_t referenceCounter = 0; uint8_t count = 1; // number of stacked items diff --git a/src/items.cpp b/src/items.cpp index 9fff95df..392da582 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -86,75 +86,6 @@ const std::unordered_map ItemParseAttributes {"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}, {"suppressdrunk", ITEM_PARSE_SUPPRESSDRUNK}, {"suppressenergy", ITEM_PARSE_SUPPRESSENERGY}, {"suppressfire", ITEM_PARSE_SUPPRESSFIRE}, @@ -698,12 +629,12 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) } case ITEM_PARSE_CLASSIFICATION: { - it.classification = pugi::cast(valueAttribute.value()); + it.classification = valueAttribute.as_string(); break; } case ITEM_PARSE_TIER: { - it.tier = pugi::cast(valueAttribute.value()); + it.tier = valueAttribute.as_string(); break; } @@ -811,28 +742,40 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) tmpStrValue = asLowerCaseString(valueAttribute.as_string()); if (tmpStrValue == "head") { it.slotPosition |= SLOTP_HEAD; + it.equipSlot = SLOTP_HEAD; } else if (tmpStrValue == "body") { it.slotPosition |= SLOTP_ARMOR; + it.equipSlot = SLOTP_ARMOR; } else if (tmpStrValue == "legs") { it.slotPosition |= SLOTP_LEGS; + it.equipSlot = SLOTP_LEGS; } else if (tmpStrValue == "feet") { it.slotPosition |= SLOTP_FEET; + it.equipSlot = SLOTP_FEET; } else if (tmpStrValue == "backpack") { it.slotPosition |= SLOTP_BACKPACK; + it.equipSlot = SLOTP_BACKPACK; } else if (tmpStrValue == "two-handed") { it.slotPosition |= SLOTP_TWO_HAND; + it.equipSlot = SLOTP_TWO_HAND; } else if (tmpStrValue == "right-hand") { it.slotPosition &= ~SLOTP_LEFT; + it.equipSlot = SLOTP_LEFT; } else if (tmpStrValue == "left-hand") { it.slotPosition &= ~SLOTP_RIGHT; + it.equipSlot = SLOTP_RIGHT; } else if (tmpStrValue == "necklace") { it.slotPosition |= SLOTP_NECKLACE; + it.equipSlot = SLOTP_NECKLACE; } else if (tmpStrValue == "ring") { it.slotPosition |= SLOTP_RING; + it.equipSlot = SLOTP_RING; } else if (tmpStrValue == "ammo") { it.slotPosition |= SLOTP_AMMO; + it.equipSlot = SLOTP_AMMO; } else if (tmpStrValue == "hand") { it.slotPosition |= SLOTP_HAND; + it.equipSlot = SLOTP_HAND; } else { std::cout << "[Warning - Items::parseItemNode] Unknown slotType: " << valueAttribute.as_string() << std::endl; } @@ -1061,358 +1004,6 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } - case ITEM_PARSE_FIELDABSORBPERCENTENERGY: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTFIRE: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTPOISON: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTALL: { - int16_t value = pugi::cast(valueAttribute.value()); - for (auto& i : abilities.absorbPercent) { - i += value; - } - break; - } - - case ITEM_PARSE_ABSORBPERCENTELEMENTS: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - break; - } - - case ITEM_PARSE_ABSORBPERCENTMAGIC: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += value; - break; - } - - case ITEM_PARSE_ABSORBPERCENTENERGY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTFIRE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTPOISON: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTICE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTHOLY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTDEATH: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTLIFEDRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTMANADRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTDROWN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTPHYSICAL: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTHEALING: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTUNDEFINED: { - 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_SUPPRESSDRUNK: { if (valueAttribute.as_bool()) { abilities.conditionSuppressions |= CONDITION_DRUNK; diff --git a/src/items.h b/src/items.h index b048f4cb..b10ed939 100644 --- a/src/items.h +++ b/src/items.h @@ -112,66 +112,6 @@ enum ItemParseAttributes_t { ITEM_PARSE_LIFELEECHAMOUNT, ITEM_PARSE_MANALEECHCHANCE, ITEM_PARSE_MANALEECHAMOUNT, - ITEM_PARSE_FIELDABSORBPERCENTENERGY, - ITEM_PARSE_FIELDABSORBPERCENTFIRE, - ITEM_PARSE_FIELDABSORBPERCENTPOISON, - ITEM_PARSE_ABSORBPERCENTALL, - ITEM_PARSE_ABSORBPERCENTELEMENTS, - ITEM_PARSE_ABSORBPERCENTMAGIC, - ITEM_PARSE_ABSORBPERCENTENERGY, - ITEM_PARSE_ABSORBPERCENTFIRE, - ITEM_PARSE_ABSORBPERCENTPOISON, - ITEM_PARSE_ABSORBPERCENTICE, - ITEM_PARSE_ABSORBPERCENTHOLY, - ITEM_PARSE_ABSORBPERCENTDEATH, - ITEM_PARSE_ABSORBPERCENTLIFEDRAIN, - ITEM_PARSE_ABSORBPERCENTMANADRAIN, - ITEM_PARSE_ABSORBPERCENTDROWN, - ITEM_PARSE_ABSORBPERCENTPHYSICAL, - ITEM_PARSE_ABSORBPERCENTHEALING, - ITEM_PARSE_ABSORBPERCENTUNDEFINED, - 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_SUPPRESSDRUNK, ITEM_PARSE_SUPPRESSENERGY, ITEM_PARSE_SUPPRESSFIRE, @@ -223,14 +163,6 @@ struct Abilities { int32_t speed = 0; - // field damage abilities modifiers - std::array fieldAbsorbPercent = {0}; - - //damage abilities modifiers - std::array absorbPercent = {0}; - std::array reflect; - int16_t boostPercent[COMBAT_COUNT] = { 0 }; - //elemental damage uint16_t elementDamage = 0; CombatType_t elementType = COMBAT_NONE; @@ -386,6 +318,7 @@ class ItemType uint16_t transformDeEquipTo = 0; uint16_t maxItems = 8; uint16_t slotPosition = SLOTP_HAND; + uint16_t equipSlot = SLOTP_HAND; uint16_t speed = 0; uint16_t wareId = 0; uint16_t imbuementslots = 0; diff --git a/src/luascript.cpp b/src/luascript.cpp index 5d009f91..0326be25 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -28,6 +28,7 @@ #include "script.h" #include "weapons.h" #include "luavariant.h" +#include "augments.h" extern Chat* g_chat; extern Game g_game; @@ -822,14 +823,6 @@ 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; @@ -908,11 +901,11 @@ void LuaScriptInterface::pushBoolean(lua_State* L, bool value) void LuaScriptInterface::pushCombatDamage(lua_State* L, const CombatDamage& damage) { - lua_pushnumber(L, damage.primary.value); - lua_pushnumber(L, damage.primary.type); - lua_pushnumber(L, damage.secondary.value); - lua_pushnumber(L, damage.secondary.type); - lua_pushnumber(L, damage.origin); + lua_pushinteger(L, damage.primary.value); + lua_pushinteger(L, damage.primary.type); + lua_pushinteger(L, damage.secondary.value); + lua_pushinteger(L, damage.secondary.type); + lua_pushinteger(L, damage.origin); } void LuaScriptInterface::pushInstantSpell(lua_State* L, const InstantSpell& spell) @@ -954,7 +947,7 @@ void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, in setField(L, "x", position.x); setField(L, "y", position.y); - setField(L, "z", position.z); + setField(L, "z", static_cast(position.z)); setField(L, "stackpos", stackpos); setMetatable(L, -1, "Position"); @@ -965,11 +958,11 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) lua_createtable(L, 0, 8); setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); - setField(L, "lookHead", outfit.lookHead); - setField(L, "lookBody", outfit.lookBody); - setField(L, "lookLegs", outfit.lookLegs); - setField(L, "lookFeet", outfit.lookFeet); - setField(L, "lookAddons", outfit.lookAddons); + setField(L, "lookHead", static_cast(outfit.lookHead)); + setField(L, "lookBody", static_cast(outfit.lookBody)); + setField(L, "lookLegs", static_cast(outfit.lookLegs)); + setField(L, "lookFeet", static_cast(outfit.lookFeet)); + setField(L, "lookAddons", static_cast(outfit.lookAddons)); setField(L, "lookMount", outfit.lookMount); } @@ -989,7 +982,7 @@ void LuaScriptInterface::pushMount(lua_State* L, const Mount* mount) setField(L, "name", mount->name); setField(L, "speed", mount->speed); setField(L, "clientId", mount->clientId); - setField(L, "id", mount->id); + setField(L, "id", static_cast(mount->id)); setField(L, "premium", mount->premium); } @@ -1015,13 +1008,6 @@ void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lo } } -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); } @@ -1508,7 +1494,13 @@ void LuaScriptInterface::registerFunctions() registerEnum(CREATURETYPE_MONSTER) registerEnum(CREATURETYPE_NPC) registerEnum(CREATURETYPE_SUMMON_OWN) - registerEnum(CREATURETYPE_SUMMON_OTHERS) + registerEnum(CREATURETYPE_SUMMON_HOSTILE) + registerEnum(CREATURETYPE_SUMMON_GUILD) + registerEnum(CREATURETYPE_SUMMON_PARTY) + registerEnum(CREATURETYPE_BOSS) + registerEnum(CREATURETYPE_SUMMON_HOSTILE) + registerEnum(CREATURETYPE_ATTACKABLE) + registerEnum(CREATURETYPE_SUMMON_ALL) registerEnum(CLIENTOS_LINUX) registerEnum(CLIENTOS_WINDOWS) @@ -2001,6 +1993,32 @@ void LuaScriptInterface::registerFunctions() registerEnum(DiscordMessageType::MESSAGE_LOG); registerEnum(DiscordMessageType::MESSAGE_INFO); + // Attack Modifiers + registerEnum(ATTACK_MODIFIER_FIRST) + registerEnum(ATTACK_MODIFIER_NONE) + registerEnum(ATTACK_MODIFIER_LIFESTEAL) + registerEnum(ATTACK_MODIFIER_MANASTEAL) + registerEnum(ATTACK_MODIFIER_STAMINASTEAL) + registerEnum(ATTACK_MODIFIER_SOULSTEAL) + registerEnum(ATTACK_MODIFIER_CRITICAL) + registerEnum(ATTACK_MODIFIER_PIERCING) + registerEnum(ATTACK_MODIFIER_CONVERSION) + registerEnum(ATTACK_MODIFIER_LAST) + + // Defense Modifiers + registerEnum(DEFENSE_MODIFIER_FIRST) + registerEnum(DEFENSE_MODIFIER_NONE) + registerEnum(DEFENSE_MODIFIER_ABSORB) + registerEnum(DEFENSE_MODIFIER_RESTORE) + registerEnum(DEFENSE_MODIFIER_REPLENISH) + registerEnum(DEFENSE_MODIFIER_REVIVE) + registerEnum(DEFENSE_MODIFIER_REFLECT) + registerEnum(DEFENSE_MODIFIER_DEFLECT) + registerEnum(DEFENSE_MODIFIER_RICOCHET) + registerEnum(DEFENSE_MODIFIER_RESIST) + registerEnum(DEFENSE_MODIFIER_RESIST) + registerEnum(DEFENSE_MODIFIER_LAST) + // _G registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); registerGlobalBoolean("VIRTUAL_PARENT", true); @@ -2094,6 +2112,8 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::PLAYER_CONSOLE_LOGS); registerEnumIn("configKeys", ConfigManager::BED_OFFLINE_TRAINING); registerEnumIn("configKeys", ConfigManager::HOUSE_DOOR_SHOW_PRICE); + registerEnumIn("configKeys", ConfigManager::AUGMENT_SLOT_PROTECTION); + registerEnumIn("configKeys", ConfigManager::AUGMENT_STAMINA_RULE); registerEnumIn("configKeys", ConfigManager::REWARD_BASE_RATE); registerEnumIn("configKeys", ConfigManager::REWARD_RATE_DAMAGE_DONE); @@ -2352,11 +2372,6 @@ 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); registerMethod("Item", "getImbuementSlots", LuaScriptInterface::luaItemGetImbuementSlots); registerMethod("Item", "getFreeImbuementSlots", LuaScriptInterface::luaItemGetFreeImbuementSlots); @@ -2369,6 +2384,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "removeImbuement", LuaScriptInterface::luaItemRemoveImbuement); registerMethod("Item", "getImbuements", LuaScriptInterface::luaItemGetImbuements); + registerMethod("Item", "addAugment", LuaScriptInterface::luaItemAddAugment); + registerMethod("Item", "removeAugment", LuaScriptInterface::luaItemRemoveAugment); + registerMethod("Item", "isAugmented", LuaScriptInterface::luaItemIsAugmented); + registerMethod("Item", "hasAugment", LuaScriptInterface::luaItemHasAugment); + registerMethod("Item", "getAugments", LuaScriptInterface::luaItemGetAugments); + // Imbuement registerClass("Imbuement", "", LuaScriptInterface::luaImbuementCreate); registerMetaMethod("Imbuement", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2387,6 +2408,26 @@ void LuaScriptInterface::registerFunctions() registerMethod("Imbuement", "isEquipDecayed", LuaScriptInterface::luaImbuementIsEquipDecay); registerMethod("Imbuement", "isInfightDecayed", LuaScriptInterface::luaImbuementIsInfightDecay); + // DamageModifer + registerClass("DamageModifier", "", LuaScriptInterface::luaDamageModifierCreate); + registerMetaMethod("DamageModifier", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMethod("DamageModifier", "setValue", LuaScriptInterface::luaDamageModifierSetValue); + registerMethod("DamageModifier", "setRateFactor", LuaScriptInterface::luaDamageModifierSetRateFactor); + registerMethod("DamageModifier", "setCombatFilter", LuaScriptInterface::luaDamageModifierSetCombatFilter); + registerMethod("DamageModifier", "setOriginFilter", LuaScriptInterface::luaDamageModifierSetOriginFilter); + + // Augment + registerClass("Augment", "", LuaScriptInterface::luaAugmentCreate); + registerMetaMethod("Augment", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMethod("Augment", "setName", LuaScriptInterface::luaAugmentSetName); + registerMethod("Augment", "setDescription", LuaScriptInterface::luaAugmentSetDescription); + registerMethod("Augment", "getName", LuaScriptInterface::luaAugmentGetName); + registerMethod("Augment", "getDescription", LuaScriptInterface::luaAugmentGetDescription); + registerMethod("Augment", "addDamageModifier", LuaScriptInterface::luaAugmentAddDamageModifier); + registerMethod("Augment", "removeDamageModifier", LuaScriptInterface::luaAugmentRemoveDamageModifier); + registerMethod("Augment", "getAttackModifiers", LuaScriptInterface::luaAugmentGetDefenseModifiers); + registerMethod("Augment", "getDefenseModifiers", LuaScriptInterface::luaAugmentGetDefenseModifiers); + // Container registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); registerMetaMethod("Container", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2676,6 +2717,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "sendCreatureSquare", LuaScriptInterface::luaPlayerSendCreatureSquare); + registerMethod("Player", "addAugment", LuaScriptInterface::luaPlayerAddAugment); + registerMethod("Player", "removeAugment", LuaScriptInterface::luaPlayerRemoveAugment); + registerMethod("Player", "isAugmented", LuaScriptInterface::luaPlayerIsAugmented); + registerMethod("Player", "hasAugment", LuaScriptInterface::luaPlayerHasAugment); + registerMethod("Player", "getAugments", LuaScriptInterface::luaPlayerGetAugments); + // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2965,6 +3012,7 @@ void LuaScriptInterface::registerFunctions() registerMetaMethod("MonsterType", "__eq", LuaScriptInterface::luaUserdataCompare); registerMethod("MonsterType", "isAttackable", LuaScriptInterface::luaMonsterTypeIsAttackable); + registerMethod("MonsterType", "isRewardBoss", LuaScriptInterface::luaMonsterTypeIsRewardBoss); registerMethod("MonsterType", "isChallengeable", LuaScriptInterface::luaMonsterTypeIsChallengeable); registerMethod("MonsterType", "isConvinceable", LuaScriptInterface::luaMonsterTypeIsConvinceable); registerMethod("MonsterType", "isSummonable", LuaScriptInterface::luaMonsterTypeIsSummonable); @@ -3325,30 +3373,30 @@ void LuaScriptInterface::registerClass(const std::string& className, const std:: lua_setfield(luaState, metatable, "__index"); // className.metatable['h'] = hash - lua_pushnumber(luaState, std::hash()(className)); + lua_pushinteger(luaState, std::hash()(className)); lua_rawseti(luaState, metatable, 'h'); // className.metatable['p'] = parents - lua_pushnumber(luaState, parents); + lua_pushinteger(luaState, parents); lua_rawseti(luaState, metatable, 'p'); // className.metatable['t'] = type if (className == "Item") { - lua_pushnumber(luaState, LuaData_Item); + lua_pushinteger(luaState, LuaData_Item); } else if (className == "Container") { - lua_pushnumber(luaState, LuaData_Container); + lua_pushinteger(luaState, LuaData_Container); } else if (className == "Teleport") { - lua_pushnumber(luaState, LuaData_Teleport); + lua_pushinteger(luaState, LuaData_Teleport); } else if (className == "Player") { - lua_pushnumber(luaState, LuaData_Player); + lua_pushinteger(luaState, LuaData_Player); } else if (className == "Monster") { - lua_pushnumber(luaState, LuaData_Monster); + lua_pushinteger(luaState, LuaData_Monster); } else if (className == "Npc") { - lua_pushnumber(luaState, LuaData_Npc); + lua_pushinteger(luaState, LuaData_Npc); } else if (className == "Tile") { - lua_pushnumber(luaState, LuaData_Tile); + lua_pushinteger(luaState, LuaData_Tile); } else { - lua_pushnumber(luaState, LuaData_Unknown); + lua_pushinteger(luaState, LuaData_Unknown); } lua_rawseti(luaState, metatable, 't'); @@ -3405,7 +3453,7 @@ void LuaScriptInterface::registerVariable(const std::string& tableName, const st void LuaScriptInterface::registerGlobalVariable(const std::string& name, lua_Number value) { // _G[name] = value - lua_pushnumber(luaState, value); + lua_pushinteger(luaState, value); lua_setglobal(luaState, name.c_str()); } @@ -3477,7 +3525,7 @@ int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) if (--itemCount == 0) { if (newItem->getParent()) { uint32_t uid = getScriptEnv()->addThing(newItem); - lua_pushnumber(L, uid); + lua_pushinteger(L, uid); return 1; } else { //stackable item stacked with existing object, newItem will be released @@ -3502,7 +3550,7 @@ int LuaScriptInterface::luaGetWorldTime(lua_State* L) { //getWorldTime() int16_t time = g_game.getWorldTime(); - lua_pushnumber(L, time); + lua_pushinteger(L, time); return 1; } @@ -3510,8 +3558,8 @@ int LuaScriptInterface::luaGetWorldLight(lua_State* L) { //getWorldLight() LightInfo lightInfo = g_game.getWorldLightInfo(); - lua_pushnumber(L, lightInfo.level); - lua_pushnumber(L, lightInfo.color); + lua_pushinteger(L, lightInfo.level); + lua_pushinteger(L, lightInfo.color); return 2; } @@ -3535,7 +3583,7 @@ int LuaScriptInterface::luaGetWorldUpTime(lua_State* L) { //getWorldUpTime() uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; - lua_pushnumber(L, uptime); + lua_pushinteger(L, uptime); return 1; } @@ -3609,7 +3657,7 @@ int LuaScriptInterface::luaCreateCombatArea(lua_State* L) } area->setupArea(vecArea, rowsArea); - lua_pushnumber(L, areaId); + lua_pushinteger(L, areaId); return 1; } @@ -3815,7 +3863,7 @@ int LuaScriptInterface::luaGetDepotId(lua_State* L) return 1; } - lua_pushnumber(L, depotLocker->getDepotId()); + lua_pushinteger(L, depotLocker->getDepotId()); return 1; } @@ -3931,7 +3979,7 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) eventDesc.eventId = g_scheduler.addEvent(createSchedulerTask(delay, [=]() { g_luaEnvironment.executeTimerEvent(lastTimerEventId); })); g_luaEnvironment.timerEvents.emplace(lastTimerEventId, std::move(eventDesc)); - lua_pushnumber(L, lastTimerEventId++); + lua_pushinteger(L, lastTimerEventId++); return 1; } @@ -3971,7 +4019,7 @@ int LuaScriptInterface::luaSaveServer(lua_State* L) int LuaScriptInterface::luaCleanMap(lua_State* L) { - lua_pushnumber(L, g_game.map.clean()); + lua_pushinteger(L, g_game.map.clean()); return 1; } @@ -4128,7 +4176,7 @@ int LuaScriptInterface::luaConfigManagerGetString(lua_State* L) pushString(L, g_config.getString(getNumber(L, -1))); return 1; } - +// may need a new one of these for ints, same for registerGlobal methods int LuaScriptInterface::luaConfigManagerGetNumber(lua_State* L) { lua_pushnumber(L, g_config.getNumber(getNumber(L, -1))); @@ -4192,7 +4240,7 @@ int LuaScriptInterface::luaDatabaseAsyncExecute(lua_State* L) int LuaScriptInterface::luaDatabaseStoreQuery(lua_State* L) { if (DBResult_ptr res = Database::getInstance().storeQuery(getString(L, -1))) { - lua_pushnumber(L, ScriptEnvironment::addResult(res)); + lua_pushinteger(L, ScriptEnvironment::addResult(res)); } else { pushBoolean(L, false); } @@ -4218,7 +4266,7 @@ int LuaScriptInterface::luaDatabaseAsyncStoreQuery(lua_State* L) lua_rawgeti(luaState, LUA_REGISTRYINDEX, ref); if (result) { - lua_pushnumber(luaState, ScriptEnvironment::addResult(result)); + lua_pushinteger(luaState, ScriptEnvironment::addResult(result)); } else { pushBoolean(luaState, false); } @@ -4248,7 +4296,7 @@ int LuaScriptInterface::luaDatabaseEscapeBlob(lua_State* L) int LuaScriptInterface::luaDatabaseLastInsertId(lua_State* L) { - lua_pushnumber(L, Database::getInstance().getLastInsertId()); + lua_pushinteger(L, Database::getInstance().getLastInsertId()); return 1; } @@ -4276,7 +4324,7 @@ int LuaScriptInterface::luaResultGetNumber(lua_State* L) } const std::string& s = getString(L, 2); - lua_pushnumber(L, res->getNumber(s)); + lua_pushinteger(L, res->getNumber(s)); return 1; } @@ -4372,7 +4420,7 @@ int LuaScriptInterface::luaRawGetMetatable(lua_State* L) int LuaScriptInterface::luaSystemTime(lua_State* L) { // os.mtime() - lua_pushnumber(L, OTSYS_TIME()); + lua_pushinteger(L, OTSYS_TIME()); return 1; } @@ -4585,7 +4633,7 @@ int LuaScriptInterface::luaGameGetExperienceStage(lua_State* L) { // Game.getExperienceStage(level) uint32_t level = getNumber(L, 1); - lua_pushnumber(L, g_config.getExperienceStage(level)); + lua_pushinteger(L, g_config.getExperienceStage(level)); return 1; } @@ -4594,9 +4642,9 @@ int LuaScriptInterface::luaGameGetExperienceForLevel(lua_State* L) // Game.getExperienceForLevel(level) const uint32_t level = getNumber(L, 1); if (level == 0) { - lua_pushnumber(L, 0); + lua_pushinteger(L, 0); } else { - lua_pushnumber(L, Player::getExpForLevel(level)); + lua_pushinteger(L, Player::getExpForLevel(level)); } return 1; } @@ -4604,21 +4652,21 @@ int LuaScriptInterface::luaGameGetExperienceForLevel(lua_State* L) int LuaScriptInterface::luaGameGetMonsterCount(lua_State* L) { // Game.getMonsterCount() - lua_pushnumber(L, g_game.getMonstersOnline()); + lua_pushinteger(L, g_game.getMonstersOnline()); return 1; } int LuaScriptInterface::luaGameGetPlayerCount(lua_State* L) { // Game.getPlayerCount() - lua_pushnumber(L, g_game.getPlayersOnline()); + lua_pushinteger(L, g_game.getPlayersOnline()); return 1; } int LuaScriptInterface::luaGameGetNpcCount(lua_State* L) { // Game.getNpcCount() - lua_pushnumber(L, g_game.getNpcsOnline()); + lua_pushinteger(L, g_game.getNpcsOnline()); return 1; } @@ -4739,7 +4787,7 @@ int LuaScriptInterface::luaGameGetVocations(lua_State* L) int LuaScriptInterface::luaGameGetGameState(lua_State* L) { // Game.getGameState() - lua_pushnumber(L, g_game.getGameState()); + lua_pushinteger(L, g_game.getGameState()); return 1; } @@ -4755,7 +4803,7 @@ int LuaScriptInterface::luaGameSetGameState(lua_State* L) int LuaScriptInterface::luaGameGetWorldType(lua_State* L) { // Game.getWorldType() - lua_pushnumber(L, g_game.getWorldType()); + lua_pushinteger(L, g_game.getWorldType()); return 1; } @@ -4779,7 +4827,7 @@ int LuaScriptInterface::luaGameGetReturnMessage(lua_State* L) int LuaScriptInterface::luaGameGetItemAttributeByName(lua_State* L) { // Game.getItemAttributeByName(name) - lua_pushnumber(L, stringToItemAttribute(getString(L, 1))); + lua_pushinteger(L, stringToItemAttribute(getString(L, 1))); return 1; } @@ -4997,18 +5045,18 @@ int LuaScriptInterface::luaGameStartRaid(lua_State* L) Raid* raid = g_game.raids.getRaidByName(raidName); if (!raid || !raid->isLoaded()) { - lua_pushnumber(L, RETURNVALUE_NOSUCHRAIDEXISTS); + lua_pushinteger(L, RETURNVALUE_NOSUCHRAIDEXISTS); return 1; } if (g_game.raids.getRunning()) { - lua_pushnumber(L, RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING); + lua_pushinteger(L, RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING); return 1; } g_game.raids.setRunning(raid); raid->startRaid(); - lua_pushnumber(L, RETURNVALUE_NOERROR); + lua_pushinteger(L, RETURNVALUE_NOERROR); return 1; } @@ -5018,7 +5066,7 @@ int LuaScriptInterface::luaGameGetClientVersion(lua_State* L) lua_createtable(L, 0, 3); setField(L, "min", CLIENT_VERSION_MIN); setField(L, "max", CLIENT_VERSION_MAX); - setField(L, "string", CLIENT_VERSION_STR); + setField(L, "string", static_cast(CLIENT_VERSION_STR)); return 1; } @@ -5043,7 +5091,7 @@ int LuaScriptInterface::luaGameGetAccountStorageValue(lua_State* L) uint32_t accountId = getNumber(L, 1); uint32_t key = getNumber(L, 2); - lua_pushnumber(L, g_game.getAccountStorageValue(accountId, key)); + lua_pushinteger(L, g_game.getAccountStorageValue(accountId, key)); return 1; } @@ -5178,9 +5226,9 @@ int LuaScriptInterface::luaVariantGetNumber(lua_State* L) // Variant:getNumber() const LuaVariant& variant = getVariant(L, 1); if (variant.isNumber()) { - lua_pushnumber(L, variant.getNumber()); + lua_pushinteger(L, variant.getNumber()); } else { - lua_pushnumber(L, 0); + lua_pushinteger(L, 0); } return 1; } @@ -5284,7 +5332,7 @@ 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( + lua_pushinteger(L, std::max( std::max( std::abs(Position::getDistanceX(position, positionEx)), std::abs(Position::getDistanceY(position, positionEx)) @@ -5455,7 +5503,7 @@ int LuaScriptInterface::luaTileGetThingCount(lua_State* L) // tile:getThingCount() Tile* tile = getUserdata(L, 1); if (tile) { - lua_pushnumber(L, tile->getThingCount()); + lua_pushinteger(L, tile->getThingCount()); } else { lua_pushnil(L); } @@ -5687,7 +5735,7 @@ int LuaScriptInterface::luaTileGetItemCountById(lua_State* L) } } - lua_pushnumber(L, tile->getItemTypeCount(itemId, subType)); + lua_pushinteger(L, tile->getItemTypeCount(itemId, subType)); return 1; } @@ -5816,7 +5864,7 @@ int LuaScriptInterface::luaTileGetItemCount(lua_State* L) return 1; } - lua_pushnumber(L, tile->getItemCount()); + lua_pushinteger(L, tile->getItemCount()); return 1; } @@ -5825,7 +5873,7 @@ int LuaScriptInterface::luaTileGetDownItemCount(lua_State* L) // tile:getDownItemCount() Tile* tile = getUserdata(L, 1); if (tile) { - lua_pushnumber(L, tile->getDownItemCount()); + lua_pushinteger(L, tile->getDownItemCount()); } else { lua_pushnil(L); } @@ -5841,7 +5889,7 @@ int LuaScriptInterface::luaTileGetTopItemCount(lua_State* L) return 1; } - lua_pushnumber(L, tile->getTopItemCount()); + lua_pushinteger(L, tile->getTopItemCount()); return 1; } @@ -5880,7 +5928,7 @@ int LuaScriptInterface::luaTileGetCreatureCount(lua_State* L) return 1; } - lua_pushnumber(L, tile->getCreatureCount()); + lua_pushinteger(L, tile->getCreatureCount()); return 1; } @@ -5920,7 +5968,7 @@ int LuaScriptInterface::luaTileGetThingIndex(lua_State* L) Thing* thing = getThing(L, 2); if (thing) { - lua_pushnumber(L, tile->getThingIndex(thing)); + lua_pushinteger(L, tile->getThingIndex(thing)); } else { lua_pushnil(L); } @@ -5949,19 +5997,24 @@ int LuaScriptInterface::luaTileQueryAdd(lua_State* L) return 1; } - if (Item* item = getUserdata(L, 2)) { - uint32_t flags = getNumber(L, 3, 0); - lua_pushnumber(L, tile->queryAdd(*item, flags)); + Thing* thing = getThing(L, 2); + if (!thing) { + lua_pushnil(L); return 1; } - if (Creature* creature = getUserdata(L, 2)) { + if (Creature* creature = thing->getCreature()) { uint32_t flags = getNumber(L, 3, 0); - lua_pushnumber(L, tile->queryAdd(*creature, flags)); + lua_pushinteger(L, tile->queryAdd(*creature, flags)); return 1; } - + if (Item* item = thing->getItem()) { + uint32_t flags = getNumber(L, 3, 0); + lua_pushinteger(L, tile->queryAdd(*item, flags)); + return 1; + } + lua_pushnil(L); return 1; } @@ -6033,7 +6086,7 @@ int LuaScriptInterface::luaTileAddItemEx(lua_State* L) if (ret == RETURNVALUE_NOERROR) { ScriptEnvironment::removeTempItem(item); } - lua_pushnumber(L, ret); + lua_pushinteger(L, ret); return 1; } @@ -6079,7 +6132,7 @@ int LuaScriptInterface::luaNetworkMessageGetByte(lua_State* L) // networkMessage:getByte() NetworkMessage* message = getUserdata(L, 1); if (message) { - lua_pushnumber(L, message->getByte()); + lua_pushinteger(L, message->getByte()); } else { lua_pushnil(L); } @@ -6091,7 +6144,7 @@ int LuaScriptInterface::luaNetworkMessageGetU16(lua_State* L) // networkMessage:getU16() NetworkMessage* message = getUserdata(L, 1); if (message) { - lua_pushnumber(L, message->get()); + lua_pushinteger(L, message->get()); } else { lua_pushnil(L); } @@ -6103,7 +6156,7 @@ int LuaScriptInterface::luaNetworkMessageGetU32(lua_State* L) // networkMessage:getU32() NetworkMessage* message = getUserdata(L, 1); if (message) { - lua_pushnumber(L, message->get()); + lua_pushinteger(L, message->get()); } else { lua_pushnil(L); } @@ -6115,7 +6168,7 @@ int LuaScriptInterface::luaNetworkMessageGetU64(lua_State* L) // networkMessage:getU64() NetworkMessage* message = getUserdata(L, 1); if (message) { - lua_pushnumber(L, message->get()); + lua_pushinteger(L, message->get()); } else { lua_pushnil(L); } @@ -6319,7 +6372,7 @@ int LuaScriptInterface::luaNetworkMessageTell(lua_State* L) // networkMessage:tell() NetworkMessage* message = getUserdata(L, 1); if (message) { - lua_pushnumber(L, message->getBufferPosition() - message->INITIAL_BUFFER_POSITION); + lua_pushinteger(L, message->getBufferPosition() - message->INITIAL_BUFFER_POSITION); } else { lua_pushnil(L); } @@ -6331,7 +6384,7 @@ int LuaScriptInterface::luaNetworkMessageLength(lua_State* L) // networkMessage:len() NetworkMessage* message = getUserdata(L, 1); if (message) { - lua_pushnumber(L, message->getLength()); + lua_pushinteger(L, message->getLength()); } else { lua_pushnil(L); } @@ -6400,7 +6453,7 @@ int LuaScriptInterface::luaModalWindowGetId(lua_State* L) // modalWindow:getId() ModalWindow* window = getUserdata(L, 1); if (window) { - lua_pushnumber(L, window->id); + lua_pushinteger(L, window->id); } else { lua_pushnil(L); } @@ -6464,7 +6517,7 @@ int LuaScriptInterface::luaModalWindowGetButtonCount(lua_State* L) // modalWindow:getButtonCount() ModalWindow* window = getUserdata(L, 1); if (window) { - lua_pushnumber(L, window->buttons.size()); + lua_pushinteger(L, window->buttons.size()); } else { lua_pushnil(L); } @@ -6476,7 +6529,7 @@ int LuaScriptInterface::luaModalWindowGetChoiceCount(lua_State* L) // modalWindow:getChoiceCount() ModalWindow* window = getUserdata(L, 1); if (window) { - lua_pushnumber(L, window->choices.size()); + lua_pushinteger(L, window->choices.size()); } else { lua_pushnil(L); } @@ -6518,7 +6571,7 @@ int LuaScriptInterface::luaModalWindowGetDefaultEnterButton(lua_State* L) // modalWindow:getDefaultEnterButton() ModalWindow* window = getUserdata(L, 1); if (window) { - lua_pushnumber(L, window->defaultEnterButton); + lua_pushinteger(L, window->defaultEnterButton); } else { lua_pushnil(L); } @@ -6543,7 +6596,7 @@ int LuaScriptInterface::luaModalWindowGetDefaultEscapeButton(lua_State* L) // modalWindow:getDefaultEscapeButton() ModalWindow* window = getUserdata(L, 1); if (window) { - lua_pushnumber(L, window->defaultEscapeButton); + lua_pushinteger(L, window->defaultEscapeButton); } else { lua_pushnil(L); } @@ -6675,7 +6728,7 @@ int LuaScriptInterface::luaItemGetId(lua_State* L) // item:getId() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getID()); + lua_pushinteger(L, item->getID()); } else { lua_pushnil(L); } @@ -6775,7 +6828,7 @@ int LuaScriptInterface::luaItemGetUniqueId(lua_State* L) if (uniqueId == 0) { uniqueId = getScriptEnv()->addThing(item); } - lua_pushnumber(L, uniqueId); + lua_pushinteger(L, uniqueId); } else { lua_pushnil(L); } @@ -6787,7 +6840,7 @@ int LuaScriptInterface::luaItemGetActionId(lua_State* L) // item:getActionId() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getActionId()); + lua_pushinteger(L, item->getActionId()); } else { lua_pushnil(L); } @@ -6813,7 +6866,7 @@ int LuaScriptInterface::luaItemGetCount(lua_State* L) // item:getCount() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getItemCount()); + lua_pushinteger(L, item->getItemCount()); } else { lua_pushnil(L); } @@ -6825,7 +6878,7 @@ int LuaScriptInterface::luaItemGetCharges(lua_State* L) // item:getCharges() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getCharges()); + lua_pushinteger(L, item->getCharges()); } else { lua_pushnil(L); } @@ -6837,7 +6890,7 @@ int LuaScriptInterface::luaItemGetFluidType(lua_State* L) // item:getFluidType() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getFluidType()); + lua_pushinteger(L, item->getFluidType()); } else { lua_pushnil(L); } @@ -6849,7 +6902,7 @@ int LuaScriptInterface::luaItemGetWeight(lua_State* L) // item:getWeight() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getWeight()); + lua_pushinteger(L, item->getWeight()); } else { lua_pushnil(L); } @@ -6861,7 +6914,7 @@ int LuaScriptInterface::luaItemGetWorth(lua_State* L) // item:getWorth() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getWorth()); + lua_pushinteger(L, item->getWorth()); } else { lua_pushnil(L); } @@ -6873,7 +6926,7 @@ int LuaScriptInterface::luaItemGetSubType(lua_State* L) // item:getSubType() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getSubType()); + lua_pushinteger(L, item->getSubType()); } else { lua_pushnil(L); } @@ -6988,7 +7041,7 @@ int LuaScriptInterface::luaItemGetAttribute(lua_State* L) } if (ItemAttributes::isIntAttrType(attribute)) { - lua_pushnumber(L, item->getIntAttr(attribute)); + lua_pushinteger(L, item->getIntAttr(attribute)); } else if (ItemAttributes::isStrAttrType(attribute)) { pushString(L, item->getStrAttr(attribute)); } else { @@ -7237,6 +7290,11 @@ int LuaScriptInterface::luaItemTransform(lua_State* L) return 1; } + if (item->isAugmented() || item->hasImbuements()) { + lua_pushboolean(L, false); + return 1; + } + uint16_t itemId; if (isNumber(L, 2)) { itemId = getNumber(L, 2); @@ -7281,6 +7339,12 @@ int LuaScriptInterface::luaItemDecay(lua_State* L) // item:decay(decayId) Item* item = getUserdata(L, 1); if (item) { + + if (item->isAugmented() || item->hasImbuements()) { + lua_pushboolean(L, false); + return 1; + } + if (isNumber(L, 2)) { item->setDecayTo(getNumber(L, 2)); } @@ -7368,67 +7432,13 @@ int LuaScriptInterface::luaItemIsStoreItem(lua_State* L) return 1; } -int LuaScriptInterface::luaItemSetReflect(lua_State* L) -{ - // item:setReflect(combatType, reflect) - Item* item = getUserdata(L, 1); - if (!item) { - lua_pushnil(L); - return 1; - } - - 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::luaItemSetBoostPercent(lua_State* L) -{ - // 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::luaItemGetBoostPercent(lua_State* L) -{ - // 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; -} - int LuaScriptInterface::luaItemGetImbuementSlots(lua_State* L) { // item:getImbuementSlots() -- returns how many total slots Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getImbuementSlots()); + lua_pushinteger(L, item->getImbuementSlots()); } else { lua_pushnil(L); } @@ -7441,7 +7451,7 @@ int LuaScriptInterface::luaItemGetFreeImbuementSlots(lua_State* L) Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getFreeImbuementSlots()); + lua_pushinteger(L, item->getFreeImbuementSlots()); } else { lua_pushnil(L); } @@ -7585,6 +7595,122 @@ int LuaScriptInterface::luaItemGetImbuements(lua_State* L) return 1; } +int LuaScriptInterface::luaItemAddAugment(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (item->isStackable() || item->canDecay() || !item->canTransform() || item->getCharges() || !item->hasProperty(CONST_PROP_MOVEABLE)) { + lua_pushboolean(L, false); + return 1; + } + + if (isString(L, 2)) { + std::cout << getString(L, 2) << " \n"; + if (auto augment = Augments::GetAugment(getString(L, 2))) { + lua_pushboolean(L, item->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Item::addAugment() argument not found as any name in augments loaded on startup! \n"); + } + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, item->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Item::addAugment() invalid userdata passed as argument! \n"); + } + } else { + reportError(__FUNCTION__, "Item::addAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemRemoveAugment(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + auto name = getString(L, 2); + lua_pushboolean(L, item->removeAugment(name)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, item->removeAugment(augment)); + } else { + reportError(__FUNCTION__, "Item::removeAugment() invalid userdata type passed as argument! \n"); + lua_pushnil(L); + } + } else { + reportError(__FUNCTION__, "Item::removeAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemIsAugmented(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + lua_pushboolean(L, item->isAugmented()); + return 1; +} + +int LuaScriptInterface::luaItemHasAugment(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + auto name = getString(L, 2); + lua_pushboolean(L, item->hasAugment(name)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, item->hasAugment(augment)); + } else { + reportError(__FUNCTION__, "Item::hasAugment() invalid userdata type passed as argument! \n"); + lua_pushnil(L); + } + } else { + reportError(__FUNCTION__, "Item::hasAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetAugments(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + std::vector> augments = item->getAugments(); + lua_createtable(L, augments.size(), 0); + + int index = 0; + for (std::shared_ptr augment : augments) { + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + // Imbuement int LuaScriptInterface::luaImbuementCreate(lua_State* L) @@ -7604,7 +7730,7 @@ int LuaScriptInterface::luaImbuementGetType(lua_State* L) // imbuement:getType() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, static_cast(imbue->imbuetype)); + lua_pushinteger(L, imbue->imbuetype); } else { lua_pushnil(L); } @@ -7616,7 +7742,7 @@ int LuaScriptInterface::luaImbuementGetValue(lua_State* L) // imbuement:getValue() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, imbue->value); + lua_pushinteger(L, imbue->value); } else { lua_pushnil(L); } @@ -7629,7 +7755,7 @@ int LuaScriptInterface::luaImbuementGetDuration(lua_State* L) // imbuement:getDuration() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, static_cast(imbue->duration)); + lua_pushinteger(L, imbue->duration); } else { lua_pushnil(L); } @@ -7641,7 +7767,7 @@ int LuaScriptInterface::luaImbuementIsSkill(lua_State* L) // imbuement:isSkill() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, imbue->isSkill()); + lua_pushinteger(L, imbue->isSkill()); } else { lua_pushnil(L); } @@ -7653,7 +7779,7 @@ int LuaScriptInterface::luaImbuementIsSpecialSkill(lua_State* L) // imbuement:isSpecialSkill() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, imbue->isSpecialSkill()); + lua_pushinteger(L, imbue->isSpecialSkill()); } else { lua_pushnil(L); } @@ -7665,7 +7791,7 @@ int LuaScriptInterface::luaImbuementIsDamage(lua_State* L) // imbuement:isDamage() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, imbue->isDamage()); + lua_pushinteger(L, imbue->isDamage()); } else { lua_pushnil(L); @@ -7678,7 +7804,7 @@ int LuaScriptInterface::luaImbuementIsResist(lua_State* L) // imbuement:isResist() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, imbue->isResist()); + lua_pushinteger(L, imbue->isResist()); } else { lua_pushnil(L); } @@ -7690,7 +7816,7 @@ int LuaScriptInterface::luaImbuementIsStat(lua_State* L) // imbuement:isStat() std::shared_ptr imbue = getSharedPtr(L, 1); if (imbue) { - lua_pushnumber(L, imbue->isStat()); + lua_pushinteger(L, imbue->isStat()); } else { lua_pushnil(L); } @@ -7773,6 +7899,311 @@ int LuaScriptInterface::luaImbuementIsInfightDecay(lua_State* L) return 1; } +int LuaScriptInterface::luaDamageModifierCreate(lua_State* L) +{ // To-do : DamageModifier(DamageModifier) + // DamageModifier(stance, type, value, percent/flat, chance, combatType, originType, creatureType, race) + auto stance = getNumber(L, 2); + auto modType = getNumber(L, 3); + auto amount = getNumber(L, 4); + auto factor = getNumber(L, 5); + auto chance = getNumber(L, 6); + auto combatType = getNumber(L, 7, COMBAT_NONE); + auto originType = getNumber(L, 8, ORIGIN_NONE); + auto creatureType = getNumber(L, 9, CREATURETYPE_ATTACKABLE); + auto race = getNumber(L, 10, RACE_NONE); + auto creatureName = getString(L, 11); + + // to-do: handle no param defaults and throw error + if (stance && modType && amount && factor) { + pushSharedPtr(L, DamageModifier::makeModifier(stance, modType, amount, factor, chance, combatType, originType, creatureType, race, creatureName)); + setMetatable(L, -1, "DamageModifier"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetValue(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + uint8_t amount = getNumber(L, 2); + if (amount) { + modifier->setValue(amount); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetRateFactor(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + uint8_t factor = getNumber(L, 2); + if (factor >= 0) { + modifier->setFactor(factor); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetCombatFilter(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + CombatType_t combatType = getNumber(L, 2); + if (combatType >= 0) { + modifier->setCombatType(combatType); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetOriginFilter(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + CombatOrigin origin = getNumber(L, 2); + if (origin >= 0) { + modifier->setOriginType(origin); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaAugmentCreate(lua_State* L) +{ // To-do : Augment(augment) and Augment(name) <-- where name is looked up from global collection. + // Augment(name, description, modifier or table_of_modifiers) + + if (isString(L, 2)) { + auto name = getString(L, 2); + auto augment = Augments::GetAugment(name); + + if (augment) { + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + return 1; // return early here because we found a global augment with this name + } + + auto description = getString(L, 3); + if (isUserdata(L, 4)) { + auto modifier = getSharedPtr(L, 4); + if (modifier) { + auto augment = Augment::MakeAugment(name, description); + augment->addModifier(modifier); + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + } else { + reportError(__FUNCTION__, "Invalid Userdata For Modifier Parameter used during Augment Creation \n"); + lua_pushnil(L); + } + } else if (isTable(L, 4)) { + auto list = std::vector>(); + list.reserve(24); + + // Iterate over the table at index 4 + lua_pushnil(L); // First key for lua_next + while (lua_next(L, 4) != 0) { + // Check if the value is userdata and of type DamageModifier + if (isUserdata(L, -1)) { + auto modifier = getSharedPtr(L, -1); + if (modifier) { + list.emplace_back(modifier); + } else { + reportError(__FUNCTION__, "Invalid userdata in table element\n"); + } + } else { + reportError(__FUNCTION__, "Non-userdata found in table element\n"); + } + // Remove the value, keep the key for lua_next + lua_pop(L, 1); + } + + // Create augment with all modifiers + // To-do : Add augments created this particular way to global table + auto augment = Augment::MakeAugment(name, description); + for (auto& modifier : list) { + augment->addModifier(modifier); + } + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + } else { + reportError(__FUNCTION__, "Invalid parameter for Augment Creation\n"); + lua_pushnil(L); + } + } + + + return 1; +} + + +int LuaScriptInterface::luaAugmentSetName(lua_State* L) +{ + // Augment:setName(newName) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto newName = getString(L, 2); + augment->setName(newName); + return 0; +} + +int LuaScriptInterface::luaAugmentSetDescription(lua_State* L) +{ + // Augment:getDescription(newDescription) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto newDescription = getString(L, 2); + augment->setDescription(newDescription); + return 0; +} + +int LuaScriptInterface::luaAugmentGetName(lua_State* L) { + // Augment:getName() + auto augment = getSharedPtr(L, 1); // Get augment object + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::string name = augment->getName(); + pushString(L, name); + return 1; +} + +int LuaScriptInterface::luaAugmentGetDescription(lua_State* L) { + // Augment:getDescription() + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::string description = augment->getDescription(); + pushString(L, description); + return 1; +} + +// To-do : The following methods that return 0 should all be converted to return something (boolean for most). +int LuaScriptInterface::luaAugmentAddDamageModifier(lua_State* L) +{ + // Augment:addDamageModifier(modifier) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto modifier = getSharedPtr(L, 2); + if (!modifier) { + reportError(__FUNCTION__, "Invalid DamageModifier userdata\n"); + return 0; + } + + augment->addModifier(modifier); + return 0; +} + +int LuaScriptInterface::luaAugmentRemoveDamageModifier(lua_State* L) +{ + // Augment:RemoveDamageModifier(modifier) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto modifier = getSharedPtr(L, 2); + if (!modifier) { + reportError(__FUNCTION__, "Invalid DamageModifier userdata\n"); + return 0; + } + + augment->removeModifier(modifier); + return 0; +} + +int LuaScriptInterface::luaAugmentGetAttackModifiers(lua_State* L) { + // Augment:GetAttackModifiers([modType]) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::vector> modifiers; + + if (lua_gettop(L) > 1 && lua_isinteger(L, 2)) { + uint8_t modType = static_cast(lua_tointeger(L, 2)); + modifiers = augment->getAttackModifiers(modType); + } else { + modifiers = augment->getAttackModifiers(); + } + + lua_newtable(L); + int index = 1; + for (const auto& modifier : modifiers) { + pushSharedPtr(L, modifier); + setMetatable(L, -1, "DamageModifier"); + lua_rawseti(L, -2, index++); + } + + return 1; +} + +int LuaScriptInterface::luaAugmentGetDefenseModifiers(lua_State* L) { + // Augment:GetDefenseModifiers([modType]) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::vector> modifiers; + + if (lua_gettop(L) > 1 && lua_isinteger(L, 2)) { + uint8_t modType = static_cast(lua_tointeger(L, 2)); + modifiers = augment->getDefenseModifiers(modType); + } else { + modifiers = augment->getDefenseModifiers(); + } + + lua_newtable(L); + int index = 1; + for (const auto& modifier : modifiers) { + pushSharedPtr(L, modifier); + setMetatable(L, -1, "DamageModifier"); + lua_rawseti(L, -2, index++); + } + + return 1; +} + + // Container int LuaScriptInterface::luaContainerCreate(lua_State* L) { @@ -7794,7 +8225,7 @@ int LuaScriptInterface::luaContainerGetSize(lua_State* L) // container:getSize() Container* container = getUserdata(L, 1); if (container) { - lua_pushnumber(L, container->size()); + lua_pushinteger(L, container->size()); } else { lua_pushnil(L); } @@ -7806,7 +8237,7 @@ int LuaScriptInterface::luaContainerGetCapacity(lua_State* L) // container:getCapacity() Container* container = getUserdata(L, 1); if (container) { - lua_pushnumber(L, container->capacity()); + lua_pushinteger(L, container->capacity()); } else { lua_pushnil(L); } @@ -7831,7 +8262,7 @@ int LuaScriptInterface::luaContainerGetEmptySlots(lua_State* L) } } } - lua_pushnumber(L, slots); + lua_pushinteger(L, slots); return 1; } @@ -7840,7 +8271,7 @@ int LuaScriptInterface::luaContainerGetItemHoldingCount(lua_State* L) // container:getItemHoldingCount() Container* container = getUserdata(L, 1); if (container) { - lua_pushnumber(L, container->getItemHoldingCount()); + lua_pushinteger(L, container->getItemHoldingCount()); } else { lua_pushnil(L); } @@ -7950,7 +8381,7 @@ int LuaScriptInterface::luaContainerAddItem(lua_State* L) } if (hasTable) { - lua_pushnumber(L, i); + lua_pushinteger(L, i); pushUserdata(L, item); setItemMetatable(L, -1, item); lua_settable(L, -3); @@ -7989,7 +8420,7 @@ int LuaScriptInterface::luaContainerAddItemEx(lua_State* L) if (ret == RETURNVALUE_NOERROR) { ScriptEnvironment::removeTempItem(item); } - lua_pushnumber(L, ret); + lua_pushinteger(L, ret); return 1; } @@ -7998,7 +8429,7 @@ int LuaScriptInterface::luaContainerGetCorpseOwner(lua_State* L) // container:getCorpseOwner() Container* container = getUserdata(L, 1); if (container) { - lua_pushnumber(L, container->getCorpseOwner()); + lua_pushinteger(L, container->getCorpseOwner()); } else { lua_pushnil(L); } @@ -8026,7 +8457,7 @@ int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) } int32_t subType = getNumber(L, 3, -1); - lua_pushnumber(L, container->getItemTypeCount(itemId, subType)); + lua_pushinteger(L, container->getItemTypeCount(itemId, subType)); return 1; } @@ -8324,7 +8755,7 @@ int LuaScriptInterface::luaCreatureGetId(lua_State* L) // creature:getId() const Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getID()); + lua_pushinteger(L, creature->getID()); } else { lua_pushnil(L); } @@ -8451,8 +8882,8 @@ int LuaScriptInterface::luaCreatureGetLight(lua_State* L) } LightInfo lightInfo = creature->getCreatureLight(); - lua_pushnumber(L, lightInfo.level); - lua_pushnumber(L, lightInfo.color); + lua_pushinteger(L, lightInfo.level); + lua_pushinteger(L, lightInfo.color); return 2; } @@ -8479,7 +8910,7 @@ int LuaScriptInterface::luaCreatureGetSpeed(lua_State* L) // creature:getSpeed() const Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getSpeed()); + lua_pushinteger(L, creature->getSpeed()); } else { lua_pushnil(L); } @@ -8491,7 +8922,7 @@ int LuaScriptInterface::luaCreatureGetBaseSpeed(lua_State* L) // creature:getBaseSpeed() const Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getBaseSpeed()); + lua_pushinteger(L, creature->getBaseSpeed()); } else { lua_pushnil(L); } @@ -8576,7 +9007,7 @@ int LuaScriptInterface::luaCreatureGetDirection(lua_State* L) // creature:getDirection() const Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getDirection()); + lua_pushinteger(L, creature->getDirection()); } else { lua_pushnil(L); } @@ -8600,7 +9031,7 @@ int LuaScriptInterface::luaCreatureGetHealth(lua_State* L) // creature:getHealth() const Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getHealth()); + lua_pushinteger(L, creature->getHealth()); } else { lua_pushnil(L); } @@ -8652,7 +9083,7 @@ int LuaScriptInterface::luaCreatureGetMaxHealth(lua_State* L) // creature:getMaxHealth() const Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getMaxHealth()); + lua_pushinteger(L, creature->getMaxHealth()); } else { lua_pushnil(L); } @@ -8712,7 +9143,7 @@ int LuaScriptInterface::luaCreatureGetSkull(lua_State* L) // creature:getSkull() Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getSkull()); + lua_pushinteger(L, creature->getSkull()); } else { lua_pushnil(L); } @@ -9047,7 +9478,7 @@ int LuaScriptInterface::luaCreatureGetPathTo(lua_State* L) int index = 0; for (auto it = dirList.rbegin(); it != dirList.rend(); ++it) { - lua_pushnumber(L, *it); + lua_pushinteger(L, *it); lua_rawseti(L, -2, ++index); } } else { @@ -9072,14 +9503,14 @@ int LuaScriptInterface::luaCreatureMove(lua_State* L) lua_pushnil(L); return 1; } - lua_pushnumber(L, g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT)); + lua_pushinteger(L, g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT)); } else { Tile* tile = getUserdata(L, 2); if (!tile) { lua_pushnil(L); return 1; } - lua_pushnumber(L, g_game.internalMoveCreature(*creature, *tile, getNumber(L, 3))); + lua_pushinteger(L, g_game.internalMoveCreature(*creature, *tile, getNumber(L, 3))); } return 1; } @@ -9089,7 +9520,7 @@ int LuaScriptInterface::luaCreatureGetZone(lua_State* L) // creature:getZone() Creature* creature = getUserdata(L, 1); if (creature) { - lua_pushnumber(L, creature->getZone()); + lua_pushinteger(L, creature->getZone()); } else { lua_pushnil(L); } @@ -9112,7 +9543,7 @@ int LuaScriptInterface::luaPlayerCreate(lua_State* L) ReturnValue ret = g_game.getPlayerByNameWildcard(getString(L, 2), player); if (ret != RETURNVALUE_NOERROR) { lua_pushnil(L); - lua_pushnumber(L, ret); + lua_pushinteger(L, ret); return 2; } } else if (isUserdata(L, 2)) { @@ -9146,7 +9577,7 @@ int LuaScriptInterface::luaPlayerGetGuid(lua_State* L) // player:getGuid() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getGUID()); + lua_pushinteger(L, player->getGUID()); } else { lua_pushnil(L); } @@ -9158,7 +9589,7 @@ int LuaScriptInterface::luaPlayerGetIp(lua_State* L) // player:getIp() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getIP()); + lua_pushinteger(L, player->getIP()); } else { lua_pushnil(L); } @@ -9170,7 +9601,7 @@ int LuaScriptInterface::luaPlayerGetAccountId(lua_State* L) // player:getAccountId() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getAccount()); + lua_pushinteger(L, player->getAccount()); } else { lua_pushnil(L); } @@ -9182,7 +9613,7 @@ int LuaScriptInterface::luaPlayerGetLastLoginSaved(lua_State* L) // player:getLastLoginSaved() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getLastLoginSaved()); + lua_pushinteger(L, player->getLastLoginSaved()); } else { lua_pushnil(L); } @@ -9194,7 +9625,7 @@ int LuaScriptInterface::luaPlayerGetLastLogout(lua_State* L) // player:getLastLogout() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getLastLogout()); + lua_pushinteger(L, player->getLastLogout()); } else { lua_pushnil(L); } @@ -9206,7 +9637,7 @@ int LuaScriptInterface::luaPlayerGetAccountType(lua_State* L) // player:getAccountType() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getAccountType()); + lua_pushinteger(L, player->getAccountType()); } else { lua_pushnil(L); } @@ -9232,7 +9663,7 @@ int LuaScriptInterface::luaPlayerGetCapacity(lua_State* L) // player:getCapacity() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getCapacity()); + lua_pushinteger(L, player->getCapacity()); } else { lua_pushnil(L); } @@ -9258,7 +9689,7 @@ int LuaScriptInterface::luaPlayerGetFreeCapacity(lua_State* L) // player:getFreeCapacity() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getFreeCapacity()); + lua_pushinteger(L, player->getFreeCapacity()); } else { lua_pushnil(L); } @@ -9270,7 +9701,7 @@ int LuaScriptInterface::luaPlayerGetDepotItemCount(lua_State* L) // player:getDepotItemCount() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getDepotItemCount()); + lua_pushinteger(L, player->getDepotItemCount()); } else { lua_pushnil(L); @@ -9344,7 +9775,7 @@ int LuaScriptInterface::luaPlayerGetSkullTime(lua_State* L) // player:getSkullTime() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getSkullTicks()); + lua_pushinteger(L, player->getSkullTicks()); } else { lua_pushnil(L); } @@ -9381,7 +9812,7 @@ int LuaScriptInterface::luaPlayerGetExperience(lua_State* L) // player:getExperience() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getExperience()); + lua_pushinteger(L, player->getExperience()); } else { lua_pushnil(L); } @@ -9423,7 +9854,7 @@ int LuaScriptInterface::luaPlayerGetLevel(lua_State* L) // player:getLevel() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getLevel()); + lua_pushinteger(L, player->getLevel()); } else { lua_pushnil(L); } @@ -9435,7 +9866,7 @@ int LuaScriptInterface::luaPlayerGetMagicLevel(lua_State* L) // player:getMagicLevel() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getMagicLevel()); + lua_pushinteger(L, player->getMagicLevel()); } else { lua_pushnil(L); } @@ -9447,7 +9878,7 @@ int LuaScriptInterface::luaPlayerGetBaseMagicLevel(lua_State* L) // player:getBaseMagicLevel() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getBaseMagicLevel()); + lua_pushinteger(L, player->getBaseMagicLevel()); } else { lua_pushnil(L); } @@ -9459,7 +9890,7 @@ int LuaScriptInterface::luaPlayerGetMana(lua_State* L) // player:getMana() const Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getMana()); + lua_pushinteger(L, player->getMana()); } else { lua_pushnil(L); } @@ -9494,7 +9925,7 @@ int LuaScriptInterface::luaPlayerGetMaxMana(lua_State* L) // player:getMaxMana() const Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getMaxMana()); + lua_pushinteger(L, player->getMaxMana()); } else { lua_pushnil(L); } @@ -9521,7 +9952,7 @@ int LuaScriptInterface::luaPlayerGetManaSpent(lua_State* L) // player:getManaSpent() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getSpentMana()); + lua_pushinteger(L, player->getSpentMana()); } else { lua_pushnil(L); } @@ -9559,7 +9990,7 @@ int LuaScriptInterface::luaPlayerGetBaseMaxHealth(lua_State* L) // player:getBaseMaxHealth() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->healthMax); + lua_pushinteger(L, player->healthMax); } else { lua_pushnil(L); } @@ -9571,7 +10002,7 @@ int LuaScriptInterface::luaPlayerGetBaseMaxMana(lua_State* L) // player:getBaseMaxMana() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->manaMax); + lua_pushinteger(L, player->manaMax); } else { lua_pushnil(L); } @@ -9584,7 +10015,7 @@ int LuaScriptInterface::luaPlayerGetSkillLevel(lua_State* L) skills_t skillType = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player && skillType <= SKILL_LAST) { - lua_pushnumber(L, player->skills[skillType].level); + lua_pushinteger(L, player->skills[skillType].level); } else { lua_pushnil(L); } @@ -9597,7 +10028,7 @@ int LuaScriptInterface::luaPlayerGetEffectiveSkillLevel(lua_State* L) skills_t skillType = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player && skillType <= SKILL_LAST) { - lua_pushnumber(L, player->getSkillLevel(skillType)); + lua_pushinteger(L, player->getSkillLevel(skillType)); } else { lua_pushnil(L); } @@ -9610,7 +10041,7 @@ int LuaScriptInterface::luaPlayerGetSkillPercent(lua_State* L) skills_t skillType = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player && skillType <= SKILL_LAST) { - lua_pushnumber(L, player->skills[skillType].percent); + lua_pushinteger(L, player->skills[skillType].percent); } else { lua_pushnil(L); } @@ -9623,7 +10054,7 @@ int LuaScriptInterface::luaPlayerGetSkillTries(lua_State* L) skills_t skillType = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player && skillType <= SKILL_LAST) { - lua_pushnumber(L, player->skills[skillType].tries); + lua_pushinteger(L, player->skills[skillType].tries); } else { lua_pushnil(L); } @@ -9666,7 +10097,7 @@ int LuaScriptInterface::luaPlayerGetSpecialSkill(lua_State* L) SpecialSkills_t specialSkillType = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player && specialSkillType <= SPECIALSKILL_LAST) { - lua_pushnumber(L, player->getSpecialSkill(specialSkillType)); + lua_pushinteger(L, player->getSpecialSkill(specialSkillType)); } else { lua_pushnil(L); } @@ -9714,7 +10145,7 @@ int LuaScriptInterface::luaPlayerGetOfflineTrainingTime(lua_State* L) // player:getOfflineTrainingTime() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getOfflineTrainingTime()); + lua_pushinteger(L, player->getOfflineTrainingTime()); } else { lua_pushnil(L); } @@ -9755,7 +10186,7 @@ int LuaScriptInterface::luaPlayerGetOfflineTrainingSkill(lua_State* L) // player:getOfflineTrainingSkill() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getOfflineTrainingSkill()); + lua_pushinteger(L, player->getOfflineTrainingSkill()); } else { lua_pushnil(L); } @@ -9797,7 +10228,7 @@ int LuaScriptInterface::luaPlayerGetItemCount(lua_State* L) } int32_t subType = getNumber(L, 3, -1); - lua_pushnumber(L, player->getItemTypeCount(itemId, subType)); + lua_pushinteger(L, player->getItemTypeCount(itemId, subType)); return 1; } @@ -9881,7 +10312,7 @@ int LuaScriptInterface::luaPlayerGetSex(lua_State* L) // player:getSex() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getSex()); + lua_pushinteger(L, player->getSex()); } else { lua_pushnil(L); } @@ -9973,7 +10404,7 @@ int LuaScriptInterface::luaPlayerGetGuildLevel(lua_State* L) // player:getGuildLevel() Player* player = getUserdata(L, 1); if (player && player->getGuild()) { - lua_pushnumber(L, player->getGuildRank()->level); + lua_pushinteger(L, player->getGuildRank()->level); } else { lua_pushnil(L); } @@ -10064,7 +10495,7 @@ int LuaScriptInterface::luaPlayerGetStamina(lua_State* L) // player:getStamina() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getStaminaMinutes()); + lua_pushinteger(L, player->getStaminaMinutes()); } else { lua_pushnil(L); } @@ -10091,7 +10522,7 @@ int LuaScriptInterface::luaPlayerGetSoul(lua_State* L) // player:getSoul() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getSoul()); + lua_pushinteger(L, player->getSoul()); } else { lua_pushnil(L); } @@ -10117,7 +10548,7 @@ int LuaScriptInterface::luaPlayerGetMaxSoul(lua_State* L) // player:getMaxSoul() Player* player = getUserdata(L, 1); if (player && player->vocation) { - lua_pushnumber(L, player->vocation->getSoulMax()); + lua_pushinteger(L, player->vocation->getSoulMax()); } else { lua_pushnil(L); } @@ -10129,7 +10560,7 @@ int LuaScriptInterface::luaPlayerGetBankBalance(lua_State* L) // player:getBankBalance() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getBankBalance()); + lua_pushinteger(L, player->getBankBalance()); } else { lua_pushnil(L); } @@ -10169,9 +10600,9 @@ int LuaScriptInterface::luaPlayerGetStorageValue(lua_State* L) uint32_t key = getNumber(L, 2); int32_t value; if (player->getStorageValue(key, value)) { - lua_pushnumber(L, value); + lua_pushinteger(L, value); } else { - lua_pushnumber(L, -1); + lua_pushinteger(L, -1); } return 1; } @@ -10271,7 +10702,7 @@ int LuaScriptInterface::luaPlayerAddItem(lua_State* L) } if (hasTable) { - lua_pushnumber(L, i); + lua_pushinteger(L, i); pushUserdata(L, item); setItemMetatable(L, -1, item); lua_settable(L, -3); @@ -10320,7 +10751,7 @@ int LuaScriptInterface::luaPlayerAddItemEx(lua_State* L) if (returnValue == RETURNVALUE_NOERROR) { ScriptEnvironment::removeTempItem(item); } - lua_pushnumber(L, returnValue); + lua_pushinteger(L, returnValue); return 1; } @@ -10356,7 +10787,7 @@ int LuaScriptInterface::luaPlayerGetMoney(lua_State* L) // player:getMoney() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getMoney()); + lua_pushinteger(L, player->getMoney()); } else { lua_pushnil(L); } @@ -10771,7 +11202,7 @@ int LuaScriptInterface::luaPlayerGetPremiumEndsAt(lua_State* L) // player:getPremiumEndsAt() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->premiumEndsAt); + lua_pushinteger(L, player->premiumEndsAt); } else { lua_pushnil(L); } @@ -11142,7 +11573,7 @@ int LuaScriptInterface::luaPlayerGetContainerId(lua_State* L) Container* container = getUserdata(L, 2); if (container) { - lua_pushnumber(L, player->getContainerID(container)); + lua_pushinteger(L, player->getContainerID(container)); } else { lua_pushnil(L); } @@ -11173,7 +11604,7 @@ int LuaScriptInterface::luaPlayerGetContainerIndex(lua_State* L) // player:getContainerIndex(id) Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getContainerIndex(getNumber(L, 2))); + lua_pushinteger(L, player->getContainerIndex(getNumber(L, 2))); } else { lua_pushnil(L); } @@ -11248,7 +11679,7 @@ int LuaScriptInterface::luaPlayerGetFightMode(lua_State* L) // player:getFightMode() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->fightMode); + lua_pushinteger(L, player->fightMode); } else { lua_pushnil(L); } @@ -11294,7 +11725,7 @@ int LuaScriptInterface::luaPlayerGetIdleTime(lua_State* L) lua_pushnil(L); return 1; } - lua_pushnumber(L, player->getIdleTime()); + lua_pushinteger(L, player->getIdleTime()); return 1; } @@ -11331,6 +11762,116 @@ int LuaScriptInterface::luaPlayerSendCreatureSquare(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerAddAugment(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + if (auto augment = Augments::GetAugment(getString(L, 2))) { + lua_pushboolean(L, player->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Player::addAugment() argument not found as any name in augments loaded on startup! \n"); + } + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, player->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Player::addAugment() invalid userdata passed as argument! \n"); + } + } else { + reportError(__FUNCTION__, "Player::addAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveAugment(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + auto name = getString(L, 2); + lua_pushboolean(L, player->removeAugment(name)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, player->removeAugment(augment)); + } else { + reportError(__FUNCTION__, "Player::removeAugment() invalid userdata type passed as argument! \n"); + lua_pushnil(L); + } + } else { + reportError(__FUNCTION__, "Player::removeAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsAugmented(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + lua_pushboolean(L, player->isAugmented()); + return 1; +} + +int LuaScriptInterface::luaPlayerHasAugment(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2) && isBoolean(L, 3)) { + auto name = getString(L, 2); + auto checkItems = getBoolean(L, 3); + lua_pushboolean(L, player->hasAugment(name, checkItems)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr& augment = getSharedPtr(L, 2)) { + if (isBoolean(L, 3)) { + auto checkItems = getBoolean(L, 3); + lua_pushboolean(L, player->hasAugment(augment, checkItems)); + } else { + lua_pushboolean(L, player->hasAugment(augment, true)); + } + } + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetAugments(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + std::vector> augments; + + lua_newtable(L); + int index = 1; + for (const auto& augment : augments) { + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + lua_rawseti(L, -2, index++); + } + return 1; +} + // Monster int LuaScriptInterface::luaMonsterCreate(lua_State* L) { @@ -11585,7 +12126,7 @@ int LuaScriptInterface::luaMonsterGetFriendCount(lua_State* L) // monster:getFriendCount() Monster* monster = getUserdata(L, 1); if (monster) { - lua_pushnumber(L, monster->getFriendList().size()); + lua_pushinteger(L, monster->getFriendList().size()); } else { lua_pushnil(L); } @@ -11661,7 +12202,7 @@ int LuaScriptInterface::luaMonsterGetTargetCount(lua_State* L) // monster:getTargetCount() Monster* monster = getUserdata(L, 1); if (monster) { - lua_pushnumber(L, monster->getTargetList().size()); + lua_pushinteger(L, monster->getTargetList().size()); } else { lua_pushnil(L); } @@ -11784,7 +12325,7 @@ int LuaScriptInterface::luaNpcGetSpeechBubble(lua_State* L) // npc:getSpeechBubble() Npc* npc = getUserdata(L, 1); if (npc) { - lua_pushnumber(L, npc->getSpeechBubble()); + lua_pushinteger(L, npc->getSpeechBubble()); } else { lua_pushnil(L); } @@ -11859,7 +12400,7 @@ int LuaScriptInterface::luaGuildGetId(lua_State* L) // guild:getId() Guild* guild = getUserdata(L, 1); if (guild) { - lua_pushnumber(L, guild->getId()); + lua_pushinteger(L, guild->getId()); } else { lua_pushnil(L); } @@ -11930,7 +12471,7 @@ int LuaScriptInterface::luaGuildGetRankById(lua_State* L) lua_createtable(L, 0, 3); setField(L, "id", rank->id); setField(L, "name", rank->name); - setField(L, "level", rank->level); + setField(L, "level", static_cast(rank->level)); } else { lua_pushnil(L); } @@ -11952,7 +12493,7 @@ int LuaScriptInterface::luaGuildGetRankByLevel(lua_State* L) lua_createtable(L, 0, 3); setField(L, "id", rank->id); setField(L, "name", rank->name); - setField(L, "level", rank->level); + setField(L, "level", static_cast(rank->level)); } else { lua_pushnil(L); } @@ -12006,7 +12547,7 @@ int LuaScriptInterface::luaGroupGetId(lua_State* L) // group:getId() Group* group = getUserdata(L, 1); if (group) { - lua_pushnumber(L, group->id); + lua_pushinteger(L, group->id); } else { lua_pushnil(L); } @@ -12030,7 +12571,7 @@ int LuaScriptInterface::luaGroupGetFlags(lua_State* L) // group:getFlags() Group* group = getUserdata(L, 1); if (group) { - lua_pushnumber(L, group->flags); + lua_pushinteger(L, group->flags); } else { lua_pushnil(L); } @@ -12054,7 +12595,7 @@ int LuaScriptInterface::luaGroupGetMaxDepotItems(lua_State* L) // group:getMaxDepotItems() Group* group = getUserdata(L, 1); if (group) { - lua_pushnumber(L, group->maxDepotItems); + lua_pushinteger(L, group->maxDepotItems); } else { lua_pushnil(L); } @@ -12066,7 +12607,7 @@ int LuaScriptInterface::luaGroupGetMaxVipEntries(lua_State* L) // group:getMaxVipEntries() Group* group = getUserdata(L, 1); if (group) { - lua_pushnumber(L, group->maxVipEntries); + lua_pushinteger(L, group->maxVipEntries); } else { lua_pushnil(L); } @@ -12112,7 +12653,7 @@ int LuaScriptInterface::luaVocationGetId(lua_State* L) // vocation:getId() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getId()); + lua_pushinteger(L, vocation->getId()); } else { lua_pushnil(L); } @@ -12124,7 +12665,7 @@ int LuaScriptInterface::luaVocationGetClientId(lua_State* L) // vocation:getClientId() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getClientId()); + lua_pushinteger(L, vocation->getClientId()); } else { lua_pushnil(L); } @@ -12162,7 +12703,7 @@ int LuaScriptInterface::luaVocationGetRequiredSkillTries(lua_State* L) if (vocation) { skills_t skillType = getNumber(L, 2); uint16_t skillLevel = getNumber(L, 3); - lua_pushnumber(L, vocation->getReqSkillTries(skillType, skillLevel)); + lua_pushinteger(L, vocation->getReqSkillTries(skillType, skillLevel)); } else { lua_pushnil(L); } @@ -12175,7 +12716,7 @@ int LuaScriptInterface::luaVocationGetRequiredManaSpent(lua_State* L) Vocation* vocation = getUserdata(L, 1); if (vocation) { uint32_t magicLevel = getNumber(L, 2); - lua_pushnumber(L, vocation->getReqMana(magicLevel)); + lua_pushinteger(L, vocation->getReqMana(magicLevel)); } else { lua_pushnil(L); } @@ -12187,7 +12728,7 @@ int LuaScriptInterface::luaVocationGetCapacityGain(lua_State* L) // vocation:getCapacityGain() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getCapGain()); + lua_pushinteger(L, vocation->getCapGain()); } else { lua_pushnil(L); } @@ -12199,7 +12740,7 @@ int LuaScriptInterface::luaVocationGetHealthGain(lua_State* L) // vocation:getHealthGain() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getHPGain()); + lua_pushinteger(L, vocation->getHPGain()); } else { lua_pushnil(L); } @@ -12211,7 +12752,7 @@ int LuaScriptInterface::luaVocationGetHealthGainTicks(lua_State* L) // vocation:getHealthGainTicks() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getHealthGainTicks()); + lua_pushinteger(L, vocation->getHealthGainTicks()); } else { lua_pushnil(L); } @@ -12223,7 +12764,7 @@ int LuaScriptInterface::luaVocationGetHealthGainAmount(lua_State* L) // vocation:getHealthGainAmount() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getHealthGainAmount()); + lua_pushinteger(L, vocation->getHealthGainAmount()); } else { lua_pushnil(L); } @@ -12235,7 +12776,7 @@ int LuaScriptInterface::luaVocationGetManaGain(lua_State* L) // vocation:getManaGain() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getManaGain()); + lua_pushinteger(L, vocation->getManaGain()); } else { lua_pushnil(L); } @@ -12247,7 +12788,7 @@ int LuaScriptInterface::luaVocationGetManaGainTicks(lua_State* L) // vocation:getManaGainTicks() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getManaGainTicks()); + lua_pushinteger(L, vocation->getManaGainTicks()); } else { lua_pushnil(L); } @@ -12259,7 +12800,7 @@ int LuaScriptInterface::luaVocationGetManaGainAmount(lua_State* L) // vocation:getManaGainAmount() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getManaGainAmount()); + lua_pushinteger(L, vocation->getManaGainAmount()); } else { lua_pushnil(L); } @@ -12271,7 +12812,7 @@ int LuaScriptInterface::luaVocationGetMaxSoul(lua_State* L) // vocation:getMaxSoul() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getSoulMax()); + lua_pushinteger(L, vocation->getSoulMax()); } else { lua_pushnil(L); } @@ -12283,7 +12824,7 @@ int LuaScriptInterface::luaVocationGetSoulGainTicks(lua_State* L) // vocation:getSoulGainTicks() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getSoulGainTicks()); + lua_pushinteger(L, vocation->getSoulGainTicks()); } else { lua_pushnil(L); } @@ -12295,7 +12836,7 @@ int LuaScriptInterface::luaVocationGetAttackSpeed(lua_State* L) // vocation:getAttackSpeed() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getAttackSpeed()); + lua_pushinteger(L, vocation->getAttackSpeed()); } else { lua_pushnil(L); } @@ -12307,7 +12848,7 @@ int LuaScriptInterface::luaVocationGetBaseSpeed(lua_State* L) // vocation:getBaseSpeed() Vocation* vocation = getUserdata(L, 1); if (vocation) { - lua_pushnumber(L, vocation->getBaseSpeed()); + lua_pushinteger(L, vocation->getBaseSpeed()); } else { lua_pushnil(L); } @@ -12403,7 +12944,7 @@ int LuaScriptInterface::luaTownGetId(lua_State* L) // town:getId() Town* town = getUserdata(L, 1); if (town) { - lua_pushnumber(L, town->getID()); + lua_pushinteger(L, town->getID()); } else { lua_pushnil(L); } @@ -12453,7 +12994,7 @@ int LuaScriptInterface::luaHouseGetId(lua_State* L) // house:getId() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getId()); + lua_pushinteger(L, house->getId()); } else { lua_pushnil(L); } @@ -12508,7 +13049,7 @@ int LuaScriptInterface::luaHouseGetRent(lua_State* L) // house:getRent() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getRent()); + lua_pushinteger(L, house->getRent()); } else { lua_pushnil(L); } @@ -12534,7 +13075,7 @@ int LuaScriptInterface::luaHouseGetPaidUntil(lua_State* L) // house:getPaidUntil() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getPaidUntil()); + lua_pushinteger(L, house->getPaidUntil()); } else { lua_pushnil(L); } @@ -12560,7 +13101,7 @@ int LuaScriptInterface::luaHouseGetPayRentWarnings(lua_State* L) // house:getPayRentWarnings() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getPayRentWarnings()); + lua_pushinteger(L, house->getPayRentWarnings()); } else { lua_pushnil(L); } @@ -12586,7 +13127,7 @@ int LuaScriptInterface::luaHouseGetOwnerGuid(lua_State* L) // house:getOwnerGuid() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getOwner()); + lua_pushinteger(L, house->getOwner()); } else { lua_pushnil(L); } @@ -12621,28 +13162,28 @@ int LuaScriptInterface::luaHouseStartTrade(lua_State* L) } if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { - lua_pushnumber(L, RETURNVALUE_TRADEPLAYERFARAWAY); + lua_pushinteger(L, RETURNVALUE_TRADEPLAYERFARAWAY); return 1; } if (house->getOwner() != player->getGUID()) { - lua_pushnumber(L, RETURNVALUE_YOUDONTOWNTHISHOUSE); + lua_pushinteger(L, RETURNVALUE_YOUDONTOWNTHISHOUSE); return 1; } if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) { - lua_pushnumber(L, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE); + lua_pushinteger(L, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE); return 1; } if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) { - lua_pushnumber(L, RETURNVALUE_TRADEPLAYERHIGHESTBIDDER); + lua_pushinteger(L, RETURNVALUE_TRADEPLAYERHIGHESTBIDDER); return 1; } Item* transferItem = house->getTransferItem(); if (!transferItem) { - lua_pushnumber(L, RETURNVALUE_YOUCANNOTTRADETHISHOUSE); + lua_pushinteger(L, RETURNVALUE_YOUCANNOTTRADETHISHOUSE); return 1; } @@ -12651,7 +13192,7 @@ int LuaScriptInterface::luaHouseStartTrade(lua_State* L) house->resetTransferItem(); } - lua_pushnumber(L, RETURNVALUE_NOERROR); + lua_pushinteger(L, RETURNVALUE_NOERROR); return 1; } @@ -12681,7 +13222,7 @@ int LuaScriptInterface::luaHouseGetBedCount(lua_State* L) // house:getBedCount() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getBedCount()); + lua_pushinteger(L, house->getBedCount()); } else { lua_pushnil(L); } @@ -12714,7 +13255,7 @@ int LuaScriptInterface::luaHouseGetDoorCount(lua_State* L) // house:getDoorCount() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getDoors().size()); + lua_pushinteger(L, house->getDoors().size()); } else { lua_pushnil(L); } @@ -12732,7 +13273,7 @@ int LuaScriptInterface::luaHouseGetDoorIdByPosition(lua_State* L) Door* door = house->getDoorByPosition(getPosition(L, 2)); if (door) { - lua_pushnumber(L, door->getDoorId()); + lua_pushinteger(L, door->getDoorId()); } else { lua_pushnil(L); } @@ -12791,7 +13332,7 @@ int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) // house:getTileCount() House* house = getUserdata(L, 1); if (house) { - lua_pushnumber(L, house->getTiles().size()); + lua_pushinteger(L, house->getTiles().size()); } else { lua_pushnil(L); } @@ -13080,7 +13621,7 @@ int LuaScriptInterface::luaItemTypeGetType(lua_State* L) // itemType:getType() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->type); + lua_pushinteger(L, itemType->type); } else { lua_pushnil(L); } @@ -13092,7 +13633,7 @@ int LuaScriptInterface::luaItemTypeGetGroup(lua_State* L) // itemType:getGroup() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->group); + lua_pushinteger(L, itemType->group); } else { lua_pushnil(L); } @@ -13104,7 +13645,7 @@ int LuaScriptInterface::luaItemTypeGetId(lua_State* L) // itemType:getId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->id); + lua_pushinteger(L, itemType->id); } else { lua_pushnil(L); } @@ -13116,7 +13657,7 @@ int LuaScriptInterface::luaItemTypeGetClientId(lua_State* L) // itemType:getClientId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->clientId); + lua_pushinteger(L, itemType->clientId); } else { lua_pushnil(L); } @@ -13152,7 +13693,7 @@ int LuaScriptInterface::luaItemTypeGetRotateTo(lua_State* L) // itemType:getRotateTo() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->rotateTo); + lua_pushinteger(L, itemType->rotateTo); } else { lua_pushnil(L); } @@ -13188,7 +13729,7 @@ int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State *L) // itemType:getSlotPosition() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->slotPosition); + lua_pushinteger(L, itemType->slotPosition); } else { lua_pushnil(L); } @@ -13200,7 +13741,7 @@ int LuaScriptInterface::luaItemTypeGetCharges(lua_State* L) // itemType:getCharges() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->charges); + lua_pushinteger(L, itemType->charges); } else { lua_pushnil(L); } @@ -13212,7 +13753,7 @@ int LuaScriptInterface::luaItemTypeGetFluidSource(lua_State* L) // itemType:getFluidSource() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->fluidSource); + lua_pushinteger(L, itemType->fluidSource); } else { lua_pushnil(L); } @@ -13224,7 +13765,7 @@ int LuaScriptInterface::luaItemTypeGetCapacity(lua_State* L) // itemType:getCapacity() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->maxItems); + lua_pushinteger(L, itemType->maxItems); } else { lua_pushnil(L); } @@ -13243,7 +13784,7 @@ int LuaScriptInterface::luaItemTypeGetWeight(lua_State* L) } uint64_t weight = static_cast(itemType->weight) * std::max(1, count); - lua_pushnumber(L, weight); + lua_pushinteger(L, weight); return 1; } @@ -13256,7 +13797,7 @@ int LuaScriptInterface::luaItemTypeGetWorth(lua_State* L) return 1; } - lua_pushnumber(L, itemType->worth); + lua_pushinteger(L, itemType->worth); return 1; } @@ -13265,7 +13806,7 @@ int LuaScriptInterface::luaItemTypeGetHitChance(lua_State* L) // itemType:getHitChance() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->hitChance); + lua_pushinteger(L, itemType->hitChance); } else { lua_pushnil(L); } @@ -13277,7 +13818,7 @@ int LuaScriptInterface::luaItemTypeGetShootRange(lua_State* L) // itemType:getShootRange() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->shootRange); + lua_pushinteger(L, itemType->shootRange); } else { lua_pushnil(L); } @@ -13289,7 +13830,7 @@ int LuaScriptInterface::luaItemTypeGetAttack(lua_State* L) // itemType:getAttack() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->attack); + lua_pushinteger(L, itemType->attack); } else { lua_pushnil(L); } @@ -13301,7 +13842,7 @@ int LuaScriptInterface::luaItemTypeGetAttackSpeed(lua_State* L) // itemType:getAttackSpeed() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->attackSpeed); + lua_pushinteger(L, itemType->attackSpeed); } else { lua_pushnil(L); } @@ -13337,7 +13878,7 @@ int LuaScriptInterface::luaItemTypeGetDefense(lua_State* L) // itemType:getDefense() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->defense); + lua_pushinteger(L, itemType->defense); } else { lua_pushnil(L); } @@ -13349,7 +13890,7 @@ int LuaScriptInterface::luaItemTypeGetExtraDefense(lua_State* L) // itemType:getExtraDefense() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->extraDefense); + lua_pushinteger(L, itemType->extraDefense); } else { lua_pushnil(L); } @@ -13361,7 +13902,7 @@ int LuaScriptInterface::luaItemTypeGetArmor(lua_State* L) // itemType:getArmor() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->armor); + lua_pushinteger(L, itemType->armor); } else { lua_pushnil(L); } @@ -13373,7 +13914,7 @@ int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L) // itemType:getWeaponType() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->weaponType); + lua_pushinteger(L, itemType->weaponType); } else { lua_pushnil(L); } @@ -13385,7 +13926,7 @@ int LuaScriptInterface::luaItemTypeGetAmmoType(lua_State* L) // itemType:getAmmoType() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->ammoType); + lua_pushinteger(L, itemType->ammoType); } else { lua_pushnil(L); } @@ -13397,7 +13938,7 @@ int LuaScriptInterface::luaItemTypeGetShootType(lua_State* L) // itemType:getShootType() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->shootType); + lua_pushinteger(L, itemType->shootType); } else { lua_pushnil(L); @@ -13412,7 +13953,7 @@ int LuaScriptInterface::luaItemTypeGetCorpseType(lua_State* L) // itemType:getCorpseType() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->corpseType); + lua_pushinteger(L, itemType->corpseType); } else { lua_pushnil(L); } @@ -13446,7 +13987,7 @@ int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) // Stats lua_createtable(L, 0, STAT_LAST + 1); for (int32_t i = STAT_FIRST; i <= STAT_LAST; i++) { - lua_pushnumber(L, abilities.stats[i]); + lua_pushinteger(L, abilities.stats[i]); lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "stats"); @@ -13454,7 +13995,7 @@ int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) // Stats percent lua_createtable(L, 0, STAT_LAST + 1); for (int32_t i = STAT_FIRST; i <= STAT_LAST; i++) { - lua_pushnumber(L, abilities.statsPercent[i]); + lua_pushinteger(L, abilities.statsPercent[i]); lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "statsPercent"); @@ -13462,7 +14003,7 @@ int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) // Skills lua_createtable(L, 0, SKILL_LAST + 1); for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - lua_pushnumber(L, abilities.skills[i]); + lua_pushinteger(L, abilities.skills[i]); lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "skills"); @@ -13470,26 +14011,10 @@ int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) // 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_pushinteger(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"); } return 1; } @@ -13637,7 +14162,7 @@ int LuaScriptInterface::luaItemTypeGetElementType(lua_State* L) auto& abilities = itemType->abilities; if (abilities) { - lua_pushnumber(L, abilities->elementType); + lua_pushinteger(L, abilities->elementType); } else { lua_pushnil(L); } @@ -13655,7 +14180,7 @@ int LuaScriptInterface::luaItemTypeGetElementDamage(lua_State* L) auto& abilities = itemType->abilities; if (abilities) { - lua_pushnumber(L, abilities->elementDamage); + lua_pushinteger(L, abilities->elementDamage); } else { lua_pushnil(L); } @@ -13667,7 +14192,7 @@ int LuaScriptInterface::luaItemTypeGetTransformEquipId(lua_State* L) // itemType:getTransformEquipId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->transformEquipTo); + lua_pushinteger(L, itemType->transformEquipTo); } else { lua_pushnil(L); } @@ -13679,7 +14204,7 @@ int LuaScriptInterface::luaItemTypeGetTransformDeEquipId(lua_State* L) // itemType:getTransformDeEquipId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->transformDeEquipTo); + lua_pushinteger(L, itemType->transformDeEquipTo); } else { lua_pushnil(L); } @@ -13691,7 +14216,7 @@ int LuaScriptInterface::luaItemTypeGetDestroyId(lua_State* L) // itemType:getDestroyId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->destroyTo); + lua_pushinteger(L, itemType->destroyTo); } else { lua_pushnil(L); } @@ -13703,7 +14228,7 @@ int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) // itemType:getDecayId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->decayTo); + lua_pushinteger(L, itemType->decayTo); } else { lua_pushnil(L); } @@ -13715,7 +14240,7 @@ int LuaScriptInterface::luaItemTypeGetRequiredLevel(lua_State* L) // itemType:getRequiredLevel() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->minReqLevel); + lua_pushinteger(L, itemType->minReqLevel); } else { lua_pushnil(L); } @@ -13802,7 +14327,7 @@ int LuaScriptInterface::luaCombatGetParameter(lua_State* L) return 1; } - lua_pushnumber(L, value); + lua_pushinteger(L, value); return 1; } @@ -14042,7 +14567,7 @@ int LuaScriptInterface::luaConditionGetId(lua_State* L) // condition:getId() Condition* condition = getUserdata(L, 1); if (condition) { - lua_pushnumber(L, condition->getId()); + lua_pushinteger(L, condition->getId()); } else { lua_pushnil(L); } @@ -14054,7 +14579,7 @@ int LuaScriptInterface::luaConditionGetSubId(lua_State* L) // condition:getSubId() Condition* condition = getUserdata(L, 1); if (condition) { - lua_pushnumber(L, condition->getSubId()); + lua_pushinteger(L, condition->getSubId()); } else { lua_pushnil(L); } @@ -14066,7 +14591,7 @@ int LuaScriptInterface::luaConditionGetType(lua_State* L) // condition:getType() Condition* condition = getUserdata(L, 1); if (condition) { - lua_pushnumber(L, condition->getType()); + lua_pushinteger(L, condition->getType()); } else { lua_pushnil(L); } @@ -14078,7 +14603,7 @@ int LuaScriptInterface::luaConditionGetIcons(lua_State* L) // condition:getIcons() Condition* condition = getUserdata(L, 1); if (condition) { - lua_pushnumber(L, condition->getIcons()); + lua_pushinteger(L, condition->getIcons()); } else { lua_pushnil(L); } @@ -14090,7 +14615,7 @@ int LuaScriptInterface::luaConditionGetEndTime(lua_State* L) // condition:getEndTime() Condition* condition = getUserdata(L, 1); if (condition) { - lua_pushnumber(L, condition->getEndTime()); + lua_pushinteger(L, condition->getEndTime()); } else { lua_pushnil(L); } @@ -14115,7 +14640,7 @@ int LuaScriptInterface::luaConditionGetTicks(lua_State* L) // condition:getTicks() Condition* condition = getUserdata(L, 1); if (condition) { - lua_pushnumber(L, condition->getTicks()); + lua_pushinteger(L, condition->getTicks()); } else { lua_pushnil(L); } @@ -14172,7 +14697,7 @@ int LuaScriptInterface::luaConditionGetParameter(lua_State* L) return 1; } - lua_pushnumber(L, value); + lua_pushinteger(L, value); return 1; } @@ -14289,6 +14814,18 @@ int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeIsRewardBoss(lua_State* L) +{ + // lua monsterType:isRewardBoss() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isRewardBoss); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeIsChallengeable(lua_State* L) { // get: monsterType:isChallengeable() set: monsterType:isChallengeable(bool) @@ -14567,7 +15104,7 @@ int LuaScriptInterface::luaMonsterTypeHealth(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.health); + lua_pushinteger(L, monsterType->info.health); } else { monsterType->info.health = getNumber(L, 2); pushBoolean(L, true); @@ -14584,7 +15121,7 @@ int LuaScriptInterface::luaMonsterTypeMaxHealth(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.healthMax); + lua_pushinteger(L, monsterType->info.healthMax); } else { monsterType->info.healthMax = getNumber(L, 2); pushBoolean(L, true); @@ -14601,7 +15138,7 @@ int LuaScriptInterface::luaMonsterTypeRunHealth(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.runAwayHealth); + lua_pushinteger(L, monsterType->info.runAwayHealth); } else { monsterType->info.runAwayHealth = getNumber(L, 2); pushBoolean(L, true); @@ -14618,7 +15155,7 @@ int LuaScriptInterface::luaMonsterTypeExperience(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.experience); + lua_pushinteger(L, monsterType->info.experience); } else { monsterType->info.experience = getNumber(L, 2); pushBoolean(L, true); @@ -14635,7 +15172,7 @@ int LuaScriptInterface::luaMonsterTypeSkull(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.skull); + lua_pushinteger(L, monsterType->info.skull); } else { if (isNumber(L, 2)) { monsterType->info.skull = getNumber(L, 2); @@ -14656,7 +15193,7 @@ int LuaScriptInterface::luaMonsterTypeCombatImmunities(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.damageImmunities); + lua_pushinteger(L, monsterType->info.damageImmunities); } else { std::string immunity = getString(L, 2); if (immunity == "physical") { @@ -14706,7 +15243,7 @@ int LuaScriptInterface::luaMonsterTypeConditionImmunities(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.conditionImmunities); + lua_pushinteger(L, monsterType->info.conditionImmunities); } else { std::string immunity = getString(L, 2); if (immunity == "physical") { @@ -14876,7 +15413,7 @@ int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) lua_createtable(L, monsterType->info.elementMap.size(), 0); for (const auto& elementEntry : monsterType->info.elementMap) { - lua_pushnumber(L, elementEntry.second); + lua_pushinteger(L, elementEntry.second); lua_rawseti(L, -2, elementEntry.first); } return 1; @@ -15079,7 +15616,7 @@ int LuaScriptInterface::luaMonsterTypeMaxSummons(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.maxSummons); + lua_pushinteger(L, monsterType->info.maxSummons); } else { monsterType->info.maxSummons = getNumber(L, 2); pushBoolean(L, true); @@ -15096,7 +15633,7 @@ int LuaScriptInterface::luaMonsterTypeArmor(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.armor); + lua_pushinteger(L, monsterType->info.armor); } else { monsterType->info.armor = getNumber(L, 2); pushBoolean(L, true); @@ -15113,7 +15650,7 @@ int LuaScriptInterface::luaMonsterTypeDefense(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.defense); + lua_pushinteger(L, monsterType->info.defense); } else { monsterType->info.defense = getNumber(L, 2); pushBoolean(L, true); @@ -15148,7 +15685,7 @@ int LuaScriptInterface::luaMonsterTypeRace(lua_State* L) std::string race = getString(L, 2); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.race); + lua_pushinteger(L, monsterType->info.race); } else { if (race == "venom") { monsterType->info.race = RACE_VENOM; @@ -15179,7 +15716,7 @@ int LuaScriptInterface::luaMonsterTypeCorpseId(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.lookcorpse); + lua_pushinteger(L, monsterType->info.lookcorpse); } else { monsterType->info.lookcorpse = getNumber(L, 2); lua_pushboolean(L, true); @@ -15196,7 +15733,7 @@ int LuaScriptInterface::luaMonsterTypeManaCost(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.manaCost); + lua_pushinteger(L, monsterType->info.manaCost); } else { monsterType->info.manaCost = getNumber(L, 2); pushBoolean(L, true); @@ -15213,7 +15750,7 @@ int LuaScriptInterface::luaMonsterTypeBaseSpeed(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.baseSpeed); + lua_pushinteger(L, monsterType->info.baseSpeed); } else { monsterType->info.baseSpeed = getNumber(L, 2); pushBoolean(L, true); @@ -15233,8 +15770,8 @@ int LuaScriptInterface::luaMonsterTypeLight(lua_State* L) return 1; } if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.light.level); - lua_pushnumber(L, monsterType->info.light.color); + lua_pushinteger(L, monsterType->info.light.level); + lua_pushinteger(L, monsterType->info.light.color); return 2; } else { monsterType->info.light.color = getNumber(L, 2); @@ -15250,7 +15787,7 @@ int LuaScriptInterface::luaMonsterTypeStaticAttackChance(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.staticAttackChance); + lua_pushinteger(L, monsterType->info.staticAttackChance); } else { monsterType->info.staticAttackChance = getNumber(L, 2); pushBoolean(L, true); @@ -15267,7 +15804,7 @@ int LuaScriptInterface::luaMonsterTypeTargetDistance(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.targetDistance); + lua_pushinteger(L, monsterType->info.targetDistance); } else { monsterType->info.targetDistance = getNumber(L, 2); pushBoolean(L, true); @@ -15284,7 +15821,7 @@ int LuaScriptInterface::luaMonsterTypeYellChance(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.yellChance); + lua_pushinteger(L, monsterType->info.yellChance); } else { monsterType->info.yellChance = getNumber(L, 2); pushBoolean(L, true); @@ -15301,7 +15838,7 @@ int LuaScriptInterface::luaMonsterTypeYellSpeedTicks(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.yellSpeedTicks); + lua_pushinteger(L, monsterType->info.yellSpeedTicks); } else { monsterType->info.yellSpeedTicks = getNumber(L, 2); pushBoolean(L, true); @@ -15318,7 +15855,7 @@ int LuaScriptInterface::luaMonsterTypeChangeTargetChance(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.changeTargetChance); + lua_pushinteger(L, monsterType->info.changeTargetChance); } else { monsterType->info.changeTargetChance = getNumber(L, 2); pushBoolean(L, true); @@ -15335,7 +15872,7 @@ int LuaScriptInterface::luaMonsterTypeChangeTargetSpeed(lua_State* L) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.changeTargetSpeed); + lua_pushinteger(L, monsterType->info.changeTargetSpeed); } else { monsterType->info.changeTargetSpeed = getNumber(L, 2); pushBoolean(L, true); @@ -15905,7 +16442,7 @@ int LuaScriptInterface::luaPartyGetMemberCount(lua_State* L) // party:getMemberCount() Party* party = getUserdata(L, 1); if (party) { - lua_pushnumber(L, party->getMemberCount()); + lua_pushinteger(L, party->getMemberCount()); } else { lua_pushnil(L); } @@ -15936,7 +16473,7 @@ int LuaScriptInterface::luaPartyGetInviteeCount(lua_State* L) // party:getInviteeCount() Party* party = getUserdata(L, 1); if (party) { - lua_pushnumber(L, party->getInvitationCount()); + lua_pushinteger(L, party->getInvitationCount()); } else { lua_pushnil(L); } @@ -16204,7 +16741,7 @@ int LuaScriptInterface::luaSpellId(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getId()); + lua_pushinteger(L, spell->getId()); } else { spell->setId(getNumber(L, 2)); pushBoolean(L, true); @@ -16221,8 +16758,8 @@ int LuaScriptInterface::luaSpellGroup(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getGroup()); - lua_pushnumber(L, spell->getSecondaryGroup()); + lua_pushinteger(L, spell->getGroup()); + lua_pushinteger(L, spell->getSecondaryGroup()); return 2; } else if (lua_gettop(L) == 2) { SpellGroup_t group = getNumber(L, 2); @@ -16231,7 +16768,7 @@ int LuaScriptInterface::luaSpellGroup(lua_State* L) pushBoolean(L, true); } else if (isString(L, 2)) { group = stringToSpellGroup(getString(L, 2)); - if (group != SPELLGROUP_NONE) { + if (group != SPELLGROUP_UNKNOWN) { spell->setGroup(group); } else { std::cout << "[Warning - Spell::group] Unknown group: " << getString(L, 2) << std::endl; @@ -16253,7 +16790,7 @@ int LuaScriptInterface::luaSpellGroup(lua_State* L) pushBoolean(L, true); } else if (isString(L, 2) && isString(L, 3)) { primaryGroup = stringToSpellGroup(getString(L, 2)); - if (primaryGroup != SPELLGROUP_NONE) { + if (primaryGroup != SPELLGROUP_UNKNOWN) { spell->setGroup(primaryGroup); } else { std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) << std::endl; @@ -16261,7 +16798,7 @@ int LuaScriptInterface::luaSpellGroup(lua_State* L) return 1; } secondaryGroup = stringToSpellGroup(getString(L, 3)); - if (secondaryGroup != SPELLGROUP_NONE) { + if (secondaryGroup != SPELLGROUP_UNKNOWN) { spell->setSecondaryGroup(secondaryGroup); } else { std::cout << "[Warning - Spell::group] Unknown secondaryGroup: " << getString(L, 3) << std::endl; @@ -16287,7 +16824,7 @@ int LuaScriptInterface::luaSpellCooldown(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getCooldown()); + lua_pushinteger(L, spell->getCooldown()); } else { spell->setCooldown(getNumber(L, 2)); pushBoolean(L, true); @@ -16304,8 +16841,8 @@ int LuaScriptInterface::luaSpellGroupCooldown(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getGroupCooldown()); - lua_pushnumber(L, spell->getSecondaryCooldown()); + lua_pushinteger(L, spell->getGroupCooldown()); + lua_pushinteger(L, spell->getSecondaryCooldown()); return 2; } else if (lua_gettop(L) == 2) { spell->setGroupCooldown(getNumber(L, 2)); @@ -16327,7 +16864,7 @@ int LuaScriptInterface::luaSpellLevel(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getLevel()); + lua_pushinteger(L, spell->getLevel()); } else { spell->setLevel(getNumber(L, 2)); pushBoolean(L, true); @@ -16344,7 +16881,7 @@ int LuaScriptInterface::luaSpellMagicLevel(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getMagicLevel()); + lua_pushinteger(L, spell->getMagicLevel()); } else { spell->setMagicLevel(getNumber(L, 2)); pushBoolean(L, true); @@ -16361,7 +16898,7 @@ int LuaScriptInterface::luaSpellMana(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getMana()); + lua_pushinteger(L, spell->getMana()); } else { spell->setMana(getNumber(L, 2)); pushBoolean(L, true); @@ -16378,7 +16915,7 @@ int LuaScriptInterface::luaSpellManaPercent(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getManaPercent()); + lua_pushinteger(L, spell->getManaPercent()); } else { spell->setManaPercent(getNumber(L, 2)); pushBoolean(L, true); @@ -16395,7 +16932,7 @@ int LuaScriptInterface::luaSpellSoul(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getSoulCost()); + lua_pushinteger(L, spell->getSoulCost()); } else { spell->setSoulCost(getNumber(L, 2)); pushBoolean(L, true); @@ -16412,7 +16949,7 @@ int LuaScriptInterface::luaSpellRange(lua_State* L) Spell* spell = getUserdata(L, 1); if (spell) { if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getRange()); + lua_pushinteger(L, spell->getRange()); } else { spell->setRange(getNumber(L, 2)); pushBoolean(L, true); @@ -16781,7 +17318,7 @@ int LuaScriptInterface::luaSpellRuneLevel(lua_State* L) } if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getLevel()); + lua_pushinteger(L, spell->getLevel()); } else { spell->setLevel(level); pushBoolean(L, true); @@ -16806,7 +17343,7 @@ int LuaScriptInterface::luaSpellRuneMagicLevel(lua_State* L) } if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getMagicLevel()); + lua_pushinteger(L, spell->getMagicLevel()); } else { spell->setMagicLevel(magLevel); pushBoolean(L, true); @@ -16830,7 +17367,7 @@ int LuaScriptInterface::luaSpellRuneId(lua_State* L) } if (lua_gettop(L) == 1) { - lua_pushnumber(L, rune->getRuneItemId()); + lua_pushinteger(L, rune->getRuneItemId()); } else { rune->setRuneItemId(getNumber(L, 2)); pushBoolean(L, true); @@ -16854,7 +17391,7 @@ int LuaScriptInterface::luaSpellCharges(lua_State* L) } if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getCharges()); + lua_pushinteger(L, spell->getCharges()); } else { spell->setCharges(getNumber(L, 2)); pushBoolean(L, true); @@ -16984,9 +17521,9 @@ int LuaScriptInterface::luaActionRegister(lua_State* L) return 1; } pushBoolean(L, g_actions->registerLuaEvent(action)); - g_actions->clearItemIdRange(action); - g_actions->clearUniqueIdRange(action); - g_actions->clearActionIdRange(action); + action->clearActionIdRange(); + action->clearItemIdRange(); + action->clearUniqueIdRange(); } else { lua_pushnil(L); } @@ -17001,10 +17538,10 @@ int LuaScriptInterface::luaActionItemId(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_actions->addItemId(action, getNumber(L, 2 + i)); + action->addItemId(getNumber(L, 2 + i)); } } else { - g_actions->addItemId(action, getNumber(L, 2)); + action->addItemId(getNumber(L, 2)); } pushBoolean(L, true); } else { @@ -17021,10 +17558,10 @@ int LuaScriptInterface::luaActionActionId(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_actions->addActionId(action, getNumber(L, 2 + i)); + action->addActionId(getNumber(L, 2 + i)); } } else { - g_actions->addActionId(action, getNumber(L, 2)); + action->addActionId(getNumber(L, 2)); } pushBoolean(L, true); } else { @@ -17041,10 +17578,10 @@ int LuaScriptInterface::luaActionUniqueId(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_actions->addUniqueId(action, getNumber(L, 2 + i)); + action->addUniqueId(getNumber(L, 2 + i)); } } else { - g_actions->addUniqueId(action, getNumber(L, 2)); + action->addUniqueId(getNumber(L, 2)); } pushBoolean(L, true); } else { @@ -17344,20 +17881,19 @@ int LuaScriptInterface::luaMoveEventRegister(lua_State* L) MoveEvent* moveevent = getUserdata(L, 1); if (moveevent) { if ((moveevent->getEventType() == MOVE_EVENT_EQUIP || moveevent->getEventType() == MOVE_EVENT_DEEQUIP) && moveevent->getSlot() == SLOTP_WHEREEVER) { - uint32_t id = g_moveEvents->getItemIdRange(moveevent).at(0); + uint32_t id = moveevent->getItemIdRange().at(0); ItemType& it = Item::items.getItemType(id); moveevent->setSlot(it.slotPosition); } if (!moveevent->isScripted()) { pushBoolean(L, g_moveEvents->registerLuaFunction(moveevent)); - g_moveEvents->clearItemIdRange(moveevent); return 1; } pushBoolean(L, g_moveEvents->registerLuaEvent(moveevent)); - g_moveEvents->clearItemIdRange(moveevent); - g_moveEvents->clearActionIdRange(moveevent); - g_moveEvents->clearUniqueIdRange(moveevent); - g_moveEvents->clearPosList(moveevent); + moveevent->clearItemIdRange(); + moveevent->clearActionIdRange(); + moveevent->clearUniqueIdRange(); + moveevent->clearPosList(); } else { lua_pushnil(L); } @@ -17527,10 +18063,10 @@ int LuaScriptInterface::luaMoveEventItemId(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_moveEvents->addItemId(moveevent, getNumber(L, 2 + i)); + moveevent->addItemId(getNumber(L, 2 + i)); } } else { - g_moveEvents->addItemId(moveevent, getNumber(L, 2)); + moveevent->addItemId(getNumber(L, 2)); } pushBoolean(L, true); } else { @@ -17547,10 +18083,10 @@ int LuaScriptInterface::luaMoveEventActionId(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_moveEvents->addActionId(moveevent, getNumber(L, 2 + i)); + moveevent->addActionId(getNumber(L, 2 + i)); } } else { - g_moveEvents->addActionId(moveevent, getNumber(L, 2)); + moveevent->addActionId(getNumber(L, 2)); } pushBoolean(L, true); } else { @@ -17567,10 +18103,10 @@ int LuaScriptInterface::luaMoveEventUniqueId(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_moveEvents->addUniqueId(moveevent, getNumber(L, 2 + i)); + moveevent->addUniqueId(getNumber(L, 2 + i)); } } else { - g_moveEvents->addUniqueId(moveevent, getNumber(L, 2)); + moveevent->addUniqueId(getNumber(L, 2)); } pushBoolean(L, true); } else { @@ -17587,10 +18123,10 @@ int LuaScriptInterface::luaMoveEventPosition(lua_State* L) int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc if (parameters > 1) { for (int i = 0; i < parameters; ++i) { - g_moveEvents->addPosList(moveevent, getPosition(L, 2 + i)); + moveevent->addPosList(getPosition(L, 2 + i)); } } else { - g_moveEvents->addPosList(moveevent, getPosition(L, 2)); + moveevent->addPosList(getPosition(L, 2)); } pushBoolean(L, true); } else { diff --git a/src/luascript.h b/src/luascript.h index 8a150013..cbe57030 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -42,6 +42,27 @@ class Monster; class InstantSpell; class Spell; +template +concept EnumType = std::is_enum_v && !std::is_same_v; + +template +concept IntegerType = + std::is_integral_v + && !std::is_same_v + && !std::is_same_v + && !std::is_same_v + && !std::is_same_v + && (std::is_signed_v || std::is_unsigned_v); + +template +concept IntLuaType = EnumType || IntegerType; + +template +concept Boolean = std::is_same_v; + +template +concept StringType = std::is_same_v || std::is_same_v; + enum { EVENT_ID_LOADING = 1, EVENT_ID_USER = 1000, @@ -338,7 +359,6 @@ class LuaScriptInterface 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); @@ -398,16 +418,26 @@ class LuaScriptInterface 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) + + static void setField(lua_State* L, const char* index, std::floating_point auto value) { lua_pushnumber(L, value); lua_setfield(L, -2, index); } - static void setField(lua_State* L, const char* index, const std::string& value) + static void setField(lua_State* L, const char* index, IntLuaType auto value) + { + lua_pushinteger(L, value); + lua_setfield(L, -2, index); + } + + static void setField(lua_State* L, const char* index, Boolean auto value) + { + lua_pushboolean(L, value); + lua_setfield(L, -2, index); + } + + static void setField(lua_State* L, const char* index, StringType auto value) { pushString(L, value); lua_setfield(L, -2, index); @@ -775,12 +805,6 @@ class LuaScriptInterface static int luaItemSetStoreItem(lua_State* L); static int luaItemIsStoreItem(lua_State* L); - static int luaItemSetReflect(lua_State* L); - static int luaItemGetReflect(lua_State* L); - - static int luaItemSetBoostPercent(lua_State* L); - static int luaItemGetBoostPercent(lua_State* L); - static int luaItemGetImbuementSlots(lua_State* L); static int luaItemGetFreeImbuementSlots(lua_State* L); static int luaItemCanImbue(lua_State* L); @@ -793,6 +817,13 @@ class LuaScriptInterface static int luaItemRemoveImbuement(lua_State* L); static int luaItemGetImbuements(lua_State* L); + static int luaItemAddAugment(lua_State* L); + static int luaItemRemoveAugment(lua_State* L); + static int luaItemIsAugmented(lua_State* L); + static int luaItemHasAugment(lua_State* L); + static int luaItemGetAugments(lua_State* L); + + // Imbuement static int luaImbuementCreate(lua_State* L); @@ -811,6 +842,24 @@ class LuaScriptInterface static int luaImbuementIsEquipDecay(lua_State* L); static int luaImbuementIsInfightDecay(lua_State* L); + // DamageModifier + static int luaDamageModifierCreate(lua_State* L); + static int luaDamageModifierSetValue(lua_State* L); + static int luaDamageModifierSetRateFactor(lua_State* L); + static int luaDamageModifierSetCombatFilter(lua_State* L); + static int luaDamageModifierSetOriginFilter(lua_State* L); + + // Augment + static int luaAugmentCreate(lua_State* L); + static int luaAugmentSetName(lua_State* L); + static int luaAugmentSetDescription(lua_State* L); + static int luaAugmentGetName(lua_State* L); + static int luaAugmentGetDescription(lua_State* L); + static int luaAugmentAddDamageModifier(lua_State* L); + static int luaAugmentRemoveDamageModifier(lua_State* L); + static int luaAugmentGetAttackModifiers(lua_State* L); + static int luaAugmentGetDefenseModifiers(lua_State* L); + // Container static int luaContainerCreate(lua_State* L); @@ -1097,6 +1146,12 @@ class LuaScriptInterface static int luaPlayerSendCreatureSquare(lua_State* L); + static int luaPlayerAddAugment(lua_State* L); + static int luaPlayerRemoveAugment(lua_State* L); + static int luaPlayerIsAugmented(lua_State* L); + static int luaPlayerHasAugment(lua_State* L); + static int luaPlayerGetAugments(lua_State* L); + // Monster static int luaMonsterCreate(lua_State* L); @@ -1371,6 +1426,7 @@ class LuaScriptInterface static int luaMonsterTypeCreate(lua_State* L); static int luaMonsterTypeIsAttackable(lua_State* L); + static int luaMonsterTypeIsRewardBoss(lua_State* L); static int luaMonsterTypeIsChallengeable(lua_State* L); static int luaMonsterTypeIsConvinceable(lua_State* L); static int luaMonsterTypeIsSummonable(lua_State* L); diff --git a/src/map.cpp b/src/map.cpp index de24d641..1f3b56f1 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -407,9 +407,27 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo minRangeY = (minRangeY == 0 ? -maxViewportY : -minRangeY); maxRangeY = (maxRangeY == 0 ? maxViewportY : maxRangeY); - std::array cache_values{ - -maxViewportX , maxViewportX , -maxViewportY , maxViewportY - }; + chunkKey.minRangeX = minRangeX; + chunkKey.maxRangeX = maxRangeX; + chunkKey.minRangeY = minRangeY; + chunkKey.maxRangeY = maxRangeY; + chunkKey.x = centerPos.x; + chunkKey.y = centerPos.y; + chunkKey.z = centerPos.z; + chunkKey.multifloor = multifloor; + chunkKey.onlyPlayers = onlyPlayers; + + auto it = chunksSpectatorCache.find(chunkKey); + if (it != chunksSpectatorCache.end()) { + if (!spectators.empty()) { + spectators.addSpectators(it->second); + } else { + spectators = it->second; + } + foundCache = true; + } else { + cacheResult = true; + } if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && maxRangeY == maxViewportY && multifloor) { if (onlyPlayers) { @@ -467,11 +485,7 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); if (cacheResult) { - if (onlyPlayers) { - playersSpectatorCache[centerPos] = spectators; - } else { - spectatorCache[centerPos] = spectators; - } + chunksSpectatorCache.emplace(chunkKey, spectators); } } } diff --git a/src/map.h b/src/map.h index 5a7e824c..243a5b4f 100644 --- a/src/map.h +++ b/src/map.h @@ -34,6 +34,45 @@ static constexpr int32_t MAX_NODES = 512; static constexpr int32_t MAP_NORMALWALKCOST = 10; static constexpr int32_t MAP_DIAGONALWALKCOST = 25; +struct alignas(16) ChunkKey { + int32_t minRangeX, maxRangeX, minRangeY, maxRangeY; + uint16_t x, y; + uint8_t z; + bool multifloor, onlyPlayers; + + bool operator==(const ChunkKey& other) const noexcept { + return std::memcmp(this, &other, sizeof(ChunkKey)) == 0; + } +}; +static ChunkKey chunkKey; +struct ChunkKeyHash { + std::size_t operator()(const ChunkKey& key) const noexcept { + std::size_t hash = 0; + hash_combine(hash, key.minRangeX, key.maxRangeX, key.minRangeY, key.maxRangeY, + key.x, key.y, key.z, key.multifloor, key.onlyPlayers); + return hash; + } + +private: + template + static void hash_combine(std::size_t& seed, Args&&... args) { + (hash_combine_impl(seed, std::forward(args)), ...); + } + + template + static void hash_combine_impl(std::size_t& seed, const T& v) { + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } +}; + +struct ChunkKeyEqual { + bool operator()(const ChunkKey& lhs, const ChunkKey& rhs) const noexcept { + return std::memcmp(&lhs, &rhs, sizeof(ChunkKey)) == 0; + } +}; + +using ChunkCache = std::unordered_map; + class AStarNodes { public: @@ -169,6 +208,10 @@ class Map * \returns true if the map was loaded successfully */ bool loadMap(const std::string& identifier, bool loadHouses); + void clearChunkSpectatorCache() { + playersSpectatorCache.clear(); + chunksSpectatorCache.clear(); + } /** * Save a map. @@ -268,7 +311,7 @@ class Map private: SpectatorCache spectatorCache; SpectatorCache playersSpectatorCache; - + ChunkCache chunksSpectatorCache; QTreeNode root; std::filesystem::path spawnfile; diff --git a/src/monster.cpp b/src/monster.cpp index 9b70d963..3b914df8 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -309,7 +309,7 @@ void Monster::onCreatureSay(Creature* creature, SpeakClasses type, const std::st LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - lua_pushnumber(L, type); + lua_pushinteger(L, type); LuaScriptInterface::pushString(L, text); scriptInterface->callVoidFunction(4); @@ -736,7 +736,7 @@ void Monster::onThink(uint32_t interval) LuaScriptInterface::pushUserdata(L, this); LuaScriptInterface::setMetatable(L, -1, "Monster"); - lua_pushnumber(L, interval); + lua_pushinteger(L, interval); if (scriptInterface->callFunction(2)) { return; @@ -1842,116 +1842,102 @@ bool Monster::canWalkTo(Position pos, Direction direction) const void Monster::death(Creature*) { - + auto monsterId = getID(); + auto it = g_game.rewardBossTracking.find(monsterId); // rewardboss - if (getMonster()->isRewardBoss()) { - uint32_t monsterId = getMonster()->getID(); - auto& rewardBossContributionInfo = g_game.rewardBossTracking; - - auto it = rewardBossContributionInfo.find(monsterId); - if (it == rewardBossContributionInfo.end()) return; - - auto& scoreInfo = it->second; - - uint32_t mostScoreContributor = 0; - int32_t highestScore = 0; - int32_t totalScore = 0; - int32_t contributors = 0; - int32_t totalDamageDone = 0; - int32_t totalDamageTaken = 0; - int32_t totalHealingDone = 0; - - for (const auto& [playerId, scoreInfo] : scoreInfo.playerScoreTable) { - int32_t playerScore = scoreInfo.damageDone + scoreInfo.damageTaken + scoreInfo.healingDone; - totalScore += playerScore; - totalDamageDone += scoreInfo.damageDone; - totalDamageTaken += scoreInfo.damageTaken; - totalHealingDone += scoreInfo.healingDone; - contributors++; - - if (playerScore > highestScore) { - highestScore = playerScore; - mostScoreContributor = playerId; - } - } - - const auto& creatureLoot = mType->info.lootItems; - int64_t currentTime = time(nullptr); - - for (const auto& [playerId, scoreInfo] : rewardBossContributionInfo[monsterId].playerScoreTable) { - double damageDone = scoreInfo.damageDone; - double damageTaken = scoreInfo.damageTaken; - double healingDone = scoreInfo.healingDone; - - // Base loot rate calculation with zero checks - double contrubutionScore = 0; - if (damageDone > 0) { - contrubutionScore += damageDone; - } - if (damageTaken > 0) { - contrubutionScore += damageTaken; - } - if (healingDone > 0) { - contrubutionScore += healingDone; + if (it != g_game.rewardBossTracking.end()) { + if (isRewardBoss()) { + auto& bossScoreTable = it->second; + uint32_t topContributerId = 0; + int32_t topScore = 0; + int32_t totalScore = 0; + int32_t totalDamageDone = 0; + int32_t totalDamageTaken = 0; + int32_t totalHealingDone = 0; + int32_t contributors = bossScoreTable.playerScoreTable.size(); + + for (const auto& [playerId, score] : bossScoreTable.playerScoreTable) { + int32_t playerScore = score.damageDone + score.damageTaken + score.healingDone; + totalScore += playerScore; + totalDamageDone += score.damageDone; + totalDamageTaken += score.damageTaken; + totalHealingDone += score.healingDone; + + if (playerScore > topScore) { + topScore = playerScore; + topContributerId = playerId; + } } - double expectedScore = ((contrubutionScore / totalScore) * g_config.getFloat(ConfigManager::REWARD_BASE_RATE)); - double lootRate = std::min(expectedScore, 1.0); - - Player* player = g_game.getPlayerByGUID(playerId); - - auto rewardContainer = Item::CreateItem(ITEM_REWARD_CONTAINER)->getContainer(); - rewardContainer->setIntAttr(ITEM_ATTRIBUTE_DATE, currentTime); - rewardContainer->setIntAttr(ITEM_ATTRIBUTE_REWARDID, getMonster()->getID()); - - bool hasLoot = false; - - for (const auto& lootBlock : creatureLoot) { - float adjustedChance = (lootBlock.chance * lootRate) * g_config.getNumber(ConfigManager::RATE_LOOT); - - if (lootBlock.unique && mostScoreContributor == playerId) { - // Ensure that the mostScoreContributor can receive multiple unique items - auto lootItem = Item::CreateItem(lootBlock.id, uniform_random(1, lootBlock.countmax)); - lootItem->setIntAttr(ITEM_ATTRIBUTE_DATE, currentTime); - lootItem->setIntAttr(ITEM_ATTRIBUTE_REWARDID, getMonster()->getID()); - rewardContainer->internalAddThing(lootItem); - hasLoot = true; - } - else if (!lootBlock.unique) { - // Normal loot distribution for non-unique items - if (uniform_random(1, MAX_LOOTCHANCE) <= adjustedChance) { - auto lootItem = Item::CreateItem(lootBlock.id, uniform_random(1, lootBlock.countmax)); - lootItem->setIntAttr(ITEM_ATTRIBUTE_DATE, currentTime); - lootItem->setIntAttr(ITEM_ATTRIBUTE_REWARDID, getMonster()->getID()); - rewardContainer->internalAddThing(lootItem); - hasLoot = true; + const auto& creatureLoot = mType->info.lootItems; + int64_t currentTime = time(nullptr); + + for (const auto& [playerId, score] : bossScoreTable.playerScoreTable) { + + auto contributionScore = + (score.damageDone * g_config.getFloat(ConfigManager::REWARD_RATE_DAMAGE_DONE)) + + (score.damageTaken * g_config.getFloat(ConfigManager::REWARD_RATE_DAMAGE_TAKEN)) + + (score.healingDone * (g_config.getFloat(ConfigManager::REWARD_RATE_DAMAGE_DONE))); + // we should never see 0's here, but better safe than sorry. + float expectedScore = (contributionScore) ? (totalScore / (contributors * 3.0)) : 0; + int32_t lootRate = std::max(g_config.getFloat(ConfigManager::REWARD_BASE_RATE), 1.0); + + Player* player = g_game.getPlayerByGUID(playerId); + auto rewardContainer = Item::CreateItem(ITEM_REWARD_CONTAINER)->getContainer(); + rewardContainer->setIntAttr(ITEM_ATTRIBUTE_DATE, currentTime); + rewardContainer->setIntAttr(ITEM_ATTRIBUTE_REWARDID, getMonster()->getID()); + + bool hasLoot = false; + auto isTopPlayer = (playerId == topContributerId) ? true : false; + // we only need to confirm contribution counts because users can set specific types of contribution rates to 0 + // contribution only counts if you pull your own weight, so lets check expected score + if (contributionScore >= expectedScore) { + for (const auto& lootBlock : creatureLoot) { + if (!lootBlock.unique || (lootBlock.unique && isTopPlayer)) { + int32_t adjustedChance = (static_cast(lootBlock.chance) * lootRate); + + auto chance = uniform_random(1, MAX_LOOTCHANCE); + auto count = uniform_random(1, lootBlock.countmax); + + if (chance <= adjustedChance) { + auto lootItem = Item::CreateItem(lootBlock.id, count); + if (!lootItem->isStackable()) { + lootItem->setIntAttr(ITEM_ATTRIBUTE_DATE, currentTime); + lootItem->setIntAttr(ITEM_ATTRIBUTE_REWARDID, monsterId); + } + if (g_game.internalAddItem(rewardContainer, lootItem) == RETURNVALUE_NOERROR) { + hasLoot = true; + } + } + } + } + } else { + // player contributed but not enough. + if (player) { + player->sendTextMessage(MESSAGE_LOOT, "You did not receive any loot."); } } - } + if (hasLoot) { + if (player) { + if (g_game.internalAddItem(player->getRewardChest().getContainer(), rewardContainer) == RETURNVALUE_NOERROR) { + player->sendTextMessage(MESSAGE_LOOT, "The following items dropped by " + getMonster()->getName() + " are available in your reward chest: " + rewardContainer->getContentDescription() + "."); + } + } else { + DBInsert rewardQuery("INSERT INTO `player_rewarditems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`, `augments`) VALUES "); + PropWriteStream propWriteStream; + + ItemBlockList itemList; + int32_t currentPid = 1; + for (Item* subItem : rewardContainer->getItemList()) { + itemList.emplace_back(currentPid, subItem); + } - if (hasLoot) { - if (player) { - player->getRewardChest().internalAddThing(rewardContainer); - player->sendTextMessage(MESSAGE_LOOT, "The following items dropped by " + getMonster()->getName() + " are available in your reward chest: " + rewardContainer->getContentDescription() + "."); - } - else { - DBInsert rewardQuery("INSERT INTO `player_rewarditems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - PropWriteStream propWriteStream; - - ItemBlockList itemList; - int32_t currentPid = 1; - for (Item* subItem : rewardContainer->getItemList()) { - itemList.emplace_back(currentPid, subItem); + IOLoginData::addRewardItems(playerId, itemList, rewardQuery, propWriteStream); } - - IOLoginData::addRewardItems(playerId, itemList, rewardQuery, propWriteStream); } } - else if (player) { - player->sendTextMessage(MESSAGE_LOOT, "You did not receive any loot."); - } + g_game.resetDamageTracking(monsterId); } - g_game.resetDamageTracking(monsterId); } setAttackedCreature(nullptr); diff --git a/src/monster.h b/src/monster.h index 51ae0aca..73f74bae 100644 --- a/src/monster.h +++ b/src/monster.h @@ -64,6 +64,7 @@ class Monster final : public Creature } CreatureType_t getType() const override { + // to-do : write the logic for all the various summons return CREATURETYPE_MONSTER; } @@ -152,6 +153,9 @@ class Monster final : public Creature const CreatureHashSet& getFriendList() const { return friendList; } + CreatureHashSet& getFriendList() { + return friendList; + } bool isTarget(const Creature* creature) const; bool isFleeing() const { diff --git a/src/movement.cpp b/src/movement.cpp index 9830b7d6..577cc1dd 100644 --- a/src/movement.cpp +++ b/src/movement.cpp @@ -200,9 +200,9 @@ bool MoveEvents::registerLuaFunction(MoveEvent* event) } } - if (isValid(itemIdRange, event)) { - const auto& range = getItemIdRange(event); - for (auto& id : range) { + if (moveEvent->getItemIdRange().size() > 0) { + if (moveEvent->getItemIdRange().size() == 1) { + uint32_t id = moveEvent->getItemIdRange().at(0); addEvent(*moveEvent, id, itemIdMap); if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { ItemType& it = Item::items.getItemType(id); @@ -211,7 +211,18 @@ bool MoveEvents::registerLuaFunction(MoveEvent* event) it.minReqMagicLevel = moveEvent->getReqMagLv(); it.vocationString = moveEvent->getVocationString(); } - addEvent(*moveEvent, id, itemIdMap); + } else { + uint32_t iterId = 0; + while (++iterId < moveEvent->getItemIdRange().size()) { + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(moveEvent->getItemIdRange().at(iterId)); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + addEvent(*moveEvent, moveEvent->getItemIdRange().at(iterId), itemIdMap); + } } } else { return false; @@ -239,9 +250,9 @@ bool MoveEvents::registerLuaEvent(MoveEvent* event) } } - if (isValid(itemIdRange, event)) { - const auto& range = getItemIdRange(event); - for (auto& id : range) { + if (moveEvent->getItemIdRange().size() > 0) { + if (moveEvent->getItemIdRange().size() == 1) { + uint32_t id = moveEvent->getItemIdRange().at(0); addEvent(*moveEvent, id, itemIdMap); if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { ItemType& it = Item::items.getItemType(id); @@ -250,22 +261,48 @@ bool MoveEvents::registerLuaEvent(MoveEvent* event) it.minReqMagicLevel = moveEvent->getReqMagLv(); it.vocationString = moveEvent->getVocationString(); } - addEvent(*moveEvent, id, itemIdMap); + } else { + auto v = moveEvent->getItemIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(*i); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + addEvent(*moveEvent, *i, itemIdMap); + } } - } else if (isValid(actionIdRange, event)) { - const auto& range = getActionIdRange(event); - for (auto& id : range) { + } else if (moveEvent->getActionIdRange().size() > 0) { + if (moveEvent->getActionIdRange().size() == 1) { + int32_t id = moveEvent->getActionIdRange().at(0); addEvent(*moveEvent, id, actionIdMap); + } else { + auto v = moveEvent->getActionIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + addEvent(*moveEvent, *i, actionIdMap); + } } - } else if (isValid(uniqueIdRange, event)) { - const auto& range = getUniqueIdRange(event); - for (auto& id : range) { + } else if (moveEvent->getUniqueIdRange().size() > 0) { + if (moveEvent->getUniqueIdRange().size() == 1) { + int32_t id = moveEvent->getUniqueIdRange().at(0); addEvent(*moveEvent, id, uniqueIdMap); + } else { + auto v = moveEvent->getUniqueIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + addEvent(*moveEvent, *i, uniqueIdMap); + } } - } else if (isValidPos(posList, event)) { - const auto& range = getPosList(event); - for (auto& pos : range) { + } else if (moveEvent->getPosList().size() > 0) { + if (moveEvent->getPosList().size() == 1) { + Position pos = moveEvent->getPosList().at(0); addEvent(*moveEvent, pos, positionMap); + } else { + auto v = moveEvent->getPosList(); + for (auto i = v.begin(); i != v.end(); i++) { + addEvent(*moveEvent, *i, positionMap); + } } } else { return false; @@ -961,7 +998,7 @@ bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot, bool isCh LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); LuaScriptInterface::pushThing(L, item); - lua_pushnumber(L, slot); + lua_pushinteger(L, slot); LuaScriptInterface::pushBoolean(L, isCheck); return scriptInterface->callFunction(4); diff --git a/src/movement.h b/src/movement.h index b8f6544a..9b5a993c 100644 --- a/src/movement.h +++ b/src/movement.h @@ -1,8 +1,8 @@ // Copyright 2024 Black Tek 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 +#ifndef FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 +#define FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 #include "baseevents.h" #include "item.h" @@ -51,31 +51,6 @@ class MoveEvents final : public BaseEvents MoveEvent* getEvent(Item* item, MoveEvent_t eventType); - bool isValid(std::map> map, MoveEvent* event) - { - return map.find(event) != map.end(); - } - bool isValidPos(std::map> map, MoveEvent* event) - { - return map.find(event) != map.end(); - } - - void clearItemIdRange(MoveEvent* event) { itemIdRange.erase(event); } - const std::vector& getItemIdRange(MoveEvent* event) const { return itemIdRange.at(event); } - void addItemId(MoveEvent* event, uint32_t id) { itemIdRange[event].emplace_back(id); } - - void clearActionIdRange(MoveEvent* event) { actionIdRange.erase(event); } - const std::vector& getActionIdRange(MoveEvent* event) const { return actionIdRange.at(event); } - void addActionId(MoveEvent* event, uint32_t id) { actionIdRange[event].emplace_back(id); } - - void clearUniqueIdRange(MoveEvent* event) { uniqueIdRange.erase(event); } - const std::vector& getUniqueIdRange(MoveEvent* event) const { return uniqueIdRange.at(event); } - void addUniqueId(MoveEvent* event, uint32_t id) { uniqueIdRange[event].emplace_back(id); } - - void clearPosList(MoveEvent* event) { posList.erase(event); } - const std::vector& getPosList(MoveEvent* event) const { return posList.at(event); } - void addPosList(MoveEvent* event, Position pos) { posList[event].emplace_back(pos); } - bool registerLuaEvent(MoveEvent* event); bool registerLuaFunction(MoveEvent* event); void clear(bool fromLua) override final; @@ -102,10 +77,6 @@ class MoveEvents final : public BaseEvents MoveListMap actionIdMap; MoveListMap itemIdMap; MovePosListMap positionMap; - std::map> itemIdRange; - std::map> actionIdRange; - std::map> uniqueIdRange; - std::map> posList; LuaScriptInterface scriptInterface; }; diff --git a/src/npc.cpp b/src/npc.cpp index 5196763e..758cded6 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -712,10 +712,10 @@ int NpcScriptInterface::luagetDistanceTo(lua_State* L) const Position& thingPos = thing->getPosition(); const Position& npcPos = npc->getPosition(); if (npcPos.z != thingPos.z) { - lua_pushnumber(L, -1); + lua_pushinteger(L, -1); } else { int32_t dist = std::max(Position::getDistanceX(npcPos, thingPos), Position::getDistanceY(npcPos, thingPos)); - lua_pushnumber(L, dist); + lua_pushinteger(L, dist); } return 1; } @@ -735,7 +735,7 @@ int NpcScriptInterface::luaGetNpcCid(lua_State* L) //getNpcCid() Npc* npc = getScriptEnv()->getNpc(); if (npc) { - lua_pushnumber(L, npc->getID()); + lua_pushinteger(L, npc->getID()); } else { lua_pushnil(L); } @@ -913,7 +913,7 @@ int NpcScriptInterface::luaDoSellItem(lua_State* L) if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) { delete item; - lua_pushnumber(L, sellCount); + lua_pushinteger(L, sellCount); return 1; } @@ -929,7 +929,7 @@ int NpcScriptInterface::luaDoSellItem(lua_State* L) if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) { delete item; - lua_pushnumber(L, sellCount); + lua_pushinteger(L, sellCount); return 1; } @@ -937,7 +937,7 @@ int NpcScriptInterface::luaDoSellItem(lua_State* L) } } - lua_pushnumber(L, sellCount); + lua_pushinteger(L, sellCount); return 1; } @@ -1197,7 +1197,7 @@ void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, cons scriptInterface->pushFunction(creatureSayEvent); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - lua_pushnumber(L, type); + lua_pushinteger(L, type); LuaScriptInterface::pushString(L, text); scriptInterface->callFunction(3); } @@ -1223,9 +1223,9 @@ void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t LuaScriptInterface::pushCallback(L, callback); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, itemId); - lua_pushnumber(L, count); - lua_pushnumber(L, amount); + lua_pushinteger(L, itemId); + lua_pushinteger(L, count); + lua_pushinteger(L, amount); LuaScriptInterface::pushBoolean(L, ignore); LuaScriptInterface::pushBoolean(L, inBackpacks); scriptInterface->callFunction(6); diff --git a/src/otserv.cpp b/src/otserv.cpp index c13024ce..dee7b2ef 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -21,6 +21,7 @@ #include "script.h" #include #include +#include "augments.h" #if __has_include("gitmetadata.h") #include "gitmetadata.h" #endif @@ -97,44 +98,42 @@ int main(int argc, char* argv[]) return 0; } +#include +#include + 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 << "*** DIRTY - NOT OFFICIAL RELEASE ***" << std::endl; - #endif -#else - std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; -#endif - std::cout << std::endl; - - std::cout << "Compiled with " << BOOST_COMPILER << std::endl; - std::cout << "Compiled on " << __DATE__ << ' ' << __TIME__ << " for platform "; -#if defined(__amd64__) || defined(_M_X64) - std::cout << "x64" << std::endl; -#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) - std::cout << "x86" << std::endl; -#elif defined(__arm__) - std::cout << "ARM" << std::endl; -#else - std::cout << "unknown" << std::endl; + std::cout << STATUS_SERVER_NAME << std::endl; + std::cout << " 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 << " Status: Unofficial (dirty version)" << std::endl; #endif -#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 << "|| " << STATUS_SERVER_NAME << " ||" << std::endl; + std::cout << " Version: " << STATUS_SERVER_VERSION << std::endl; + std::cout << " Status: Official (clean version)" << std::endl; #endif - std::cout << std::endl; - std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl; - std::cout << std::endl; + // todo: make a function.. printPlatformInfo() + + std::cout << " Compiler: " << BOOST_COMPILER << std::endl; + std::cout << " Date: " << __DATE__ << ' ' << __TIME__ << " " << std::endl; + + // Todo: determine if there is more information worth having here. + std::cout << " Developer: " << STATUS_SERVER_DEVELOPERS << std::endl; + std::cout << " Maintainer: " << STATUS_SERVER_MAINTAINER << std::endl; + std::cout << " Community: " << STATUS_SERVER_COMMUNITY_LINK << std::endl; + std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl; } void mainLoader(int, char*[], ServiceManager* services) -{ - //dispatcher thread +{ // Todo : Rewrite all the loading prints to print confirmation. + // : write timings for each thing and all things. + // : colorize all the prints. + + // dispatcher thread g_game.setGameState(GAME_STATE_STARTUP); srand(static_cast(OTSYS_TIME())); @@ -168,6 +167,7 @@ void mainLoader(int, char*[], ServiceManager* services) } // read global config + std::cout << ":: Initializing Game Server..." << std::endl; std::cout << ">> Loading config" << std::endl; if (!g_config.load()) { startupErrorMessage("Unable to load " + configFile + "!"); @@ -286,6 +286,9 @@ void mainLoader(int, char*[], ServiceManager* services) return; } + std::cout << ">> Loading augments" << std::endl; + Augments::loadAll(); + std::cout << ">> Initializing gamestate" << std::endl; g_game.setGameState(GAME_STATE_INIT); diff --git a/src/player.cpp b/src/player.cpp index b08759e0..c32091a5 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -17,6 +17,8 @@ #include "scheduler.h" #include "weapons.h" #include "rewardchest.h" +#include "player.h" +#include "spells.h" extern ConfigManager g_config; extern Game g_game; @@ -31,6 +33,653 @@ MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; +// Stuff needed for combat situations + +using RawArea = std::vector; +using RawAreaVec = std::vector; +using DeflectionEffectMap = std::unordered_map; +using DeflectAreaMap = std::unordered_map; + +static const DeflectionEffectMap _StandardDeflectionMap = DeflectionEffectMap{ + {1, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {2, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 2, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {3, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {4, {{0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {5, {{0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {6, {{0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}} +}; + + +static const DeflectionEffectMap _DiagonalDeflectionMap = DeflectionEffectMap{ + {1, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + {2, { // Double Diagonal + {0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 2, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0} + }}, + {3, { // Triple Diagonal + {0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0} + }}, + {4, { // Quad Diagonal + {0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, + 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, + 0, 1, 1, 0, 0, + 0, 1, 2, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0} + }}, + {5, { // Quint Diagonal + {0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 1, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, + 1, 1, 2, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0} + }}, + {6, { // Sext Diagonal + {0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, + 1, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 1, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 1, 2, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0} + }} +}; + +static const DeflectAreaMap DeflectAreas = DeflectAreaMap{ + {DIRECTION_NORTH, _StandardDeflectionMap}, + {DIRECTION_SOUTH, _StandardDeflectionMap}, + {DIRECTION_WEST, _StandardDeflectionMap}, + {DIRECTION_EAST, _StandardDeflectionMap}, + {DIRECTION_NORTHWEST, _DiagonalDeflectionMap}, + {DIRECTION_NORTHEAST, _DiagonalDeflectionMap}, + {DIRECTION_SOUTHWEST, _DiagonalDeflectionMap}, + {DIRECTION_SOUTHEAST, _DiagonalDeflectionMap}, +}; + +std::unordered_map deflectionAreas = { + {1, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {2, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 2, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {3, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {4, {{0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {5, {{0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + + {6, {{0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}} +}; + +std::unordered_map deflectionDiagonalAreas = { + {1, {{0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}}}, + {2, { // Double Diagonal + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 2, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0} + }}, + {3, { // Triple Diagonal + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0} + }}, + {4, { // Quad Diagonal + {0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0} + }}, + {5, { // Quint Diagonal + {0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 1, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 3, 1, 0, + 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0} + }}, + {6, { // Sext Diagonal + {0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, + 1, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 1, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 3, 1, 0, + 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0} + }} +}; + + + +// double Diagonal +RawAreaVec DeflectDiagonal2xAreas = { + { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 2, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0 + }, +}; + +// triple Diagonal +RawAreaVec DeflectDiagonal3xAreas = { + { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0 + } +}; + +// quad Diagonal +RawAreaVec DeflectDiagonal4xAreas = { + { + 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0 + } +}; + +// quint Diagonal +RawAreaVec DeflectDiagonal5xAreas = { + { + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 1, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 3, 1, 0, + 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0 + } +}; + +// sext Diagonal +RawAreaVec DeflectDiagonal6xAreas = { + { + 0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, + 1, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 1, 3, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 3, 1, 0, + 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0 + } +}; + + +// single +RawArea Deflect1xArea = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +// doubles +RawAreaVec Deflect2xAreas = { + { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 2, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, +}; + + +// triple +RawArea Deflect3xArea = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + + + +// quad +RawAreaVec Deflect4xAreas = { + { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + } +}; + + + +// quint (5's) +RawAreaVec Deflect5xAreas = { + { + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + } +}; + + +// sext (6's) +RawAreaVec Deflect6xAreas = { + { + 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 3, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0 + }, + + { + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 1, 3, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 + } +}; + + + +static std::vector GetDeflectArea(uint32_t targets) { + switch (targets) { + case 1: + return Deflect1xArea; + case 2: + return Deflect2xAreas[uniform_random(0, Deflect2xAreas.size() - 1)]; + case 3: + return Deflect3xArea; + case 4: + return Deflect4xAreas[uniform_random(0, Deflect4xAreas.size() - 1)]; + case 5: + return Deflect5xAreas[uniform_random(0, Deflect5xAreas.size() - 1)]; + default: + return Deflect6xAreas[uniform_random(0, Deflect6xAreas.size() - 1)]; + } +} + +static std::vector GetDiaganolDeflectArea(uint32_t targets) { + switch (targets) { + case 1: + return Deflect1xArea; + case 2: + return DeflectDiagonal2xAreas[uniform_random(0, DeflectDiagonal2xAreas.size() - 1)]; + case 3: + return DeflectDiagonal3xAreas[uniform_random(0, DeflectDiagonal3xAreas.size() - 1)]; + case 4: + return DeflectDiagonal4xAreas[uniform_random(0, DeflectDiagonal4xAreas.size() - 1)]; + case 5: + return DeflectDiagonal5xAreas[uniform_random(0, DeflectDiagonal5xAreas.size() - 1)]; + default: + return DeflectDiagonal6xAreas[uniform_random(0, DeflectDiagonal6xAreas.size() - 1)]; + } +} + + Player::Player(ProtocolGame_ptr p) : Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)), inbox(new Inbox(ITEM_INBOX)), storeInbox(new StoreInbox(ITEM_STORE_INBOX)) { @@ -762,7 +1411,7 @@ bool Player::canSeeGhostMode(const Creature*) const bool Player::canWalkthrough(const Creature* creature) const { - if (group->access || creature->isInGhostMode()) { + if (group->access || creature->isInGhostMode() || (g_config.getBoolean(ConfigManager::ALLOW_WALKTHROUGH) && creature->getPlayer() && creature->getPlayer()->isAccessPlayer())) { return true; } @@ -1988,8 +2637,7 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ } if (!ignoreResistances) { - Reflect reflect; - + size_t combatIndex = combatTypeToIndex(combatType); for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { if (!isItemAbilityEnabled(static_cast(slot))) { @@ -2007,81 +2655,14 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ damage = 0; return BLOCK_ARMOR; } - continue; } - const int16_t& absorbPercent = it.abilities->absorbPercent[combatIndex]; - if (absorbPercent != 0) { - damage -= std::round(damage * (absorbPercent / 100.)); - - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); - } - } - - reflect += item->getReflect(combatType); - - if (field) { - const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatIndex]; - if (fieldAbsorbPercent != 0) { - damage -= std::round(damage * (fieldAbsorbPercent / 100.)); - - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); - } - } - } - - if (item->hasImbuements()) { - for (auto imbuement : item->getImbuements()) { - switch (imbuement->imbuetype) { - case ImbuementType::IMBUEMENT_TYPE_FIRE_RESIST: - if (combatType == COMBAT_FIREDAMAGE) { - damage -= std::round(damage * (imbuement->value / 100.)); - } - break; - case ImbuementType::IMBUEMENT_TYPE_EARTH_RESIST: - if (combatType == COMBAT_EARTHDAMAGE) { - damage -= std::round(damage * (imbuement->value / 100.)); - } - break; - case ImbuementType::IMBUEMENT_TYPE_ICE_RESIST: - if (combatType == COMBAT_ICEDAMAGE) { - damage -= std::round(damage * (imbuement->value / 100.)); - } - break; - case ImbuementType::IMBUEMENT_TYPE_ENERGY_RESIST: - if (combatType == COMBAT_ENERGYDAMAGE) { - damage -= std::round(damage * (imbuement->value / 100.)); - } - break; - case ImbuementType::IMBUEMENT_TYPE_DEATH_RESIST: - if (combatType == COMBAT_DEATHDAMAGE) { - damage -= std::round(damage * (imbuement->value / 100.)); - } - break; - case ImbuementType::IMBUEMENT_TYPE_HOLY_RESIST: - if (combatType == COMBAT_HOLYDAMAGE) { - damage -= std::round(damage * (imbuement->value / 100.)); - } - break; - } - } + uint16_t charges = item->getCharges(); + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges - 1); } - } - - 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) { @@ -2255,27 +2836,24 @@ Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) { Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); if (corpse && corpse->getContainer()) { - std::unordered_map names; - for (const auto& killer : getKillers()) { - ++names[killer->getName()]; - } + size_t killersSize = getKillers().size(); if (lastHitCreature) { 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." : ".")); + corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by {:s}{:s}", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", lastHitCreature->getNameDescription(), killersSize > 1 ? " and others." : ".")); + } else if (lastHitCreature != mostDamageCreature) { + corpse->setSpecialDescription(fmt::format("You recognize {:s}. {:s} was killed by {:s}, {:s}{:s}", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", mostDamageCreature->getNameDescription(), lastHitCreature->getNameDescription(), killersSize > 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) { + if (killersSize > 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 { - 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(fmt::format("You recognize {:s}. {:s} was killed by something evil {:s}", getNameDescription(), getSex() == PLAYERSEX_FEMALE ? "She" : "He", killersSize ? " and others." : ".")); } } return corpse; @@ -3872,6 +4450,32 @@ void Player::changeSoul(int32_t soulChange) sendStats(); } +// to-do: add internal protection for usage of this method and the next one. +void Player::addSoul(uint8_t gain) +{ + if (gain > 0) { + soul += gain; + } + sendStats(); +} + +void Player::addStamina(uint16_t gain) +{ + if (gain > 0) { + staminaMinutes += gain; + } + sendStats(); +} + +void Player::changeStamina(int32_t amount) +{ + if (amount > 0) { + staminaMinutes += std::min(amount, 2520 - staminaMinutes); + } else { + staminaMinutes = std::max(0, staminaMinutes + amount); + } +} + bool Player::canWear(uint32_t lookType, uint8_t addons) const { if (group->access) { @@ -4734,6 +5338,104 @@ size_t Player::getMaxDepotItems() const return g_config.getNumber(isPremium() ? ConfigManager::DEPOT_PREMIUM_LIMIT : ConfigManager::DEPOT_FREE_LIMIT); } +const bool Player::addAugment(std::shared_ptr& augment) { + if (std::find(augments.begin(), augments.end(), augment) == augments.end()) { + augments.push_back(augment); + g_events->eventPlayerOnAugment(this, augment); + return true; + } + return false; +} + +const bool Player::addAugment(std::string_view augmentName) { + + if (auto augment = Augments::GetAugment(augmentName)) { + augments.emplace_back(augment); + g_events->eventPlayerOnAugment(this, augment); + return true; + } + return false; +} + +const bool Player::removeAugment(std::shared_ptr& augment) { + auto it = std::find(augments.begin(), augments.end(), augment); + if (it != augments.end()) { + g_events->eventPlayerOnRemoveAugment(this, augment); + augments.erase(it); + return true; + } + return false; +} + +const bool Player::isAugmented() +{ + return augments.size() > 0; +} + +const bool Player::hasAugment(const std::string_view augmentName, bool checkItems) +{ + for (const auto& augment : augments) { + if (augment->getName() == augmentName) { + return true; + } + } + + if (checkItems) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + for (const auto& aug : item->getAugments()) { + if (aug->getName() == augmentName) { + return true; + } + } + } + } + + return false; +} + +const bool Player::hasAugment(const std::shared_ptr& augment, bool checkItems) +{ + for (const auto& aug : augments) { + if (aug == augment) { + return true; + } + } + + if (checkItems) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + for (const auto& aug : item->getAugments()) { + if (aug == augment) { + return true; + } + } + } + } + + return false; +} + +const std::vector> Player::getPlayerAugments() const { + return augments; +} + +const bool Player::removeAugment(std::string_view augmentName) { + auto originalSize = augments.size(); + + augments.erase(std::remove_if(augments.begin(), augments.end(), + [&](const std::shared_ptr& augment) { + auto match = augment->getName() == augmentName; + if (match) { + g_events->eventPlayerOnRemoveAugment(this, augment); + } + return augment->getName() == augmentName; + }), augments.end()); + + return augments.size() > originalSize; +} + + std::forward_list Player::getMuteConditions() const { std::forward_list muteConditions; @@ -4930,8 +5632,7 @@ void Player::removeItemImbuements(Item* item) { void Player::removeImbuementEffect(std::shared_ptr imbue) { - - + if (imbue->isSkill()) { switch (imbue->imbuetype) { case ImbuementType::IMBUEMENT_TYPE_FIST_SKILL: @@ -4994,7 +5695,6 @@ void Player::removeImbuementEffect(std::shared_ptr imbue) { void Player::addImbuementEffect(std::shared_ptr imbue) { - if (imbue->isSkill()) { switch (imbue->imbuetype) { case ImbuementType::IMBUEMENT_TYPE_FIST_SKILL: @@ -5202,3 +5902,750 @@ void Player::addAutoLootItems(Item* item) "Not enough space in the assigned backpack. The {} has been sent to your main backpack.", itemName)); } } + +CreatureType_t Player::getCreatureType(Monster& monster) { + auto creatureType = CREATURETYPE_MONSTER; + if (monster.getFriendList().size() > 0) { + for (auto monsterFriend : monster.getFriendList()) { + if (Player* ally = monsterFriend->getPlayer()) { + + if (ally->getGuild() && this->getGuild() && ally->getGuild() == this->getGuild()) { + creatureType = CREATURETYPE_SUMMON_GUILD; + } + + if (ally->getParty() && this->getParty() && ally->getParty() == this->getParty()) { + creatureType = CREATURETYPE_SUMMON_PARTY; + } + } + } + } + + if (monster.getMaster() && monster.getMaster()->getID() == this->getID()) { + creatureType = CREATURETYPE_SUMMON_OWN; + } + return creatureType; +} + +static ModifierTotals getValidatedTotals(const std::vector> modifierList, const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) { + uint16_t percent = 0; + uint16_t flat = 0; + // to-do: const and auto& + for (auto& modifier : modifierList) { + + if (modifier->appliesToDamage(damageType) && modifier->appliesToOrigin(originType) && modifier->appliesToTarget(creatureType, race, creatureName)) { + if (modifier->isFlatValue() && modifier->getChance() == 0 || modifier->isFlatValue() && modifier->getChance() == 100) { + flat += modifier->getValue(); + continue; + } else if (modifier->isFlatValue()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + flat += modifier->getValue(); + continue; + } + } + + if (modifier->isPercent() && modifier->getChance() == 0 || modifier->isPercent() && modifier->getChance() == 100) { + percent += modifier->getValue(); + continue; + } else if (modifier->isPercent()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + percent += modifier->getValue(); + continue; + } + } + } + } + percent = std::clamp(percent, 0, 100); + return ModifierTotals(flat, percent); +} + +std::unordered_map >> Player::getAttackModifiers() { + std::unordered_map>> modifierMap; + + if (!augments.empty()) { + for (auto& aug : augments) { + for (auto mod : aug->getAttackModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + + for (uint8_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_RING; ++slot) { + Item* item = inventory[slot]; + if (item && !item->getAugments().empty()) { + for (auto& aug : item->getAugments()) { + if (!g_config.getBoolean(ConfigManager::AUGMENT_SLOT_PROTECTION) || (item->getEquipSlot() == getPositionForSlot(static_cast(slot)))) { + for (auto mod : aug->getAttackModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } else if ( g_config.getBoolean(ConfigManager::AUGMENT_SLOT_PROTECTION) && (slot == CONST_SLOT_RIGHT || slot == CONST_SLOT_LEFT) && (item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_AMMO)) { + for (auto mod : aug->getAttackModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + } + } + + return modifierMap; +} + +std::unordered_map >> Player::getDefenseModifiers() { + std::unordered_map>> modifierMap; + + if (!augments.empty()) { + for (auto& aug : augments) { + for (auto mod : aug->getDefenseModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + + for (uint8_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_RING; ++slot) { + Item* item = inventory[slot]; + if (item && !item->getAugments().empty()) { + for (auto& aug : item->getAugments()) { + if (!g_config.getBoolean(ConfigManager::AUGMENT_SLOT_PROTECTION) || (item->getEquipSlot() == getPositionForSlot(static_cast(slot)))) { + for (auto mod : aug->getDefenseModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } else if (g_config.getBoolean(ConfigManager::AUGMENT_SLOT_PROTECTION) && (slot == CONST_SLOT_RIGHT || slot == CONST_SLOT_LEFT) && (item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_AMMO)) { + for (auto mod : aug->getDefenseModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + } + } + + return modifierMap; +} + +std::unordered_map Player::getConvertedTotals(const uint8_t modType, const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) +{ + std::unordered_map playerList; + playerList.reserve(COMBAT_COUNT); + + std::unordered_map itemList; + itemList.reserve(COMBAT_COUNT); + + [[unlikely]]if ((modType != ATTACK_MODIFIER_CONVERSION) && (modType != DEFENSE_MODIFIER_REFORM)) { + std::cout << "::: WARNING Player::getConvertedTotals called with invalid Mod Type! \n"; + return playerList; + } + + if (!augments.empty()) { + for (const auto& aug : augments) { + const auto& modifiers = modType == ATTACK_MODIFIER_CONVERSION ? aug->getAttackModifiers(modType) : aug->getDefenseModifiers(modType); + for (const auto& modifier : modifiers) { + if (modifier->appliesToDamage(damageType) && modifier->appliesToOrigin(originType) && modifier->appliesToTarget(creatureType, race, creatureName)) { + + uint16_t flat = 0; + uint16_t percent = 0; + + if (modifier->isFlatValue() && modifier->getChance() == 0 || modifier->isFlatValue() && modifier->getChance() == 100) { + flat += modifier->getValue(); + } else if (modifier->isFlatValue()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + flat += modifier->getValue(); + } + } + + if (modifier->isPercent() && modifier->getChance() == 0 || modifier->isPercent() && modifier->getChance() == 100) { + percent += modifier->getValue(); + } else if (modifier->isPercent()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + percent += modifier->getValue(); + } + } + + percent = std::min(percent, 100); + auto index = combatTypeToIndex(modifier->getConversionType()); + auto [it, inserted] = playerList.try_emplace(index, ModifierTotals{flat, percent}); + if (!inserted) { + it->second += ModifierTotals{flat, percent}; + } + } + } + } + } + + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + if (item && !item->getAugments().empty()) { + for (const auto& aug : item->getAugments()) { + const auto& modifiers = modType == ATTACK_MODIFIER_CONVERSION ? aug->getAttackModifiers(modType) : aug->getDefenseModifiers(modType); + for (const auto& modifier : modifiers) { + if (modifier->appliesToDamage(damageType) && modifier->appliesToOrigin(originType) && modifier->appliesToTarget(creatureType, race, creatureName)) { + + uint16_t flat = 0; + uint16_t percent = 0; + + if (modifier->isFlatValue() && modifier->getChance() == 0 || modifier->isFlatValue() && modifier->getChance() == 100) { + flat += modifier->getValue(); + } else if (modifier->isFlatValue()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + flat += modifier->getValue(); + } + } + + if (modifier->isPercent() && modifier->getChance() == 0 || modifier->isPercent() && modifier->getChance() == 100) { + percent += modifier->getValue(); + } else if (modifier->isPercent()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + percent += modifier->getValue(); + } + } + + percent = std::min(percent, 100); + auto index = combatTypeToIndex(modifier->getConversionType()); + auto [it, inserted] = playerList.try_emplace(index, ModifierTotals{flat, percent}); + if (!inserted) { + it->second += ModifierTotals{flat, percent}; + } + } + } + } + } + } + + return playerList; +} + +std::unordered_map Player::getAttackModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) { + + std::unordered_map modMap; + modMap.reserve(ATTACK_MODIFIER_LAST); + + auto attackMods = getAttackModifiers(); + for (uint8_t i = ATTACK_MODIFIER_NONE; i < ATTACK_MODIFIER_LAST; ++i) { + auto modTotals = getValidatedTotals(attackMods[i], damageType, originType, creatureType, race, creatureName); + modMap.try_emplace(i, modTotals); + } + return modMap; +} + +std::unordered_map Player::getDefenseModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, std::string_view creatureName) { + + std::unordered_map modMap; + modMap.reserve(DEFENSE_MODIFIER_LAST); + + auto defenseMods = getDefenseModifiers(); + for (uint8_t i = DEFENSE_MODIFIER_FIRST; i < DEFENSE_MODIFIER_LAST; ++i) { + auto modTotals = getValidatedTotals(defenseMods[i], damageType, originType, creatureType, race, creatureName); + modMap.try_emplace(i, modTotals); + } + return modMap; +} + +std::vector Player::getOpenPositionsInRadius(int radius) const { + std::vector openPositions; + const auto& center = getPosition(); + for (int x = -radius; x <= radius; ++x) { + for (int y = -radius; y <= radius; ++y) { + Position pos(center.x + x, center.y + y, center.z); + if (pos.z != center.z) { + continue; // make sure its same floor, maybe in future we remove this check XD + } + + auto tile = g_game.map.getTile(pos); + const bool isValid = tile + && g_game.canThrowObjectTo(center, pos) + && !tile->getZone() == ZONE_PROTECTION + && !tile->hasFlag(TILESTATE_PROTECTIONZONE + | TILESTATE_FLOORCHANGE + | TILESTATE_TELEPORT + | TILESTATE_IMMOVABLEBLOCKSOLID + | TILESTATE_NOPVPZONE + | TILESTATE_IMMOVABLEBLOCKPATH + | TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + + if (isValid) { + openPositions.push_back(pos); + } + } + } + + return openPositions; +} + +void Player::absorbDamage(std::optional> attacker, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat) { + int32_t absorbDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + absorbDamage += originalDamageValue * percent / 100; + } + if (flat) { + absorbDamage += flat; + } + + if (absorbDamage != 0) { + absorbDamage = std::min(absorbDamage, originalDamageValue); + originalDamage.primary.value += absorbDamage; + + auto absorb = CombatDamage{}; + absorb.leeched = true; + absorb.origin = ORIGIN_AUGMENT; + absorb.primary.type = COMBAT_HEALING; + absorb.primary.value = absorbDamage; + + auto absorbParams = CombatParams{}; + absorbParams.origin = ORIGIN_AUGMENT; + absorbParams.combatType = COMBAT_HEALING; + absorbParams.impactEffect = CONST_ME_MAGIC_RED; + absorbParams.distanceEffect = CONST_ANI_NONE; + + if (!attacker.has_value()) { + Combat::doTargetCombat(nullptr, this, absorb, absorbParams); + return; + } + + Combat::doTargetCombat(&attacker.value().get(), this, absorb, absorbParams); + } +} + +void Player::restoreManaFromDamage(std::optional> attacker, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat) { + int32_t restoreDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + restoreDamage += originalDamageValue * percent / 100; + } + if (flat) { + restoreDamage += flat; + } + + if (restoreDamage != 0) { + restoreDamage = std::min(restoreDamage, originalDamageValue); + originalDamage.primary.value += restoreDamage; + + auto restore = CombatDamage{}; + restore.leeched = true; + restore.origin = ORIGIN_AUGMENT; + restore.primary.type = COMBAT_MANADRAIN; + restore.primary.value = restoreDamage; + + auto restoreParams = CombatParams{}; + restoreParams.origin = ORIGIN_AUGMENT; + restoreParams.combatType = COMBAT_MANADRAIN; + restoreParams.impactEffect = CONST_ME_ENERGYHIT; + restoreParams.distanceEffect = CONST_ANI_NONE; + + if (!attacker.has_value()) { + Combat::doTargetCombat(nullptr, this, restore, restoreParams); + return; + } + + Combat::doTargetCombat(&attacker.value().get(), this, restore, restoreParams); + } +} + +void Player::reviveSoulFromDamage(std::optional> attacker, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat) { + int32_t reviveDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + reviveDamage += originalDamageValue * percent / 100; + } + if (flat) { + reviveDamage += flat; + } + + if (reviveDamage != 0) { + reviveDamage = std::min(reviveDamage, originalDamageValue); + originalDamage.primary.value += reviveDamage; + + auto message = (attacker.has_value()) ? + "You gained " + std::to_string(reviveDamage) + " soul from " + attacker.value().get().getName() + "'s attack." : + "You gained " + std::to_string(reviveDamage) + " soul from revival."; + + sendTextMessage(MESSAGE_HEALED, message); + changeSoul(reviveDamage); + } +} + +void Player::replenishStaminaFromDamage(std::optional> attacker, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat) { + int32_t replenishDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + replenishDamage += originalDamageValue * percent / 100; + } + if (flat) { + replenishDamage += flat; + } + + if (replenishDamage != 0) { + replenishDamage = std::min(replenishDamage, originalDamageValue); + originalDamage.primary.value += replenishDamage; + + if (!g_config.getBoolean(ConfigManager::AUGMENT_STAMINA_RULE)) { + replenishDamage = replenishDamage / 60; + } + + auto message = (attacker.has_value()) ? + "You gained " + std::to_string(replenishDamage) + " stamina from " + attacker.value().get().getName() + "'s attack." : + "You gained " + std::to_string(replenishDamage) + " stamina from replenishment."; + + sendTextMessage(MESSAGE_HEALED, message); + addStamina(static_cast(replenishDamage)); + } +} + +void Player::resistDamage(std::optional> attacker, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat) { + int32_t resistDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + resistDamage += originalDamageValue * percent / 100; + } + if (flat) { + resistDamage += flat; + } + + if (resistDamage != 0) { + resistDamage = std::min(resistDamage, originalDamageValue); + originalDamage.primary.value += resistDamage; + + auto message = (attacker.has_value()) ? + "You resisted " + std::to_string(resistDamage) + " damage from " + attacker.value().get().getName() + "'s attack." : + "You resisted " + std::to_string(resistDamage) + " damage."; + + sendTextMessage(MESSAGE_HEALED, message); + } +} + +void Player::reflectDamage(std::optional> attacker, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat, + uint8_t areaEffect, + uint8_t distanceEffect) { + + if (!attacker.has_value()) { + return; + } + + int32_t reflectDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + reflectDamage += originalDamageValue * percent / 100; + } + if (flat) { + reflectDamage += flat; + } + + if (reflectDamage != 0) { + Creature& target = attacker.value().get(); + reflectDamage = std::min(reflectDamage, originalDamageValue); + originalDamage.primary.value += reflectDamage; + + auto reflect = CombatDamage{}; + reflect.primary.type = originalDamage.primary.type; + reflect.primary.value = (0 - reflectDamage); + reflect.origin = ORIGIN_AUGMENT; + + auto params = CombatParams{}; + params.distanceEffect = distanceEffect; + params.impactEffect = areaEffect; + params.origin = ORIGIN_AUGMENT; + params.combatType = originalDamage.primary.type; + + sendTextMessage( + MESSAGE_DAMAGE_DEALT, + "You reflected " + std::to_string(reflectDamage) + " damage from " + target.getName() + "'s attack back at them." + ); + + Combat::doTargetCombat(this, &target, reflect, params); + } +} + +void Player::deflectDamage(std::optional> attackerOpt, + CombatDamage& originalDamage, + int32_t percent, + int32_t flat, + CombatOrigin paramOrigin, + uint8_t areaEffect, + uint8_t distanceEffect) { + + int32_t deflectDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + + if (percent) { + deflectDamage += originalDamageValue * percent / 100; + } + if (flat) { + deflectDamage += flat; + } + + if (deflectDamage > 0) { + deflectDamage = std::min(deflectDamage, originalDamageValue); + originalDamage.primary.value += deflectDamage; + constexpr int32_t DAMAGE_DIVIDER = 50.0; // Should be moved to global config + constexpr int32_t MAX_TARGETS = 6.0; + const int32_t calculatedTargets = std::min( + std::round((deflectDamage) / DAMAGE_DIVIDER) + 1, + MAX_TARGETS + ); + + auto defensePos = getPosition(); + auto attackPos = generateAttackPosition(attackerOpt, defensePos, paramOrigin); + auto damageArea = generateDeflectArea(attackerOpt, calculatedTargets); + + auto deflect = CombatDamage{}; + deflect.primary.type = originalDamage.primary.type; + deflect.origin = ORIGIN_AUGMENT; + deflect.primary.value = -1 * std::round(deflectDamage / calculatedTargets); + + auto params = CombatParams(); + params.origin = ORIGIN_AUGMENT; + params.combatType = originalDamage.primary.type; + params.distanceEffect = distanceEffect; + params.targetCasterOrTopMost = true; + params.impactEffect = (areaEffect == CONST_ME_NONE) + ? CombatTypeToAreaEffect(originalDamage.primary.type) + : areaEffect; + + sendTextMessage( + MESSAGE_EVENT_DEFAULT, + "You deflected " + std::to_string(deflectDamage) + " total damage." + ); + + Combat::doAreaCombat(this, attackPos, damageArea.get(), deflect, params); + } +} + +void Player::ricochetDamage(CombatDamage& originalDamage, + int32_t percent, + int32_t flat, + uint8_t areaEffect, + uint8_t distanceEffect) { + + int32_t ricochetDamage = 0; + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + + if (percent) { + ricochetDamage += originalDamageValue * percent / 100; + } + if (flat) { + ricochetDamage += flat; + } + + auto targetList = getOpenPositionsInRadius(3); + + if (ricochetDamage != 0 && targetList.size() > 0) { + const auto& targetPos = targetList[uniform_random(0, targetList.size() - 1)]; + ricochetDamage = std::min(ricochetDamage, originalDamageValue); + originalDamage.primary.value += ricochetDamage; + + auto message = "An attack on you ricocheted " + std::to_string(ricochetDamage) + " damage."; + sendTextMessage(MESSAGE_EVENT_ADVANCE, message); + + auto ricochet = CombatDamage{}; + ricochet.primary.type = originalDamage.primary.type; + ricochet.primary.value = (0 - ricochetDamage); + ricochet.origin = ORIGIN_AUGMENT; + + auto params = CombatParams(); + params.origin = ORIGIN_AUGMENT; + params.combatType = originalDamage.primary.type; + params.distanceEffect = distanceEffect; + params.targetCasterOrTopMost = true; + params.impactEffect = (areaEffect == CONST_ME_NONE) ? CombatTypeToAreaEffect(originalDamage.primary.type) : areaEffect; + + auto damageArea = std::make_unique(); + damageArea->setupArea(Deflect1xArea, 5); + Combat::doAreaCombat(this, targetPos, damageArea.get(), ricochet, params); + } +} + +void Player::convertDamage(Creature* target, CombatDamage& originalDamage, std::unordered_map conversionList) { + auto iter = conversionList.begin(); + + while (originalDamage.primary.value < 0 && iter != conversionList.end()) { + + CombatType_t combatType = indexToCombatType(iter->first); + ModifierTotals& totals = iter->second; + + int32_t convertedDamage = 0; + int32_t percent = static_cast(totals.percentTotal); + int32_t flat = static_cast(totals.flatTotal); + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + convertedDamage += originalDamageValue * percent / 100; + } + if (flat) { + convertedDamage += flat; + } + + if (convertedDamage != 0 && target) { + convertedDamage = std::min(convertedDamage, originalDamageValue); + originalDamage.primary.value += convertedDamage; + + auto converted = CombatDamage{}; + converted.primary.type = combatType; + converted.primary.value = (0 - convertedDamage); + converted.origin = ORIGIN_AUGMENT; + + auto params = CombatParams{}; + params.combatType = combatType; + params.origin = ORIGIN_AUGMENT; + + auto message = "You converted " + std::to_string(convertedDamage) + " " + getCombatName(originalDamage.primary.type) + " damage to " + getCombatName(combatType) + " during an attack on " + target->getName() + "."; + sendTextMessage(MESSAGE_DAMAGE_DEALT, message); + Combat::doTargetCombat(this, target, converted, params); + } + ++iter; + } +} + +void Player::reformDamage(std::optional> attacker, CombatDamage& originalDamage, std::unordered_map conversionList) { + auto iter = conversionList.begin(); + + while (originalDamage.primary.value < 0 && iter != conversionList.end()) { + + CombatType_t combatType = indexToCombatType(iter->first); + ModifierTotals& totals = iter->second; + + int32_t reformedDamage = 0; + int32_t percent = static_cast(totals.percentTotal); + int32_t flat = static_cast(totals.flatTotal); + const int32_t originalDamageValue = std::abs(originalDamage.primary.value); + if (percent) { + reformedDamage += originalDamageValue * percent / 100; + } + if (flat) { + reformedDamage += flat; + } + + if (reformedDamage) { + reformedDamage = std::min(reformedDamage, originalDamageValue); + originalDamage.primary.value += reformedDamage; + + auto reform = CombatDamage{}; + reform.primary.type = combatType; + reform.primary.value = (0 - reformedDamage); + reform.origin = ORIGIN_AUGMENT; + + auto params = CombatParams{}; + params.combatType = combatType; + params.origin = ORIGIN_AUGMENT; + + auto message = (attacker.has_value()) ? + "You reformed " + std::to_string(reformedDamage) + " " + getCombatName(originalDamage.primary.type) + " damage from " + getCombatName(combatType) + " during an attack on you by " + attacker.value().get().getName() + "." : + "You reformed " + std::to_string(reformedDamage) + " " + getCombatName(originalDamage.primary.type) + " damage from " + getCombatName(combatType) + "."; + + sendTextMessage(MESSAGE_DAMAGE_DEALT, message); + auto target = (attacker.has_value()) ? &attacker.value().get() : nullptr; + Combat::doTargetCombat(target, this, reform, params); + } + ++iter; + } +} + +Position Player::generateAttackPosition(std::optional> attacker, Position& defensePosition, CombatOrigin origin) { + + const Direction attackDirection = (attacker.has_value()) + ? getDirectionTo(defensePosition, attacker.value().get().getPosition()) + : getOppositeDirection(this->getDirection()); + + // Offsets + static constexpr std::array, 8> DIRECTION_PATTERNS = { { + // x_start, x_end, y_start, y_end + {-1, 1, -2, -1}, // NORTH: + {1, 2, -1, 1}, // EAST: + {-1, 1, 1, 2}, // SOUTH: + {-2, -1, -1, 1}, // WEST: + {-2, -1, 1, 2}, // SOUTHWEST: + {1, 2, 1, 2}, // SOUTHEAST: + {-2, -1, -2, -1}, // NORTHWEST: + {1, 2, -2, -1} // NORTHEAST: + } }; + + std::vector possibleTargets; + possibleTargets.reserve(9); + + const auto& pattern = DIRECTION_PATTERNS[attackDirection & 0x7]; // Mask to handle diagonal directions + + auto addLocationInline = [&](int x, int y) { + Position targetLocation{ + static_cast(defensePosition.x + x), + static_cast(defensePosition.y + y), + defensePosition.z + }; + + const auto tile = g_game.map.getTile(targetLocation); + const bool isValid = tile + && g_game.canThrowObjectTo(defensePosition, targetLocation) + && !tile->getZone() == ZONE_PROTECTION + && !tile->hasFlag(TILESTATE_PROTECTIONZONE + | TILESTATE_FLOORCHANGE + | TILESTATE_TELEPORT + | TILESTATE_IMMOVABLEBLOCKSOLID + | TILESTATE_NOPVPZONE + | TILESTATE_IMMOVABLEBLOCKPATH + | TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + + if (isValid) { + possibleTargets.emplace_back(targetLocation); + } + }; + + for (int x = pattern[0]; x <= pattern[1]; ++x) { + for (int y = pattern[2]; y <= pattern[3]; ++y) { + addLocationInline(x, y); + } + } + + const size_t vectorSize = possibleTargets.size(); + const size_t index = vectorSize ? (std::rand() % vectorSize) : 0; + return vectorSize ? possibleTargets[index] : Spells::getCasterPosition(this, getOppositeDirection(this->getDirection())); +} + +std::unique_ptr Player::generateDeflectArea(std::optional> attacker, int32_t targetCount) { + auto combatArea = std::make_unique(); + auto defendersPosition = this->getPosition(); + auto direction = (attacker.has_value()) ? getDirectionTo(defendersPosition, attacker.value().get().getPosition()) : getOppositeDirection(this->getDirection()); + + switch (direction) { + case DIRECTION_NORTH: + case DIRECTION_EAST: + case DIRECTION_SOUTH: + case DIRECTION_WEST: { + auto targetAreas = _StandardDeflectionMap.find(targetCount)->second; + if (!targetAreas.empty()) { + auto index = std::rand() % targetAreas.size(); + auto area = targetAreas[index]; + combatArea->setupArea(area, 5); + } + break; + } + case DIRECTION_SOUTHWEST: + case DIRECTION_SOUTHEAST: + case DIRECTION_NORTHWEST: + case DIRECTION_NORTHEAST: { + auto targetAreas = _DiagonalDeflectionMap.find(targetCount)->second; + if (!targetAreas.empty()) { + auto index = std::rand() % targetAreas.size(); + auto area = targetAreas[index]; + combatArea->setupExtArea(area, 5); + } + break; + } + [[unlikely]]default: + std::cerr << "Deflection area attempted to be generated from unknown direction!" << std::endl; + break; + } + + return combatArea; +} \ No newline at end of file diff --git a/src/player.h b/src/player.h index f8939dd2..c605f7da 100644 --- a/src/player.h +++ b/src/player.h @@ -21,8 +21,10 @@ #include "mounts.h" #include "storeinbox.h" #include "rewardchest.h" +#include "augments.h" #include +#include class House; class NetworkMessage; @@ -34,6 +36,8 @@ class SchedulerTask; class Bed; class Guild; +constexpr uint16_t MaximumStamina = 2520; + enum skillsid_t { SKILLVALUE_LEVEL = 0, SKILLVALUE_TRIES = 1, @@ -61,6 +65,32 @@ enum tradestate_t : uint8_t { TRADE_TRANSFER, }; +static constexpr SlotPositionBits getPositionForSlot(slots_t constSlot) { + switch (constSlot) { + case CONST_SLOT_HEAD: + return SLOTP_HEAD; + case CONST_SLOT_NECKLACE: + return SLOTP_NECKLACE; + case CONST_SLOT_BACKPACK: + return SLOTP_BACKPACK; + case CONST_SLOT_ARMOR: + return SLOTP_ARMOR; + case CONST_SLOT_RIGHT: + return SLOTP_RIGHT; + case CONST_SLOT_LEFT: + return SLOTP_LEFT; + case CONST_SLOT_LEGS: + return SLOTP_LEGS; + case CONST_SLOT_FEET: + return SLOTP_FEET; + case CONST_SLOT_AMMO: + return SLOTP_AMMO; + case CONST_SLOT_RING: + return SLOTP_RING; + default: throw std::invalid_argument("Invalid ConstSlot value"); + } +} + struct VIPEntry { VIPEntry(uint32_t guid, std::string_view name, std::string_view description, uint32_t icon, bool notify) : guid{ guid }, name{ name }, description{ description }, icon{ icon }, notify{ notify } {} @@ -598,6 +628,9 @@ class Player final : public Creature, public Cylinder void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; void changeMana(int32_t manaChange); void changeSoul(int32_t soulChange); + void addSoul(uint8_t soulChange); + void addStamina(uint16_t gain); + void changeStamina(int32_t amount); bool isPzLocked() const { return pzLocked; @@ -1187,6 +1220,7 @@ class Player final : public Creature, public Cylinder } void updateRegeneration(); + void addItemImbuements(Item* item); void removeItemImbuements(Item* item); void addImbuementEffect(std::shared_ptr imbue); @@ -1198,6 +1232,46 @@ class Player final : public Creature, public Cylinder void updateAutoLoot(uint16_t clientId, const std::string& name, bool remove); const std::map getAutolootItems() const; + CreatureType_t getCreatureType(Monster& monster); + + // To-do : Make all these methods into const + std::unordered_map>> getAttackModifiers(); + std::unordered_map>> getDefenseModifiers(); + + std::unordered_map getConvertedTotals(const uint8_t modType, const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName); + + std::unordered_map getAttackModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName); + std::unordered_map getDefenseModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName); + + std::vector getOpenPositionsInRadius(int radius) const; + + const bool addAugment(std::string_view augmentName); + const bool addAugment(std::shared_ptr& augment); + + const bool removeAugment(std::string_view augmentName); + const bool removeAugment(std::shared_ptr& augment); + + const bool isAugmented(); + const bool hasAugment(const std::string_view augmentName, const bool checkItems); + const bool hasAugment(const std::shared_ptr& augmentName, const bool checkItems); + const std::vector> getPlayerAugments() const; + + // To-do : convert all these params to const and ref. + void absorbDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat); + void restoreManaFromDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat); + void reviveSoulFromDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat); + void replenishStaminaFromDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat); + void resistDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat); + void reflectDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat, uint8_t areaEffect, uint8_t distanceEffect); + void deflectDamage(std::optional> attackerOpt, CombatDamage& originalDamage, int32_t percent, int32_t flat, CombatOrigin paramOrigin, uint8_t areaEffect, uint8_t distanceEffect); + void ricochetDamage(CombatDamage& originalDamage, int32_t percent, int32_t flat, uint8_t areaEffect, uint8_t distanceEffect); + void convertDamage(Creature* target, CombatDamage& originalDamage, std::unordered_map conversionList); + void reformDamage(std::optional> attackerOpt, CombatDamage& originalDamage, std::unordered_map conversionList); + + Position generateAttackPosition(std::optional> attacker, Position& defensePosition, CombatOrigin origin); + + std::unique_ptr generateDeflectArea(std::optional> attacker, int32_t targetCount); + private: std::forward_list getMuteConditions() const; @@ -1251,6 +1325,8 @@ class Player final : public Creature, public Cylinder std::map depotChests; std::map storageMap; + std::vector> augments; + std::vector outfits; std::vector autoLootItems; GuildWarVector guildWarVector; diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 6ddfe069..e81a8b24 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1952,29 +1952,6 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId) 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 { @@ -2983,7 +2960,7 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo if (masterPlayer == player) { creatureType = CREATURETYPE_SUMMON_OWN; } else { - creatureType = CREATURETYPE_SUMMON_OTHERS; + creatureType = CREATURETYPE_SUMMON_HOSTILE; } } } diff --git a/src/spawn.cpp b/src/spawn.cpp index 55b4b386..6b8eb699 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -267,11 +267,6 @@ bool Spawn::findPlayer(const Position& pos) return false; } -bool Spawn::isInSpawnZone(const Position& pos) -{ - return Spawns::isInZone(centerPos, radius, pos); -} - bool Spawn::spawnMonster(uint32_t spawnId, spawnBlock_t sb, bool startup/* = false*/) { bool isBlocked = !startup && findPlayer(sb.pos); @@ -314,6 +309,7 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p { std::unique_ptr monster_ptr(new Monster(mType)); if (!g_events->eventMonsterOnSpawn(monster_ptr.get(), pos, startup, false)) { + monster_ptr.reset(); return false; } @@ -321,10 +317,12 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p //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; + monster_ptr.reset(); return false; } } else { if (!g_game.placeCreature(monster_ptr.get(), pos, false, true)) { + monster_ptr.reset(); return false; } } @@ -385,14 +383,10 @@ void Spawn::cleanup() { auto it = spawnedMap.begin(); while (it != spawnedMap.end()) { - uint32_t spawnId = it->first; Monster* monster = it->second; if (monster->isRemoved()) { monster->decrementReferenceCounter(); it = spawnedMap.erase(it); - } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) { - spawnedMap.insert({0, monster}); - it = spawnedMap.erase(it); } else { ++it; } diff --git a/src/spawn.h b/src/spawn.h index ee7c2e26..d3eab8b9 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -39,12 +39,10 @@ class Spawn uint32_t getInterval() const { return interval; } + void startup(); - void startSpawnCheck(); void stopEvent(); - - bool isInSpawnZone(const Position& pos); void cleanup(); private: diff --git a/src/spectators.h b/src/spectators.h index 270bc133..bfce128a 100644 --- a/src/spectators.h +++ b/src/spectators.h @@ -37,6 +37,10 @@ class SpectatorVec vec.pop_back(); } + Creature* operator[] (uint8_t index) { + return vec[index]; + } + size_t size() const { return vec.size(); } bool empty() const { return vec.empty(); } Iterator begin() { return vec.begin(); } diff --git a/src/spells.cpp b/src/spells.cpp index f8e9d273..2ff13468 100644 --- a/src/spells.cpp +++ b/src/spells.cpp @@ -217,7 +217,7 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) for (auto& it : instants) { const std::string& instantSpellWords = it.second.getWords(); size_t spellLen = instantSpellWords.length(); - if (caseInsensitiveEqual(words, instantSpellWords)) { + if (caseInsensitiveStartsWith(words, instantSpellWords)) { if (!result || spellLen > result->getWords().size()) { result = &it.second; if (words.length() == spellLen) { @@ -403,10 +403,20 @@ bool Spell::configureSpell(const pugi::xml_node& node) spellId = pugi::cast(attr.value()); } + + if ((attr = node.attribute("aggressive"))) { + aggressive = booleanString(attr.as_string()); + } + + if (group == SPELLGROUP_NONE) { + group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); + } + if ((attr = node.attribute("group"))) { std::string tmpStr = asLowerCaseString(attr.as_string()); if (tmpStr == "none" || tmpStr == "0") { group = SPELLGROUP_NONE; + groupCooldown = 0; } else if (tmpStr == "attack" || tmpStr == "1") { group = SPELLGROUP_ATTACK; } else if (tmpStr == "healing" || tmpStr == "2") { @@ -420,7 +430,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) } } - if ((attr = node.attribute("groupcooldown"))) { + if (group != SPELLGROUP_NONE && (attr = node.attribute("groupcooldown"))) { groupCooldown = pugi::cast(attr.value()); } @@ -441,7 +451,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) } } - if ((attr = node.attribute("secondarygroupcooldown"))) { + if (secondaryGroup != SPELLGROUP_NONE && (attr = node.attribute("secondarygroupcooldown"))) { secondaryGroupCooldown = pugi::cast(attr.value()); } @@ -520,14 +530,6 @@ bool Spell::configureSpell(const pugi::xml_node& node) pzLock = booleanString(attr.as_string()); } - if ((attr = node.attribute("aggressive"))) { - aggressive = booleanString(attr.as_string()); - } - - if (group == SPELLGROUP_NONE) { - group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); - } - for (auto vocationNode : node.children()) { if (!(attr = vocationNode.attribute("name"))) { continue; @@ -760,24 +762,29 @@ bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) return true; } +void Spell::addCooldowns(Player* player) const +{ + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (group != SPELLGROUP_NONE && groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroup != SPELLGROUP_NONE && secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); + } +} + void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const { if (finishedCast) { if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { - if (cooldown > 0) { - 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); - player->addCondition(condition); - } - - if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); - player->addCondition(condition); - } + addCooldowns(player); } if (aggressive) { @@ -877,20 +884,7 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) target = playerTarget; if (!target || target->isRemoved() || target->isDead()) { if (!casterTargetOrDirection) { - if (cooldown > 0) { - 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); - player->addCondition(condition); - } - - if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); - player->addCondition(condition); - } + addCooldowns(player); player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); @@ -937,20 +931,7 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) 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); - player->addCondition(condition); - } - - if (groupCooldown > 0) { - 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); - player->addCondition(condition); - } + addCooldowns(player); player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); diff --git a/src/spells.h b/src/spells.h index dc38d5f9..d55b32ea 100644 --- a/src/spells.h +++ b/src/spells.h @@ -188,6 +188,9 @@ class Spell : public BaseSpell } void setGroup(SpellGroup_t g) { group = g; + if(group == SPELLGROUP_NONE) { + groupCooldown = 0; + } } SpellGroup_t getSecondaryGroup() const { return secondaryGroup; @@ -277,6 +280,7 @@ class Spell : public BaseSpell bool playerSpellCheck(Player* player) const; bool playerInstantSpellCheck(Player* player, const Position& toPos); bool playerRuneSpellCheck(Player* player, const Position& toPos); + void addCooldowns(Player* player) const; VocSpellMap vocSpellMap; diff --git a/src/talkaction.cpp b/src/talkaction.cpp index 50f2fbf0..38235b68 100644 --- a/src/talkaction.cpp +++ b/src/talkaction.cpp @@ -166,7 +166,7 @@ bool TalkAction::executeSay(Player* player, const std::string& words, const std: LuaScriptInterface::pushString(L, words); LuaScriptInterface::pushString(L, param); - lua_pushnumber(L, type); + lua_pushinteger(L, type); return scriptInterface->callFunction(4); } diff --git a/src/tile.cpp b/src/tile.cpp index 69a69767..92c592db 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -499,8 +499,10 @@ ReturnValue Tile::queryAdd(const Creature& creature, uint32_t flags) const if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { for (const Creature* tileCreature : *creatures) { - if (!tileCreature->isInGhostMode()) { - return RETURNVALUE_NOTENOUGHROOM; + if (!tileCreature->isInGhostMode() && (tileCreature->getPlayer() && !tileCreature->getPlayer()->isAccessPlayer() )) { + if (creature.getPlayer() && !creature.getPlayer()->isAccessPlayer() && !creature.getPlayer()->canWalkthrough(tileCreature)) { + return RETURNVALUE_NOTENOUGHROOM; + } } } } @@ -899,10 +901,7 @@ void Tile::addThing(int32_t, Thing* thing) { Creature* creature = thing->getCreature(); if (creature) { - g_game.map.clearSpectatorCache(); - if (creature->getPlayer()) { - g_game.map.clearPlayersSpectatorCache(); - } + g_game.map.clearChunkSpectatorCache(); creature->setParent(this); CreatureVector* creatures = makeCreatures(); @@ -1108,10 +1107,7 @@ void Tile::removeThing(Thing* thing, uint32_t count) if (creatures) { auto it = std::find(creatures->begin(), creatures->end(), thing); if (it != creatures->end()) { - g_game.map.clearSpectatorCache(); - if (creature->getPlayer()) { - g_game.map.clearPlayersSpectatorCache(); - } + g_game.map.clearChunkSpectatorCache(); creatures->erase(it); } @@ -1488,10 +1484,7 @@ void Tile::internalAddThing(uint32_t, Thing* thing) Creature* creature = thing->getCreature(); if (creature) { - g_game.map.clearSpectatorCache(); - if (creature->getPlayer()) { - g_game.map.clearPlayersSpectatorCache(); - } + g_game.map.clearChunkSpectatorCache(); CreatureVector* creatures = makeCreatures(); creatures->insert(creatures->begin(), creature); diff --git a/src/tools.cpp b/src/tools.cpp index 1320f6c5..e48f9fcf 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1246,7 +1246,9 @@ int64_t OTSYS_TIME() SpellGroup_t stringToSpellGroup(const std::string& value) { std::string tmpStr = asLowerCaseString(value); - if (tmpStr == "attack" || tmpStr == "1") { + if (tmpStr == "none" || tmpStr == "0") { + return SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { return SPELLGROUP_ATTACK; } else if (tmpStr == "healing" || tmpStr == "2") { return SPELLGROUP_HEALING; @@ -1256,7 +1258,7 @@ SpellGroup_t stringToSpellGroup(const std::string& value) return SPELLGROUP_SPECIAL; } - return SPELLGROUP_NONE; + return SPELLGROUP_UNKNOWN; } std::vector depotBoxes = { @@ -1302,4 +1304,4 @@ std::string getStatName(uint8_t id) default: return "unknown"; } -} \ No newline at end of file +} diff --git a/src/tools.h b/src/tools.h index 694728c1..b3e3cbd1 100644 --- a/src/tools.h +++ b/src/tools.h @@ -11,6 +11,38 @@ #include "const.h" #include "enums.h" +static constexpr MagicEffectClasses CombatTypeToAreaEffect(CombatType_t combatType) { + switch (combatType) { + case COMBAT_PHYSICALDAMAGE: + return CONST_ME_BLOCKHIT; + case COMBAT_ENERGYDAMAGE: + return CONST_ME_ENERGYHIT; + case COMBAT_EARTHDAMAGE: + return CONST_ME_HITBYPOISON; + case COMBAT_FIREDAMAGE: + return CONST_ME_HITBYFIRE; + case COMBAT_UNDEFINEDDAMAGE: + return CONST_ME_PURPLEENERGY; + case COMBAT_LIFEDRAIN: + return CONST_ME_REDSMOKE; + case COMBAT_MANADRAIN: + return CONST_ME_LOSEENERGY; + case COMBAT_HEALING: + return CONST_ME_MAGIC_RED; + case COMBAT_DROWNDAMAGE: + return CONST_ME_WATERSPLASH; + case COMBAT_ICEDAMAGE: + return CONST_ME_ICEATTACK; + case COMBAT_HOLYDAMAGE: + return CONST_ME_HOLYDAMAGE; + case COMBAT_DEATHDAMAGE: + return CONST_ME_MORTAREA; + + default: + return CONST_ME_BLOCKHIT; + } +} + void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result); const std::vector& getShuffleDirections(); @@ -48,6 +80,22 @@ Direction getDirection(const std::string& string); Position getNextPosition(Direction direction, Position pos); Direction getDirectionTo(const Position& from, const Position& to); +static Direction getOppositeDirection(Direction currentDirection) { + switch (currentDirection) { + case DIRECTION_NORTH: + return DIRECTION_SOUTH; + case DIRECTION_SOUTH: + return DIRECTION_NORTH; + case DIRECTION_WEST: + return DIRECTION_EAST; + case DIRECTION_EAST: + return DIRECTION_WEST; + default: + return DIRECTION_SOUTH; + } + return DIRECTION_SOUTH; +} + std::string getFirstLine(const std::string& str); std::string formatDate(time_t time); diff --git a/vcpkg.json b/vcpkg.json index f3c6763f..53f9dac7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -8,6 +8,8 @@ "boost-system", "boost-variant", "fmt", + "tomlplusplus", + "openssl", { "name": "libiconv", "platform": "osx"