diff --git a/.gitignore b/.gitignore
index a1b8c921e..51b59868c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,3 +77,9 @@ xcode/Barony/Barony.xcodeproj/xcuserdata/*
*.ps1
!LICENSE.nativefiledialog.txt
/VS.2015/Barony/x64/Debug
+*.sarif
+*.i
+*.enc
+*.ilk
+/VS.2015/x64
+*.sqlite-journal
diff --git a/INSTALL.md b/INSTALL.md
index dca7a88be..f63df4103 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -14,12 +14,13 @@ You will need the following libraries to build Barony:
* CMake (on Windows, use versions at least as new as 3.8.0)
OPTIONAL dependencies:
- * One of FMOD Core API 2.00.08 or OpenAL for audio support.
+ * FMOD Core API 2.02.14.
FMOD Studio API can be downloaded at https://www.fmod.com/download (you do need an account to download it).
You can disable FMOD by running cmake with -DFMOD_ENABLED=OFF (it's also disabled if not found).
OpenAL can be used with -DOPENAL_ENABLED
+ * Note - OpenAL support has been deprecated and is currently unmaintained.
You will also need the following tools:
diff --git a/VS.2015/Barony.sln b/VS.2015/Barony.sln
index a6bebcf43..c25a9763c 100644
--- a/VS.2015/Barony.sln
+++ b/VS.2015/Barony.sln
@@ -21,6 +21,8 @@ Global
EOS Debug|x64 = EOS Debug|x64
EOS|Win32 = EOS|Win32
EOS|x64 = EOS|x64
+ EOSDebugInstrumentation|Win32 = EOSDebugInstrumentation|Win32
+ EOSDebugInstrumentation|x64 = EOSDebugInstrumentation|x64
NODRM|Win32 = NODRM|Win32
NODRM|x64 = NODRM|x64
Release|Win32 = Release|Win32
@@ -55,6 +57,10 @@ Global
{A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOS|Win32.Build.0 = EOS|Win32
{A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOS|x64.ActiveCfg = EOS|x64
{A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOS|x64.Build.0 = EOS|x64
+ {A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOSDebugInstrumentation|Win32.ActiveCfg = EOSDebugInstrumentation|Win32
+ {A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOSDebugInstrumentation|Win32.Build.0 = EOSDebugInstrumentation|Win32
+ {A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOSDebugInstrumentation|x64.ActiveCfg = EOSDebugInstrumentation|x64
+ {A4A170FF-C081-4F33-94FE-5C60E89C5990}.EOSDebugInstrumentation|x64.Build.0 = EOSDebugInstrumentation|x64
{A4A170FF-C081-4F33-94FE-5C60E89C5990}.NODRM|Win32.ActiveCfg = NODRM|Win32
{A4A170FF-C081-4F33-94FE-5C60E89C5990}.NODRM|Win32.Build.0 = NODRM|Win32
{A4A170FF-C081-4F33-94FE-5C60E89C5990}.NODRM|x64.ActiveCfg = NODRM|x64
@@ -92,6 +98,8 @@ Global
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOS|Win32.Build.0 = EOS|Win32
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOS|x64.ActiveCfg = EOS|x64
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOS|x64.Build.0 = EOS|x64
+ {8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOSDebugInstrumentation|Win32.ActiveCfg = EOSDebugInstrumentation|Win32
+ {8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOSDebugInstrumentation|x64.ActiveCfg = EOSDebugInstrumentation|x64
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.NODRM|Win32.ActiveCfg = NODRM|Win32
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.NODRM|Win32.Build.0 = NODRM|Win32
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.NODRM|x64.ActiveCfg = NODRM|x64
@@ -103,6 +111,7 @@ Global
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.Steam Crossplay|Win32.ActiveCfg = Steam Crossplay|Win32
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.Steam Crossplay|Win32.Build.0 = Steam Crossplay|Win32
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.Steam Crossplay|x64.ActiveCfg = Steam Crossplay|x64
+ {8D0CEB30-DFEC-462D-B107-1EA02D317151}.Steam Crossplay|x64.Build.0 = Steam Crossplay|x64
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.Steam Debug|Win32.ActiveCfg = Steam Debug|Win32
{8D0CEB30-DFEC-462D-B107-1EA02D317151}.Steam Debug|x64.ActiveCfg = Steam Debug|x64
EndGlobalSection
diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj
index fe0e0b2be..c608f4ed8 100644
--- a/VS.2015/Barony/Barony.vcxproj
+++ b/VS.2015/Barony/Barony.vcxproj
@@ -41,6 +41,14 @@
EOS Debug
x64
+
+ EOSDebugInstrumentation
+ Win32
+
+
+ EOSDebugInstrumentation
+ x64
+
EOS
Win32
@@ -180,6 +188,13 @@
true
MultiByte
+
+ Application
+ false
+ v143
+ true
+ MultiByte
+
Application
false
@@ -195,6 +210,14 @@
MultiByte
false
+
+ Application
+ false
+ v143
+ true
+ MultiByte
+ false
+
Application
false
@@ -272,12 +295,18 @@
+
+
+
+
+
+
@@ -385,6 +414,13 @@
true
barony
+
+ false
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\inc;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\include;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\include;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\include;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\include;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\include;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\public;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\include;C:\programming\turning_wheel\GameLibs\missing_windows_includes;P:\Ben\Documents\Visual Studio 2015\Projects\libphysfs;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\SDL2;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\physfs
+ $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\lib;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\lib\x86;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\lib\x86;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\lib\x86;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\lib;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\lib;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\redistributable_bin;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\lib\x86;P:\Ben\Documents\Visual Studio 2015\Projects\libphysfs;$(SolutionDir)..\VS-libs
+ true
+ barony
+
false
$(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\inc;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\include;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\include;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\include;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\include;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\include;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\public;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\include;C:\programming\turning_wheel\GameLibs\missing_windows_includes;P:\Ben\Documents\Visual Studio 2015\Projects\libphysfs;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\SDL2;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\physfs
@@ -393,7 +429,14 @@
barony
- false
+ true
+ $(VC_IncludePath);$(WindowsSDK_IncludePath)
+ $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(SolutionDir)..\VS-libs
+ false
+ barony
+
+
+ true
$(VC_IncludePath);$(WindowsSDK_IncludePath)
$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(SolutionDir)..\VS-libs
false
@@ -660,6 +703,9 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
$(IntDir)%(RelativeDir)
/DBUILD_PR="$(BARONY_BUILD_PR)" /DBUILD_CC="$(BARONY_BUILD_CC)" /DBUILD_CS="$(BARONY_BUILD_CS)" /DBUILD_DE="$(BARONY_BUILD_DE)" /DBUILD_SA="$(BARONY_BUILD_SA)" /DBUILD_GSE="$(BARONY_BUILD_GSTATS)" /DBUILD_PFTID="$(BARONY_BUILD_PFTID)" /DBUILD_PFHID="$(BARONY_BUILD_PFHID)" %(AdditionalOptions)
stdcpp17
+
+
+
Windows
@@ -669,6 +715,8 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;steam_api64.lib;SDL2_ttf.lib;physfs.lib;EOSSDK-Win64-Shipping.lib;libtheoraplayer.lib;glew32.lib;nfd.lib;libcurl.lib;ws2_32.lib;Normaliz.lib;Crypt32.lib;Wldap32.lib;XPlatCppWindows.lib;lib_json.lib;opus.lib;%(AdditionalDependencies)
false
true
+
+
copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)Release\barony_test.exe"
@@ -839,6 +887,45 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS\barony_test.exe"
copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 3\barony_test.exe"
+copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe"
+
+
+ true
+
+
+
+
+ Level3
+
+
+ Disabled
+ false
+ true
+ _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;%(PreprocessorDefinitions);USE_EOS;USE_IMGUI
+ "$(SolutionDir)\..\VS-includes"
+ true
+
+
+ false
+ ProgramDatabase
+ MultiThreadedDebugDLL
+ false
+ /DBUILD_PR="$(BARONY_BUILD_PR)" /DBUILD_CC="$(BARONY_BUILD_CC)" /DBUILD_CS="$(BARONY_BUILD_CS)" /DBUILD_DE="$(BARONY_BUILD_DE)" /DBUILD_SA="$(BARONY_BUILD_SA)" /DBUILD_GSE="$(BARONY_BUILD_GSTATE)" %(AdditionalOptions)
+ $(IntDir)%(RelativeDir)
+ false
+
+
+ Windows
+ true
+ true
+ true
+ OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;EOSSDK-Win32-Shipping.lib;steam_api.lib;libtheoraplayer.lib;glew32.lib;%(AdditionalDependencies)
+ false
+ true
+
+
+ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS\barony_test.exe"
+copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 3\barony_test.exe"
copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe"
@@ -890,7 +977,7 @@ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe"
Disabled
- false
+ true
true
_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;%(PreprocessorDefinitions);USE_EOS;USE_IMGUI;USE_THEORA_VIDEO;CURL_STATICLIB;USE_LIBCURL;USE_PLAYFAB;USE_OPUS
$(SolutionDir)\..\VS-includes
@@ -898,7 +985,7 @@ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe"
false
- ProgramDatabase
+ EditAndContinue
MultiThreadedDebugDLL
false
$(IntDir)%(RelativeDir)
@@ -910,11 +997,56 @@ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe"
Windows
true
- true
- true
+ false
+ false
+ OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;EOSSDK-Win64-Shipping.lib;steam_api64.lib;libtheoraplayer.lib;glew32.lib;nfd.lib;libcurl.lib;ws2_32.lib;Normaliz.lib;Crypt32.lib;Wldap32.lib;XPlatCppWindows_debug.lib;lib_json_debug.lib;opus.lib;%(AdditionalDependencies)
+ false
+ false
+
+
+
+
+ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS\barony_test.exe"
+copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 3\barony_test.exe"
+copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe"
+
+
+ true
+
+
+
+
+ Level3
+
+
+ Disabled
+ true
+ true
+ _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;%(PreprocessorDefinitions);USE_EOS;USE_IMGUI;USE_THEORA_VIDEO;CURL_STATICLIB;USE_LIBCURL;USE_PLAYFAB;USE_OPUS
+ $(SolutionDir)\..\VS-includes
+ true
+
+
+ false
+ EditAndContinue
+ MultiThreadedDebugDLL
+ false
+ $(IntDir)%(RelativeDir)
+ /DBUILD_PR="$(BARONY_BUILD_PR)" /DBUILD_CC="$(BARONY_BUILD_CC)" /DBUILD_CS="$(BARONY_BUILD_CS)" /DBUILD_DE="$(BARONY_BUILD_DE)" /DBUILD_SA="$(BARONY_BUILD_SA)" /DBUILD_GSE="$(BARONY_BUILD_GSTATE)" /DBUILD_PFTID="$(BARONY_BUILD_PFTID)" /DBUILD_PFHID="$(BARONY_BUILD_PFHID)" %(AdditionalOptions)
+ stdcpp17
+ false
+ true
+
+
+ Windows
+ true
+ false
+ false
OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;EOSSDK-Win64-Shipping.lib;steam_api64.lib;libtheoraplayer.lib;glew32.lib;nfd.lib;libcurl.lib;ws2_32.lib;Normaliz.lib;Crypt32.lib;Wldap32.lib;XPlatCppWindows_debug.lib;lib_json_debug.lib;opus.lib;%(AdditionalDependencies)
false
true
+
+
copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS\barony_test.exe"
@@ -1190,8 +1322,11 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
+
+
+
@@ -1204,9 +1339,12 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
+
+
+
@@ -1217,6 +1355,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
+
@@ -1256,6 +1395,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel%
+
diff --git a/VS.2015/Barony/Barony.vcxproj.filters b/VS.2015/Barony/Barony.vcxproj.filters
index 494cd70a2..8a534d382 100644
--- a/VS.2015/Barony/Barony.vcxproj.filters
+++ b/VS.2015/Barony/Barony.vcxproj.filters
@@ -501,6 +501,27 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
@@ -683,6 +704,9 @@
Header Files
+
+ Header Files
+
diff --git a/VS.2015/editor/editor.vcxproj b/VS.2015/editor/editor.vcxproj
index c458b24e2..8a683d11a 100644
--- a/VS.2015/editor/editor.vcxproj
+++ b/VS.2015/editor/editor.vcxproj
@@ -41,6 +41,14 @@
EOS Debug
x64
+
+ EOSDebugInstrumentation
+ Win32
+
+
+ EOSDebugInstrumentation
+ x64
+
EOS
Win32
@@ -177,6 +185,13 @@
true
MultiByte
+
+ Application
+ false
+ v143
+ true
+ MultiByte
+
Application
false
@@ -191,6 +206,13 @@
true
MultiByte
+
+ Application
+ false
+ v143
+ true
+ MultiByte
+
Application
false
@@ -268,12 +290,18 @@
+
+
+
+
+
+
@@ -357,6 +385,13 @@
P:\Program Files (x86)\Microsoft Visual Studio 14.0\Team Tools\Static Analysis Tools\Rule Sets\NativeRecommendedRules.ruleset
false
+
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\inc;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\include;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\include;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\include;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\include;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\include;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\public;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\include;C:\programming\turning_wheel\GameLibs\missing_windows_includes;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\SDL2;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\physfs
+ $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\lib;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\lib\x86;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\lib\x86;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\lib\x86;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\lib;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\lib;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\redistributable_bin;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\lib\x86;P:\Ben\Documents\Visual Studio 2015\Projects\libphysfs;$(SolutionDir)..\VS-libs
+ $(ProjectName)
+ P:\Program Files (x86)\Microsoft Visual Studio 14.0\Team Tools\Static Analysis Tools\Rule Sets\NativeRecommendedRules.ruleset
+ false
+
$(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\inc;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\include;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\include;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\include;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\include;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\include;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\public;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\include;C:\programming\turning_wheel\GameLibs\missing_windows_includes;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\SDL2;P:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\physfs
$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);C:\Program Files (x86)\FMOD SoundSystem\FMOD Programmers API Windows\api\lib;C:\programming\turning_wheel\GameLibs\SDL2-2.0.4\lib\x86;C:\programming\turning_wheel\GameLibs\SDL2_image-2.0.0\lib\x86;C:\programming\turning_wheel\GameLibs\SDL2_net-2.0.0\lib\x86;C:\programming\turning_wheel\GameLibs\libpng-1.2.37-lib\lib;C:\programming\turning_wheel\GameLibs\zlib-1.2.3-lib\lib;C:\programming\turning_wheel\GameLibs\steamworks_sdk_138a\sdk\redistributable_bin;C:\programming\turning_wheel\GameLibs\SDL2_ttf-2.0.14\lib\x86;P:\Ben\Documents\Visual Studio 2015\Projects\libphysfs;$(SolutionDir)..\VS-libs
@@ -369,6 +404,11 @@
$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(SolutionDir)..\VS-libs
$(ProjectName)
+
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);
+ $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(SolutionDir)..\VS-libs
+ $(ProjectName)
+
$(VC_IncludePath);$(WindowsSDK_IncludePath);
$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(SolutionDir)..\VS-libs
@@ -681,6 +721,31 @@
Windows
+
+
+ Level3
+ Disabled
+ true
+ true
+ true
+ _CRT_SECURE_NO_WARNINGS;EDITOR;BARONY_DRM_FREE;%(PreprocessorDefinitions)
+ MultiThreadedDebugDLL
+ "$(SolutionDir)\..\VS-includes"
+
+
+ true
+ $(IntDir)%(RelativeDir)
+ false
+ false
+
+
+ true
+ true
+ true
+ OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng.lib;zlib.lib;steam_api.lib;SDL2_ttf.lib;physfs.lib;glew32.lib;%(AdditionalDependencies)
+ Windows
+
+
Level3
@@ -720,13 +785,46 @@
true
stdcpp17
+ EditAndContinue
+ false
true
- true
- true
+ false
+ false
+ OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;glew32.lib;%(AdditionalDependencies)
+ Windows
+ Default
+
+
+
+
+
+
+
+
+ Level3
+ Disabled
+ true
+ true
+ true
+ _CRT_SECURE_NO_WARNINGS;_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING;BARONY_DRM_FREE;EDITOR;%(PreprocessorDefinitions)
+ MultiThreaded
+ "$(SolutionDir)\..\VS-includes"
+
+
+ true
+ stdcpp17
+ EditAndContinue
+ false
+
+
+ true
+ false
+ false
OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;glew32.lib;%(AdditionalDependencies)
Windows
+ Default
diff --git a/lang/compendium_lang/contents_codex.json b/lang/compendium_lang/contents_codex.json
index 9fd03269d..eb0da7c5e 100644
--- a/lang/compendium_lang/contents_codex.json
+++ b/lang/compendium_lang/contents_codex.json
@@ -9,6 +9,7 @@
{"LEVELING UP": "leveling up"},
{"STATS & METASTATS": "stats metastats"},
{"SKILLS": "skills"},
+ {"GOLD": "gold"},
{" ACTIONS": "-"},
{"MELEE ATTACKS": "melee"},
{"CRITICAL STRIKES": "crits"},
@@ -17,6 +18,7 @@
{"SNEAKING": "sneaking"},
{"LEGENDARY STRIKES": "legendary strikes"},
{"BLOCKING": "blocking"},
+ {"OFF-HAND CASTING": "offhand casting"},
{"STRAFE & BACKPEDAL": "strafing"},
{"MEMORIZED CASTING": "memorized"},
{"SPELLBOOK CASTING": "spellbook casting"},
@@ -47,10 +49,10 @@
{"STEALTH SKILL": "stealth skill"},
{"BLOCKING SKILL": "blocking skill"},
{"RANGED SKILL": "ranged skill"},
- {"MAGIC SKILL": "magic skill"},
- {"CASTING SKILL": "casting skill"},
- {"APPRAISAL SKILL": "appraisal skill"},
- {"SWIMMING SKILL": "swimming skill"},
+ {"SORCERY SKILL": "sorcery skill"},
+ {"MYSTICISM SKILL": "mysticism skill"},
+ {"THAUMATURGY SKILL": "thaumaturgy skill"},
+ {"LORE SKILL": "lore skill"},
{"LEADERSHIP SKILL": "leadership skill"},
{"TRADING SKILL": "trading skill"},
{"ALCHEMY SKILL": "alchemy skill"},
@@ -58,14 +60,12 @@
],
"contents_alphabetical": [
{"ALCHEMY SKILL": "alchemy skill"},
- {"APPRAISAL SKILL": "appraisal skill"},
{"ARMOR CLASS (AC)": "ac"},
{"AXE SKILL": "axe skill"},
{"BACKSTABBING": "backstabs"},
{"BLOCKING SKILL": "blocking skill"},
{"BLOCKING": "blocking"},
{"CASTING MEMORIZED SPELLS": "memorized"},
- {"CASTING SKILL": "casting skill"},
{"CHARISMA (CHR)": "chr"},
{"CLASS": "class"},
{"CLASSES LIST": "classes list"},
@@ -74,19 +74,22 @@
{"DEXTERITY (DEX)": "dex"},
{"EXPERIENCE POINTS (XP)": "xp"},
{"FLANKING STRIKES": "flanking"},
+ {"GOLD": "gold"},
{"HEALTH POINTS (HP)": "hp"},
{"INTELLIGENCE (INT)": "int"},
{"ITEM DURABILITY": "equipment degradation"},
{"LEADERSHIP SKILL": "leadership skill"},
{"LEGENDARY STRIKES": "legendary strikes"},
{"LEVELING UP": "leveling up"},
+ {"LORE SKILL": "lore skill"},
{"MACE SKILL": "mace skill"},
{"MAGIC POINTS (MP)": "mp"},
{"MAGIC POWER (PWR)": "pwr"},
{"MAGIC RESIST (RES)": "res"},
- {"MAGIC SKILL": "magic skill"},
{"MELEE ATTACKS": "melee"},
{"MISSILE ATTACKS": "missiles"},
+ {"MYSTICISM SKILL": "mysticism skill"},
+ {"OFF-HAND CASTING": "offhand casting"},
{"PERCEPTION (PER)": "per"},
{"POLEARM SKILL": "polearm skill"},
{"RACE & ALIGNMENT": "races"},
@@ -94,13 +97,14 @@
{"REGENERATION (RGN)": "rgn"},
{"SKILLS": "skills"},
{"SNEAKING": "sneaking"},
+ {"SORCERY SKILL": "sorcery skill"},
{"SPELLBOOK CASTING": "spellbook casting"},
{"STATS & METASTATS": "stats metastats"},
{"STEALTH SKILL": "stealth skill"},
{"STRAFE & BACKPEDAL": "strafing"},
{"STRENGTH (STR)": "str"},
- {"SWIMMING SKILL": "swimming skill"},
{"SWORD SKILL": "sword skill"},
+ {"THAUMATURGY SKILL": "thaumaturgy skill"},
{"THROWN ATTACKS": "thrown"},
{"TINKERING SKILL": "tinkering skill"},
{"TRADING SKILL": "trading skill"},
diff --git a/lang/compendium_lang/contents_items.json b/lang/compendium_lang/contents_items.json
index 0960805bc..df0f7eadb 100644
--- a/lang/compendium_lang/contents_items.json
+++ b/lang/compendium_lang/contents_items.json
@@ -4,6 +4,8 @@
{" ARMOR": "-"},
{"SHIELDS": "shields"},
{"LEATHER ARMOR": "leather armor"},
+ {"QUILTED ARMOR": "quilted armor"},
+ {"CHAIN ARMOR": "chain armor"},
{"IRON ARMOR": "iron armor"},
{"STEEL ARMOR": "steel armor"},
{"CRYSTAL ARMOR": "crystal armor"},
@@ -17,12 +19,16 @@
{"CROWNS & HEADDRESSES": "crowns & headdresses"},
{"FACE ACCESSORIES": "face accessories"},
{"MASKS & VISORS": "masks & visors"},
- {"CLOAKS & SHIRTS": "cloaks & clothing"},
+ {"CLOAKS & CLOTHING": "cloaks & clothing"},
{"BACKPACKS": "backpacks"},
{" MELEE WEAPONS": "-"},
{"SWORDS": "swords"},
+ {"GREATSWORDS": "greatswords"},
+ {"RAPIER": "rapier"},
{"AXES": "axes"},
{"MACES": "maces"},
+ {"FLAILS": "flails"},
+ {"SHILLELAGH": "shillelagh"},
{"POLEARMS": "polearms"},
{"DYRNWYN": "dyrnwyn"},
{"PARASHU": "parashu"},
@@ -44,15 +50,18 @@
{"TOMAHAWKS & DAGGERS": "tomahawks & daggers"},
{"CHAKRAMS & SHURIKENS": "chakrams and shurikens"},
{"BOOMERANG": "boomerang"},
+ {"BOLAS": "bolas"},
+ {"SLUDGE BALLS": "sludge balls"},
{" CRYSTALS": "-"},
{"ROCKS & GEMS": "rocks & gems"},
{"CRYSTAL SHARD": "crystal shard"},
- {"MYSTIC ORB": "mystic orb"},
+ {"MYSTIC ORB": "mystic orb"},
{" FOOD": "-"},
{"MORSELS": "cheese and apples"},
{"CREAM PIE": "cream pie"},
{"TOMALLEY": "tomalley"},
{"MEALS": "meat, fish, and bread"},
+ {"RATIONS": "rations"},
{"TIN & TIN OPENER": "tin and tin opener"},
{"BLOOD VIALS": "blood vials"},
{" POTIONS & DRINKS": "-"},
@@ -67,7 +76,9 @@
{"TOWEL": "towel"},
{"MINING PICK": "mining pick"},
{"TINKERING KIT": "tinkering kit"},
+ {"FRYPAN": "frypan"},
{"LOCKPICK": "lockpick"},
+ {"KEYS": "keys"},
{"SKELETON KEY": "skeleton key"},
{" GADGETS": "-"},
{"BEARTRAP": "beartrap"},
@@ -79,39 +90,48 @@
{"TELEPORTATION TRAP": "teleportation trap"},
{" OTHER": "-"},
{"MAIL & LORE BOOKS": "mail & lore books"},
- {"DEATH BOX": "death box"}
+ {"DEATH BOX": "death box"},
+ {"DUCK": "duck"}
],
"contents_alphabetical": [
{"ALEMBIC": "alembic"},
{"ARBALEST": "arbalest"},
{"AXES": "axes"},
{"BACKPACKS": "backpacks"},
- {"BEARTRAP": "beartrap"},
- {"BLOOD VIALS": "blood vials"},
+ {"BEARTRAP": "beartrap"},
+ {"BLOOD VIALS": "blood vials"},
+ {"BOLAS": "bolas"},
{"BOOMERANG": "boomerang"},
{"BOWS": "bows"},
+ {"CHAIN ARMOR": "chain armor"},
{"CHAKRAMS & SHURIKENS": "chakrams and shurikens"},
- {"CLOAKS & SHIRTS": "cloaks & clothing"},
+ {"CLOAKS & CLOTHING": "cloaks & clothing"},
{"CREAM PIE": "cream pie"},
{"CROSSBOW": "crossbow"},
{"CROWNS & HEADDRESSES": "crowns & headdresses"},
{"CRYSTAL AMMO": "crystal ammo"},
{"CRYSTAL ARMOR": "crystal armor"},
{"CRYSTAL SHARD": "crystal shard"},
+ {"DARTS & PLUMBATA": "darts and plumbata"},
{"DEATH BOX": "death box"},
{"DEFENSIVE POTIONS": "defensive potions"},
{"DJINNI'S BRACE": "djinnis brace"},
{"DRAGON'S MAIL": "dragons mail"},
+ {"DUCK": "duck"},
{"DUMMYBOT": "dummybot"},
{"DYRNWYN": "dyrnwyn"},
{"EMPTY BOTTLE": "empty bottle"},
{"FACE ACCESSORIES": "face accessories"},
{"FIRE & HUNTING AMMO": "fire and hunting ammo"},
{"FIST WEAPONS": "fist weapons"},
+ {"FLAILS": "flails"},
+ {"FRYPAN": "frypan"},
+ {"GREATSWORDS": "greatswords"},
{"GUNGNIR": "gungnir"},
{"GYROBOT": "gyrobot"},
{"HATS & HOODS": "hats & hoods"},
{"IRON ARMOR": "iron armor"},
+ {"KEYS": "keys"},
{"KHRYSELAKATOS": "khryselakatos"},
{"LEATHER ARMOR": "leather armor"},
{"LOCKPICK": "lockpick"},
@@ -128,11 +148,15 @@
{"ORACLE'S TREADS": "oracles treads"},
{"PARASHU": "parashu"},
{"POLEARMS": "polearms"},
+ {"QUILTED ARMOR": "quilted armor"},
+ {"RAPIER": "rapier"},
+ {"RATIONS": "rations"},
{"ROCKS & GEMS": "rocks & gems"},
{"SENTRY BOT & SPELLBOT": "sentrybot and magic sentry"},
{"SHARUR": "sharur"},
{"SHIELDS": "shields"},
- {"SILVER & PIERCING AMMO": "silver and piercing ammo"},
+ {"SHILLELAGH": "shillelagh"},
+ {"SILVER & PIERCING AMMO": "silver and piercing ammo"},
{"SKELETON KEY": "skeleton key"},
{"SLINGSHOT": "slingshot"},
{"SPHINX'S CASQUE": "sphinxs casque"},
diff --git a/lang/compendium_lang/contents_magic.json b/lang/compendium_lang/contents_magic.json
index 16219383f..04ea4a751 100644
--- a/lang/compendium_lang/contents_magic.json
+++ b/lang/compendium_lang/contents_magic.json
@@ -3,16 +3,22 @@
"contents": [
{" MAGIC ITEMS": "-"},
{"MAGICSTAFFS": "magicstaffs"},
+ {"SCEPTER": "scepter"},
+ {"SOURCE FOCI": "source_foci"},
+ {"HOLY SYMBOLS": "holy_symbols"},
+ {"DARK ICONS": "dark_icons"},
+ {"MUSICAL INSTRUMENTS": "instruments"},
{"ENCHANTED RINGS": "enchanted rings"},
{"ENCHANTED AMULETS": "enchanted amulets"},
+ {"JEWELS": "jewels"},
{" ARCANA": "-"},
{"SCROLLS": "scrolls"},
{"ENCHANTED FEATHER": "enchanted feather"},
{" SORCERY": "-"},
{"EVOCATION": "evocation"},
{"CONTRAVENTION": "contravention"},
- {"KINESIS": "kinesis"},
- {"TEMULTURGY": "temulturgy"},
+ {"KINESIS": "kinesis"},
+ {"TEMULTURGY": "temulturgy"},
{" THAUMATURGY": "-"},
{"BLESSINGS": "blessings"},
{"DIVINATIONS": "divinations"},
@@ -25,22 +31,28 @@
{"IMPETURIA": "impeturia"}
],
"contents_alphabetical": [
+ {"SCEPTER": "scepter"},
{"BLESSINGS": "blessings"},
{"CONTRAVENTION": "contravention"},
{"DAIMONIA": "daimonia"},
+ {"DARK ICONS": "dark_icons"},
{"DIVINATIONS": "divinations"},
{"ENCHANTED AMULETS": "enchanted amulets"},
{"ENCHANTED FEATHER": "enchanted feather"},
{"ENCHANTED RINGS": "enchanted rings"},
{"EVOCATION": "evocation"},
+ {"HOLY SYMBOLS": "holy_symbols"},
{"IMPETURIA": "impeturia"},
+ {"JEWELS": "jewels"},
{"KINESIS": "kinesis"},
{"MAGICSTAFFS": "magicstaffs"},
{"MEDITATIONS": "meditations"},
{"MIRACULOUS FEATS": "miraculous feats"},
+ {"MUSICAL INSTRUMENTS": "instruments"},
{"PSIANIMUS": "psianimus"},
{"SARKOMANCY": "sarkomancy"},
{"SCROLLS": "scrolls"},
+ {"SOURCE FOCI": "source_foci"},
{"TEMULTURGY": "temulturgy"}
]
}
\ No newline at end of file
diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json
index 68d47edb3..aa2cf3a78 100644
--- a/lang/compendium_lang/contents_monsters.json
+++ b/lang/compendium_lang/contents_monsters.json
@@ -12,13 +12,13 @@
{" BEASTS": "-"},
{"RAT": "rat"},
{"ALGERNON": "algernon"},
+ {"BAT": "bat"},
{"SPIDER": "spider"},
{"CRAB": "crab"},
{"SHELOB": "shelob"},
{"BUBBLES": "bubbles"},
{"TROLL": "troll"},
{"THUMPUS": "thumpus"},
- {"BAT": "bat"},
{"SCORPION": "scorpion"},
{"SKRABBLAG": "skrabblag"},
{"SCARAB": "scarab"},
@@ -38,6 +38,7 @@
{"GHOULS": "ghoul"},
{"CORAL GRIMES": "coral grimes"},
{"ENSLAVED GHOULS": "enslaved ghoul"},
+ {"REVENANT SKULL": "revenant_skull"},
{"VAMPIRE": "vampire"},
{"BRAM KINDLY": "bram kindly"},
{"SHADOW": "shadow"},
@@ -57,13 +58,14 @@
{"BAPHOMET": "devil"},
{" CONSTRUCTS": "-"},
{"AUTOMATON": "automaton"},
+ {"CRYSTAL GOLEM": "crystalgolem"},
{"SENTRYBOT": "sentrybot"},
{"SPELLBOT": "spellbot"},
{"DUMMYBOT": "dummybot"},
- {" ELEMENTALS": "-"},
+ {"MIMIC": "mimic"},
+ {" ELEMENTALS": "-"},
{"SLIMES": "slime"},
- {"CRYSTAL GOLEM": "crystalgolem"},
- {"MIMIC": "mimic"}
+ {"EARTH SPRITE": "earth_elemental"}
],
"contents_alphabetical": [
{"ALGERNON": "algernon"},
@@ -83,6 +85,7 @@
{"DEMON": "demon"},
{"DEU DE'BREAU": "deudebreau"},
{"DUMMYBOT": "dummybot"},
+ {"EARTH SPRITE": "earth sprite"},
{"ENSLAVED GHOULS": "enslaved ghoul"},
{"ERUDYCE": "lichice"},
{"FUNNY BONES": "funny bones"},
@@ -104,6 +107,7 @@
{"MYSTERIOUS MERCHANT": "mysterious shop"},
{"ORPHEUS": "lichfire"},
{"RAT": "rat"},
+ {"REVENANT SKULL": "revenant_skull"},
{"SCARAB": "scarab"},
{"SCORPION": "scorpion"},
{"SENTRYBOT": "sentrybot"},
diff --git a/lang/compendium_lang/contents_world.json b/lang/compendium_lang/contents_world.json
index 66cf4c1f9..aca454de4 100644
--- a/lang/compendium_lang/contents_world.json
+++ b/lang/compendium_lang/contents_world.json
@@ -4,6 +4,8 @@
{" FURNISHINGS": "-"},
{"GATE": "portcullis"},
{"LEVER": "lever"},
+ {"BUTTON": "button"},
+ {"WALL LOCKS": "wall locks"},
{"DOOR": "door"},
{"MURKY WATER": "murky water"},
{"LAVA": "lava"},
@@ -22,20 +24,23 @@
{"MINES": "mines"},
{"SWAMPS": "swamps"},
{"LABYRINTH": "labyrinth"},
- {"RUINS": "ruins"},
{"UNDERWORLD": "underworld"},
+ {"RUINS": "ruins"},
{"HELL": "hell"},
{"CRYSTAL CAVES": "crystal caves"},
- {"ARCANE CITADEL": "arcane citadel"},
+ {"ARCANE CITADEL": "arcane citadel"},
{" STATIONS": "-"},
{"SHOP": "shop"},
{"SINK": "sink"},
+ {"CAULDRON": "cauldron"},
+ {"WORKBENCH": "workbench"},
{"FOUNTAIN": "fountain"},
- {"CHEST": "chest"},
+ {"CHEST": "chest"},
{"GRAVESTONE": "gravestone"},
+ {"ASSIST SHRINE": "assist shrine"},
{"DAEDALUS SHRINE": "daedalus"},
{"OBELISK": "obelisk"},
- {"BELL": "bell"},
+ {"BELL": "bell"},
{" TRAPS": "-"},
{"BOULDER TRAP": "boulder trap"},
{"ARROW TRAP": "arrow trap"},
@@ -43,6 +48,7 @@
{"MAGIC TRAP": "magic trap"},
{"SUMMONING TRAP": "summoning trap"},
{"BRIMSTONE BOULDER": "brimstone boulder"},
+ {"ARCANE BOULDER": "arcane boulder"},
{"CEILING TRAP": "ceiling trap"},
{" SECRET PLACES": "-"},
{"GNOMISH MINES": "gnomish mines"},
@@ -51,9 +57,9 @@
{"HAUNTED CASTLE": "haunted castle"},
{"SOKOBAN": "sokoban"},
{"MINOTAUR MAZE": "minotaur maze"},
- {"MYSTIC LIBRARY": "mystic library"},
+ {"MYSTIC LIBRARY": "mystic library"},
{"COCKATRICE LAIR": "cockatrice lair"},
- {"BRAM'S CASTLE": "brams castle"},
+ {"BRAM'S CASTLE": "brams castle"},
{" GUILD LORE": "-"},
{"MERCHANTS' GUILD": "merchants guild"},
{"MAGICIANS' GUILD": "magicians guild"},
@@ -62,13 +68,17 @@
{"MASONS' GUILD": "masons guild"}
],
"contents_alphabetical": [
+ {"ARCANE BOULDER": "arcane boulder"},
{"ARCANE CITADEL": "arcane citadel"},
{"ARROW TRAP": "arrow trap"},
+ {"ASSIST SHRINE": "assist shrine"},
{"BELL": "bell"},
{"BOULDER TRAP": "boulder trap"},
{"BRAM'S CASTLE": "brams castle"},
{"BREAKABLE BARRIERS": "breakable barriers"},
{"BRIMSTONE BOULDER": "brimstone boulder"},
+ {"BUTTON": "button"},
+ {"CAULDRON": "cauldron"},
{"CEILING TRAP": "ceiling trap"},
{"CHEST": "chest"},
{"CITADEL SANCTUM": "citadel sanctum"},
@@ -113,6 +123,8 @@
{"THE CHURCH": "the church"},
{"THE MOLTEN THRONE": "molten throne"},
{"TRANSITION FLOOR": "transition floor"},
- {"UNDERWORLD": "underworld"}
+ {"UNDERWORLD": "underworld"},
+ {"WALL LOCKS": "wall locks"},
+ {"WORKBENCH": "cauldron"}
]
}
\ No newline at end of file
diff --git a/lang/compendium_lang/lang_codex.json b/lang/compendium_lang/lang_codex.json
index 50c301f28..943e1bf41 100644
--- a/lang/compendium_lang/lang_codex.json
+++ b/lang/compendium_lang/lang_codex.json
@@ -49,28 +49,30 @@
],
"details_line_highlights": []
},
- "appraisal skill": {
+ "lore skill": {
"blurb": [
- "While any adventurer can see what an item",
- "is just by looking at it, APPRAISAL reveals",
- "details, especially magical ones. The more",
- "valuable the item, the more challenging it",
- "is to appraise."
+ "While any adventurer can see what an",
+ "item is just by looking at it, skill in LORE",
+ "can reveal important magical details.",
+ "The more valuable the item, the more",
+ "challenging it is to appraise.",
+ "Skilled loremasters are great with song."
],
"details": [
- "Appraising inexpensive items, like",
- "cheese or leather items, is usually",
- "great for untrained practice.",
- "",
- "A few classes start without the ability",
+ "As adventurers grow in Lore skill,",
+ "manually managing which items are",
+ "selected for appraisal can become",
+ "more important. Higher value items",
+ "may take much more time to identify.",
+ "Focus on items that may be of actual",
+ "use and discard the rest to spend",
+ "less time fiddling with inventory.",
+ "",
+ "A few classes start with poor ability",
"to appraise, thanks to very poor",
- "perception.",
- "",
- "Using unidentified potions or scrolls",
- "may also yield an increase to",
- "appraisal skill, up to Basic (20) skill.",
- "",
- "Glasses may also help."
+ "perception. Using unidentified potions",
+ "or scrolls may yield an increase to",
+ "lore skill, up to Basic (20) skill."
],
"details_line_highlights": []
},
@@ -167,26 +169,6 @@
],
"details_line_highlights": []
},
- "casting skill": {
- "blurb": [
- "Improving CASTING SKILL reduces the",
- "chances that a spell fizzles, and increases",
- "MP RGN. Upon reaching legendary casting,",
- "an adventurer gains the forcebolt",
- "spell as a cantrip."
- ],
- "details": [
- "When your casting skill is low, casting",
- "low-cost spells will be the most",
- "reliable path to growth.",
- "",
- "It may be tempting to cast a big spell,",
- "but it may fizzle and rob you of many",
- "chances to cast more, cheaper spells",
- "by emptying your MP/EN/ST supply."
- ],
- "details_line_highlights": []
- },
"chr": {
"blurb": [
"Each point in CHARISMA (CHR) improves the",
@@ -211,7 +193,7 @@
"Gaining early access to a shopkeeper's",
"private stock can mean the difference",
"between a great adventure and a",
- "desparate one."
+ "desperate one."
],
"details_line_highlights": []
},
@@ -505,27 +487,6 @@
],
"details_line_highlights": []
},
- "magic skill": {
- "blurb": [
- "Improving MAGIC SKILL allows an",
- "adventurer to learn more advanced spells",
- "and increases PWR. A legend in magic",
- "also learns the dominate spell."
- ],
- "details": [
- "A good spellcaster always stays in",
- "practice!",
- "",
- "For those with MP/ST regeneration, a",
- "full bar means you're losing out on",
- "mana you could be recuperating.",
- "",
- "While it's good to save it for when you",
- "need it, chip away at the top of a full",
- "bar to keep training."
- ],
- "details_line_highlights": []
- },
"melee": {
"blurb": [
"Simply wield your fist or a melee weapon,",
@@ -898,16 +859,16 @@
"and penalties."
],
"details": [
- "Most monster races can recruit their",
- "own kind. Only humans and automatons",
- "are welcome inside human-run shops.",
- "",
- "When selecting your race during",
- "character creation, use the \"Show",
- "Race Info\" button to reveal all of",
- "your strengths, weaknesses, abilities,",
- "and allegiances.",
- "",
+ "Most races can recruit their own kind.",
+ "While human shopkeepers will",
+ "attack threats on sight, salamanders,",
+ "myconids, dryads, gnomes, and",
+ "automatons are welcome in shops.",
+ "",
+ "When selecting your race, use",
+ "the \"Show Race Info\" button to",
+ "reveal all strengths, weaknesses,",
+ "abilities, and allegiances.",
"To make the dungeons easier, pair",
"each race with a class that minimizes",
"the downsides, and doubles-down on",
@@ -1150,26 +1111,6 @@
],
"details_line_highlights": []
},
- "swimming skill": {
- "blurb": [
- "Swimming isn't usually the most important",
- "skill, but when you're fleeing from a",
- "dangerous threat, it might just be the most",
- "important skill of your life. Especially",
- "useful in the swamps."
- ],
- "details": [
- "While the waterwalking provided by",
- "the swimming skill's legendary bonus",
- "can be useful in many situations, some",
- "adventurers may find themselves",
- "wishing they could take a quick dip to",
- "put out burning flames.",
- "",
- "Consider your next soak carefully!"
- ],
- "details_line_highlights": []
- },
"sword skill": {
"blurb": [
"Slashing against foes with a sword",
@@ -1392,5 +1333,370 @@
"you can keep gaining experience."
],
"details_line_highlights": []
+ },
+ "sorcery skill": {
+ "blurb": [
+ "The SORCERY SKILL allows casters to",
+ "learn more advanced Sorcery spells and",
+ "increases PWR using them.",
+ "A Legend in Sorcery has increased",
+ "casting speeds and even greater PWR",
+ "while using their Sorcery spells."
+ ],
+ "details": [
+ "Sorcery is the most direct of all",
+ "magic disciplines for adventuring.",
+ "No other school provides such",
+ "prolific access to combat spells",
+ "at all levels of skill. In addition,",
+ "powerful utility and defensive magic",
+ "are found in the sorcerer's grimoire.",
+ "",
+ "Like all magic users, sorcerers",
+ "should trade out spells as they grow",
+ "in skill. But they'll do well to",
+ "remember their most reliable spells",
+ "when facing greater challenges."
+ ],
+ "details_line_highlights": []
+ },
+ "thaumaturgy skill": {
+ "blurb": [
+ "The THAUMATURGY SKILL allows casters",
+ "to learn more advanced Thaumaturgy",
+ "spells and increases PWR using them.",
+ "A Legend in Thaumaturgy has increased",
+ "casting speeds and even greater PWR",
+ "while using their Thaumaturgy spells."
+ ],
+ "details": [
+ "The preferred school for devout",
+ "adventurers, thaumaturges provide",
+ "protection, restoration, and access to",
+ "divine wisdom or providence. These",
+ "offer assistance both to individual",
+ "adventurers and their parties.",
+ "",
+ "Though most reliable for utility uses,",
+ "some spells offer divine wrath against",
+ "undead and demonic foes.",
+ "",
+ "Thaumaturges tend to succeed when",
+ "magic is used to supplement other",
+ "practical adventuring skills."
+ ],
+ "details_line_highlights": []
+ },
+ "mysticism skill": {
+ "blurb": [
+ "The MYSTICISM SKILL allows casters",
+ "to learn more advanced Mysticism",
+ "spells and increases PWR using them.",
+ "A Legend in Mysticism has increased",
+ "casting speeds and even greater PWR",
+ "while using their Mysticism spells."
+ ],
+ "details": [
+ "Spells available to mystics tend to",
+ "provide methods of control. Though",
+ "often indirect compared to the other",
+ "schools, a successful mystic prefers",
+ "to create and exploit opportunity.",
+ "",
+ "While not as reliable as sorcery for",
+ "combat spells, mysticism attack spells",
+ "may open up or pay off on other kinds",
+ "of opportunities to devastating effect.",
+ "",
+ "Mysticism reliably allows dominion",
+ "over objects and minds for the",
+ "more strategic adventurer."
+ ],
+ "details_line_highlights": []
+ },
+ "offhand casting": {
+ "blurb": [
+ "Different kinds of magic items can be",
+ "held for OFF-HAND CASTING, allowing the",
+ "wielder to channel a magic effect.",
+ "Channeling may rapidly recast the item's",
+ "effect, or build up a spell's charge,",
+ "until channeling is released."
+ ],
+ "details": [
+ "Three categories of off-hand casting",
+ "items align with the three main schools",
+ "of magic. These benefit the spells cast",
+ "from that school with additional PWR,",
+ "but they also provide their own magic",
+ "effects. The fourth category benefits",
+ "from the Lore skill.",
+ "",
+ "SOURCE FOCI:",
+ "Particularly for sorcerers who like",
+ "to keep cleverer spells prepared, an",
+ "elemental source focus provides fast",
+ "access to quick blasts of damage.",
+ "",
+ "HOLY SYMBOL:",
+ "Carried faithfully by thaumaturges,",
+ "holy symbols reward preparation with",
+ "largely preventative benefits.",
+ "",
+ "DARK ICON:",
+ "Ideal for when one might have allies",
+ "to buy time in a fight, dark icons",
+ "afflict powerful curses at some",
+ "distance. The curse applies to an",
+ "area, potentially giving greater",
+ "control over enemy combatants.",
+ "",
+ "BARD INSTRUMENTS:",
+ "Continually adding powerful buffs",
+ "to the player and any nearby allies",
+ "or followers, Lore and Charisma aids",
+ "the musician as they pour MP into",
+ "their song for supernatural benefits."
+ ],
+ "details_line_highlights": [
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {
+ "color": {
+ "r": 198,
+ "g": 190,
+ "b": 179,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {},
+ {
+ "color": {
+ "r": 198,
+ "g": 190,
+ "b": 179,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {},
+ {
+ "color": {
+ "r": 198,
+ "g": 190,
+ "b": 179,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 198,
+ "g": 190,
+ "b": 179,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ },
+ {
+ "color": {
+ "r": 157,
+ "g": 129,
+ "b": 93,
+ "a": 255
+ }
+ }
+ ]
+ },
+ "gold": {
+ "blurb": [
+ "GOLD is a resource used most often",
+ "by adventurers for trade at shops.",
+ "It has weight, so it's wise not to",
+ "lug it around without intention.",
+ "Some spells can consume gold to",
+ "power a material-altering effect."
+ ],
+ "details": [
+ "Gold may often purchase great",
+ "opportunity for victory. Whether",
+ "an adventurer is in need of healing,",
+ "a new spell, food, or a new piece of",
+ "equipment, gold may be the sole",
+ "resource which could solve all of",
+ "these problems, with the right vendor.",
+ "",
+ "While gold contributes to the score",
+ "of any given run at the dungeon,",
+ "monster races may find little use for",
+ "the beloved metal, given how rarely",
+ "they may be able to interact with",
+ "shops."
+
+ ],
+ "details_line_highlights": []
}
}
\ No newline at end of file
diff --git a/lang/compendium_lang/lang_items.json b/lang/compendium_lang/lang_items.json
index 40e96bd63..ef27feb89 100644
--- a/lang/compendium_lang/lang_items.json
+++ b/lang/compendium_lang/lang_items.json
@@ -681,5 +681,125 @@
"The traveler carried the spirit out,",
"but only its clothes crossed over."
]
+ },
+ "quilted armor": {
+ "blurb": [
+ "While often used for padding underneath",
+ "metal armors, tightly stitched layers",
+ "of fine cloth make for good protection",
+ "on their own.",
+ "Stacks of fabric can reduce the",
+ "penetration of arrows and spearheads."
+ ]
+ },
+ "chain armor": {
+ "blurb": [
+ "Fashioned by the finest smiths, chain armor",
+ "is a rare commodity.",
+ "While its flexibility makes it less protective",
+ "than other metal armors, it is particularly",
+ "adept at deflecting the cutting edges of ",
+ "swords and axes."
+ ]
+ },
+ "frypan": {
+ "blurb": [
+ "Frontier cooks swear by this iron",
+ "cookware's indestructibility, so long",
+ "as the metal is treated properly!",
+ "A campfire is all that's needed for",
+ "this portable kitchen to function.",
+ "While unwieldy, it can be used as a shield!"
+ ]
+ },
+ "rations": {
+ "blurb": [
+ "Connoisseurs of the humble ration are",
+ "content not knowing what goes into",
+ "their ready-packed meals.",
+ "",
+ "A skilled cook can make them tastier,",
+ "even for the pickiest of palates."
+ ]
+ },
+ "sludge balls": {
+ "blurb": [
+ "Despite being a byproduct of food,",
+ "SLUDGE BALLS have no nutritional value.",
+ "",
+ "Resourceful adventurers use them",
+ "as thrown ammunition to humiliate and",
+ "debilitate their foes simultaneously."
+ ]
+ },
+ "keys": {
+ "blurb": [
+ "While KEYS are typically found somewhere",
+ "near their matching lock, some rare",
+ "keys are hidden much more carefully.",
+ "",
+ "Those made from more precious metals",
+ "give access to more precious treasures."
+ ]
+ },
+ "flails": {
+ "blurb": [
+ "Long-chain varieties of FLAIL are",
+ "rare, but the extra range provided is a",
+ "substantial advantage for a mace.",
+ "While whirling it overhead to build",
+ "momentum for a critical strike,",
+ "enemies face rapid light attacks."
+ ]
+ },
+ "greatswords": {
+ "blurb": [
+ "Larger blades like the claymore",
+ "are made of fine, sturdy steel,",
+ "whose weight can parry other attacks.",
+ "Their broad and heavy cutting blades",
+ "can be used to cleave through multiple",
+ "nearby foes with a critical strike."
+ ]
+ },
+ "rapier": {
+ "blurb": [
+ "In the hands of a dextrous fencer, the",
+ "nimble RAPIER can riposte when parrying,",
+ "allowing a talented swordsman to defeat",
+ "foes with a patient and consistent defense.",
+ "Their fine craftsmanship makes them an",
+ "exceptionally rare favorite of Bards."
+ ]
+ },
+ "duck": {
+ "blurb": [
+ "Like many birds, DUCKS are territorial",
+ "and loyal to their partners for life. They'll",
+ "courageously pester deadly threats, and",
+ "are surprisingly deft at avoiding harm.",
+ "They may become distracted by fishing",
+ "opportunities."
+ ]
+ },
+ "bolas": {
+ "blurb": [
+ "Simple BOLAS are a favorite tool for",
+ "guerilla skirmishers, using weights at",
+ "the end of cord to make a projectile",
+ "with one job: Entangling its target.",
+ "While they deal little damage, victims",
+ "are very vulnerable while wrapped."
+ ]
+ },
+ "shillelagh": {
+ "blurb": [
+ "A knobby length of wood is cured",
+ "according to folk tradition, making",
+ "SHILLELAGHS nearly unbreakable.",
+ "Hermits hollow and fill the clubbed",
+ "end with dense magically-reactive metal,",
+ "empowering strikes against hindered foes."
+ ]
}
}
\ No newline at end of file
diff --git a/lang/compendium_lang/lang_magic.json b/lang/compendium_lang/lang_magic.json
index b143f3973..4f9ae0683 100644
--- a/lang/compendium_lang/lang_magic.json
+++ b/lang/compendium_lang/lang_magic.json
@@ -13,7 +13,7 @@
"blurb": [
"While most magic could be described as",
"breaking the rules of reality, the study",
- "of CONTRAVENTION allows sorcerors to",
+ "of CONTRAVENTION allows sorcerers to",
"change how rules effect one's self.",
"This requires ongoing magic energy to",
"sustain, but can be disabled at will."
@@ -71,7 +71,7 @@
},
"evocation": {
"blurb": [
- "Sorcerors practice EVOCATION to conjure",
+ "Sorcerers practice EVOCATION to conjure",
"energies and material using their minds.",
"Largely this is used to launch harmful",
"projectiles at enemies by adventurers,",
@@ -84,7 +84,7 @@
"Mystics who practice IMPETURIA do so by",
"calling to the spirit of the object they",
"wish to manipulate, and compel it to alter",
- "its state. Sorcerors attempting to do the",
+ "its state. Sorcerers attempting to do the",
"same thing with kinesis have so far been",
"unsuccessful."
]
@@ -164,8 +164,68 @@
"not simply hurl one force at another.",
"The study of TEMULTURGY is specialized",
"toward disrupting an object's structure.",
- "So far sorcerors haven't figured out",
+ "So far sorcerers haven't figured out",
"how to practice this on living targets."
]
- }
+ },
+ "scepter": {
+ "blurb": [
+ "With its incredible destructive potential",
+ "the ARCHON SCEPTER is fed by the wielder's",
+ "surrendered memories.",
+ "Those found wielding one usually claim",
+ "they own it by birthright. Others cannot",
+ "recall where they obtained it."
+ ]
+ },
+ "source_foci": {
+ "blurb": [
+ "Sorcerers often carry raw elemental",
+ "manifestations as SOURCE FOCI, improving",
+ "their connection to magical energies.",
+ "Each one emits a glow, and readily",
+ "provides a channel for rapid evocation",
+ "when needed for dealing with threats."
+ ]
+ },
+ "holy_symbols": {
+ "blurb": [
+ "Casting out darkness wherever they go,",
+ "devout thaumaturgists wield HOLY SYMBOLS.",
+ "Dedicating a prayer with one of these in",
+ "hand provides its blessing to any willing",
+ "soul when the magical chant concludes.",
+ "Taking more time improves the effect."
+ ]
+ },
+ "dark_icons": {
+ "blurb": [
+ "DARK ICONS are used by mystics to",
+ "channel their magical arts to lay",
+ "curses against their enemies.",
+ "Each one provides its own malus that",
+ "the caster invokes with whispers,",
+ "hindering enemies from some distance."
+ ]
+ },
+ "instruments": {
+ "blurb": [
+ "While mundane on their own, MUSICAL",
+ "INSTRUMENTS channel magic from the",
+ "musician's spirit, uplifting friends",
+ "who are present to hear it.",
+ "When played with dedication and care,",
+ "songs hang in the air for a while."
+ ]
+ },
+ "jewels": {
+ "blurb": [
+ "JEWELS are valuable and alluring to",
+ "most intelligent creatures, serving",
+ "as fine payment for loyal followers.",
+ "Many jewelers embed their gemstones",
+ "in gold, enhancing their beauty",
+ "in addition to their value."
+ ]
+ }
}
\ No newline at end of file
diff --git a/lang/compendium_lang/lang_monsters.json b/lang/compendium_lang/lang_monsters.json
index 456d3011c..b35e64306 100644
--- a/lang/compendium_lang/lang_monsters.json
+++ b/lang/compendium_lang/lang_monsters.json
@@ -781,7 +781,9 @@
"Their slimy meat is unappealing to some",
"but it is quite nutritious."
],
- "abilities": [],
+ "abilities": [
+ "- Weakness Cursing Attacks"
+ ],
"inventory": [
"- Tomalleys",
"- Low value gemstones"
@@ -1029,7 +1031,7 @@
},
"xyggi": {
"blurb": [
- "The cult of sorcerors believes that all",
+ "The cult of sorcerers believes that all",
"intellect is born of magic, claiming that",
"where you find one, the other will follow.",
"",
@@ -1045,5 +1047,149 @@
"- Tomalleys",
"- Low value gemstones"
]
- }
+ },
+ "gremlin": {
+ "blurb": [
+ "GREMLINS are creatures with such taste",
+ "for chaos that they could not coexist",
+ "with the denizens of Hell itself.",
+ "Only goblins suffer their company,",
+ "and even then, at arm's length.",
+ "NOT TO BE TRUSTED."
+ ],
+ "abilities": [
+ "- Deface",
+ "- Vandalize"
+ ],
+ "inventory": [
+ "- Bone and Blackiron Weapons",
+ "- Shawls and Hoods"
+ ]
+ },
+ "dryad": {
+ "blurb": [
+ "While the origins are unclear, a forest",
+ "of sufficient size and age is often under",
+ "the protection of DRYAD conservators.",
+ "Sightings in any realm are rare, but it",
+ "is best to remain in their good",
+ "graces if one finds you."
+ ],
+ "abilities": [
+ "- Roots",
+ "- Thorns"
+ ],
+ "inventory": [
+ "- Nuts",
+ "- Branch Weapons"
+ ]
+ },
+ "myconid": {
+ "blurb": [
+ "Wherever you find decay in dryad-",
+ "protected forests, there you will also",
+ "find MYCONIDS. While the two aren't",
+ "hostile to one another, they also do",
+ "not appear to cohabitate. Beware caves",
+ "full of mushrooms that may be guarded."
+ ],
+ "abilities": [
+ "- Spores"
+ ],
+ "inventory": [
+ "- Mushroom Slices",
+ "- Bone Weapons"
+ ]
+ },
+ "salamander": {
+ "blurb": [
+ "Legends say that SALAMANDERS carry the",
+ "fires of loyalty and justice with fierce",
+ "devotion to the Eternals in their hearts.",
+ "When enraged, they appear as blazing",
+ "angels, but rely on sturdy tradition",
+ "during times of lower passion."
+ ],
+ "abilities": [
+ "- Breathe Fire",
+ "- Radiant Form",
+ "- Stone Form"
+ ],
+ "inventory": [
+ "- Silver Weapons and Armor",
+ "- Claymores",
+ "- Holy Symbols"
+ ]
+ },
+ "revenant_skull": {
+ "blurb": [
+ "A product of dark mysticism, the",
+ "REVENANT SKULL is summoned by piercing",
+ "void just as a cursed soul crosses over.",
+ "Though useful, it's considered by the",
+ "cult of sorcerers to be an",
+ "incomplete spell."
+ ],
+ "abilities": [
+ "- Levitate",
+ "- Slow"
+ ],
+ "inventory": [
+
+ ]
+ },
+ "fire sprite": {
+ "blurb": [
+ "Whether through naivety or mischief,",
+ "FIRE SPRITES like to bless others with",
+ "a gift of burning flames. Novice",
+ "sorcerers can learn a lot from them, as",
+ "their temprament is the calmest of the",
+ "fire spirits. A slow burn, if you will."
+ ],
+ "abilities": [
+ "- Levitate",
+ "- Fire Dart"
+ ],
+ "inventory": [
+
+ ]
+ },
+ "earth_elemental": {
+ "blurb": [
+ "The stubborn EARTH SPRITE requires the",
+ "talents of a skilled mystic to conjure.",
+ "After being lured into local earth, it",
+ "crashes down and serves as a",
+ "durable guardian for as long",
+ "as its form holds shape."
+ ],
+ "abilities": [
+ "- Cave In",
+ "- Levitation",
+ "- Spin (self)",
+ "- Block"
+ ],
+ "inventory": [
+
+ ]
+ },
+ "duck": {
+ "blurb": [
+ "Like many birds, DUCKS are territorial,",
+ "and loyal to their partners for life.",
+ "They will courageously pester deadly",
+ "threats, and are surprisingly deft at",
+ "avoiding harm. Ducks may become",
+ "distracted by fishing opportunities."
+ ],
+ "abilities": [
+ "- Distract",
+ "- Levitation",
+ "- Invulnerable"
+ ],
+ "inventory": [
+
+ ]
+ }
}
\ No newline at end of file
diff --git a/lang/compendium_lang/lang_world.json b/lang/compendium_lang/lang_world.json
index b9e7db6a5..c0934c28a 100644
--- a/lang/compendium_lang/lang_world.json
+++ b/lang/compendium_lang/lang_world.json
@@ -846,10 +846,10 @@
"This also reveals the map between the",
"shrine and the exit!",
"",
- "If activated within 30 seconds of the",
- "minotaur's arrival, the shrine boosts",
- "user's escape with an additional",
- "blessing of speed.",
+ "If activated within 30 seconds before",
+ "the minotaur's arrival, the shrine",
+ "boosts the user's escape with an",
+ "additional blessing of speed.",
"",
"Adventurers exploring together may",
"find a bit more confidence splitting",
@@ -1986,19 +1986,16 @@
],
"details": [
"SECRET LEVEL TREASURES:",
- "- Red Mystic Orb",
"- Gungnir",
"",
- "For most, it is wise to grab the orb",
- "and outrun the minotaur to the exit.",
"Look for nearby gatehouse levers if",
"closed gates block the way. A spell",
"of opening, or digging, can help!",
"",
- "The pedestal at the end of the maze",
- "cripples the minotaur temporarily",
- "when the orb is placed upon it.",
- "Don't forget to reclaim the orb!"
+ "The silver key at the start of the",
+ "maze gives access to Gungnir in the",
+ "center. But the key may be useful if",
+ "you don't have need of the spear!"
],
"details_line_highlights": [
{
@@ -2090,7 +2087,6 @@
],
"details": [
"SECRET LEVEL TREASURES:",
- "- Blue Mystic Orb",
"- Chests of random magic items",
"- Several humans to recruit",
"",
@@ -2504,7 +2500,7 @@
],
"details": [
"SECRET LEVEL TREASURES:",
- "- Djinn's Brace",
+ "- Djinni's Brace",
"- A fortune in gold pieces",
"",
"To solve this challenge, one must rid",
@@ -2672,13 +2668,12 @@
],
"details": [
"SECRET LEVEL TREASURES:",
- "- Green Mystic Orb",
"- 4 Potions of strength",
"- A hoard of gold pieces",
"- Several treasure chests",
"",
- "Hidden rooms make use of the orb's",
- "power, revealing more treasures.",
+ "Hidden rooms make use of a silver",
+ "key, revealing more treasures.",
"The Temple's traps may shut down",
"access to secret areas if explorers",
"are careless, or prisoners are freed.",
@@ -2800,7 +2795,7 @@
"SECRET LEVEL TREASURES:",
"- Khryselakatos (Lvl 7)",
"",
- "The underworld can be accessed tiwce",
+ "The underworld can be accessed twice",
"via secret entrances on floor 5 and 18.",
"In both cases, adventurers who find",
"a trapdoor down to a second floor of",
@@ -2852,5 +2847,163 @@
}
}
]
+ },
+ "cauldron": {
+ "blurb": [
+ "Whether you need to boil potion",
+ "mixtures, or cook up a hot meal,",
+ "the sturdy CAULDRON is a practical",
+ "fixture for any facility.",
+ "Shame it's too heavy",
+ "to take along with you!"
+ ],
+ "details": [
+ "Finding a cauldron is an opportunity",
+ "for any adventurer, whether or not",
+ "they are skilled in alchemy, to ease",
+ "pressure on inventory space. Cooking",
+ "consolidates any food into rations,",
+ "while decanting consolidates potions.",
+ "",
+ "Combining potions and rations allows",
+ "for the creation of flavored food",
+ "which can enhance adventurer stats.",
+ "",
+ "Those with 80 or more skill in alchemy",
+ "are able to make fresh rations, safe",
+ "from the perils of indigestion."
+ ],
+ "details_line_highlights": []
+ },
+ "workbench": {
+ "blurb": [
+ "While not as convenient as the portable",
+ "tinkering kit, the WORKBENCH has all the",
+ "same tools available for resourceful",
+ "adventurers to fashion useful items for",
+ "the obstacles which lie ahead."
+ ],
+ "details": [
+ "As a sturdier table and set of heftier",
+ "tools, workbenches do not degrade,",
+ "making it an efficient find, even if",
+ "carrying a tinkering kit.",
+ "",
+ "Many adventurers may benefit from",
+ "occasional tinkering, particularly",
+ "for keeping tools handy, like",
+ "empty flasks, lockpicks, or traps.",
+ "",
+ "As only the mechanist class starts with",
+ "the kit, but several others have some",
+ "tinkering skill, the workbench allows",
+ "broader use of tinkering tools."
+ ],
+ "details_line_highlights": []
+ },
+ "button": {
+ "blurb": [
+ "Like the lever, BUTTONS can be pressed",
+ "manually, but may also be struck from",
+ "afar with projectiles to activate",
+ "dungeon mechanisms.",
+ "Some are designed to reset after a time."
+ ],
+ "details": [
+ "Not much needs to be said about the",
+ "humble dungeon button.",
+ "",
+ "Architects may prefer buttons over",
+ "levers, as no clear floor space is",
+ "required, and buttons are easier",
+ "to hide on walls where outsiders",
+ "are not intended to access.",
+ "",
+ "Beware a button which resets, as",
+ "it usually heralds a puzzle, or",
+ "perhaps a trap for the careless."
+ ],
+ "details_line_highlights": []
+ },
+ "wall locks": {
+ "blurb": [
+ "Usually used to secure impenetrable iron",
+ "doors, the WALL LOCK requires a key made",
+ "from matching materials.",
+ "Locks may operate other dungeon gadgets",
+ "and, in some cases, may be toggled to",
+ "activate and deactivate a gadget."
+ ],
+ "details": [
+ "As iron doors are most often used to",
+ "guard precious items or gold, keys are",
+ "often traded for treasures.",
+ "Adventurers should make use of the",
+ "handy window installed in iron doors",
+ "to first peek at the prize within,",
+ "presuming they're tall enough.",
+ "",
+ "Iron, Bronze, and some Silver locks",
+ "may be picked by sufficiently skilled",
+ "Tinkerers, or with a Skeleton Key.",
+ "But as the value of the treasure",
+ "increases with the depth of the",
+ "dungeon, so too does security."
+ ],
+ "details_line_highlights": []
+ },
+ "arcane boulder": {
+ "blurb": [
+ "Though boulders are already a deadly",
+ "hazard, concentrations of crystal ",
+ "imbues them with dangerous magic.",
+ "",
+ "Arcane boulders roll with enhanced speed",
+ "and momentum, defying physical laws."
+ ],
+ "details": [
+ "The most deadly boulder trap, those",
+ "who live long enough to face these",
+ "imposing orbs should be adept at",
+ "avoiding them.",
+ "",
+ "Beware, they fall and roll much",
+ "faster, and can roll over liquid",
+ "and pits unimpeded, and their",
+ "crushing power is enhanced with",
+ "a magical detonation.",
+ "",
+ "Those with Push or Telekinesis may",
+ "be able to put these magical",
+ "qualities to use."
+ ],
+ "details_line_highlights": []
+ },
+ "assist shrine": {
+ "blurb": [
+ "The ASSIST SHRINE did not always grace",
+ "the Minehead entry. The Church claims the",
+ "Eternals placed it there to:",
+ "''Bless the courageous to defeat Herx and",
+ "end his curse; Give hope to the hopeless.''",
+ "(for better or worse)"
+ ],
+ "details": [
+ "When claiming items from the assist",
+ "shrine, a party should be aware that",
+ "they will be judged according to the",
+ "sum total of assistance taken.",
+ "For parties with mixed expertise, this",
+ "can allow stronger adventurers to",
+ "bear the full challenge, while novices",
+ "face a much less daunting challenge.",
+ "",
+ "Adventuring solo, even novice heroes",
+ "can accomplish great feats by taking",
+ "much more assistance.",
+ "",
+ "There is no shame in doing so!"
+ ],
+ "details_line_highlights": []
}
}
\ No newline at end of file
diff --git a/lang/en.txt b/lang/en.txt
index a156ba9c9..68d2a4e52 100644
--- a/lang/en.txt
+++ b/lang/en.txt
@@ -397,11 +397,11 @@ thou makest use of them.#
236 Tinkering#
237 Stealth#
238 Trading#
-239 Appraise#
-240 Swimming#
+239 Lore#
+240 Thaumaturgy#
241 Leader#
-242 Casting#
-243 Magic#
+242 Mysticism#
+243 Sorcery#
244 Ranged#
245 Sword#
246 Mace#
@@ -664,8 +664,8 @@ thou makest use of them.#
446 You shot the %s!#
447 But it didn't even flinch!#
448 You shot %s!#
-449 But she didn't even flinch!#
-450 But he didn't even flinch!#
+449 But they didn't even flinch!#
+450 But they didn't even flinch!#
451 You are hit by an arrow!#
452 But it bounced off your armor!#
453 The arrow poisons you!#
@@ -1065,8 +1065,8 @@ You should call a plumber!#
692 You killed the %s!#
693 You critically hit %s!#
694 You hit %s!#
-695 She didn't even flinch!#
-696 He didn't even flinch!#
+695 They didn't even flinch!#
+696 They didn't even flinch!#
697 You killed %s!#
698 The %s %s!#
699 %s %s!#
@@ -1301,17 +1301,17 @@ Thank you!#
851 A field of light surrounds you!#
852 This scroll is blank.#
853 Your hand feels warm for a moment.#
-854 Your weapon glows black.#
+854 Your weapon glows ominously.#
855 Your weapon glows blue.#
856 Your weapon violently glows blue.#
857 Your skin feels warm for a moment.#
-858 Your %s glows black.#
+858 Your %s glows ominously.#
859 Your %s glows blue.#
860 Your %s violently glows blue.#
861 You feel like someone is helping you.#
862 You feel funny for a moment.#
863 The scroll smokes and crumbles to ash!#
-864 The scroll erupts in a tower of flame!#
+864 The scroll erupts in a circle of flame!#
865 A feast suddenly appears before you!#
866 Your stomach grumbles, but it passes.#
867 Your food pouch feels lighter!#
@@ -1942,8 +1942,8 @@ Compatible save file not found.#
1506 goes for a swim in some lava.#
1507 walks into a spike trap and dies.#
1508 gets killed by a flying %s.#
-1509 was betrayed by his fellow %s.#
-1510 was betrayed by her fellow %s.#
+1509 was betrayed by a fellow %s.#
+1510 was betrayed by a fellow %s.#
1511 is violently dispatched by an angry human.#
1512 gets nibbled to death by a lowly rat.#
1513 is viciously flayed by a goblin.#
@@ -1961,8 +1961,8 @@ Compatible save file not found.#
1525 is unceremoniously pulverized by the minotaur.#
1526 is damned to hell. Literally.#
1527 attempts a robbery and fails.#
-1528 kills himself during an invocation.#
-1529 kills herself during an invocation.#
+1528 was killed attempting an invocation.#
+1529 was killed attempting an invocation.#
1530 diets for a little too long.#
1531 is finished off by the poison.#
1532 bleeds to death.#
@@ -3596,10 +3596,10 @@ last of your magic is drained!#
3238 DEPRECATED#
3239 Skill number invalid!#
-3240 Your appraisal is not high enough to identify
+3240 Your Lore skill is not high enough to identify
"%s".#
3241 You feel the curse lift as you incant the spell...#
-3242 The tomes' knowledge grants you control over the curse!#
+3242 The tome's knowledge grants you control over the curse!#
3243 Begone from my shop, %s %s!#
3244 learns about the "no %s allowed" policy of %s's shop.#
3245 %d potions dropped from the fountain!#
@@ -4029,7 +4029,7 @@ last of your magic is drained!#
3660 %d complex #
3661 %d intricate #
3662 %d artisan #
-3663 Auto salvage...#
+3663 Salvage...#
3664 You are unable to salvage %s.#
3665 Salvaged %d %s.#
3666 You have nothing else to learn from salvaging.#
@@ -4088,13 +4088,13 @@ any gyrobot findings.#
3717 DEPRECATED#
3718 Inscribe#
3719 Repair#
-3720 Select a blank scroll to inscribe#
+3720 Select a blank scroll to inscribe:#
3721 DEPRECATED#
3722 DEPRECATED#
3723 Clear Slot#
3724 Inscribed %s!#
3725 You trace over some of the faded text...#
-3726 Select a spellbook to repair#
+3726 Select a spellbook to repair:#
3727 Your %s is depleted!#
3728 Your %s depletes before you can complete
the inscription!#
@@ -5072,8 +5072,8 @@ The next input you activate will be bound to this action.#
5217 Mouse & Keyboard#
5218 Mouse Sensitivity#
5219 Control the speed by which mouse movement affects camera movement.#
-5220 Number Keys in Inventory#
-5221 Allow the player to bind inventory items to the hotbar using the number keys on their keyboard.#
+5220 Hotbar Keys in Inventory#
+5221 Allow the player to bind inventory items to the hotbar using the hotbar slot keys on their keyboard.#
5222 Reverse Mouse#
5223 Reverse mouse up and down movement for controlling the orientation of the player.#
5224 Smooth Mouse#
@@ -6609,7 +6609,87 @@ Host to enable the game setting.
(%s %s)#
6356 You claim %d Assistance points from the shrine.#
6357 %s claims %d Assistance points from the Assist shrine.#
-
+6358 dryad#
+6359 myconid#
+6360 salamander#
+6361 gremlin#
+6362 dryads#
+6363 myconids#
+6364 salamanders#
+6365 gremlins#
+6366 strikes#
+6367 strikes#
+6368 strikes#
+6369 strikes#
+6370 *sigh...*#
+6371 *croak*#
+6372 Hail, champion.#
+6373 Parrat soo skeba da.#
+6374 You reset the %s lock.#
+6375 You activate the %s lock.#
+6376 The %s key is stuck tight!#
+6377 The %s lock is currently in use.#
+6378 You activate the lock using your %s.#
+6379 You have no matching key for that %s lock.#
+6380 That button is already pressed.#
+6381 You press the button.#
+6382 You hit the button!#
+# lock types
+6383 stone#
+6384 spirit#
+6385 bronze#
+6386 iron#
+6387 silver#
+6388 gold#
+6389 crystal#
+6390 machine#
+6391 key#
+6392 lock#
+6393 button#
+6394 You see a %s lock.#
+6395 You see a %s lock with a key.#
+6396 You see a button.#
+6397 Use %s key #
+6398 #
+6399 Reset %s lock#
+6400 Activate %s lock#
+6401 Press button#
+6402 (%d held)#
+6403 The iron door's lock is controlled elsewhere!#
+6404 You open the iron door.#
+6405 You close the iron door.#
+6406 The iron door locks as you close it!#
+6407 The iron door is locked!#
+6408 You lock the iron door!#
+6409 You see an iron door.#
+6410 Your spell destroys the iron door!#
+6411 You open the iron door!#
+6412 You hit the iron door!#
+6413 The iron door crashes open!#
+6414 iron door#
+6415 You unlock the iron door.#
+6416 You lock the iron door.#
+6417 You failed to unlock the iron door.#
+6418 This iron door is already unlocked.#
+6419 An arcane protection holds the iron door firmly in place!#
+6420 Close iron door#
+6421 Open iron door#
+6422 You lack the skill to pick this %s lock!#
+6423 There is already a %s key inside this lock.#
+6424 This %s lock can't be picked!#
+6425 The skeleton key doesn't fit!#
+6426 Your %s activates the %s lock!#
+6427 Your %s resets the %s lock.#
+6428 You failed to lockpick the %s lock.#
+6429 The %s lock seems unable to be reset by lockpicking.#
+6430 This %s lock has already been lockpicked open.#
+6431 Your song's effect cannot be extended any further!#
+6432 Your strike pierced the %s's armor!#
+6433 Your strike pierced %s's armor!#
+6434 The %s parries your blow!#
+6435 %s parries your blow!#
+6436 You parry the %s's blow!#
+6437 You parry %s's blow!#
6438 Input Device#
6439 The input device for voice chat.#
6440 Voice#
@@ -6637,4 +6717,552 @@ Host to enable the game setting.
6462 Raw Input#
6463 Modified#
-6599 END#
+6464 Guard!#
+6465 Your protection stops an attack!#
+6466 The %s guards your spell!#
+6467 %s guards your spell!#
+6468 The %s guards your attack!#
+6469 %s guards your attack!#
+6470 Your protection stops a spell!#
+6471 Your protection stops a flying %s!#
+6472 Your protection stops a %s!#
+6473 Your ward your body against physical harm!#
+6474 You ward your spirit against harmful magics!#
+6475 Your ward youself against mundane and magical attack!#
+6476 Your joints are filled with nimble grace.#
+6477 Your muscles swell with greater might.#
+6478 Your mind stirs deeply with wise counsel.#
+6479 Your body hardens with sturdy toughness.#
+6480 Your magic calls for a target of holy wrath!#
+6481 You detect an ally!#
+6482 You divine the location of a nearby shrine.#
+6483 Nearby perils are revealed to you!#
+6484 Nearby perils are revealed to you!#
+6485 The location of nearby treasures emerges in your mind...#
+6486 You give blessings for the food that sustains you.#
+6487 You seek out a wandering soul in search of penance.#
+6488 You beg for providence from lowly places.#
+6489 You call for willing souls to join your quest!#
+6490 Your protection saves you from the boulder!#
+6491 Your protection saves you from the falling bell!#
+6492 Your protection saves you from the spikes!#
+6493 You ward you body against devious traps.#
+6494 Divine forces are leading you to treasure!#
+6495 An enemy is revealed to you!#
+6496 You recall your spell without a target.#
+6497 You command holy exorcism!#
+6498 Your spell had no target!#
+6499 Cast spell...#
+6500 Cast spell on #
+6501 Cancel#
+6502 Your blood is warded against impurities!#
+6503 Your spell (%s) was interrupted!#
+6504 Failed to sustain spell (%s).#
+6505 The ward against impurities fades...#
+6506 Your blood stirs with noble purpose!#
+6507 Your blood returns to normal...#
+6508 Your attacks are enchanted with holy light!#
+6509 Holy light fades from your attacks...#
+6510 Your attack smites the %s!#
+6511 Your attack smites %s!#
+6512 Alter Instrument#
+6513 Select instrument to transmute:#
+6514 This %s
+ is unable to be altered.#
+6515 Your %s transforms into
+ %s!#
+6516 The %s grows in strength!#
+6517 The %s shrivels away!#
+6518 The %s is severely weakened by your curse!#
+6519 The %s is befuddled by magic!#
+6520 Your spell focus improves!#
+6521 Your weapons are imbued with venom!#
+6522 Your curse humbles the %s!#
+6523 The %s is crushed by your will.#
+6524 You release a torrent of slippery grease!#
+6525 You release a wave of raw magical energy!#
+6526 %s is weakened!#
+6527 The %s is weakened!#
+6528 You feel weak!#
+6529 The magical venom evaporates from your weapons.#
+6530 Your spell focus returns to normal.#
+6531 The %s poisons you!#
+6532 %s poisons you!#
+6533 The %s is poisoned!#
+6534 %s is poisoned!#
+6535 The amulet feels warm against your neck.#
+6536 You feel greasy!#
+6537 The %s is compelled to your command!#
+6538 Gold Cost:#
+6539 Not enough gold#
+6540 Insufficient gold!#
+6541 Select a weapon to transmute:#
+6542 Select an item to transmute:#
+6543 Select a jewel to transmute:#
+6544 Transmutes Into:#
+6545 Metallurgy#
+6546 Geomancy#
+6547 Forge Key#
+6548 Forge Jewel#
+6549 Enhance Weapon#
+6550 Reshape Weapon#
+6551 Alter Arrow#
+6552 Not enough mana#
+6553 Transmuted %s using
+ %s.#
+6554 Transmuted %dG
+ using %s.#
+6555 Mana Cost:#
+6556 Change Focus#
+6557 You awaken a void chest!#
+6558 Your spell does nothing while the chest is open.#
+6559 Wait! That's a void mimic!#
+6560 Select an item to send to the void:#
+6561 Send To Void#
+6562 Sent %s
+ to the void.#
+6563 This does not belong in the void#
+6564 This %s
+ does not belong in the void.#
+6565 The void is already being accessed by somebody else.#
+6566 The void cannot accept any more items.#
+6567 The mimic voids your %s!#
+6568 revenant skull#
+6569 minimimic#
+6570 revenant skulls#
+6571 minimimics#
+6572 hits#
+6573 bites#
+6574 *rattle*#
+6575 *creak*#
+6576 You curse the flesh of the %s!#
+6577 You summon a spirited %s!#
+6578 There is no room here to summon!#
+6579 spirit weapon#
+6580 adorcised possession#
+6581 flame elemental#
+6582 hologram#
+6583 moth#
+6584 earth sprite#
+6585 duck#
+6586 monster_unused_6#
+6587 monster_unused_7#
+6588 monster_unused_8#
+6589 adorcised possessions#
+6590 flame elementals#
+6591 holograms#
+6592 moths#
+6593 earth sprites#
+6594 ducks#
+6595 monster_unused_6s#
+6596 monster_unused_7s#
+6597 monster_unused_8s#
+6598 hits#
+6599 hits#
+6600 hits#
+6601 hits#
+6602 hits#
+6603 hits#
+6604 hits#
+6605 hits#
+6606 hits#
+6607 ...#
+6608 ...#
+6609 ...#
+6610 ...#
+6611 ...#
+6612 *quack!*#
+6613 ...#
+6614 ...#
+6615 ...#
+6616 Adorcise#
+6617 Select a weapon to summon:#
+6618 This %s
+ cannot be adorcised.#
+6619 returns to the floor.#
+6620 vanishes!#
+6621 Your %s
+ returns to the floor.#
+6622 Insufficient gold!#
+6623 The %s is numbed!#
+6624 %s is numbed!#
+6625 You feel numb!#
+6626 dies from delayed pain.#
+6627 You feel unstoppable!#
+6628 Pain returns to your body!#
+6629 The severity of your wounds sets in!#
+6630 The %s could not reach any allies!#
+6631 %s could not reach any allies!#
+6632 The %s loses their courage!#
+6633 The %s wanders in search of an ally!#
+6634 %s wanders in search of an ally!#
+6635 The %s could not reach any foes!#
+6636 %s could not reach any foes!#
+6637 The %s is encouraged!#
+6638 The %s wanders in search of a foe!#
+6639 %s wanders in search of a foe!#
+6640 The %s becomes a scapegoat!#
+6641 You become a scapegoat!#
+6642 Your spore pods shrivel away ...#
+6643 You bristle with swollen spore pods!#
+6644 The %s drops their weapon!#
+6645 %s drops their weapon!#
+6646 The %s drops their equipment!#
+6647 %s drops their equipment!#
+6648 Your food returns to normal.#
+6649 Your provisions return to normal.#
+6650 You beg for your food to be multiplied...#
+6651 You beg for your provisions to be multiplied...#
+6652 Greater abundance preserved your %s!#
+6653 Abundance preserved your %s!#
+6654 Your magic protects the %s from damage!#
+6655 You cover your equipment with a magic barrier!#
+6656 The protective barrier fades from your equipment.#
+6657 Your magic could not preserve your %s!#
+6658 This trap has already been sabotaged.#
+6659 You sabotage the trap!#
+6660 You blast the %s!#
+6661 You blast %s!#
+6662 Some scrap pours out from the trap!#
+6663 Your body turns to mist!#
+6664 Your body materializes again.#
+6665 Your mana could not sustain your mist form!#
+6666 Your mist form protects you!#
+6667 You create a hologram in your image!#
+6668 fades into nothing.#
+6669 gets spooked by a revenant skull.#
+6670 gets looted by a minimimic.#
+6671 gets reduced to ashes by a flame elemental.#
+6672 is slain by the invisible hand of an adorcised weapon.#
+6673 explodes!#
+6674 You conjure a %s!#
+6675 You were unable to conjure a %s!#
+6676 You are already wielding a %s!#
+6677 You splinter the %s's %s!#
+6678 You splinter %s's %s!#
+6679 You break the %s's %s!#
+6680 You break %s's %s!#
+6681 Your equipment feels lighter!#
+6682 You feel the heft of your equipment again.#
+6683 You reach out to items with unseen energy...#
+6684 Your grasp on nearby items dissipates.#
+6685 Your tether your throwing weapons with invisible magic!#
+6686 Your throwing weapons return to normal.#
+6687 Your %s returns to you!#
+6688 You deface the %s!#
+6689 You've disturbed a %s!#
+6690 Your demesne door was closed.#
+6691 Your demesne door fades.#
+6692 There is no room to create a demesne door!#
+6693 doorway#
+6694 You enchant the doorway!#
+6695 You create a portal!#
+6696 You step through the portal.#
+6697 You forged %d %s!#
+6698 gets mothballed.#
+6699 Your off-hand items are imbued with defensive force!#
+6700 Your off-hand items are imbued with magic reflection!#
+6701 ASSIST ITEM SELECTION#
+6702 CLASS SELECTION#
+6703 RACE SELECTION#
+6704 The %s spins around!#
+6705 %s spins around!#
+6706 The %s gets dizzied!#
+6707 %s gets dizzied!#
+6708 Vandalize#
+6709 Desecrate#
+6710 Sanctify#
+6711 Bless#
+6712 Cleanse#
+6713 Select an instrument to summon:#
+6714 Select an item to vandalize:#
+6715 Select an item to desecrate:#
+6716 Select a bottle of water to bless:#
+6717 Select an item to sanctify:#
+6718 Select food to cleanse:#
+6719 This %s
+ is unable to be vandalized.#
+6720 This %s
+ is unable to be desecrated.#
+6721 This %s
+ is unable to be blessed.#
+6722 This %s
+ is unable to be sanctified.#
+6723 This %s
+ is unable to be cleansed.#
+6724 Vandalized %dG using
+ %s.#
+6725 Your %s looks more edible.#
+6726 Unable to bless further#
+6727 You are already under the effects of %s.#
+6728 You begin drawing energy from a well of magic...#
+6729 You empower your next cast with ritual preparation...#
+6730 You are enveloped by a flame cloak!#
+6731 You are surrounded with protective flames!#
+6732 Your flame cloak ignites the %s!#
+6733 Your flame cloak ignites %s!#
+6734 A flame cloak sets you on fire!#
+6735 You prepare to absorb hostile magical energy!#
+6736 There is no stable ceiling to complete the spell!#
+6737 gets rocked by an earth sprite.#
+6738 gets overgrown by a dryad.#
+6739 gets vandalised by a gremlin.#
+6740 gets capped by a myconid.#
+6741 learns how cold-blooded a salamander can be.#
+6742 %s is hit with a rooting seed!#
+6743 The %s is hit with a rooting seed!#
+6744 You are hit by a rooting seed!#
+6745 %s is hit with a poison seed!#
+6746 The %s is hit with a poison seed!#
+6747 You are hit by a poison seed!#
+6748 There is no ground to complete the spell!#
+6749 mushroom#
+6750 There is no room to complete the spell!#
+6751 You stop shimmering.#
+6752 You are covered with shimmering dust!#
+6753 gets capped by a mushroom.#
+6754 Mushroom#
+6755 The air is filled with a thick fog.#
+6756 You are pulled by telekinetic force!#
+6757 You are caught in a vortex!#
+6758 You are slammed back down!#
+6759 Leaves#
+6760 becomes dust in the wind.#
+6761 %s is covered with shimmering dust!#
+6762 The %s is covered with shimmering dust!#
+6763 You are ensnared!#
+6764 The %s is ensnared!#
+6765 %s is ensnared!#
+6766 The %s was unaffected by the bolas!#
+6767 %s was unaffected by the bolas!#
+6768 [4 rations needed]#
+6769 You combine the %s with some %s...#
+6770 You dissolve the %s and collect it with a %s...#
+6771 Created %s.#
+6772 [No tin opener]#
+6773 Cook#
+6774 [Insufficient fuel]#
+6775 shrub#
+6776 footlocker#
+6777 weapon rack#
+6778 Deserters & Disciples DLC is required
+to replay this challenge!
+
+
+#
+6779 Gnome#
+6780 Gremlin#
+6781 Dryad#
+6782 Myconid#
+6783 Salamander#
+6784 bard#
+6785 sapper#
+6786 scion#
+6787 hermit#
+6788 paladin#
+# main menu capitalized
+6789 Bard#
+6790 Sapper#
+6791 Scion#
+6792 Hermit#
+6793 Paladin#
+6794 You lack the foliage to cast the spell!#
+6795 You are covered with thorns!#
+6796 You are covered with bladevines!#
+6797 Your thorns withdraw.#
+6798 Your bladevines crumble away.#
+6799 You steady yourself with firm mycelium!#
+6800 You plant yourself with sturdy roots!#
+6801 You dismount the binding mycelium.#
+6802 You dismount the binding roots.#
+6803 returns to dust.#
+6804 Higher %s skill required#
+6805 Higher skill required#
+6806 finds its final resting place.#
+6807 revenant skeleton#
+6808 You command a %s from the deceased!#
+6809 You splinter the construction of the %s!#
+6810 You splinter the construction of %s!#
+6811 Your splintered armor wounds you!#
+6812 Your shattered armor heavily wounds you!#
+6813 Your mana could not return your %s!#
+6814 You are unable to create a passage through this wall.#
+6815 The %s comes to life!#
+6816 dresser#
+6817 drawer#
+6818 candelabra#
+
+6826 You are blessed with peace!#
+6827 You are blessed with justice!#
+6828 You are blessed with providence!#
+6829 You are blessed with purity!#
+6830 You are blessed with sanctuary!#
+6831 Unlearn#
+6832 Sacrifice a spell to recharge:#
+6833 Recharge#
+6834 Recharge#
+6835 Confirm Unlearn (%d)#
+6836 You forget the %s spell.#
+6837 This %s
+ cannot be used to recharge.#
+6838 Your %s
+ is already fully charged!#
+6839 Your %s is depleted!#
+6840 The icon holds your enemies in stasis!#
+6841 The icon roots your enemies in place!#
+6842 Filtering Spells:
+%s#
+6843 Invoke#
+6844 Play#
+6845 You recall your previous light.#
+6846 Player Movement Deadzone#
+6847 Affect the analog deadzone of the control stick used for player movement.#
+6848 Camera Turn Deadzone#
+6849 Affect the analog deadzone of the control stick used for turning the player camera.#
+6850 tablet of %s#
+6851 tablets of %s#
+6852 The %s deteriorates and crumbles to ash!#
+6853 didn't survive the impact.#
+6854 Velocity#
+6855 Not enough resources#
+6856 Your spell can not absorb any more magic!#
+6857 Your spell absorbs the incoming hit!#
+6858 Your absorbed magic increased your damage!#
+6859 Your flame cloak falls to ashes.#
+6860 Your flame cloak is roaring with flames!#
+6861 The scroll erupts in a cloak of flame!#
+6862 You are protected by a magical bubble!#
+6863 Your magical bubble fades.#
+6864 The guard over your body fades.#
+6865 The guard over your spirit fades.#
+6866 Your divine guard fades.#
+6867 Your amulet glows a faint red.#
+6868 You have nothing else to learn by trading with this shopkeep.#
+6869 The duck slips out from your pack!#
+6870 Instrument Background Tracks#
+6871 Enable instrument music tracks to continue while their effects are active but not actively being played.#
+6872 Instrument Foreground Tracks#
+6873 Instrument Foreground Tracks description#
+6874 You project your spirit away!#
+6875 Your spirit rejoins your body.#
+6876 Your spirit projection begins to fade...#
+6877 Profile#
+6878 Quack#
+6879 Return#
+6880 Hotbar Keys Update Selection#
+6881 Allow the highlighted hotbar slot to change when using the individual slot keys to activate the hotbar.#
+6882 The rot invigorates you!#
+6883 You shed your burning foliage!#
+6884 You sprout forth a shrub!#
+6885 You spore forth a mushroom!#
+6886 Your vandalism energizes you!#
+6887 Select transmutation result:#
+6888 Confirm#
+6889 Deselect#
+6890 %s grows in strength!#
+6891 The %s cannot grow any stronger!#
+6892 %s cannot grow any stronger!#
+6893 %s shrivels away!#
+6894 The %s cannot shrivel down any further!#
+6895 %s cannot shrivel down any further!#
+6896 You feel enlarged!#
+6897 You feel diminished!#
+6898 Your stature feels ordinary once more.#
+6899 You were unable to scry any other allies.#
+6900 %s loses their courage!#
+6901 %s is encouraged!#
+6902 %s is compelled to your command!#
+6903 %s becomes a scapegoat!#
+6904 You curse the flesh of %s!#
+6905 The %s's vitality is defied!#
+6906 %s's vitality is defied!#
+6907 Your vitality is defied!#
+6908 Your spell has no effect right now!#
+6909 %s's spell power is diminished!#
+6910 The %s's spell power is diminished!#
+6911 Your spell power is diminished!#
+6912 You suddenly feel rather tall!#
+6913 You suddenly feel rather small!#
+6914 You suddenly detect a change of appearance!#
+6915 Cauldron#
+6916 Your dousing flames increase your fervor!#
+6917 Your impervious armor fades.#
+6918 You are blazing with radiance!#
+6919 Your scales constrict into impervious stone!#
+6920 Your temperance returns to normal.#
+6921 Your radiance reaches a more stable level.#
+6922 You are unable to charge your symbol any further!#
+6923 You are unable to charge your icon any further!#
+6924 Your foliage flourishes with nutrition!#
+6925 Your foliage grows!#
+6926 Your prayer on %s fades.#
+6927 Your prayer on the %s fades.#
+6928 The %s receives a prayer for healing!#
+6929 %s receives a prayer for healing!#
+6930 You pray for healing!#
+6931 Your prayer fades.#
+6932 You receive a healing prayer!#
+6933 Your prayer was interrupted!#
+6934 The blood ward cures your poisoning!#
+6935 The blood ward cures your bleeding!#
+6936 You are unable to scry any allies.#
+6937 You are unable to scry any traps nearby.#
+6938 You are unable to scry any other traps nearby.#
+6939 You are unable to scry any treasures nearby.#
+6940 You are unable to scry any other treasures nearby.#
+6941 You were not deemed worthy of an additional donation.#
+6942 Your call for divine providence falls unanswered.#
+6943 You discover a gift of divine providence!#
+6944 Your ally %s discovers a gift of divine providence!#
+6945 %s's ally %s discovers a gift of divine providence!#
+6946 You are unable to detect any more monsters nearby.#
+6947 You are unable to detect any monsters nearby.#
+6948 You are unable to pinpoint any more monsters nearby.#
+6949 You are unable to pinpoint any monsters nearby.#
+6950 The %s gets smited with divine precision!#
+6951 %s gets smited with divine precision!#
+6952 You cover the area with a divine sigil!#
+6953 You cover the area with divine sanctuary!#
+6954 The %s is purchased into your service!#
+6955 %s is purchased into your service!#
+6956 (%02d:%02d%) Appraising...#
+6957 (%02d:%02d%) %3.0f%% Appraising...#
+6958 You sabotage the construction of the %s!#
+6959 You sabotage the construction of %s!#
+6960 Your skeleton key is damaged.#
+6961 Your skeleton key is broken.#
+6962 Your current power is unable to dominate any more creatures.#
+6963 The scarab inflicts a curse of weakness!#
+6964 The scarab inflicts a greater curse of weakness!#
+6965 N/A#
+6966 Your influence is not high enough to compel the %s!#
+6967 Your influence is not high enough to compel %s!#
+6968 The %s refuses to be compelled further!#
+6969 %s refuses to be compelled further!#
+6970 was unable to drop some of their equipment.#
+6971 Your magical bubble enhances!#
+6972 Your magical bubble has reached the limit of your powers.#
+6973 Some of your gold has vanished!#
+6974 cauldron#
+6975 The cauldron is currently in use by someone else.#
+6976 You see a cauldron.#
+6977 Use cauldron#
+6978 Workbench#
+6979 Use workbench#
+6980 You see a workbench.#
+6981 workbench#
+6982 The workbench is currently in use by someone else.#
+6983 Mailbox#
+6984 Use mailbox#
+6985 You see a mailbox.#
+6986 mailbox#
+6987 The mailbox is currently in use by someone else.#
+6988 Claim#
+6989 Deserters and Disciples DLC
+has been unlocked!#
+6990 You are blinded by a flying slop ball!
+ *SPLAT*#
+6991 Yum! Rotten food!#
+6992 Your unidentified items are further appraised!#
+
+6999 END#
diff --git a/lang/item_names.json b/lang/item_names.json
index 119cf9060..19b4320e1 100644
--- a/lang/item_names.json
+++ b/lang/item_names.json
@@ -1230,7 +1230,7 @@
"name_unidentified": "bandana"
},
"hat_circlet": {
- "name_identified": "circlet",
+ "name_identified": "circlet of mysticism",
"name_unidentified": "circlet"
},
"hat_crown": {
@@ -1328,7 +1328,775 @@
"mask_marigold": {
"name_identified": "marigold",
"name_unidentified": "marigold"
- }
+ },
+ "key_stone": {
+ "name_identified": "stone key",
+ "name_unidentified": "stone key"
+ },
+ "key_bone": {
+ "name_identified": "spirit key",
+ "name_unidentified": "spirit key"
+ },
+ "key_bronze": {
+ "name_identified": "bronze key",
+ "name_unidentified": "bronze key"
+ },
+ "key_iron": {
+ "name_identified": "iron key",
+ "name_unidentified": "iron key"
+ },
+ "key_silver": {
+ "name_identified": "silver key",
+ "name_unidentified": "silver key"
+ },
+ "key_gold": {
+ "name_identified": "gold key",
+ "name_unidentified": "gold key"
+ },
+ "key_crystal": {
+ "name_identified": "crystal key",
+ "name_unidentified": "crystal key"
+ },
+ "key_machine": {
+ "name_identified": "machine key",
+ "name_unidentified": "machine key"
+ },
+ "tool_foci_fire": {
+ "name_identified": "focus of flames",
+ "name_unidentified": "focus"
+ },
+ "instrument_flute": {
+ "name_identified": "flute",
+ "name_unidentified": "flute"
+ },
+ "instrument_lyre": {
+ "name_identified": "lyre",
+ "name_unidentified": "lyre"
+ },
+ "instrument_drum": {
+ "name_identified": "drum",
+ "name_unidentified": "drum"
+ },
+ "instrument_lute": {
+ "name_identified": "lute",
+ "name_unidentified": "lute"
+ },
+ "instrument_horn": {
+ "name_identified": "horn",
+ "name_unidentified": "horn"
+ },
+ "rapier": {
+ "name_identified": "rapier",
+ "name_unidentified": "rapier"
+ },
+ "amulet_burningresist": {
+ "name_identified": "amulet of burning resistance",
+ "name_unidentified": "amulet"
+ },
+ "grease_ball": {
+ "name_identified": "grease ball",
+ "name_unidentified": "grease ball"
+ },
+ "branch_staff": {
+ "name_identified": "branch staff",
+ "name_unidentified": "branch staff"
+ },
+ "branch_bow": {
+ "name_identified": "branch bow",
+ "name_unidentified": "branch bow"
+ },
+ "branch_bow_infected": {
+ "name_identified": "infected branch bow",
+ "name_unidentified": "infected branch bow"
+ },
+ "dust_ball": {
+ "name_identified": "dust ball",
+ "name_unidentified": "dust ball"
+ },
+ "bolas": {
+ "name_identified": "bolas",
+ "name_unidentified": "bolas"
+ },
+ "steel_flail": {
+ "name_identified": "steel flail",
+ "name_unidentified": "steel flail"
+ },
+ "food_ration": {
+ "name_identified": "ration",
+ "name_unidentified": "ration"
+ },
+ "food_ration_spicy": {
+ "name_identified": "spicy ration",
+ "name_unidentified": "ration"
+ },
+ "food_ration_sour": {
+ "name_identified": "sour ration",
+ "name_unidentified": "ration"
+ },
+ "food_ration_bitter": {
+ "name_identified": "bitter ration",
+ "name_unidentified": "ration"
+ },
+ "food_ration_hearty": {
+ "name_identified": "hearty ration",
+ "name_unidentified": "ration"
+ },
+ "food_ration_herbal": {
+ "name_identified": "herbal ration",
+ "name_unidentified": "ration"
+ },
+ "food_ration_sweet": {
+ "name_identified": "sweet ration",
+ "name_unidentified": "ration"
+ },
+ "slop_ball": {
+ "name_identified": "slop ball",
+ "name_unidentified": "slop ball"
+ },
+ "tool_frying_pan": {
+ "name_identified": "frypan",
+ "name_unidentified": "frypan"
+ },
+ "cleat_boots": {
+ "name_identified": "cleats",
+ "name_unidentified": "cleats"
+ },
+ "bandit_breastpiece": {
+ "name_identified": "bandit leather",
+ "name_unidentified": "bandit leather"
+ },
+ "tunic_blouse": {
+ "name_identified": "blouse",
+ "name_unidentified": "blouse"
+ },
+ "bone_breastpiece": {
+ "name_identified": "bone breastpiece",
+ "name_unidentified": "bone breastpiece"
+ },
+ "blackiron_breastpiece": {
+ "name_identified": "blackiron breastpiece",
+ "name_unidentified": "blackiron breastpiece"
+ },
+ "silver_breastpiece": {
+ "name_identified": "silver breastpiece",
+ "name_unidentified": "silver breastpiece"
+ },
+ "iron_pauldrons": {
+ "name_identified": "iron pauldrons",
+ "name_unidentified": "iron pauldrons"
+ },
+ "quilted_gambeson": {
+ "name_identified": "quilted gambeson",
+ "name_unidentified": "quilted gambeson"
+ },
+ "robe_cultist": {
+ "name_identified": "cultist robe",
+ "name_unidentified": "cultist robe"
+ },
+ "robe_healer": {
+ "name_identified": "healer robe",
+ "name_unidentified": "healer robe"
+ },
+ "robe_monk": {
+ "name_identified": "monk robe",
+ "name_unidentified": "monk robe"
+ },
+ "robe_wizard": {
+ "name_identified": "wizard robe",
+ "name_unidentified": "wizard robe"
+ },
+ "shawl": {
+ "name_identified": "shawl",
+ "name_unidentified": "shawl"
+ },
+ "chain_hauberk": {
+ "name_identified": "chain hauberk",
+ "name_unidentified": "chain hauberk"
+ },
+ "bone_bracers": {
+ "name_identified": "bone bracers",
+ "name_unidentified": "bone bracers"
+ },
+ "blackiron_gauntlets": {
+ "name_identified": "blackiron gauntlets",
+ "name_unidentified": "blackiron gauntlets"
+ },
+ "silver_gauntlets": {
+ "name_identified": "silver gauntlets",
+ "name_unidentified": "silver gauntlets"
+ },
+ "quilted_gloves": {
+ "name_identified": "quilted gloves",
+ "name_unidentified": "quilted gloves"
+ },
+ "chain_gloves": {
+ "name_identified": "chain gloves",
+ "name_unidentified": "chain gloves"
+ },
+ "bone_boots": {
+ "name_identified": "bone boots",
+ "name_unidentified": "bone boots"
+ },
+ "blackiron_boots": {
+ "name_identified": "blackiron boots",
+ "name_unidentified": "blackiron boots"
+ },
+ "silver_boots": {
+ "name_identified": "silver boots",
+ "name_unidentified": "silver boots"
+ },
+ "quilted_boots": {
+ "name_identified": "quilted boots",
+ "name_unidentified": "quilted boots"
+ },
+ "loafers": {
+ "name_identified": "loafers",
+ "name_unidentified": "loafers"
+ },
+ "chain_boots": {
+ "name_identified": "chain boots",
+ "name_unidentified": "chain boots"
+ },
+ "scutum": {
+ "name_identified": "scutum",
+ "name_unidentified": "scutum"
+ },
+ "bone_shield": {
+ "name_identified": "bone shield",
+ "name_unidentified": "bone shield"
+ },
+ "blackiron_shield": {
+ "name_identified": "blackiron shield",
+ "name_unidentified": "blackiron shield"
+ },
+ "silver_shield": {
+ "name_identified": "silver shield",
+ "name_unidentified": "silver shield"
+ },
+ "cloak_dendrite": {
+ "name_identified": "dendrite cloak",
+ "name_unidentified": "dendrite cloak"
+ },
+ "bone_helm": {
+ "name_identified": "bone helm",
+ "name_unidentified": "bone helm"
+ },
+ "blackiron_helm": {
+ "name_identified": "blackiron helm",
+ "name_unidentified": "blackiron helm"
+ },
+ "silver_helm": {
+ "name_identified": "silver helm",
+ "name_unidentified": "silver helm"
+ },
+ "hat_felt": {
+ "name_identified": "felt hat",
+ "name_unidentified": "felt hat"
+ },
+ "quilted_cap": {
+ "name_identified": "quilted cap",
+ "name_unidentified": "quilted cap"
+ },
+ "hood_teal": {
+ "name_identified": "teal hood",
+ "name_unidentified": "teal hood"
+ },
+ "chain_coif": {
+ "name_identified": "chain coif",
+ "name_unidentified": "chain coif"
+ },
+ "food_shroom": {
+ "name_identified": "mushroom slice",
+ "name_unidentified": "mushroom slice"
+ },
+ "food_nut": {
+ "name_identified": "nut",
+ "name_unidentified": "nut"
+ },
+ "tool_foci_snow": {
+ "name_identified": "focus of snow",
+ "name_unidentified": "focus"
+ },
+ "tool_foci_needles": {
+ "name_identified": "focus of needles",
+ "name_unidentified": "focus"
+ },
+ "tool_foci_arcs": {
+ "name_identified": "focus of arcs",
+ "name_unidentified": "focus"
+ },
+ "tool_foci_sand": {
+ "name_identified": "focus of sandblast",
+ "name_unidentified": "focus"
+ },
+ "tool_foci_dark_life": {
+ "name_identified": "icon of claim life",
+ "name_unidentified": "icon"
+ },
+ "tool_foci_dark_rift": {
+ "name_identified": "icon of void rift",
+ "name_unidentified": "icon"
+ },
+ "tool_foci_dark_silence": {
+ "name_identified": "icon of silence",
+ "name_unidentified": "icon"
+ },
+ "tool_foci_dark_vengeance": {
+ "name_identified": "icon of vengeance",
+ "name_unidentified": "icon"
+ },
+ "tool_foci_dark_suppress": {
+ "name_identified": "icon of suppression",
+ "name_unidentified": "icon"
+ },
+ "tool_foci_light_peace": {
+ "name_identified": "symbol of peace",
+ "name_unidentified": "symbol"
+ },
+ "tool_foci_light_justice": {
+ "name_identified": "symbol of justice",
+ "name_unidentified": "symbol"
+ },
+ "tool_foci_light_providence": {
+ "name_identified": "symbol of providence",
+ "name_unidentified": "symbol"
+ },
+ "tool_foci_light_purity": {
+ "name_identified": "symbol of purity",
+ "name_unidentified": "symbol"
+ },
+ "tool_foci_light_sanctuary": {
+ "name_identified": "symbol of sanctuary",
+ "name_unidentified": "symbol"
+ },
+ "magicstaff_scepter": {
+ "name_identified": "archon scepter",
+ "name_unidentified": "scepter"
+ },
+ "tome_sorcery": {
+ "name_identified": "tablet",
+ "name_unidentified": "tablet"
+ },
+ "tome_mysticism": {
+ "name_identified": "tablet",
+ "name_unidentified": "tablet"
+ },
+ "tome_thaumaturgy": {
+ "name_identified": "tablet",
+ "name_unidentified": "tablet"
+ },
+ "hat_circlet_sorcery": {
+ "name_identified": "circlet of sorcery",
+ "name_unidentified": "circlet"
+ },
+ "hat_circlet_thaumaturgy": {
+ "name_identified": "circlet of thaumaturgy",
+ "name_unidentified": "circlet"
+ },
+ "tool_duck": {
+ "name_identified": "duck",
+ "name_unidentified": "duck"
+ },
+ "shillelagh_mace": {
+ "name_identified": "shillelagh",
+ "name_unidentified": "shillelagh"
+ },
+ "claymore_sword": {
+ "name_identified": "claymore",
+ "name_unidentified": "claymore"
+ },
+ "anelace_sword": {
+ "name_identified": "anelace",
+ "name_unidentified": "anelace"
+ },
+ "lance_spear": {
+ "name_identified": "lance",
+ "name_unidentified": "lance"
+ },
+ "steel_falshion": {
+ "name_identified": "steel falshion",
+ "name_unidentified": "steel falshion"
+ },
+ "steel_greataxe": {
+ "name_identified": "steel greataxe",
+ "name_unidentified": "steel greataxe"
+ },
+ "blackiron_axe": {
+ "name_identified": "blackiron axe",
+ "name_unidentified": "blackiron axe"
+ },
+ "blackiron_crossbow": {
+ "name_identified": "blackiron crossbow",
+ "name_unidentified": "blackiron crossbow"
+ },
+ "blackiron_dart": {
+ "name_identified": "blackiron dart",
+ "name_unidentified": "blackiron dart"
+ },
+ "blackiron_mace": {
+ "name_identified": "blackiron mace",
+ "name_unidentified": "blackiron mace"
+ },
+ "blackiron_sword": {
+ "name_identified": "blackiron sword",
+ "name_unidentified": "blackiron sword"
+ },
+ "blackiron_trident": {
+ "name_identified": "blackiron trident",
+ "name_unidentified": "blackiron trident"
+ },
+ "bone_axe": {
+ "name_identified": "bone axe",
+ "name_unidentified": "bone axe"
+ },
+ "bone_mace": {
+ "name_identified": "bone mace",
+ "name_unidentified": "bone mace"
+ },
+ "bone_shortbow": {
+ "name_identified": "bone shortbow",
+ "name_unidentified": "bone shortbow"
+ },
+ "bone_spear": {
+ "name_identified": "bone spear",
+ "name_unidentified": "bone spear"
+ },
+ "bone_sword": {
+ "name_identified": "bone sword",
+ "name_unidentified": "bone sword"
+ },
+ "bone_throwing": {
+ "name_identified": "bone",
+ "name_unidentified": "bone"
+ },
+ "silver_axe": {
+ "name_identified": "silver axe",
+ "name_unidentified": "silver axe"
+ },
+ "silver_glaive": {
+ "name_identified": "silver glaive",
+ "name_unidentified": "silver glaive"
+ },
+ "silver_mace": {
+ "name_identified": "silver mace",
+ "name_unidentified": "silver mace"
+ },
+ "silver_plumbata": {
+ "name_identified": "silver plumbata",
+ "name_unidentified": "silver plumbata"
+ },
+ "silver_sword": {
+ "name_identified": "silver sword",
+ "name_unidentified": "silver sword"
+ },
+ "quiver_bone": {
+ "name_identified": "quiver of bone ammo",
+ "name_unidentified": "quiver"
+ },
+ "quiver_blackiron": {
+ "name_identified": "quiver of blackiron ammo",
+ "name_unidentified": "quiver"
+ },
+ "gem_jewel": {
+ "name_identified": "jewel",
+ "name_unidentified": "jewel"
+ },
+ "spellbook_meteor": {
+ "name_identified": "spellbook of meteor",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_ice_wave": {
+ "name_identified": "spellbook of ice wave",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_guard_body": {
+ "name_identified": "spellbook of guard body",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_guard_spirit": {
+ "name_identified": "spellbook of guard spirit",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_divine_guard": {
+ "name_identified": "spellbook of divine guard",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_prof_nimbleness": {
+ "name_identified": "spellbook of nimbleness",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_prof_greater_might": {
+ "name_identified": "spellbook of greater might",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_prof_counsel": {
+ "name_identified": "spellbook of counsel",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_prof_sturdiness": {
+ "name_identified": "spellbook of sturdiness",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_bless_food": {
+ "name_identified": "spellbook of blessed meals",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_pinpoint": {
+ "name_identified": "spellbook of pinpoint",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_donation": {
+ "name_identified": "spellbook of donation",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_scry_allies": {
+ "name_identified": "spellbook of scry allies",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_scry_traps": {
+ "name_identified": "spellbook of scry traps",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_scry_treasures": {
+ "name_identified": "spellbook of scry treasures",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_detect_enemy": {
+ "name_identified": "spellbook of detect enemy",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_turn_undead": {
+ "name_identified": "spellbook of exorcise",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_heal_other": {
+ "name_identified": "spellbook of minor heal other",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_blood_ward": {
+ "name_identified": "spellbook of blood ward",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_divine_zeal": {
+ "name_identified": "spellbook of divine zeal",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_maximise": {
+ "name_identified": "spellbook of maximise",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_minimise": {
+ "name_identified": "spellbook of minimise",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_incoherence": {
+ "name_identified": "spellbook of incoherence",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_overcharge": {
+ "name_identified": "spellbook of overcharge",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_envenom_weapon": {
+ "name_identified": "spellbook of envenom weapon",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_psychic_spear": {
+ "name_identified": "spellbook of psychic spear",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_defy_flesh": {
+ "name_identified": "spellbook of defy flesh",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_grease_spray": {
+ "name_identified": "spellbook of grease spray",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_blood_waves": {
+ "name_identified": "spellbook of blood waves",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_command": {
+ "name_identified": "spellbook of compel",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_metallurgy": {
+ "name_identified": "spellbook of metallurgy",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_forge_key": {
+ "name_identified": "spellbook of forge key",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_reshape_weapon": {
+ "name_identified": "spellbook of reshape weapon",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_alter_arrow": {
+ "name_identified": "spellbook of alter arrow",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_void_chest": {
+ "name_identified": "spellbook of void chest",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_lead_bolt": {
+ "name_identified": "spellbook of lead bolt",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_numbing_bolt": {
+ "name_identified": "spellbook of numbing bolt",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_curse_flesh": {
+ "name_identified": "spellbook of curse flesh",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_cowardice": {
+ "name_identified": "spellbook of cowardice",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_seek_ally": {
+ "name_identified": "spellbook of seek ally",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_deep_shade": {
+ "name_identified": "spellbook of deep shade",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_spirit_weapon": {
+ "name_identified": "spellbook of spirit weapon",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_spores": {
+ "name_identified": "spellbook of spores",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_windgate": {
+ "name_identified": "spellbook of windgate",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_telekinesis": {
+ "name_identified": "spellbook of telekinesis",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_disarm": {
+ "name_identified": "spellbook of disarm",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_abundance": {
+ "name_identified": "spellbook of abundance",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_preserve": {
+ "name_identified": "spellbook of preserve",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_sabotage": {
+ "name_identified": "spellbook of sabotage",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_mist_form": {
+ "name_identified": "spellbook of mist form",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_force_shield": {
+ "name_identified": "spellbook of kinetic defense",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_splinter_gear": {
+ "name_identified": "spellbook of splinter armor",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_attract_items": {
+ "name_identified": "spellbook of attract items",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_absorb_magic": {
+ "name_identified": "spellbook of absorb magic",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_tunnel": {
+ "name_identified": "spellbook of portal",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_null_area": {
+ "name_identified": "spellbook of null area",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_fire_sprite": {
+ "name_identified": "spellbook of summon fire sprite",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_spin": {
+ "name_identified": "spellbook of spin",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_cleanse_food": {
+ "name_identified": "spellbook of cleanse food",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_flame_cloak": {
+ "name_identified": "spellbook of flame cloak",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_lightning_bolt": {
+ "name_identified": "spellbook of lightning bolt",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_disrupt_earth": {
+ "name_identified": "spellbook of disrupt earth",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_fire_wall": {
+ "name_identified": "spellbook of fire wall",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_slam": {
+ "name_identified": "spellbook of lift",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_ignite": {
+ "name_identified": "spellbook of ignite",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_shatter_objects": {
+ "name_identified": "spellbook of shatter objects",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_kinetic_field": {
+ "name_identified": "spellbook of kinetic field",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_thorns": {
+ "name_identified": "spellbook of thorns",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_magicians_armor": {
+ "name_identified": "spellbook of magicians' bubble",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_heal_minor": {
+ "name_identified": "spellbook of minor heal",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_sigil": {
+ "name_identified": "spellbook of holy sigil",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_sanctuary": {
+ "name_identified": "spellbook of divine fortress",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_holy_beam": {
+ "name_identified": "spellbook of holy beam",
+ "name_unidentified": "spellbook"
+ },
+ "spellbook_dominate": {
+ "name_identified": "spellbook of dominate",
+ "name_unidentified": "spellbook"
+ }
},
"spell_names": {
"spell_forcebolt": {
@@ -1510,6 +2278,462 @@
},
"spell_slime_metal": {
"name": "Slime Spray (Metal)"
+ },
+ "spell_foci_fire": {
+ "name": "Focus of Flames"
+ },
+ "spell_foci_snow": {
+ "name": "Focus of Snow"
+ },
+ "spell_foci_needles": {
+ "name": "Focus of Needles"
+ },
+ "spell_foci_arcs": {
+ "name": "Focus of Arcs"
+ },
+ "spell_foci_sandblast": {
+ "name": "Focus of Sandblast"
+ },
+ "spell_meteor": {
+ "name": "Meteor"
+ },
+ "spell_flames": {
+ "name": "Flames"
+ },
+ "spell_ice_wave": {
+ "name": "Ice Wave"
+ },
+ "spell_earth_elemental": {
+ "name": "Summon Earth Sprite"
+ },
+ "spell_bless_food": {
+ "name": "Blessed Meals"
+ },
+ "spell_shrub": {
+ "name": "Germinate"
+ },
+ "spell_conjure_food": {
+ "name": "Conjure Food"
+ },
+ "spell_guard_body": {
+ "name": "Guard Body"
+ },
+ "spell_guard_spirit": {
+ "name": "Guard Spirit"
+ },
+ "spell_divine_guard": {
+ "name": "Divine Guard"
+ },
+ "spell_prof_nimbleness": {
+ "name": "Nimbleness"
+ },
+ "spell_prof_greater_might": {
+ "name": "Greater Might"
+ },
+ "spell_prof_counsel": {
+ "name": "Counsel"
+ },
+ "spell_prof_sturdiness": {
+ "name": "Sturdiness"
+ },
+ "spell_pinpoint": {
+ "name": "Pinpoint"
+ },
+ "spell_donation": {
+ "name": "Donation"
+ },
+ "spell_scry_allies": {
+ "name": "Scry Allies"
+ },
+ "spell_scry_shrines": {
+ "name": "Scry Shrines"
+ },
+ "spell_scry_traps": {
+ "name": "Scry Traps"
+ },
+ "spell_scry_treasures": {
+ "name": "Scry Treasures"
+ },
+ "spell_penance": {
+ "name": "Penance"
+ },
+ "spell_call_allies": {
+ "name": "Call Allies"
+ },
+ "spell_sacred_path": {
+ "name": "Sacred Path"
+ },
+ "spell_manifest_destiny": {
+ "name": "Manifest Destiny"
+ },
+ "spell_detect_enemy": {
+ "name": "Detect Enemy"
+ },
+ "spell_detect_enemies": {
+ "name": "Detect Enemies"
+ },
+ "spell_turn_undead": {
+ "name": "Exorcise"
+ },
+ "spell_heal_other": {
+ "name": "Minor Heal Other"
+ },
+ "spell_blood_ward": {
+ "name": "Blood Ward"
+ },
+ "spell_true_blood": {
+ "name": "True Blood"
+ },
+ "spell_divine_zeal": {
+ "name": "Divine Zeal"
+ },
+ "spell_alter_instrument": {
+ "name": "Alter Instrument"
+ },
+ "spell_maximise": {
+ "name": "Maximise"
+ },
+ "spell_minimise": {
+ "name": "Minimise"
+ },
+ "spell_jump": {
+ "name": "Jump"
+ },
+ "spell_incoherence": {
+ "name": "Incoherence"
+ },
+ "spell_overcharge": {
+ "name": "Overcharge"
+ },
+ "spell_envenom_weapon": {
+ "name": "Envenom Weapon"
+ },
+ "spell_psychic_spear": {
+ "name": "Psychic Spear"
+ },
+ "spell_defy_flesh": {
+ "name": "Defy Flesh"
+ },
+ "spell_grease_spray": {
+ "name": "Grease Spray"
+ },
+ "spell_blood_waves": {
+ "name": "Blood Waves"
+ },
+ "spell_booby_trap": {
+ "name": "Booby Trap"
+ },
+ "spell_command": {
+ "name": "Compel"
+ },
+ "spell_metallurgy": {
+ "name": "Metallurgy"
+ },
+ "spell_geomancy": {
+ "name": "Geomancy"
+ },
+ "spell_forge_key": {
+ "name": "Forge Key"
+ },
+ "spell_forge_jewel": {
+ "name": "Forge Jewel"
+ },
+ "spell_enhance_weapon": {
+ "name": "Enhance Weapon"
+ },
+ "spell_reshape_weapon": {
+ "name": "Reshape Weapon"
+ },
+ "spell_alter_arrow": {
+ "name": "Alter Arrow"
+ },
+ "spell_void_chest": {
+ "name": "Void Chest"
+ },
+ "spell_puncture_void": {
+ "name": "Puncture Void"
+ },
+ "spell_lead_bolt": {
+ "name": "Lead Bolt"
+ },
+ "spell_mercury_bolt": {
+ "name": "Mercury Bolt"
+ },
+ "spell_numbing_bolt": {
+ "name": "Numbing Bolt"
+ },
+ "spell_delay_pain": {
+ "name": "Delay Pain"
+ },
+ "spell_curse_flesh": {
+ "name": "Curse Flesh"
+ },
+ "spell_revenant_curse": {
+ "name": "Revenant Curse"
+ },
+ "spell_cowardice": {
+ "name": "Cowardice"
+ },
+ "spell_courage": {
+ "name": "Courage"
+ },
+ "spell_seek_ally": {
+ "name": "Seek Ally"
+ },
+ "spell_seek_foe": {
+ "name": "Seek Foe"
+ },
+ "spell_deep_shade": {
+ "name": "Deep Shade"
+ },
+ "spell_shade_bolt": {
+ "name": "Shade Bolt"
+ },
+ "spell_spirit_weapon": {
+ "name": "Spirit Weapon"
+ },
+ "spell_adorcism": {
+ "name": "Adorcism"
+ },
+ "spell_taboo": {
+ "name": "Scapegoat"
+ },
+ "spell_wonderlight": {
+ "name": "Wonderlight"
+ },
+ "spell_spores": {
+ "name": "Spores"
+ },
+ "spell_spore_bomb": {
+ "name": "Spore Bomb"
+ },
+ "spell_windgate": {
+ "name": "Windgate"
+ },
+ "spell_vortex": {
+ "name": "Vortex"
+ },
+ "spell_telekinesis": {
+ "name": "Telekinesis"
+ },
+ "spell_kinetic_push": {
+ "name": "Kinetic Push"
+ },
+ "spell_disarm": {
+ "name": "Disarm"
+ },
+ "spell_strip": {
+ "name": "Strip"
+ },
+ "spell_abundance": {
+ "name": "Abundance"
+ },
+ "spell_greater_abundance": {
+ "name": "Greater Abundance"
+ },
+ "spell_preserve": {
+ "name": "Preserve"
+ },
+ "spell_restore": {
+ "name": "Restore"
+ },
+ "spell_sabotage": {
+ "name": "Sabotage"
+ },
+ "spell_harvest_trap": {
+ "name": "Harvest Trap"
+ },
+ "spell_mist_form": {
+ "name": "Mist Form"
+ },
+ "spell_hologram": {
+ "name": "Hologram"
+ },
+ "spell_force_shield": {
+ "name": "Kinetic Defense"
+ },
+ "spell_reflector": {
+ "name": "Reflector"
+ },
+ "spell_splinter_gear": {
+ "name": "Splinter Armor"
+ },
+ "spell_lighten_load": {
+ "name": "Lighten Load"
+ },
+ "spell_attract_items": {
+ "name": "Attract Items"
+ },
+ "spell_return_items": {
+ "name": "Return Items"
+ },
+ "spell_absorb_magic": {
+ "name": "Absorb Magic"
+ },
+ "spell_seize_magic": {
+ "name": "Seize Magic"
+ },
+ "spell_deface": {
+ "name": "Deface"
+ },
+ "spell_sunder_monument": {
+ "name": "Sunder Monument"
+ },
+ "spell_demesne_door": {
+ "name": "Demesne Door"
+ },
+ "spell_tunnel": {
+ "name": "Portal"
+ },
+ "spell_null_area": {
+ "name": "Null Area"
+ },
+ "spell_sphere_silence": {
+ "name": "Sphere of Silence"
+ },
+ "spell_forge_metal_scrap": {
+ "name": "Forge Metal"
+ },
+ "spell_forge_magic_scrap": {
+ "name": "Forge Magic"
+ },
+ "spell_fire_sprite": {
+ "name": "Summon Fire Sprite"
+ },
+ "spell_flame_elemental": {
+ "name": "Flame Elemental"
+ },
+ "spell_spin": {
+ "name": "Spin"
+ },
+ "spell_dizzy": {
+ "name": "Dizzy"
+ },
+ "spell_vandalise": {
+ "name": "Vandalise"
+ },
+ "spell_desecrate": {
+ "name": "Desecrate"
+ },
+ "spell_sanctify": {
+ "name": "Sanctify"
+ },
+ "spell_sanctify_water": {
+ "name": "Sanctify Water"
+ },
+ "spell_cleanse_food": {
+ "name": "Cleanse Food"
+ },
+ "spell_adorcise_instrument": {
+ "name": "Adorcise Instrument"
+ },
+ "spell_flame_cloak": {
+ "name": "Flame Cloak"
+ },
+ "spell_critical_spell": {
+ "name": "Critical Spell"
+ },
+ "spell_magic_well": {
+ "name": "Magic Well"
+ },
+ "spell_flame_shield": {
+ "name": "Flame Shield"
+ },
+ "spell_lightning_bolt": {
+ "name": "Lightning Bolt"
+ },
+ "spell_disrupt_earth": {
+ "name": "Disrupt Earth"
+ },
+ "spell_earth_spines": {
+ "name": "Earth Spines"
+ },
+ "spell_lightning_nexus": {
+ "name": "Lightning Nexus"
+ },
+ "spell_fire_wall": {
+ "name": "Fire Wall"
+ },
+ "spell_lift": {
+ "name": "Lift"
+ },
+ "spell_slam": {
+ "name": "Lift"
+ },
+ "spell_ignite": {
+ "name": "Ignite"
+ },
+ "spell_shatter_objects": {
+ "name": "Shatter Objects"
+ },
+ "spell_kinetic_field": {
+ "name": "Kinetic Field"
+ },
+ "spell_ice_block": {
+ "name": "Ice Block"
+ },
+ "spell_meteor_shower": {
+ "name": "Meteor Shower"
+ },
+ "spell_chronomic_field": {
+ "name": "Chronomic Field"
+ },
+ "spell_eternals_gaze": {
+ "name": "Eternals' Gaze"
+ },
+ "spell_shatter_earth": {
+ "name": "Shatter Earth"
+ },
+ "spell_roots": {
+ "name": "Roots"
+ },
+ "spell_mushroom": {
+ "name": "Germinate"
+ },
+ "spell_mycelium": {
+ "name": "Mycelium"
+ },
+ "spell_mycelium_spores": {
+ "name": "Mycelium Spores"
+ },
+ "spell_heal_pulse": {
+ "name": "Heal Pulse"
+ },
+ "spell_thorns": {
+ "name": "Thorns"
+ },
+ "spell_bladevines": {
+ "name": "Bladevines"
+ },
+ "spell_bastion_mushroom": {
+ "name": "Bastion"
+ },
+ "spell_bastion_roots": {
+ "name": "Bastion"
+ },
+ "spell_magicians_armor": {
+ "name": "Magicians' Bubble"
+ },
+ "spell_project_spirit": {
+ "name": "Project Spirit"
+ },
+ "spell_breathe_fire": {
+ "name": "Breathe Fire"
+ },
+ "spell_heal_minor": {
+ "name": "Minor Heal"
+ },
+ "spell_holy_fire": {
+ "name": "Holy Fire"
+ },
+ "spell_sigil": {
+ "name": "Holy Sigil"
+ },
+ "spell_sanctuary": {
+ "name": "Divine Fortress"
+ },
+ "spell_holy_beam": {
+ "name": "Holy Beam"
}
}
}
\ No newline at end of file
diff --git a/playernames-female.txt b/playernames-female.txt
index cbe9c407a..58fe11e1f 100644
--- a/playernames-female.txt
+++ b/playernames-female.txt
@@ -362,4 +362,5 @@ Suraya
Coventia Asgorath
Lara Cake
Feebie Catsong
-Elaine Signfield
\ No newline at end of file
+Elaine Signfield
+Nomad Wagon
\ No newline at end of file
diff --git a/playernames-male.txt b/playernames-male.txt
index 18a777cb8..c8bb80fb9 100644
--- a/playernames-male.txt
+++ b/playernames-male.txt
@@ -367,4 +367,5 @@ Telpemir
Scumbo Bunger
Trigger
Lord Jeraldo
-Toniel Deeno
\ No newline at end of file
+Toniel Deeno
+Nomad Wagon
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c8e3e3c91..67ee22168 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -106,6 +106,13 @@ list(APPEND GAME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/lobbies.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/shader.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/playfab.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_d.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_m.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_s.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_g.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_summons.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_moth.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/monster_duck.cpp"
)
list(APPEND EDITOR_SOURCES
diff --git a/src/actarrow.cpp b/src/actarrow.cpp
index 3d2f691bb..294edfe3c 100644
--- a/src/actarrow.cpp
+++ b/src/actarrow.cpp
@@ -58,12 +58,15 @@ enum ArrowSpriteTypes : int
PROJECTILE_FIRE_SPRITE,
PROJECTILE_HEAVY_SPRITE,
PROJECTILE_CRYSTAL_SPRITE,
- PROJECTILE_HUNTING_SPRITE
+ PROJECTILE_HUNTING_SPRITE,
+ PROJECTILE_SEED_ROOT_SPRITE = 1881,
+ PROJECTILE_SEED_POISON_SPRITE = 1882,
+ PROJECTILE_BONE_SPRITE = 2304,
+ PROJECTILE_BLACKIRON_SPRITE = 2305
};
void actArrow(Entity* my)
{
- double dist = 0.0;
node_t* node = nullptr;
// lifespan
@@ -83,7 +86,64 @@ void actArrow(Entity* my)
return;
}
- if ( my->arrowQuiverType == QUIVER_FIRE || my->sprite == PROJECTILE_FIRE_SPRITE )
+ if ( my->sprite == PROJECTILE_SEED_ROOT_SPRITE
+ || my->sprite == PROJECTILE_SEED_POISON_SPRITE )
+ {
+ if ( ARROW_LIFE > 1 )
+ {
+ if ( ARROW_STUCK == 0 )
+ {
+ my->light = addLight(my->x / 16, my->y / 16, "magic_green");
+ if ( flickerLights )
+ {
+ //Torches will never flicker if this setting is disabled.
+ ARROW_FLICKER++;
+ }
+ if ( ARROW_FLICKER > 5 )
+ {
+ ARROW_LIGHTING = (ARROW_LIGHTING == 1) + 1;
+
+ if ( ARROW_LIGHTING == 1 )
+ {
+ my->removeLightField();
+ my->light = addLight(my->x / 16, my->y / 16, "magic_green");
+ }
+ else
+ {
+ my->removeLightField();
+ my->light = addLight(my->x / 16, my->y / 16, "magic_green_flicker");
+ }
+ ARROW_FLICKER = 0;
+ }
+ }
+
+ if ( ARROW_STUCK == 0 )
+ {
+ my->removeLightField();
+ Entity* particle = spawnMagicParticleCustom(my, 1816, 0.5, 4);
+ if ( particle )
+ {
+ particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
+ //particle->flags[SPRITE] = true;
+ particle->ditheringDisabled = true;
+ }
+ }
+ }
+ }
+ else if ( (my->arrowQuiverType == QUIVER_PIERCE || my->sprite == PROJECTILE_PIERCE_SPRITE) )
+ {
+ if ( ARROW_STUCK == 0 )
+ {
+ Entity* particle = spawnMagicParticleCustom(my, 158, 0.5, 4);
+ if ( particle )
+ {
+ particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
+ particle->flags[SPRITE] = true;
+ particle->ditheringDisabled = true;
+ }
+ }
+ }
+ else if ( my->arrowQuiverType == QUIVER_FIRE || my->sprite == PROJECTILE_FIRE_SPRITE )
{
if ( ARROW_LIFE > 1 )
{
@@ -178,11 +238,11 @@ void actArrow(Entity* my)
}
}
}
- else if ( my->arrowQuiverType == QUIVER_PIERCE || my->sprite == PROJECTILE_PIERCE_SPRITE )
+ else if ( my->arrowQuiverType == QUIVER_LIGHTWEIGHT || my->sprite == PROJECTILE_SWIFT_SPRITE )
{
if ( ARROW_STUCK == 0 )
{
- Entity* particle = spawnMagicParticleCustom(my, 158, 0.5, 4);
+ Entity* particle = spawnMagicParticleCustom(my, 156, 0.5, 4);
if ( particle )
{
particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
@@ -191,11 +251,11 @@ void actArrow(Entity* my)
}
}
}
- else if ( my->arrowQuiverType == QUIVER_LIGHTWEIGHT || my->sprite == PROJECTILE_SWIFT_SPRITE )
+ else if ( my->arrowQuiverType == QUIVER_HUNTING || my->sprite == PROJECTILE_HUNTING_SPRITE )
{
if ( ARROW_STUCK == 0 )
{
- Entity* particle = spawnMagicParticleCustom(my, 156, 0.5, 4);
+ Entity* particle = spawnMagicParticleCustom(my, 157, 0.5, 4);
if ( particle )
{
particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
@@ -204,20 +264,44 @@ void actArrow(Entity* my)
}
}
}
- else if ( my->arrowQuiverType == QUIVER_HUNTING || my->sprite == PROJECTILE_HUNTING_SPRITE )
+ else if ( my->arrowQuiverType == QUIVER_BLACKIRON || my->sprite == PROJECTILE_BLACKIRON_SPRITE )
{
if ( ARROW_STUCK == 0 )
{
- Entity* particle = spawnMagicParticleCustom(my, 157, 0.5, 4);
+ Entity* particle = spawnMagicParticleCustom(my, 155, 0.5, 4);
if ( particle )
{
particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
particle->flags[SPRITE] = true;
- particle->ditheringDisabled = true;
+ particle->ditheringDisabled = true;
+ }
+ }
+ }
+ else if ( my->arrowQuiverType == QUIVER_BONE || my->sprite == PROJECTILE_BONE_SPRITE )
+ {
+ if ( ARROW_STUCK == 0 )
+ {
+ Entity* particle = spawnMagicParticleCustom(my, 155, 0.5, 4);
+ if ( particle )
+ {
+ particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
+ particle->flags[SPRITE] = true;
+ particle->ditheringDisabled = true;
}
}
}
+ if ( my->arrowArmorPierce > 0 && ARROW_STUCK == 0 && !(my->arrowQuiverType == QUIVER_PIERCE || my->sprite == PROJECTILE_PIERCE_SPRITE) )
+ {
+ Entity* particle = spawnMagicParticleCustom(my, 158, 0.5, 4);
+ if ( particle )
+ {
+ particle->lightBonus = vec4(0.5f, 0.5f, 0.5f, 0.f);
+ particle->flags[SPRITE] = true;
+ particle->ditheringDisabled = true;
+ }
+ }
+
if ( multiplayer != CLIENT )
{
Sint32 val = (1 << 31);
@@ -260,7 +344,8 @@ void actArrow(Entity* my)
if ( my->arrowFallSpeed > 0 )
{
real_t pitchChange = 0.02;
- if ( my->arrowShotByWeapon == LONGBOW )
+ if ( my->arrowShotByWeapon == LONGBOW || my->arrowShotByWeapon == BRANCH_BOW
+ || my->arrowShotByWeapon == BRANCH_BOW_INFECTED )
{
pitchChange = 0.005;
}
@@ -302,6 +387,7 @@ void actArrow(Entity* my)
}
}
+ bool hitSomething = false;
if ( multiplayer != CLIENT )
{
// horizontal motion
@@ -309,7 +395,55 @@ void actArrow(Entity* my)
ARROW_VELY = sin(my->yaw) * my->arrowSpeed;
ARROW_OLDX = my->x;
ARROW_OLDY = my->y;
- dist = clipMove(&my->x, &my->y, ARROW_VELX, ARROW_VELY, my);
+
+ my->processEntityWind();
+ bool halfSpeedCheck = false;
+ static ConsoleVariable cvar_arrow_clip("/arrow_clip_test", true);
+ if ( my->arrowSpeed > 4.0 ) // can clip through thin gates
+ {
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
+ for ( auto it : entLists )
+ {
+ if ( !*cvar_arrow_clip && (svFlags & SV_FLAG_CHEATS) )
+ {
+ break;
+ }
+ for ( node_t* node = it->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity->behavior == &actGate || entity->behavior == &actDoor || entity->behavior == &actIronDoor )
+ {
+ if ( entityDist(my, entity) <= my->arrowSpeed )
+ {
+ halfSpeedCheck = true;
+ break;
+ }
+ }
+ }
+ if ( halfSpeedCheck )
+ {
+ break;
+ }
+ }
+ }
+
+ if ( !halfSpeedCheck )
+ {
+ real_t dist = clipMove(&my->x, &my->y, ARROW_VELX, ARROW_VELY, my);
+ hitSomething = dist != sqrt(ARROW_VELX * ARROW_VELX + ARROW_VELY * ARROW_VELY);
+ }
+ else
+ {
+ real_t vel_x = ARROW_VELX / 2.0;
+ real_t vel_y = ARROW_VELY / 2.0;
+ real_t dist = clipMove(&my->x, &my->y, vel_x, vel_y, my);
+ hitSomething = dist != sqrt(vel_x * vel_x + vel_y * vel_y);
+ if ( !hitSomething )
+ {
+ dist = clipMove(&my->x, &my->y, vel_x, vel_y, my);
+ hitSomething = dist != sqrt(vel_x * vel_x + vel_y * vel_y);
+ }
+ }
}
bool arrowInGround = false;
@@ -370,17 +504,32 @@ void actArrow(Entity* my)
}
// damage monsters
- if ( arrowSpawnedInsideEntity || dist != sqrt(ARROW_VELX * ARROW_VELX + ARROW_VELY * ARROW_VELY) || arrowInGround )
+ if ( arrowSpawnedInsideEntity || hitSomething || arrowInGround )
{
if ( arrowInGround )
{
ARROW_STUCK = 2;
+ serverUpdateEntitySkill(my, 0);
}
else
{
ARROW_STUCK = 1;
+ if ( !arrowSpawnedInsideEntity && !arrowInGround && hitSomething && hit.entity )
+ {
+ if ( my->arrowArmorPierce > 0 && hit.entity && hit.entity->getUID() > 0 )
+ {
+ if ( hit.entity->getStats() || hit.entity->isDamageableCollider() )
+ {
+ ARROW_STUCK = 0;
+ my->collisionIgnoreTargets.insert(hit.entity->getUID());
+ }
+ }
+ }
+ if ( ARROW_STUCK > 0 )
+ {
+ serverUpdateEntitySkill(my, 0);
+ }
}
- serverUpdateEntitySkill(my, 0);
my->x = ARROW_OLDX;
my->y = ARROW_OLDY;
@@ -397,6 +546,10 @@ void actArrow(Entity* my)
ARROW_VELY = 0;
ARROW_VELZ = 0;
my->entityCheckIfTriggeredBomb(true);
+ if ( !hit.entity )
+ {
+ my->entityCheckIfTriggeredWallButton();
+ }
if ( hit.entity != NULL )
{
Entity* parent = uidToEntity(my->parent);
@@ -476,12 +629,29 @@ void actArrow(Entity* my)
hit.entity->colliderKillerUid = parent ? parent->getUID() : 0;
if ( parent && parent->behavior == &actPlayer )
{
- messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(hit.entity->getColliderOnBreakLangEntry()),
- Language::get(hit.entity->getColliderLangName()));
+ if ( oldHP > 0 )
+ {
+ if ( parent->getStats() && parent->getStats()->getProficiency(PRO_RANGED) < SKILL_LEVEL_BASIC
+ && local_rng.rand() % 20 == 0 )
+ {
+ parent->increaseSkill(PRO_RANGED);
+ }
+ }
+
+ if ( hit.entity->getColliderOnBreakLangEntry() != 0 )
+ {
+ messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(hit.entity->getColliderOnBreakLangEntry()),
+ Language::get(hit.entity->getColliderLangName()));
+ }
if ( hit.entity->isColliderWall() )
{
Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1);
}
+
+ if ( hit.entity->colliderOldHP > 0 )
+ {
+ players[parent->skill[2]]->mechanics.incrementBreakableCounter(Player::PlayerMechanics_t::BreakableEvent::GBREAK_COMMON, hit.entity);
+ }
}
}
@@ -496,39 +666,72 @@ void actArrow(Entity* my)
if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
{
// test for friendly fire
- if ( parent && parent->checkFriend(hit.entity) )
+ if ( parent && parent->checkFriend(hit.entity) && parent->friendlyFireProtection(hit.entity) )
{
- my->removeLightField();
- list_RemoveNode(my->mynode);
+ if ( ARROW_STUCK > 0 )
+ {
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ }
return;
}
}
+ //if ( hit.entity && hitstats )
+ //{
+ // if ( hitstats->getEffectActive(EFF_NULL_RANGED) )
+ // {
+ // Uint8 effectStrength = hitstats->getEffectActive(EFF_NULL_RANGED);
+ // int duration = hitstats->EFFECTS_TIMERS[EFF_NULL_RANGED];
+ // if ( effectStrength == 1 )
+ // {
+ // if ( hitstats->EFFECTS_TIMERS[EFF_NULL_RANGED] > 0 )
+ // {
+ // hitstats->EFFECTS_TIMERS[EFF_NULL_RANGED] = 1;
+ // }
+ // }
+ // else if ( effectStrength > 1 )
+ // {
+ // --effectStrength;
+ // hitstats->setEffectValueUnsafe(EFF_NULL_RANGED, effectStrength);
+ // hit.entity->setEffect(EFF_NULL_RANGED, effectStrength, hitstats->EFFECTS_TIMERS[EFF_NULL_RANGED], false);
+ // }
+ // if ( (parent && parent->behavior == &actPlayer) || (parent && parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader())
+ // || hit.entity->behavior == &actPlayer || hit.entity->monsterAllyGetPlayerLeader() )
+ // {
+ // spawnDamageGib(hit.entity, 0, DamageGib::DMG_GUARD, DamageGibDisplayType::DMG_GIB_GUARD, true);
+ // }
+
+ // if ( hit.entity->behavior == &actPlayer )
+ // {
+ // messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6465));
+ // }
+ // if ( parent && parent->behavior == &actPlayer )
+ // {
+ // messagePlayerMonsterEvent(parent->skill[2], makeColorRGB(255, 255, 255),
+ // *hitstats, Language::get(6468), Language::get(6469), MSG_COMBAT); // %s guards the attack
+ // }
+ // playSoundEntity(hit.entity, 166, 128);
+ // my->removeLightField();
+ // list_RemoveNode(my->mynode);
+ // return;
+ // }
+ //}
+
bool silverDamage = false;
bool huntingDamage = false;
if ( my->arrowQuiverType == QUIVER_SILVER )
{
- switch ( hitstats->type )
+ if ( hit.entity->isSmiteWeakMonster() )
{
- case SKELETON:
- case CREATURE_IMP:
- case GHOUL:
- case DEMON:
- case SUCCUBUS:
- case INCUBUS:
- case VAMPIRE:
- case LICH:
- case LICH_ICE:
- case LICH_FIRE:
- case DEVIL:
- // smite these creatures
- silverDamage = true;
- spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 981);
- playSoundEntity(hit.entity, 249, 64);
- break;
- default:
- silverDamage = false;
- break;
+ // smite these creatures
+ silverDamage = true;
+ spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 981);
+ playSoundEntity(hit.entity, 249, 64);
+ }
+ else
+ {
+ silverDamage = false;
}
}
else if ( my->arrowQuiverType == QUIVER_HUNTING )
@@ -595,7 +798,10 @@ void actArrow(Entity* my)
{
Stat* parentStats = parent->getStats();
if ( parentStats->helmet && parentStats->helmet->type == HAT_HOOD_WHISPERS
- && !monsterIsImmobileTurret(hit.entity, hitstats) && !(hitstats->type == MIMIC) )
+ && !monsterIsImmobileTurret(hit.entity, hitstats) && !hitstats->getEffectActive(EFF_STASIS)
+ && !(hitstats->type == MIMIC
+ || hitstats->type == MINIMIMIC
+ || hitstats->type == MONSTER_ADORCISED_WEAPON) )
{
real_t hitAngle = hit.entity->yawDifferenceFromEntity(my);
if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc
@@ -621,16 +827,23 @@ void actArrow(Entity* my)
}
}
- if ( hit.entity->monsterState == MONSTER_STATE_WAIT
+ if ( (hit.entity->monsterState == MONSTER_STATE_WAIT
|| hit.entity->monsterState == MONSTER_STATE_PATH
- || (hit.entity->monsterState == MONSTER_STATE_HUNT && uidToEntity(hit.entity->monsterTarget) == nullptr) )
+ || (hit.entity->monsterState == MONSTER_STATE_HUNT && uidToEntity(hit.entity->monsterTarget) == nullptr))
+ && !hitstats->getEffectActive(EFF_ROOTED) )
{
// unaware monster, get backstab damage.
int bonus = (parentStats->getModifiedProficiency(PRO_STEALTH) / 20 + 2) * (2 * stealthCapstoneBonus);
damage += ((bonus * equipmentModifier) * bonusModifier);
- if ( local_rng.rand() % 10 == 0 && hit.entity->behavior != &actPlayer )
+ if ( local_rng.rand() % 4 == 0
+ && hit.entity->behavior != &actPlayer
+ && !(parent->behavior == &actPlayer && hit.entity->monsterAllyGetPlayerLeader()) )
{
- parent->increaseSkill(PRO_STEALTH);
+ if ( parent->behavior == &actPlayer && players[parent->skill[2]]->mechanics.allowedRaiseStealthAgainstEntity(*hit.entity) )
+ {
+ parent->increaseSkill(PRO_STEALTH);
+ players[parent->skill[2]]->mechanics.enemyRaisedStealthAgainst[hit.entity->getUID()]++;
+ }
}
backstab = true;
}
@@ -640,9 +853,15 @@ void actArrow(Entity* my)
// 1 in 2 chance to flank defenses.
int bonus = (parentStats->getModifiedProficiency(PRO_STEALTH) / 20 + 1) * (stealthCapstoneBonus);
damage += ((bonus * equipmentModifier) * bonusModifier);
- if ( local_rng.rand() % 20 == 0 && hit.entity->behavior != &actPlayer )
+ if ( local_rng.rand() % 20 == 0
+ && hit.entity->behavior != &actPlayer
+ && !(parent->behavior == &actPlayer && hit.entity->monsterAllyGetPlayerLeader()) )
{
- parent->increaseSkill(PRO_STEALTH);
+ if ( parent->behavior == &actPlayer && players[parent->skill[2]]->mechanics.allowedRaiseStealthAgainstEntity(*hit.entity) )
+ {
+ parent->increaseSkill(PRO_STEALTH);
+ players[parent->skill[2]]->mechanics.enemyRaisedStealthAgainst[hit.entity->getUID()]++;
+ }
}
flanking = true;
}
@@ -689,6 +908,28 @@ void actArrow(Entity* my)
damageMultiplier = std::max(0.75, damageMultiplier);
}
+ Entity::modifyDamageMultipliersFromEffects(hit.entity, parent, damageMultiplier, DAMAGE_TABLE_RANGED, my);
+
+ if ( my->arrowArmorPierce > 0 && parent && parent->behavior == &actPlayer )
+ {
+ if ( parent->getStats() )
+ {
+ /*real_t mult = 1.0;
+ if ( parent->getStats()->getModifiedProficiency(PRO_RANGED) >= SKILL_LEVEL_LEGENDARY )
+ {
+ mult = 2.0;
+ }*/
+ if ( parent->behavior == &actMonster )
+ {
+ damageMultiplier += std::min(25, std::max(0, statGetPER(parent->getStats(), parent))) / 100.0;
+ }
+ else
+ {
+ damageMultiplier += std::max(0, statGetPER(parent->getStats(), parent)) / 100.0;
+ }
+ }
+ }
+
if ( hitWeaklyOnTarget )
{
damage = damage * (std::max(0.1, damageMultiplier - 0.5));
@@ -712,10 +953,41 @@ void actArrow(Entity* my)
}
}
+ if ( hitstats && hitstats->getEffectActive(EFF_GUARD_BODY) )
+ {
+ thaumSpellArmorProc(hit.entity, *hitstats, false, parent, EFF_GUARD_BODY);
+ }
+ if ( hitstats && hitstats->getEffectActive(EFF_DIVINE_GUARD) )
+ {
+ thaumSpellArmorProc(hit.entity, *hitstats, false, parent, EFF_DIVINE_GUARD);
+ }
+
/*messagePlayer(0, "My damage: %d, AC: %d, Pierce: %d", my->arrowPower, AC(hitstats), my->arrowArmorPierce);
messagePlayer(0, "Resolved to %d damage.", damage);*/
Sint32 oldHP = hitstats->HP;
hit.entity->modHP(-damage);
+
+ if ( hitstats )
+ {
+ Sint32 damageTaken = oldHP - hitstats->HP;
+ if ( damageTaken > 0 )
+ {
+ if ( hitstats->getEffectActive(EFF_DEFY_FLESH) )
+ {
+ hit.entity->defyFleshProc(parent);
+ }
+ hit.entity->pinpointDamageProc(parent, damageTaken);
+ }
+ if ( hitstats->getEffectActive(EFF_SPORES) )
+ {
+ if ( hit.entity->behavior == &actPlayer
+ && hitstats->type == MYCONID && hitstats->getEffectActive(EFF_GROWTH) >= 4 )
+ {
+ floorMagicCreateSpores(hit.entity, hit.entity->x, hit.entity->y, hit.entity, 0, SPELL_SPORES);
+ }
+ }
+ }
+
// write obituary
if ( parent )
{
@@ -742,6 +1014,16 @@ void actArrow(Entity* my)
}
}
}
+
+ if ( hit.entity->onEntityTrapHitSacredPath(parent) )
+ {
+ if ( hit.entity->behavior == &actPlayer )
+ {
+ messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0),
+ Language::get(6472), Language::get(6291));
+ }
+ playSoundEntity(hit.entity, 166, 128);
+ }
}
else
{
@@ -855,10 +1137,29 @@ void actArrow(Entity* my)
doSkillIncrease = false; // no skill for killing/hurting other turrets.
}
}
+ else if ( hit.entity->behavior == &actMonster
+ && (hit.entity->monsterAllyGetPlayerLeader() || (hitstats && achievementObserver.checkUidIsFromPlayer(hitstats->leader_uid) >= 0))
+ && parent && parent->behavior == &actPlayer )
+ {
+ doSkillIncrease = false; // no level up on allies
+ }
if ( hit.entity->behavior == &actPlayer && parent && parent->behavior == &actPlayer )
{
doSkillIncrease = false; // no skill for killing/hurting players
}
+ if ( hitstats->getEffectActive(EFF_STASIS) )
+ {
+ doSkillIncrease = false;
+ }
+
+ if ( doSkillIncrease && parent && parent->behavior == &actPlayer )
+ {
+ if ( parent->isInvisible() && parent->checkEnemy(hit.entity) )
+ {
+ players[parent->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_INVISIBILITY, 10.0, 1.0, hit.entity);
+ }
+ }
+
int chance = 10;
if ( doSkillIncrease && (local_rng.rand() % chance == 0) && parent && parent->getStats() )
{
@@ -895,6 +1196,47 @@ void actArrow(Entity* my)
}
}
+ bool envenomWeapon = false;
+ if ( parent )
+ {
+ Stat* parentStats = parent->getStats();
+ if ( parentStats && parentStats->getEffectActive(EFF_ENVENOM_WEAPON) && hitstats )
+ {
+ if ( local_rng.rand() % 2 == 0 )
+ {
+ int envenomDamage = std::min(
+ getSpellDamageSecondaryFromID(SPELL_ENVENOM_WEAPON, parent, parentStats, parent),
+ getSpellDamageFromID(SPELL_ENVENOM_WEAPON, parent, parentStats, parent));
+
+ hit.entity->modHP(-envenomDamage); // do the damage
+ for ( int tmp = 0; tmp < 3; ++tmp )
+ {
+ Entity* gib = spawnGib(hit.entity, 211);
+ serverSpawnGibForClient(gib);
+ }
+ if ( !hitstats->getEffectActive(EFF_POISONED) )
+ {
+ envenomWeapon = true;
+ hitstats->setEffectActive(EFF_POISONED, 1);
+
+ int duration = 160 * envenomDamage;
+ hitstats->EFFECTS_TIMERS[EFF_POISONED] = std::max(200, duration - hit.entity->getCON() * 20);
+ hitstats->poisonKiller = parent->getUID();
+ if ( hit.entity->isEntityPlayer() )
+ {
+ messagePlayerMonsterEvent(hit.entity->isEntityPlayer(), makeColorRGB(255, 0, 0), *parentStats, Language::get(6531), Language::get(6532), MSG_COMBAT);
+ serverUpdateEffects(hit.entity->isEntityPlayer());
+ }
+
+ if ( parent->behavior == &actPlayer )
+ {
+ players[parent->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_ENVENOM_WEAPON, 50.0, 1.0, hit.entity);
+ }
+ }
+ }
+ }
+ }
+
if ( hitstats->HP <= 0 && parent)
{
parent->awardXP( hit.entity, true, true );
@@ -909,15 +1251,7 @@ void actArrow(Entity* my)
// alert the monster
if ( hit.entity->behavior == &actMonster && parent != nullptr )
{
- bool alertTarget = true;
- if ( parent->behavior == &actMonster && parent->monsterAllyIndex != -1 )
- {
- if ( hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1 )
- {
- // if a player ally + hit another ally, don't aggro back
- alertTarget = false;
- }
- }
+ bool alertTarget = hit.entity->monsterAlertBeforeHit(parent);
if ( alertTarget && hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) )
{
@@ -967,7 +1301,7 @@ void actArrow(Entity* my)
// you shot the %s!
messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(446), Language::get(448), MSG_COMBAT_BASIC);
}
- if ( my->arrowArmorPierce > 0 && AC(hitstats) > 0 )
+ if ( my->arrowArmorPierce > 0 /*&& AC(hitstats) > 0*/ )
{
messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(2513), Language::get(2514), MSG_COMBAT);
}
@@ -1019,12 +1353,21 @@ void actArrow(Entity* my)
}
}
- if ( my->arrowArmorPierce > 0 && AC(hitstats) > 0 )
+ if ( my->arrowArmorPierce > 0 /*&& AC(hitstats) > 0*/ )
{
messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(2515));
}
}
+ if ( my->sprite == PROJECTILE_SEED_POISON_SPRITE )
+ {
+ floorMagicCreateSpores(nullptr, hit.entity->x, hit.entity->y, parent, 15, SPELL_SPORES);
+ }
+ else if ( my->sprite == PROJECTILE_SEED_ROOT_SPRITE )
+ {
+ floorMagicCreateRoots(hit.entity->x, hit.entity->y, parent, 3, SPELL_ROOTS, 3 * TICKS_PER_SECOND, PARTICLE_TIMER_ACTION_ROOTS_SINGLE_TILE);
+ }
+
bool statusEffectApplied = false;
if ( hitstats->HP > 0 )
{
@@ -1036,7 +1379,61 @@ void actArrow(Entity* my)
procEffect = false;
}
}
- if ( my->arrowQuiverType == QUIVER_FIRE && procEffect )
+
+ if ( hitstats->HP > 0 && hitstats->OLDHP > hitstats->HP && parent )
+ {
+ // assist damage from summons
+ if ( parent->behavior == &actMonster )
+ {
+ int summonSpellID = getSpellFromSummonedEntityForSpellEvent(parent);
+ if ( summonSpellID != SPELL_NONE )
+ {
+ if ( Stat* parentStats = parent->getStats() )
+ {
+ if ( Entity* leader = uidToEntity(parentStats->leader_uid) )
+ {
+ if ( local_rng.rand() % 8 == 0 )
+ {
+ magicOnSpellCastEvent(leader, nullptr, hit.entity, summonSpellID, spell_t::SPELL_LEVEL_EVENT_ASSIST | spell_t::SPELL_LEVEL_EVENT_MINOR_CHANCE, 1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( my->sprite == PROJECTILE_SEED_ROOT_SPRITE )
+ {
+ if ( hit.entity->setEffect(EFF_ROOTED, true, TICKS_PER_SECOND, false) )
+ {
+ statusEffectApplied = true;
+ }
+ if ( parent && parent->behavior == &actPlayer )
+ {
+ Uint32 color = makeColorRGB(0, 255, 0);
+ messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(6743), Language::get(6742), MSG_COMBAT);
+ }
+ if ( hit.entity->behavior == &actPlayer )
+ {
+ Uint32 color = makeColorRGB(255, 0, 0);
+ messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6744));
+ }
+ }
+ else if ( my->sprite == PROJECTILE_SEED_POISON_SPRITE )
+ {
+ statusEffectApplied = true;
+ if ( parent && parent->behavior == &actPlayer )
+ {
+ Uint32 color = makeColorRGB(0, 255, 0);
+ messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(6746), Language::get(6745), MSG_COMBAT);
+ }
+ if ( hit.entity->behavior == &actPlayer )
+ {
+ Uint32 color = makeColorRGB(255, 0, 0);
+ messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6747));
+ }
+ }
+ else if ( my->arrowQuiverType == QUIVER_FIRE && procEffect )
{
bool burning = hit.entity->flags[BURNING];
hit.entity->SetEntityOnFire(my);
@@ -1149,11 +1546,11 @@ void actArrow(Entity* my)
else if ( my->arrowQuiverType == QUIVER_HUNTING && !(hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE)
&& !(hitstats->type == INSECTOID) && procEffect )
{
- if ( !hitstats->EFFECTS[EFF_POISONED] )
+ if ( !hitstats->getEffectActive(EFF_POISONED) )
{
hitstats->poisonKiller = my->parent;
- hitstats->EFFECTS[EFF_POISONED] = true;
- hitstats->EFFECTS[EFF_SLOW] = true;
+ hitstats->setEffectActive(EFF_POISONED, 1);
+ hitstats->setEffectActive(EFF_SLOW, 1);
if ( my->arrowPoisonTime > 0 )
{
hitstats->EFFECTS_TIMERS[EFF_POISONED] = my->arrowPoisonTime;
@@ -1178,7 +1575,7 @@ void actArrow(Entity* my)
}
if ( hit.entity->behavior == &actPlayer )
{
- if ( local_rng.rand() % 8 == 0 && hit.entity->char_gonnavomit == 0 && !hitstats->EFFECTS[EFF_VOMITING] )
+ if ( local_rng.rand() % 8 == 0 && hit.entity->char_gonnavomit == 0 && !hitstats->getEffectActive(EFF_VOMITING) )
{
// maybe vomit
messagePlayer(hit.entity->skill[2], MESSAGE_STATUS, Language::get(634));
@@ -1197,6 +1594,20 @@ void actArrow(Entity* my)
messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT_BASIC, color, Language::get(451)); // you are hit by an arrow!
}
}
+ else if ( envenomWeapon )
+ {
+ statusEffectApplied = true;
+ if ( parent && parent->behavior == &actPlayer )
+ {
+ Uint32 color = makeColorRGB(0, 255, 0);
+ messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(6533), Language::get(6534), MSG_COMBAT);
+ }
+ }
+
+ if ( my->arrowQuiverType == QUIVER_HUNTING && procEffect )
+ {
+ hit.entity->degradeAmuletProc(hitstats, AMULET_POISONRESISTANCE);
+ }
}
else
{
@@ -1302,7 +1713,9 @@ void actArrow(Entity* my)
// if nothing chosen to degrade, check extra shield chances to degrade
if ( hitstats->shield != NULL && hitstats->shield->status > BROKEN && armor == NULL
&& !itemTypeIsQuiver(hitstats->shield->type) && itemCategory(hitstats->shield) != SPELLBOOK
- && hitstats->shield->type != TOOL_TINKERING_KIT )
+ && !itemTypeIsFoci(hitstats->shield->type)
+ && !(hitstats->shield->type >= INSTRUMENT_FLUTE && hitstats->shield->type <= INSTRUMENT_HORN)
+ && hitstats->shield->type != TOOL_TINKERING_KIT && hitstats->shield->type != TOOL_FRYING_PAN )
{
if ( hitstats->shield->type == TOOL_CRYSTALSHARD && hitstats->defending )
{
@@ -1359,7 +1772,7 @@ void actArrow(Entity* my)
{
increaseSkill = false;
}
- else if ( hitstats->EFFECTS[EFF_SHAPESHIFT] )
+ else if ( hitstats->getEffectActive(EFF_SHAPESHIFT) )
{
increaseSkill = false;
}
@@ -1390,6 +1803,7 @@ void actArrow(Entity* my)
{
shieldDegradeChance += 10;
}
+
if ( hit.entity->behavior == &actPlayer )
{
if ( itemCategory(hitstats->shield) == ARMOR )
@@ -1421,13 +1835,16 @@ void actArrow(Entity* my)
if ( armor != NULL && armor->status > BROKEN )
{
- hit.entity->degradeArmor(*hitstats, *armor, armornum);
- armorDegraded = true;
+ if ( hit.entity->degradeArmor(*hitstats, *armor, armornum) )
+ {
+ armorDegraded = true;
+ }
if ( armor->status == BROKEN )
{
if ( parent && parent->behavior == &actPlayer && hit.entity->behavior == &actMonster )
{
steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_UNSTOPPABLE_FORCE, STEAM_STAT_INT, 1);
+ players[parent->skill[2]]->mechanics.incrementBreakableCounter(Player::PlayerMechanics_t::BreakableEvent::GBREAK_DEGRADE, hit.entity);
if ( armornum == 4 && hitstats->type == BUGBEAR
&& (hitstats->defending || hit.entity->monsterAttack == MONSTER_POSE_BUGBEAR_SHIELD) )
{
@@ -1497,16 +1914,46 @@ void actArrow(Entity* my)
updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP,
false, dmgGib);
}
+ }
+
+ if ( ARROW_STUCK > 0 )
+ {
+ if ( my->sprite == PROJECTILE_SEED_POISON_SPRITE )
+ {
+ if ( !hitstats || hit.entity->isInertMimic() )
+ {
+ floorMagicCreateSpores(nullptr, hit.entity->x, hit.entity->y, parent, 15, SPELL_SPORES);
+ }
+ }
+ else if ( my->sprite == PROJECTILE_SEED_ROOT_SPRITE )
+ {
+ if ( !hitstats || hit.entity->isInertMimic() )
+ {
+ floorMagicCreateRoots(hit.entity->x, hit.entity->y, parent, 3, SPELL_ROOTS, 3 * TICKS_PER_SECOND, PARTICLE_TIMER_ACTION_ROOTS_SINGLE_TILE);
+ }
+ }
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
}
- my->removeLightField();
- list_RemoveNode(my->mynode);
}
else if ( my->sprite == PROJECTILE_ROCK_SPRITE )
{
my->removeLightField();
list_RemoveNode(my->mynode); // rocks don't stick to walls...
}
+ else if ( my->sprite == PROJECTILE_SEED_POISON_SPRITE )
+ {
+ floorMagicCreateSpores(nullptr, my->x, my->y, uidToEntity(my->parent), 15, SPELL_SPORES);
+ my->removeLightField();
+ list_RemoveNode(my->mynode); // rocks don't stick to walls...
+ }
+ else if ( my->sprite == PROJECTILE_SEED_ROOT_SPRITE )
+ {
+ floorMagicCreateRoots(my->x, my->y, uidToEntity(my->parent), 3, SPELL_ROOTS, 3 * TICKS_PER_SECOND, PARTICLE_TIMER_ACTION_ROOTS_SINGLE_TILE);
+ my->removeLightField();
+ list_RemoveNode(my->mynode); // rocks don't stick to walls...
+ }
else
{
playSoundEntity(my, 72 + local_rng.rand() % 3, 64);
diff --git a/src/actarrowtrap.cpp b/src/actarrowtrap.cpp
index 32bc49040..3768dec33 100644
--- a/src/actarrowtrap.cpp
+++ b/src/actarrowtrap.cpp
@@ -114,29 +114,45 @@ void actArrowTrap(Entity* my)
return;
}
-#ifdef USE_FMOD
- if ( ARROWTRAP_AMBIENCE == 0 )
+ if ( my->actTrapSabotaged == 0 )
{
+#ifdef USE_FMOD
+ if ( ARROWTRAP_AMBIENCE == 0 )
+ {
+ ARROWTRAP_AMBIENCE--;
+ my->stopEntitySound();
+ my->entity_sound = playSoundEntityLocal(my, 149, 64);
+ }
+ if ( my->entity_sound )
+ {
+ bool playing = false;
+ my->entity_sound->isPlaying(&playing);
+ if ( !playing )
+ {
+ my->entity_sound = nullptr;
+ }
+ }
+#else
ARROWTRAP_AMBIENCE--;
- my->entity_sound = playSoundEntityLocal(my, 149, 64);
- }
- if ( my->entity_sound )
- {
- bool playing = false;
- my->entity_sound->isPlaying(&playing);
- if ( !playing )
+ if ( ARROWTRAP_AMBIENCE <= 0 )
{
- my->entity_sound = nullptr;
+ ARROWTRAP_AMBIENCE = TICKS_PER_SECOND * 30;
+ playSoundEntityLocal( my, 149, 64 );
}
+#endif
}
-#else
- ARROWTRAP_AMBIENCE--;
- if ( ARROWTRAP_AMBIENCE <= 0 )
+ else
{
- ARROWTRAP_AMBIENCE = TICKS_PER_SECOND * 30;
- playSoundEntityLocal( my, 149, 64 );
- }
+#ifdef USE_FMOD
+ my->stopEntitySound();
#endif
+ }
+
+ if ( multiplayer == CLIENT )
+ {
+ my->flags[NOUPDATE] = true;
+ return;
+ }
if ( !my->skill[28] )
{
@@ -146,7 +162,7 @@ void actArrowTrap(Entity* my)
Entity* targetToAutoHit = nullptr;
// received on signal
- if ( my->skill[28] == 2 || ARROWTRAP_DISABLED == -1 )
+ if ( (my->skill[28] == 2 || ARROWTRAP_DISABLED == -1) && my->actTrapSabotaged == 0 )
{
if ( ARROWTRAP_FIRED % 2 == 1 ) // not ready to fire.
{
@@ -275,6 +291,14 @@ void actArrowTrap(Entity* my)
entity->arrowPoisonTime = 360;
if ( stronger ) { ARROWTRAP_REFIRE = 25; }
break;
+ case QUIVER_BONE:
+ entity->sprite = 2304;
+ if ( stronger ) { ARROWTRAP_REFIRE = 25; }
+ break;
+ case QUIVER_BLACKIRON:
+ entity->sprite = 2305;
+ if ( stronger ) { ARROWTRAP_REFIRE = 25; }
+ break;
default:
break;
}
diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp
index e58338dd6..f9c17e60e 100644
--- a/src/actbeartrap.cpp
+++ b/src/actbeartrap.cpp
@@ -133,7 +133,7 @@ void actBeartrap(Entity* my)
{
continue;
}
- if ( stat->type == GYROBOT || entity->isUntargetableBat() )
+ if ( !entity->monsterIsTargetable() )
{
continue;
}
@@ -155,7 +155,25 @@ void actBeartrap(Entity* my)
if ( entityDist(my, entity) < 6.5 )
{
entity->setEffect(EFF_PARALYZED, true, 200, false);
- entity->setEffect(EFF_BLEEDING, true, 300, false);
+ if ( entity->setEffect(EFF_BLEEDING, true, 300, false) )
+ {
+ if ( parent && parent->behavior == &actPlayer )
+ {
+ if ( stats[parent->skill[2]]->helmet && stats[parent->skill[2]]->helmet->type == PUNISHER_HOOD )
+ {
+ int mpAmount = parent->modMP(1 + local_rng.rand() % 2);
+ parent->playerInsectoidIncrementHungerToMP(mpAmount);
+ Uint32 color = makeColorRGB(0, 255, 0);
+ parent->setEffect(EFF_MP_REGEN, true, std::max(stats[parent->skill[2]]->EFFECTS_TIMERS[EFF_MP_REGEN], 10 * TICKS_PER_SECOND), false);
+ if ( parent->behavior == &actPlayer )
+ {
+ messagePlayerColor(parent->skill[2], MESSAGE_HINT, color, Language::get(3753));
+ steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_ITS_A_LIVING, STEAM_STAT_INT, 1);
+ }
+ playSoundEntity(parent, 168, 128);
+ }
+ }
+ }
int damage = 10 + 3 * (BEARTRAP_STATUS + BEARTRAP_BEATITUDE);
if ( parent )
{
@@ -236,7 +254,8 @@ void actBeartrap(Entity* my)
{
messagePlayer(player, MESSAGE_HINT, Language::get(2522));
}
- if ( local_rng.rand() % 10 == 0 )
+ if ( local_rng.rand() % 10 == 0 && !stat->getEffectActive(EFF_STASIS)
+ && !monsterIsImmobileTurret(entity, stat) )
{
parent->increaseSkill(PRO_LOCKPICKING);
}
@@ -383,7 +402,7 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa
bool wasAsleep = false;
if ( stat )
{
- wasAsleep = stat->EFFECTS[EFF_ASLEEP];
+ wasAsleep = stat->getEffectActive(EFF_ASLEEP);
}
if ( damage > 0 )
{
@@ -646,24 +665,43 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa
}
if ( triggered->behavior == &actMonster )
{
- if ( oldHP > 0 && stat->HP == 0 ) // got a kill
+ bool doSkillIncrease = true;
+ Stat* triggeredStats = triggered->getStats();
+ if ( monsterIsImmobileTurret(triggered, triggeredStats) )
{
- if ( local_rng.rand() % 5 == 0 )
- {
- parent->increaseSkill(PRO_LOCKPICKING);
- }
+ doSkillIncrease = false;
+ }
+ else if ( triggered->monsterAllyGetPlayerLeader()
+ || (triggeredStats && achievementObserver.checkUidIsFromPlayer(triggeredStats->leader_uid) >= 0) )
+ {
+ doSkillIncrease = false;
+ }
+ else if ( triggeredStats && triggeredStats->getEffectActive(EFF_STASIS) )
+ {
+ doSkillIncrease = false;
}
- else if ( oldHP > stat->HP )
+
+ if ( doSkillIncrease )
{
- if ( local_rng.rand() % 20 == 0 ) // wounded
+ if ( oldHP > 0 && stat->HP == 0 ) // got a kill
+ {
+ if ( local_rng.rand() % 5 == 0 )
+ {
+ parent->increaseSkill(PRO_LOCKPICKING);
+ }
+ }
+ else if ( oldHP > stat->HP )
+ {
+ if ( local_rng.rand() % 20 == 0 ) // wounded
+ {
+ parent->increaseSkill(PRO_LOCKPICKING);
+ }
+ }
+ else if( local_rng.rand() % 20 == 0) // any other effect
{
parent->increaseSkill(PRO_LOCKPICKING);
}
}
- else if( local_rng.rand() % 20 == 0) // any other effect
- {
- parent->increaseSkill(PRO_LOCKPICKING);
- }
if ( !achievementObserver.playerAchievements[player].bombTrack )
{
@@ -884,7 +922,7 @@ void actBomb(Entity* my)
}
else if ( onEntity )
{
- if ( onEntity->behavior == &actDoor )
+ if ( onEntity->behavior == &actDoor || onEntity->behavior == &actIronDoor )
{
if ( onEntity->doorHealth < BOMB_ENTITY_ATTACHED_START_HP || onEntity->flags[PASSABLE] || cursedExplode
|| BOMB_HIT_BY_PROJECTILE == 1 )
@@ -1005,7 +1043,7 @@ void actBomb(Entity* my)
{
continue;
}
- if ( stat->type == GYROBOT || entity->isUntargetableBat() )
+ if ( !entity->monsterIsTargetable() )
{
continue;
}
@@ -1170,6 +1208,63 @@ void actBomb(Entity* my)
}
}
+bool Entity::entityCheckIfTriggeredWallButton()
+{
+ if ( multiplayer == CLIENT )
+ {
+ return false;
+ }
+ if ( this->behavior != &actThrown && this->behavior != &actArrow )
+ {
+ return false;
+ }
+
+ bool foundButton = false;
+
+ real_t height_limit_low = (behavior == &actThrown) ? 5.0 : 4.0;
+ real_t height_limit_high = -8.0;
+
+ // check for wall buttons
+ if ( z < height_limit_low && z > height_limit_high )
+ {
+ std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(this, 1);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
+ {
+ list_t* currentList = *it;
+ node_t* node;
+ for ( node = currentList->first; node != nullptr; node = node->next )
+ {
+ if ( Entity* entity = ((Entity*)node->element) )
+ {
+ if ( entity->behavior == &::actWallButton )
+ {
+ Sint32 tmpsizex = sizex;
+ Sint32 tmpsizey = sizey;
+ sizex = std::max(sizex, 2);
+ sizey = std::max(sizey, 2);
+ if ( entityInsideEntity(this, entity) )
+ {
+ entity->wallLockPlayerInteracting = MAXPLAYERS + 1;
+ foundButton = true;
+ if ( Entity* parent = uidToEntity(this->parent) )
+ {
+ if ( parent->behavior == &actPlayer )
+ {
+ entity->wallLockPlayerInteracting = 1 + parent->skill[2];
+ }
+ }
+ }
+ sizex = tmpsizex;
+ sizey = tmpsizey;
+ }
+ }
+ }
+ }
+ }
+
+ return foundButton;
+}
+
bool Entity::entityCheckIfTriggeredBomb(bool triggerBomb)
{
if ( multiplayer == CLIENT )
@@ -1313,8 +1408,8 @@ void actDecoyBox(Entity* my)
{
// ignore pathing to this noisemaker as we're already distracted by it.
if ( entityDist(entity, my) < TOUCHRANGE
- && !myStats->EFFECTS[EFF_DISORIENTED]
- && !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] )
+ && !myStats->getEffectActive(EFF_DISORIENTED)
+ && !myStats->getEffectActive(EFF_DISTRACTED_COOLDOWN) )
{
// if we pathed within range
detected = false; // skip the message.
@@ -1347,7 +1442,7 @@ void actDecoyBox(Entity* my)
{
break;
}
- if ( !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN]
+ if ( !myStats->getEffectActive(EFF_DISTRACTED_COOLDOWN)
&& entity->monsterSetPathToLocation(my->x / 16, my->y / 16, 2,
GeneratePathTypes::GENERATE_PATH_DEFAULT) && entity->children.first )
{
@@ -1360,8 +1455,8 @@ void actDecoyBox(Entity* my)
++lured;
if ( entityDist(entity, my) < TOUCHRANGE
- && !myStats->EFFECTS[EFF_DISORIENTED]
- && !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] )
+ && !myStats->getEffectActive(EFF_DISORIENTED)
+ && !myStats->getEffectActive(EFF_DISTRACTED_COOLDOWN) )
{
detected = false; // skip the message.
@@ -1396,9 +1491,11 @@ void actDecoyBox(Entity* my)
Entity* gyrobot = uidToEntity(*c);
if ( gyrobot && gyrobot->getRace() == GYROBOT )
{
- if ( entity->entityShowOnMap < 250 )
+ if ( entity->getEntityShowOnMapDuration() == 0
+ || (entity->getEntityShowOnMapSource() == Entity::SHOW_MAP_GYRO
+ && entity->getEntityShowOnMapDuration() < TICKS_PER_SECOND * 5) )
{
- entity->entityShowOnMap = TICKS_PER_SECOND * 5;
+ entity->setEntityShowOnMap(Entity::SHOW_MAP_GYRO, TICKS_PER_SECOND * 5);
if ( parent->skill[2] != 0 )
{
serverUpdateEntitySkill(entity, 59);
diff --git a/src/actboulder.cpp b/src/actboulder.cpp
index 964beeb72..4a1f3153d 100644
--- a/src/actboulder.cpp
+++ b/src/actboulder.cpp
@@ -37,6 +37,8 @@
#define BOULDER_INIT my->skill[11]
#define BOULDER_LAVA_EXPLODE my->skill[12]
#define BOULDER_SOUND_ON_PUSH my->skill[13]
+#define BOULDER_TELEKINESIS_PULL my->skill[14]
+#define BOULDER_TELEKINESIS_PUSH my->skill[15]
const int BOULDER_LAVA_SPRITE = 989;
const int BOULDER_ARCANE_SPRITE = 990;
@@ -117,6 +119,10 @@ bool doesEntityStopBoulder(Entity* entity)
{
return true;
}
+ else if ( entity->behavior == &actIronDoor )
+ {
+ return true;
+ }
else if ( entity->behavior == &actBoulder )
{
return true;
@@ -189,6 +195,18 @@ bool doesEntityStopBoulder(Entity* entity)
{
return true;
}
+ else if ( entity->behavior == &actCauldron )
+ {
+ return true;
+ }
+ else if ( entity->behavior == &actWorkbench )
+ {
+ return true;
+ }
+ else if ( entity->behavior == &actMailbox )
+ {
+ return true;
+ }
return false;
}
@@ -219,6 +237,15 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
Stat* stats = entity->getStats();
if ( stats )
{
+ if ( stats->type == DUCK_SMALL )
+ {
+ if ( entity->monsterAttack == 0 )
+ {
+ entity->attack(local_rng.rand() % 2 ? MONSTER_POSE_MELEE_WINDUP2 : MONSTER_POSE_MELEE_WINDUP3, 0, nullptr);
+ }
+ return 0;
+ }
+
if ( entity->behavior == &actPlayer )
{
Uint32 color = makeColorRGB(255, 0, 0);
@@ -244,14 +271,24 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
}
playSoundEntity(my, 181, 128);
playSoundEntity(entity, 28, 64);
- Entity* gib = spawnGib(entity);
+ if ( Entity* gib = spawnGib(entity) )
+ {
+ serverSpawnGibForClient(gib);
+ }
int damage = 80;
- if ( my->sprite == BOULDER_LAVA_SPRITE
- || my->sprite == BOULDER_ARCANE_SPRITE )
+ if ( my->sprite == BOULDER_LAVA_SPRITE )
{
damage = 50;
}
+ else if ( my->sprite == BOULDER_ARCANE_SPRITE )
+ {
+ damage = 80;
+ }
+ if ( my->boulderShatterEarthSpell > 0 )
+ {
+ damage = my->boulderShatterEarthDamage;
+ }
int trapResist = entity->getEntityBonusTrapResist();
if ( trapResist != 0 )
@@ -260,7 +297,16 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
damage *= mult;
}
- if ( stats->helmet )
+ if ( entity->onEntityTrapHitSacredPath(my) )
+ {
+ if ( entity->behavior == &actPlayer )
+ {
+ messagePlayerColor(entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0),
+ Language::get(6490));
+ }
+ playSoundEntity(entity, 166, 128);
+ }
+ else if ( stats->helmet )
{
bool shapeshifted = (entity->behavior == &actPlayer && entity->effectShapeshift != NOTHING);
@@ -279,7 +325,10 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
}
damage = 0;
}
- stats->helmet->status = BROKEN;
+ if ( !entity->spellEffectPreserveItem(stats->helmet) )
+ {
+ stats->helmet->status = BROKEN;
+ }
}
else if ( stats->helmet->type == HELM_MINING )
{
@@ -305,9 +354,12 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
}
}
damage *= mult;
- if ( stats->helmet->status > BROKEN )
+ if ( !entity->spellEffectPreserveItem(stats->helmet) )
{
- stats->helmet->status = (Status)((int)stats->helmet->status - 1);
+ if ( stats->helmet->status > BROKEN )
+ {
+ stats->helmet->status = (Status)((int)stats->helmet->status - 1);
+ }
}
}
@@ -330,9 +382,10 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
strcpy((char*)net_packet->data, "ARMR");
net_packet->data[4] = 0;
net_packet->data[5] = stats->helmet->status;
+ SDLNet_Write16((int)stats->helmet->type, &net_packet->data[6]);
net_packet->address.host = net_clients[player - 1].host;
net_packet->address.port = net_clients[player - 1].port;
- net_packet->len = 6;
+ net_packet->len = 8;
sendPacketSafe(net_sock, -1, net_packet, player - 1);
}
}
@@ -355,17 +408,48 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
entity->modHP(-damage);
if ( entity->behavior == &actPlayer && stats->HP < oldHP )
{
- Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP);
+ Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "arcane boulder", oldHP - stats->HP);
}
entity->setObituary(Language::get(3899));
stats->killer = KilledBy::BOULDER;
}
else
{
- entity->modHP(-damage);
- if ( entity->behavior == &actPlayer && stats->HP < oldHP )
+ if ( my->boulderShatterEarthSpell > 0 )
{
- Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP);
+ Entity* caster = uidToEntity(my->boulderShatterEarthSpell);
+ applyGenericMagicDamage(caster, entity, *my, SPELL_SHATTER_EARTH, damage, true);
+ }
+ else if ( BOULDER_PLAYERPUSHED >= MAXPLAYERS )
+ {
+ int spellID = SPELL_TELEKINESIS;
+ if ( (BOULDER_PLAYERPUSHED / MAXPLAYERS) == 2 )
+ {
+ spellID = SPELL_KINETIC_PUSH;
+ }
+ applyGenericMagicDamage(players[BOULDER_PLAYERPUSHED % MAXPLAYERS]->entity,
+ entity, *my, spellID, damage, true);
+ if ( entity->behavior == &actPlayer && stats->HP < oldHP )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP);
+ }
+ }
+ else if ( BOULDER_PLAYERPUSHED >= 0 )
+ {
+ applyGenericMagicDamage(players[BOULDER_PLAYERPUSHED]->entity,
+ entity, *my, SPELL_NONE, damage, true);
+ if ( entity->behavior == &actPlayer && stats->HP < oldHP )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP);
+ }
+ }
+ else
+ {
+ entity->modHP(-damage);
+ if ( entity->behavior == &actPlayer && stats->HP < oldHP )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP);
+ }
}
entity->setObituary(Language::get(1505));
stats->killer = KilledBy::BOULDER;
@@ -381,15 +465,19 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
entity->playerAutomatonDeathCounter = TICKS_PER_SECOND * 5; // set the death timer to immediately pop for players.
}
steamAchievementClient(entity->skill[2], "BARONY_ACH_THROW_ME_THE_WHIP");
- if ( BOULDER_PLAYERPUSHED >= 0 && entity->skill[2] != BOULDER_PLAYERPUSHED )
+ if ( BOULDER_PLAYERPUSHED >= 0 && entity->skill[2] != (BOULDER_PLAYERPUSHED % MAXPLAYERS) )
{
- steamAchievementClient(BOULDER_PLAYERPUSHED, "BARONY_ACH_MOVED_ITSELF");
+ steamAchievementClient(BOULDER_PLAYERPUSHED % MAXPLAYERS, "BARONY_ACH_MOVED_ITSELF");
}
if ( my->sprite == BOULDER_LAVA_SPRITE )
{
Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "brimstone boulder", 1);
}
+ else if ( my->sprite == BOULDER_ARCANE_SPRITE )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "arcane boulder", 1);
+ }
else
{
Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "boulder trap", 1);
@@ -399,7 +487,7 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
}
if ( BOULDER_PLAYERPUSHED >= 0 && oldHP > 0 && stats->HP <= 0 )
{
- Compendium_t::Events_t::eventUpdateWorld(BOULDER_PLAYERPUSHED, Compendium_t::CPDM_COMBAT_MASONRY_BOULDERS, "masons guild", 1);
+ Compendium_t::Events_t::eventUpdateWorld(BOULDER_PLAYERPUSHED % MAXPLAYERS, Compendium_t::CPDM_COMBAT_MASONRY_BOULDERS, "masons guild", 1);
}
if ( !lifeSaving )
@@ -428,7 +516,7 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
if ( mySummon && mySummon->monsterAllySummonRank != 0 )
{
Stat* mySummonStats = mySummon->getStats();
- if ( mySummonStats )
+ if ( mySummonStats && mySummonStats->type == SKELETON )
{
int mp = (mySummonStats->MAXMP * (mySummonStats->HP / static_cast(mySummonStats->MAXHP)));
if ( numSummonedAllies == 0 )
@@ -470,6 +558,15 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
{
i = 0;
}
+ if ( my->boulderShatterEarthSpell > 0 )
+ {
+ i = 0;
+ createParticleRock(entity, 78);
+ if ( multiplayer == SERVER )
+ {
+ serverSpawnMiscParticles(entity, PARTICLE_EFFECT_ABILITY_ROCK, 78);
+ }
+ }
int c;
for ( c = 0; c < i; c++ )
{
@@ -542,6 +639,10 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
{
Compendium_t::Events_t::eventUpdateWorld(entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "brimstone boulder", 1);
}
+ else if ( my->sprite == BOULDER_ARCANE_SPRITE )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "arcane boulder", 1);
+ }
else
{
Compendium_t::Events_t::eventUpdateWorld(entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "boulder trap", 1);
@@ -588,6 +689,20 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
{
if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
{
+ if ( my->boulderShatterEarthSpell > 0 )
+ {
+ createParticleRock(entity, 78);
+ if ( multiplayer == SERVER )
+ {
+ serverSpawnMiscParticles(entity, PARTICLE_EFFECT_ABILITY_ROCK, 78);
+ }
+
+ // destroy the boulder
+ playSoundEntity(my, 67, 128);
+ list_RemoveNode(my->mynode);
+ return 1;
+ }
+
// stop the boulder
BOULDER_STOPPED = 1;
my->vel_x = 0.0; // TODOR: Anywhere this is could possible be changed to be a static 'if( BOULDER_ROLLING == 0 ) { vel = 0 }' instead of duplicating code everywhere
@@ -610,14 +725,14 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit
if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
{
playSoundEntity(entity, 28, 64);
- entity->skill[4] = 0;
- if ( !entity->skill[0] )
+ entity->doorHealth = 0;
+ if ( !entity->doorDir )
{
- entity->skill[6] = (my->x > entity->x);
+ entity->doorSmacked = (my->x > entity->x);
}
else
{
- entity->skill[6] = (my->y < entity->y);
+ entity->doorSmacked = (my->y < entity->y);
}
playSoundEntity(my, 181, 128);
}
@@ -724,9 +839,12 @@ void actBoulder(Entity* my)
}
real_t boulderModifier = 1.0;
- if ( gameModeManager.currentSession.challengeRun.isActive(GameModeManager_t::CurrentSession_t::ChallengeRun_t::CHEVENT_STRONG_TRAPS) )
+ if ( my->boulderShatterEarthSpell == 0 )
{
- boulderModifier = 2.0;
+ if ( gameModeManager.currentSession.challengeRun.isActive(GameModeManager_t::CurrentSession_t::ChallengeRun_t::CHEVENT_STRONG_TRAPS) )
+ {
+ boulderModifier = 2.0;
+ }
}
// gravity
@@ -738,6 +856,14 @@ void actBoulder(Entity* my)
BOULDER_NOGROUND = true;
}
}
+ if ( my->boulderShatterEarthSpell > 0 )
+ {
+ if ( my->z >= 0 && fabs(my->vel_z) > 1 && fabs(my->vel_z) < 2 )
+ {
+ BOULDER_NOGROUND = true; // 2nd bounce
+ }
+ }
+
if ( my->z < 0 || BOULDER_NOGROUND )
{
my->vel_z = std::min(my->vel_z + .1, 3.0);
@@ -753,9 +879,24 @@ void actBoulder(Entity* my)
}
return;
}
- if ( !BOULDER_NOGROUND )
+
+ if ( !noground && my->boulderShatterEarthSpell > 0 && my->z >= 4.0 )
+ {
+ createParticleRock(my, 78);
+ if ( multiplayer == SERVER )
+ {
+ serverSpawnMiscParticles(my, PARTICLE_EFFECT_ABILITY_ROCK, 78);
+ }
+
+ // destroy the boulder
+ playSoundEntity(my, 67, 128);
+ list_RemoveNode(my->mynode);
+ return;
+ }
+
+ //if ( !BOULDER_NOGROUND ) -- make it so falling boulders over pits does damage
{
- if ( my->z >= -8 && fabs(my->vel_z) > 2 )
+ if ( my->z >= -8 && my->z < 0.0 && fabs(my->vel_z) > 2 )
{
std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
@@ -1064,16 +1205,23 @@ void actBoulder(Entity* my)
if ( !BOULDER_ROLLING )
{
BOULDER_PLAYERPUSHED = -1;
+ int playerTelekinesis = BOULDER_TELEKINESIS_PULL - 1;
+ int playerKineticPush = BOULDER_TELEKINESIS_PUSH - 1;
+
for (i = 0; i < MAXPLAYERS; i++)
{
- if ( selectedEntity[i] == my || client_selected[i] == my )
+ if ( selectedEntity[i] == my || client_selected[i] == my || playerTelekinesis == i || playerKineticPush == i )
{
- if (inrange[i])
+ if (inrange[i] || playerTelekinesis == i || playerKineticPush == i )
{
bool hasRingOfStr = false;
if ( players[i] && players[i]->entity )
{
- if ( stats[i]->ring
+ if ( playerTelekinesis == i || playerKineticPush == i )
+ {
+ hasRingOfStr = true;
+ }
+ else if ( stats[i]->ring
&& stats[i]->ring->type == ItemType::RING_STRENGTH)
{
hasRingOfStr = true;
@@ -1083,7 +1231,8 @@ void actBoulder(Entity* my)
{
hasRingOfStr = true;
}
- else if ( stats[i]->EFFECTS[EFF_POTION_STR] )
+ else if ( stats[i]->getEffectActive(EFF_POTION_STR)
+ || stats[i]->getEffectActive(EFF_GREATER_MIGHT) )
{
hasRingOfStr = true;
}
@@ -1097,7 +1246,16 @@ void actBoulder(Entity* my)
if (players[i] && players[i]->entity)
{
BOULDER_SOUND_ON_PUSH = i + 1;
- BOULDER_ROLLING = 1;
+ if ( playerKineticPush == i )
+ {
+ BOULDER_STOPPED = 0;
+ my->vel_x = 0.0;
+ my->vel_y = 0.0;
+ }
+ else
+ {
+ BOULDER_ROLLING = 1;
+ }
/*my->x = floor(my->x / 16) * 16 + 8;
my->y = floor(my->y / 16) * 16 + 8;*/
@@ -1105,6 +1263,10 @@ void actBoulder(Entity* my)
BOULDER_DESTY = (int)(my->y / 16) * 16 + 8;
real_t tangent = atan2(players[i]->entity->y - my->y, players[i]->entity->x - my->x);
+ if ( BOULDER_TELEKINESIS_PULL > 0 && (BOULDER_TELEKINESIS_PULL - 1 == i) )
+ {
+ tangent += PI;
+ }
while ( tangent >= 2 * PI )
{
tangent -= 2 * PI;
@@ -1114,6 +1276,8 @@ void actBoulder(Entity* my)
tangent += 2 * PI;
}
real_t angle = tangent * 180.0 / PI;
+
+
if ( (tangent >= PI - PI / 4) && tangent < (PI + PI / 4) )
{
BOULDER_ROLLDIR = 0; // east
@@ -1134,6 +1298,11 @@ void actBoulder(Entity* my)
BOULDER_ROLLDIR = 3; // north
//messagePlayer(0, MESSAGE_DEBUG, "GO NORTH %.2f", angle);
}
+
+ if ( playerKineticPush == i )
+ {
+ my->yaw = BOULDER_ROLLDIR * PI / 2;
+ }
//if ( (int)(players[i]->entity->x / 16) < (int)(my->x / 16) )
//{
// BOULDER_ROLLDIR = 0; // east
@@ -1166,6 +1335,14 @@ void actBoulder(Entity* my)
break;
}
BOULDER_PLAYERPUSHED = i;
+ if ( playerTelekinesis == i )
+ {
+ BOULDER_PLAYERPUSHED = i + MAXPLAYERS;
+ }
+ else if ( playerKineticPush == i )
+ {
+ BOULDER_PLAYERPUSHED = i + 2 * MAXPLAYERS;
+ }
}
}
}
@@ -1340,7 +1517,14 @@ void actBoulder(Entity* my)
}
}
- Compendium_t::Events_t::eventUpdateWorld(BOULDER_SOUND_ON_PUSH - 1, Compendium_t::CPDM_BOULDERS_PUSHED, "boulder trap", 1);
+ if ( my->sprite == BOULDER_ARCANE_SPRITE )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(BOULDER_SOUND_ON_PUSH - 1, Compendium_t::CPDM_BOULDERS_PUSHED, "arcane boulder", 1);
+ }
+ else
+ {
+ Compendium_t::Events_t::eventUpdateWorld(BOULDER_SOUND_ON_PUSH - 1, Compendium_t::CPDM_BOULDERS_PUSHED, "boulder trap", 1);
+ }
BOULDER_SOUND_ON_PUSH = 0;
}
}
@@ -1446,6 +1630,8 @@ void actBoulder(Entity* my)
{
--BOULDER_BLOODTIME;
}
+ BOULDER_TELEKINESIS_PULL = 0;
+ BOULDER_TELEKINESIS_PUSH = 0;
}
#define BOULDERTRAP_FIRED my->skill[0]
@@ -1484,11 +1670,22 @@ void actBoulderTrapHole(Entity* my)
}
}
+int getBoulderSpriteForMap()
+{
+ if ( currentlevel >= 26 )
+ {
+ return BOULDER_ARCANE_SPRITE;
+ }
+ return 245;
+}
+
void actBoulderTrap(Entity* my)
{
int x, y;
int c;
+ if ( my->actTrapSabotaged == 0 )
+ {
#ifdef USE_FMOD
if ( BOULDERTRAP_AMBIENCE == 0 )
{
@@ -1513,6 +1710,14 @@ void actBoulderTrap(Entity* my)
playSoundEntityLocal(my, 149, 64);
}
#endif
+ }
+ else
+ {
+#ifdef USE_FMOD
+ my->stopEntitySound();
+#endif
+ return;
+ }
if ( !my->skill[28] )
{
@@ -1567,7 +1772,7 @@ void actBoulderTrap(Entity* my)
}
if ( foundTrapdoor == c )
{
- Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
+ Entity* entity = newEntity(getBoulderSpriteForMap(), 1, map.entities, nullptr); // boulder
entity->parent = my->getUID();
entity->x = (x << 4) + 8;
entity->y = (y << 4) + 8;
@@ -1621,6 +1826,8 @@ void actBoulderTrapEast(Entity* my)
int x, y;
int c;
+ if ( my->actTrapSabotaged == 0 )
+ {
#ifdef USE_FMOD
if ( my->boulderTrapAmbience == 0 )
{
@@ -1645,6 +1852,14 @@ void actBoulderTrapEast(Entity* my)
playSoundEntityLocal(my, 149, 64);
}
#endif
+ }
+ else
+ {
+#ifdef USE_FMOD
+ my->stopEntitySound();
+#endif
+ return;
+ }
if ( my->boulderTrapRefireCounter > 0 )
{
@@ -1687,7 +1902,7 @@ void actBoulderTrapEast(Entity* my)
y = ((int)(my->y)) >> 4;
if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
{
- Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
+ Entity* entity = newEntity(getBoulderSpriteForMap(), 1, map.entities, nullptr); // boulder
entity->parent = my->getUID();
entity->x = (x << 4) + 8;
entity->y = (y << 4) + 8;
@@ -1731,6 +1946,8 @@ void actBoulderTrapSouth(Entity* my)
int x, y;
int c;
+ if ( my->actTrapSabotaged == 0 )
+ {
#ifdef USE_FMOD
if ( my->boulderTrapAmbience == 0 )
{
@@ -1755,6 +1972,14 @@ void actBoulderTrapSouth(Entity* my)
playSoundEntityLocal(my, 149, 64);
}
#endif
+ }
+ else
+ {
+#ifdef USE_FMOD
+ my->stopEntitySound();
+#endif
+ return;
+ }
if ( my->boulderTrapRefireCounter > 0 )
{
@@ -1797,7 +2022,7 @@ void actBoulderTrapSouth(Entity* my)
y = ((int)(my->y)) >> 4;
if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
{
- Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
+ Entity* entity = newEntity(getBoulderSpriteForMap(), 1, map.entities, nullptr); // boulder
entity->parent = my->getUID();
entity->x = (x << 4) + 8;
entity->y = (y << 4) + 8;
@@ -1841,6 +2066,8 @@ void actBoulderTrapWest(Entity* my)
int x, y;
int c;
+ if ( my->actTrapSabotaged == 0 )
+ {
#ifdef USE_FMOD
if ( my->boulderTrapAmbience == 0 )
{
@@ -1865,6 +2092,14 @@ void actBoulderTrapWest(Entity* my)
playSoundEntityLocal(my, 149, 64);
}
#endif
+ }
+ else
+ {
+#ifdef USE_FMOD
+ my->stopEntitySound();
+#endif
+ return;
+ }
if ( my->boulderTrapRefireCounter > 0 )
{
@@ -1907,7 +2142,7 @@ void actBoulderTrapWest(Entity* my)
y = ((int)(my->y)) >> 4;
if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
{
- Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
+ Entity* entity = newEntity(getBoulderSpriteForMap(), 1, map.entities, nullptr); // boulder
entity->parent = my->getUID();
entity->x = (x << 4) + 8;
entity->y = (y << 4) + 8;
@@ -1951,6 +2186,8 @@ void actBoulderTrapNorth(Entity* my)
int x, y;
int c;
+ if ( my->actTrapSabotaged == 0 )
+ {
#ifdef USE_FMOD
if ( my->boulderTrapAmbience == 0 )
{
@@ -1975,6 +2212,14 @@ void actBoulderTrapNorth(Entity* my)
playSoundEntityLocal(my, 149, 64);
}
#endif
+ }
+ else
+ {
+#ifdef USE_FMOD
+ my->stopEntitySound();
+#endif
+ return;
+ }
if ( my->boulderTrapRefireCounter > 0 )
{
@@ -2017,7 +2262,7 @@ void actBoulderTrapNorth(Entity* my)
y = ((int)(my->y)) >> 4;
if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
{
- Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
+ Entity* entity = newEntity(getBoulderSpriteForMap(), 1, map.entities, nullptr); // boulder
entity->parent = my->getUID();
entity->x = (x << 4) + 8;
entity->y = (y << 4) + 8;
@@ -2169,7 +2414,7 @@ void boulderSokobanOnDestroy(bool pushedOffLedge)
bool Entity::isBoulderSprite()
{
- if ( sprite == 245 || sprite == 989 || sprite == 990 )
+ if ( !flags[SPRITE] && (sprite == 245 || sprite == 989 || sprite == 990) )
{
return true;
}
@@ -2220,7 +2465,7 @@ void boulderLavaOrArcaneOnDestroy(Entity* my, int sprite, Entity* boulderHitEnti
}
else
{
- boulderHitEntity->SetEntityOnFire();
+ boulderHitEntity->SetEntityOnFire(my);
Stat* stats = boulderHitEntity->getStats();
if ( stats )
{
diff --git a/src/actcampfire.cpp b/src/actcampfire.cpp
index cd335619c..5054e4acb 100644
--- a/src/actcampfire.cpp
+++ b/src/actcampfire.cpp
@@ -17,6 +17,8 @@
#include "net.hpp"
#include "player.hpp"
#include "prng.hpp"
+#include "scores.hpp"
+#include "collision.hpp"
/*-------------------------------------------------------------------------------
@@ -170,3 +172,496 @@ void actCampfire(Entity* my)
}
}
}
+
+void actCauldron(Entity* my)
+{
+ if ( !CAMPFIRE_INIT )
+ {
+ CAMPFIRE_INIT = 1;
+ CAMPFIRE_HEALTH = MAXPLAYERS;
+ my->createWorldUITooltip();
+ }
+
+ // crackling sounds
+ if ( CAMPFIRE_HEALTH > 0 )
+ {
+#ifdef USE_FMOD
+ if ( CAMPFIRE_SOUNDTIME == 0 )
+ {
+ CAMPFIRE_SOUNDTIME--;
+ my->stopEntitySound();
+ my->entity_sound = playSoundEntityLocal(my, 133, 32);
+ }
+ if ( my->entity_sound )
+ {
+ bool playing = false;
+ my->entity_sound->isPlaying(&playing);
+ if ( !playing )
+ {
+ my->entity_sound = nullptr;
+ }
+ }
+#else
+ CAMPFIRE_SOUNDTIME--;
+ if ( CAMPFIRE_SOUNDTIME <= 0 )
+ {
+ CAMPFIRE_SOUNDTIME = 480;
+ playSoundEntityLocal(my, 133, 128);
+ }
+#endif
+
+ // spew flame particles
+ if ( flickerLights )
+ {
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ {
+ entity->x += ((local_rng.rand() % 30) - 10) / 10.f;
+ entity->y += ((local_rng.rand() % 30) - 10) / 10.f;
+ entity->z -= 1;
+ }
+ }
+ if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ {
+ entity->z -= 2;
+ }
+ }
+ else
+ {
+ if ( ticks % TICKS_PER_SECOND == 0 )
+ {
+ if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ {
+ entity->z -= 2;
+ }
+ }
+ }
+
+ // light environment
+ if ( !CAMPFIRE_LIGHTING )
+ {
+ my->light = addLight(my->x / 16, my->y / 16, "campfire");
+ CAMPFIRE_LIGHTING = 1;
+ }
+ if ( flickerLights )
+ {
+ //Campfires will never flicker if this setting is disabled.
+ CAMPFIRE_FLICKER--;
+ }
+ if ( CAMPFIRE_FLICKER <= 0 )
+ {
+ CAMPFIRE_LIGHTING = (CAMPFIRE_LIGHTING == 1) + 1;
+
+ if ( CAMPFIRE_LIGHTING == 1 )
+ {
+ my->removeLightField();
+ my->light = addLight(my->x / 16, my->y / 16, "campfire");
+ }
+ else
+ {
+ my->removeLightField();
+ my->light = addLight(my->x / 16, my->y / 16, "campfire_flicker");
+ }
+ CAMPFIRE_FLICKER = 2 + local_rng.rand() % 7;
+ }
+ }
+ else
+ {
+ my->removeLightField();
+ my->light = NULL;
+
+ my->stopEntitySound();
+ }
+
+ if ( multiplayer == CLIENT )
+ {
+ return;
+ }
+
+ auto& cauldronInteracting = my->skill[6];
+ Entity* interacting = uidToEntity(cauldronInteracting);
+
+ if ( cauldronInteracting > 0 )
+ {
+ if ( !interacting || (entityDist(interacting, my) > TOUCHRANGE) )
+ {
+ int playernum = -1;
+ if ( !interacting )
+ {
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( achievementObserver.playerUids[i] == cauldronInteracting )
+ {
+ playernum = i;
+ break;
+ }
+ }
+ }
+ else if ( interacting->behavior == &actPlayer )
+ {
+ playernum = interacting->skill[2];
+ }
+ cauldronInteracting = 0;
+ serverUpdateEntitySkill(my, 6);
+ if ( multiplayer == SERVER && playernum > 0 )
+ {
+ strcpy((char*)net_packet->data, "CAUC");
+ net_packet->data[4] = playernum;
+ SDLNet_Write32(my->getUID(), &net_packet->data[5]);
+ net_packet->address.host = net_clients[playernum - 1].host;
+ net_packet->address.port = net_clients[playernum - 1].port;
+ net_packet->len = 9;
+ sendPacketSafe(net_sock, -1, net_packet, playernum - 1);
+ }
+ else if ( multiplayer == SINGLE || playernum == 0 )
+ {
+ if ( playernum >= 0 && playernum < MAXPLAYERS )
+ {
+ GenericGUI[playernum].alchemyGUI.closeAlchemyMenu();
+ }
+ }
+ }
+ }
+
+ // using
+ for ( int i = 0; i < MAXPLAYERS; i++ )
+ {
+ if ( selectedEntity[i] == my || client_selected[i] == my )
+ {
+ if ( inrange[i] && players[i]->entity )
+ {
+ if ( cauldronInteracting != 0 )
+ {
+ if ( Entity* interacting = uidToEntity(cauldronInteracting) )
+ {
+ if ( interacting != players[i]->entity )
+ {
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6975));
+ }
+ }
+ }
+ else
+ {
+ cauldronInteracting = players[i]->entity->getUID();
+ if ( multiplayer == SERVER )
+ {
+ serverUpdateEntitySkill(my, 6);
+ }
+ if ( players[i]->isLocalPlayer() )
+ {
+ GenericGUI[i].openGUI(GUI_TYPE_ALCHEMY, my);
+ }
+ else if ( multiplayer == SERVER && i > 0 )
+ {
+ strcpy((char*)net_packet->data, "CAUO");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ net_packet->address.host = net_clients[i - 1].host;
+ net_packet->address.port = net_clients[i - 1].port;
+ net_packet->len = 8;
+ sendPacketSafe(net_sock, -1, net_packet, i - 1);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+void actWorkbench(Entity* my)
+{
+ if ( !CAMPFIRE_INIT )
+ {
+ CAMPFIRE_INIT = 1;
+ CAMPFIRE_HEALTH = MAXPLAYERS;
+ my->createWorldUITooltip();
+ }
+
+// // crackling sounds
+// if ( CAMPFIRE_HEALTH > 0 )
+// {
+//#ifdef USE_FMOD
+// if ( CAMPFIRE_SOUNDTIME == 0 )
+// {
+// CAMPFIRE_SOUNDTIME--;
+// my->stopEntitySound();
+// my->entity_sound = playSoundEntityLocal(my, 133, 32);
+// }
+// if ( my->entity_sound )
+// {
+// bool playing = false;
+// my->entity_sound->isPlaying(&playing);
+// if ( !playing )
+// {
+// my->entity_sound = nullptr;
+// }
+// }
+//#else
+// CAMPFIRE_SOUNDTIME--;
+// if ( CAMPFIRE_SOUNDTIME <= 0 )
+// {
+// CAMPFIRE_SOUNDTIME = 480;
+// playSoundEntityLocal(my, 133, 128);
+// }
+//#endif
+//
+// // spew flame particles
+// if ( flickerLights )
+// {
+// for ( int i = 0; i < 3; i++ )
+// {
+// if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+// {
+// entity->x += ((local_rng.rand() % 30) - 10) / 10.f;
+// entity->y += ((local_rng.rand() % 30) - 10) / 10.f;
+// entity->z -= 1;
+// }
+// }
+// if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+// {
+// entity->z -= 2;
+// }
+// }
+// else
+// {
+// if ( ticks % TICKS_PER_SECOND == 0 )
+// {
+// if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+// {
+// entity->z -= 2;
+// }
+// }
+// }
+//
+// // light environment
+// if ( !CAMPFIRE_LIGHTING )
+// {
+// my->light = addLight(my->x / 16, my->y / 16, "campfire");
+// CAMPFIRE_LIGHTING = 1;
+// }
+// if ( flickerLights )
+// {
+// //Campfires will never flicker if this setting is disabled.
+// CAMPFIRE_FLICKER--;
+// }
+// if ( CAMPFIRE_FLICKER <= 0 )
+// {
+// CAMPFIRE_LIGHTING = (CAMPFIRE_LIGHTING == 1) + 1;
+//
+// if ( CAMPFIRE_LIGHTING == 1 )
+// {
+// my->removeLightField();
+// my->light = addLight(my->x / 16, my->y / 16, "campfire");
+// }
+// else
+// {
+// my->removeLightField();
+// my->light = addLight(my->x / 16, my->y / 16, "campfire_flicker");
+// }
+// CAMPFIRE_FLICKER = 2 + local_rng.rand() % 7;
+// }
+// }
+// else
+// {
+// my->removeLightField();
+// my->light = NULL;
+//
+// my->stopEntitySound();
+// }
+
+ if ( multiplayer == CLIENT )
+ {
+ return;
+ }
+
+ auto& workbenchInteracting = my->skill[6];
+ Entity* interacting = uidToEntity(workbenchInteracting);
+
+ if ( workbenchInteracting > 0 )
+ {
+ if ( !interacting || (entityDist(interacting, my) > TOUCHRANGE) )
+ {
+ int playernum = -1;
+ if ( !interacting )
+ {
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( achievementObserver.playerUids[i] == workbenchInteracting )
+ {
+ playernum = i;
+ break;
+ }
+ }
+ }
+ else if ( interacting->behavior == &actPlayer )
+ {
+ playernum = interacting->skill[2];
+ }
+ workbenchInteracting = 0;
+ serverUpdateEntitySkill(my, 6);
+ if ( multiplayer == SERVER && playernum > 0 )
+ {
+ strcpy((char*)net_packet->data, "WRKC");
+ net_packet->data[4] = playernum;
+ SDLNet_Write32(my->getUID(), &net_packet->data[5]);
+ net_packet->address.host = net_clients[playernum - 1].host;
+ net_packet->address.port = net_clients[playernum - 1].port;
+ net_packet->len = 9;
+ sendPacketSafe(net_sock, -1, net_packet, playernum - 1);
+ }
+ else if ( multiplayer == SINGLE || playernum == 0 )
+ {
+ if ( playernum >= 0 && playernum < MAXPLAYERS )
+ {
+ GenericGUI[playernum].tinkerGUI.closeTinkerMenu();
+ }
+ }
+ }
+ }
+
+ // using
+ for ( int i = 0; i < MAXPLAYERS; i++ )
+ {
+ if ( selectedEntity[i] == my || client_selected[i] == my )
+ {
+ if ( inrange[i] && players[i]->entity )
+ {
+ if ( workbenchInteracting != 0 )
+ {
+ if ( Entity* interacting = uidToEntity(workbenchInteracting) )
+ {
+ if ( interacting != players[i]->entity )
+ {
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6982));
+ }
+ }
+ }
+ else
+ {
+ workbenchInteracting = players[i]->entity->getUID();
+ if ( multiplayer == SERVER )
+ {
+ serverUpdateEntitySkill(my, 6);
+ }
+ if ( players[i]->isLocalPlayer() )
+ {
+ GenericGUI[i].openGUI(GUI_TYPE_TINKERING, my);
+ }
+ else if ( multiplayer == SERVER && i > 0 )
+ {
+ strcpy((char*)net_packet->data, "WRKO");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ net_packet->address.host = net_clients[i - 1].host;
+ net_packet->address.port = net_clients[i - 1].port;
+ net_packet->len = 8;
+ sendPacketSafe(net_sock, -1, net_packet, i - 1);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+void actMailbox(Entity* my)
+{
+ if ( !CAMPFIRE_INIT )
+ {
+ CAMPFIRE_INIT = 1;
+ CAMPFIRE_HEALTH = MAXPLAYERS;
+ my->createWorldUITooltip();
+ }
+
+ if ( multiplayer == CLIENT )
+ {
+ return;
+ }
+
+ auto& mailboxInteracting = my->skill[6];
+ Entity* interacting = uidToEntity(mailboxInteracting);
+
+ if ( mailboxInteracting > 0 )
+ {
+ if ( !interacting || (entityDist(interacting, my) > TOUCHRANGE) )
+ {
+ int playernum = -1;
+ if ( !interacting )
+ {
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( achievementObserver.playerUids[i] == mailboxInteracting )
+ {
+ playernum = i;
+ break;
+ }
+ }
+ }
+ else if ( interacting->behavior == &actPlayer )
+ {
+ playernum = interacting->skill[2];
+ }
+ mailboxInteracting = 0;
+ serverUpdateEntitySkill(my, 6);
+ if ( multiplayer == SERVER && playernum > 0 )
+ {
+ strcpy((char*)net_packet->data, "MBXC");
+ net_packet->data[4] = playernum;
+ SDLNet_Write32(my->getUID(), &net_packet->data[5]);
+ net_packet->address.host = net_clients[playernum - 1].host;
+ net_packet->address.port = net_clients[playernum - 1].port;
+ net_packet->len = 9;
+ sendPacketSafe(net_sock, -1, net_packet, playernum - 1);
+ }
+ else if ( multiplayer == SINGLE || playernum == 0 )
+ {
+ if ( playernum >= 0 && playernum < MAXPLAYERS )
+ {
+ GenericGUI[playernum].mailboxGUI.closeMailMenu();
+ }
+ }
+ }
+ }
+
+ // using
+ for ( int i = 0; i < MAXPLAYERS; i++ )
+ {
+ if ( selectedEntity[i] == my || client_selected[i] == my )
+ {
+ if ( inrange[i] && players[i]->entity )
+ {
+ if ( mailboxInteracting != 0 )
+ {
+ if ( Entity* interacting = uidToEntity(mailboxInteracting) )
+ {
+ if ( interacting != players[i]->entity )
+ {
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6987));
+ }
+ }
+ }
+ else
+ {
+ mailboxInteracting = players[i]->entity->getUID();
+ if ( multiplayer == SERVER )
+ {
+ serverUpdateEntitySkill(my, 6);
+ }
+ if ( players[i]->isLocalPlayer() )
+ {
+ GenericGUI[i].openGUI(GUI_TYPE_MAILBOX, my);
+ }
+ else if ( multiplayer == SERVER && i > 0 )
+ {
+ strcpy((char*)net_packet->data, "MBXO");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ net_packet->address.host = net_clients[i - 1].host;
+ net_packet->address.port = net_clients[i - 1].port;
+ net_packet->len = 8;
+ sendPacketSafe(net_sock, -1, net_packet, i - 1);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/actchest.cpp b/src/actchest.cpp
index 8bc1d071b..92b548739 100644
--- a/src/actchest.cpp
+++ b/src/actchest.cpp
@@ -153,15 +153,11 @@ void createChestInventory(Entity* my, int chestType)
// itemnum = rng.rand() % NUMITEMS; //Keep trying until you don't get a spell or invalid item.
//}
//newItem(static_cast(itemnum), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- int cat = rng.rand() % (NUMCATEGORIES - 1); // exclude spell_cat
+ int cat = rng.rand() % (Category::CATEGORY_MAX - 2); // exclude spell_cat
Item* currentItem = newItem(itemLevelCurve(static_cast(cat), 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
if ( currentItem )
{
- if ( currentItem->type >= BRONZE_TOMAHAWK && currentItem->type <= CRYSTAL_SHURIKEN )
- {
- // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
- currentItem->status = std::min(static_cast(DECREPIT + (currentItem->type - BRONZE_TOMAHAWK)), EXCELLENT);
- }
+ itemLevelCurvePostProcess(my, currentItem, rng);
}
}
break;
@@ -170,6 +166,11 @@ void createChestInventory(Entity* my, int chestType)
if ( rng.rand() % 2 )
{
//Empty.
+ Item* item = newItem(itemLevelCurve(SCROLL, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
else
{
@@ -180,7 +181,18 @@ void createChestInventory(Entity* my, int chestType)
{
if ( rng.rand() % 20 == 0 )
{
- newItem(MASK_MOUTH_ROSE, static_cast(itemStatus), -1 + rng.rand() % 3, 1, rng.rand(), false, inventory);
+ if ( rng.rand() % 2 == 0 )
+ {
+ Item* item = newItem(itemLevelCurve(SCROLL, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
+ }
+ else
+ {
+ newItem(MASK_MOUTH_ROSE, static_cast(itemStatus), -1 + rng.rand() % 3, 1, rng.rand(), false, inventory);
+ }
}
else
{
@@ -196,7 +208,11 @@ void createChestInventory(Entity* my, int chestType)
for ( i = 0; i < itemcount; ++i )
{
//newItem(static_cast(FOOD_BREAD + (rng.rand() % 7)), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(FOOD, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(FOOD, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
if ( rng.rand() % 10 == 0 )
{
@@ -209,7 +225,7 @@ void createChestInventory(Entity* my, int chestType)
break;
case 3:
//Treasures, jewelry, gems 'n stuff.
- itemcount = (rng.rand() % 5) + 1;
+ itemcount = /*(rng.rand() % 5) +*/ 1;
for ( i = 0; i < itemcount; ++i )
{
if ( rng.rand() % 4 )
@@ -228,13 +244,37 @@ void createChestInventory(Entity* my, int chestType)
{
//Spawn a ring.
//newItem(static_cast(RING_ADORNMENT + rng.rand() % 12), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(RING, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(RING, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
else
{
//Spawn an amulet.
//newItem(static_cast(AMULET_SEXCHANGE + rng.rand() % 6), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(AMULET, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(AMULET, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
+ }
+ }
+ if ( rng.rand() % 4 > 0 ) // 75%
+ {
+ Item* item = newItem(SCROLL_IDENTIFY, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
+ }
+ if ( rng.rand() % 4 == 0 ) // 25%
+ {
+ Item* item = newItem(SCROLL_REMOVECURSE, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
}
}
break;
@@ -259,7 +299,11 @@ void createChestInventory(Entity* my, int chestType)
//{
// newItem(CROSSBOW, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
//}
- newItem(itemLevelCurve(WEAPON, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(WEAPON, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
break;
case 1:
@@ -300,7 +344,11 @@ void createChestInventory(Entity* my, int chestType)
//{
// newItem(static_cast(28 + rng.rand() % 10), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
//}
- newItem(itemLevelCurve(ARMOR, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(ARMOR, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
break;
case 2:
@@ -355,8 +403,16 @@ void createChestInventory(Entity* my, int chestType)
// newItem(static_cast(28 + rng.rand() % 10), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
//}
- newItem(itemLevelCurve(WEAPON, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(ARMOR, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(WEAPON, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
+ item = newItem(itemLevelCurve(ARMOR, minimumQuality, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
// try for thrown items.
itemcount = 0 + rng.rand() % 2;
@@ -365,11 +421,7 @@ void createChestInventory(Entity* my, int chestType)
Item* thrown = newItem(itemLevelCurve(THROWN, minimumQuality, currentlevel, rng), WORN, 0, 3 + rng.rand() % 3, rng.rand(), false, inventory);
if ( thrown )
{
- if ( thrown->type >= BRONZE_TOMAHAWK && thrown->type <= CRYSTAL_SHURIKEN )
- {
- // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
- thrown->status = std::min(static_cast(DECREPIT + (thrown->type - BRONZE_TOMAHAWK)), EXCELLENT);
- }
+ itemLevelCurvePostProcess(my, thrown, rng);
}
}
}
@@ -420,11 +472,7 @@ void createChestInventory(Entity* my, int chestType)
Item* thrown = newItem(itemLevelCurve(THROWN, minimumQuality, currentlevel, rng), WORN, 0, 3 + rng.rand() % 3, rng.rand(), false, inventory);
if ( thrown )
{
- if ( thrown->type >= BRONZE_TOMAHAWK && thrown->type <= CRYSTAL_SHURIKEN )
- {
- // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
- thrown->status = std::min(static_cast(DECREPIT + (thrown->type - BRONZE_TOMAHAWK)), EXCELLENT);
- }
+ itemLevelCurvePostProcess(my, thrown, rng);
}
}
break;
@@ -445,7 +493,7 @@ void createChestInventory(Entity* my, int chestType)
* * Wizard's chest, which will contain 1-2 scrolls, a magic book, a staff, and either a wizard/magician/whatever implement of some sort or a piece of armor.
*/
int magic_type = rng.rand() % 4;
-
+ bool doneFoci = false;
switch ( magic_type )
{
case 0:
@@ -454,7 +502,11 @@ void createChestInventory(Entity* my, int chestType)
for ( i = 0; i < itemcount; ++i )
{
//newItem(static_cast(SCROLL_IDENTIFY + rng.rand() % 12), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(SCROLL, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(SCROLL, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
if ( rng.rand() % 10 == 0 )
{
@@ -478,14 +530,32 @@ void createChestInventory(Entity* my, int chestType)
for ( i = 0; i < itemcount; ++i )
{
//newItem(static_cast(SPELLBOOK_FORCEBOLT + rng.rand() % 22), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(SPELLBOOK, 0, currentlevel + 6, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(SPELLBOOK, 0, currentlevel + 6, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ int spell_level = currentlevel + 6;
+ //if ( spell_level >= 15 )
+ //{
+ // if ( rng.rand() % 8 == 0 ) // some lower level spells
+ // {
+ // spell_level = 0 + 5 * rng.rand() % 3;
+ // }
+ //}
+ itemLevelCurvePostProcess(my, item, rng, spell_level);
+ }
}
break;
case 2:
+ {
//A staff.
//newItem(static_cast(MAGICSTAFF_LIGHT + rng.rand() % 10), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(MAGICSTAFF, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(MAGICSTAFF, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
break;
+ }
case 3:
//So spawn several items at once. A wizard's chest!
@@ -494,14 +564,73 @@ void createChestInventory(Entity* my, int chestType)
for ( i = 0; i < itemcount; ++i )
{
//newItem(static_cast(SCROLL_IDENTIFY + rng.rand() % 12), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(SCROLL, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(SCROLL, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
//newItem(static_cast(SPELLBOOK_FORCEBOLT + rng.rand() % 22), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(SPELLBOOK, 0, currentlevel + 6, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(SPELLBOOK, 0, currentlevel + 6, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ int spell_level = currentlevel + 6;
+ //if ( spell_level >= 15 )
+ //{
+ // if ( rng.rand() % 8 == 0 ) // some lower level spells
+ // {
+ // spell_level = 0 + 5 * rng.rand() % 3;
+ // }
+ //}
+ itemLevelCurvePostProcess(my, item, rng, spell_level);
+ }
//newItem(static_cast(MAGICSTAFF_LIGHT + rng.rand() % 10), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(MAGICSTAFF, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- switch ( rng.rand() % 9 )
+ item = newItem(itemLevelCurve(MAGICSTAFF, 0, currentlevel + 5, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
+
+ if ( rng.rand() % 50 == 0 )
+ {
+ int limit = 5;
+ if ( rng.rand() % 10 == 0 )
+ {
+ // no limit
+ }
+ else if ( currentlevel <= 8 )
+ {
+ limit = 2;
+ }
+ else if ( currentlevel <= 12 )
+ {
+ limit = 2;
+ }
+ switch ( rng.rand() % limit )
+ {
+ case 0:
+ newItem(TOOL_FOCI_FIRE, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 1:
+ newItem(TOOL_FOCI_SNOW, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 2:
+ newItem(TOOL_FOCI_SAND, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 3:
+ newItem(TOOL_FOCI_ARCS, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 4:
+ newItem(TOOL_FOCI_NEEDLES, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ default:
+ break;
+ }
+ doneFoci = true;
+ }
+
+ switch ( rng.rand() % 10 )
{
case 0:
//A cloak. Item 24.
@@ -547,6 +676,32 @@ void createChestInventory(Entity* my, int chestType)
case 8:
newItem(HAT_HEADDRESS, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
break;
+ case 9:
+ if ( !doneFoci )
+ {
+ switch ( rng.rand() % 5 )
+ {
+ case 0:
+ newItem(TOOL_FOCI_FIRE, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 1:
+ newItem(TOOL_FOCI_SNOW, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 2:
+ newItem(TOOL_FOCI_SAND, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 3:
+ newItem(TOOL_FOCI_ARCS, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ case 4:
+ newItem(TOOL_FOCI_NEEDLES, static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ break;
+ default:
+ break;
+ }
+ doneFoci = true;
+ }
+ break;
default:
break;
}
@@ -561,7 +716,11 @@ void createChestInventory(Entity* my, int chestType)
for ( i = 0; i < itemcount; ++i )
{
//newItem(static_cast(POTION_WATER + (rng.rand() % 15)), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
- newItem(itemLevelCurve(POTION, 0, currentlevel + 7, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ Item* item = newItem(itemLevelCurve(POTION, 0, currentlevel + 7, rng), static_cast(WORN + rng.rand() % 3), 0, 1, rng.rand(), false, inventory);
+ if ( item )
+ {
+ itemLevelCurvePostProcess(my, item, rng);
+ }
}
if ( rng.rand() % 8 == 0 )
{
@@ -652,6 +811,35 @@ void Entity::actChest()
this->createWorldUITooltip();
}
+ if ( sprite == 1791 )
+ {
+ if ( chestVoidState == 0 )
+ {
+ sprite = 188;
+ if ( parent != 0 )
+ {
+ if ( Entity* lid = uidToEntity(parent) )
+ {
+ lid->sprite = 216;
+ }
+ }
+ }
+ }
+ else if ( sprite == 188 )
+ {
+ if ( chestVoidState != 0 )
+ {
+ sprite = 1791;
+ if ( parent != 0 )
+ {
+ if ( Entity* lid = uidToEntity(parent) )
+ {
+ lid->sprite = 1790;
+ }
+ }
+ }
+ }
+
if ( multiplayer == CLIENT )
{
if ( chestHasVampireBook )
@@ -704,7 +892,7 @@ void Entity::actChest()
}
}
- list_t* inventory = static_cast(children.first->element);
+ list_t* inventory = getChestInventoryList();
node_t* node = NULL;
Item* item = NULL;
@@ -712,17 +900,31 @@ void Entity::actChest()
if ( chestHealth <= 0 )
{
+ if ( chestVoidState > 0 )
+ {
+ chestVoidState = 0;
+ createParticleErupt(this, 625);
+ serverSpawnMiscParticles(this, PARTICLE_EFFECT_ERUPT, 625);
+ }
auto& rng = entity_rng ? *entity_rng : local_rng;
// the chest busts open, drops some items randomly, then destroys itself.
node_t* nextnode;
- for ( node = inventory->first; node != NULL; node = nextnode )
+ if ( chestVoidState == 0 )
{
- nextnode = node->next;
- item = (Item*)node->element;
- if ( rng.rand() % 2 == 0 )
+ for ( node = inventory->first; node != NULL; node = nextnode )
{
- dropItemMonster(item, this, NULL);
+ nextnode = node->next;
+ item = (Item*)node->element;
+ if ( rng.rand() % 2 == 0 || (item && item->type >= WOODEN_SHIELD && item->type < NUMITEMS
+ && (items[item->type].hasAttribute("UNVOIDABLE")
+ || item->type == KEY_IRON
+ || item->type == KEY_BRONZE
+ || item->type == KEY_SILVER
+ || item->type == KEY_GOLD)) )
+ {
+ dropItemMonster(item, this, NULL);
+ }
}
}
@@ -807,6 +1009,21 @@ void Entity::actChest()
}
}
+ if ( chestVoidState > 0 )
+ {
+ --chestVoidState;
+ if ( chestStatus == 1 )
+ {
+ chestVoidState = std::max(1, chestVoidState);
+ }
+ if ( chestVoidState == 0 )
+ {
+ serverUpdateEntitySkill(this, 17);
+ createParticleErupt(this, 625);
+ serverSpawnMiscParticles(this, PARTICLE_EFFECT_ERUPT, 625);
+ }
+ }
+
//Using the chest (TODO: Monsters using it?).
int chestclicked = -1;
for (int i = 0; i < MAXPLAYERS; ++i)
@@ -830,47 +1047,67 @@ void Entity::actChest()
{
if ( !chestStatus )
{
- messagePlayer(chestclicked, MESSAGE_INTERACTION, Language::get(459));
- openedChest[chestclicked] = this;
-
- Compendium_t::Events_t::eventUpdateWorld(chestclicked, Compendium_t::CPDM_CHESTS_OPENED, "chest", 1);
+ bool voidChestInUse = false;
+ if ( chestVoidState != 0 )
+ {
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( openedChest[i] && openedChest[i]->chestVoidState != 0 )
+ {
+ voidChestInUse = true;
+ }
+ }
+ }
- chestOpener = chestclicked;
- if ( !players[chestclicked]->isLocalPlayer() && multiplayer == SERVER)
+ if ( voidChestInUse )
{
- //Send all of the items to the client.
- strcpy((char*)net_packet->data, "CHST"); //Chest.
- SDLNet_Write32((Uint32)getUID(), &net_packet->data[4]); //Give the client the UID.
- net_packet->address.host = net_clients[chestclicked - 1].host;
- net_packet->address.port = net_clients[chestclicked - 1].port;
- net_packet->len = 8;
- sendPacketSafe(net_sock, -1, net_packet, chestclicked - 1);
- for (node = inventory->first; node != NULL; node = node->next)
+ messagePlayer(chestclicked, MESSAGE_INTERACTION, Language::get(6565));
+ }
+ else
+ {
+ messagePlayer(chestclicked, MESSAGE_INTERACTION, Language::get(459));
+ openedChest[chestclicked] = this;
+
+ Compendium_t::Events_t::eventUpdateWorld(chestclicked, Compendium_t::CPDM_CHESTS_OPENED, "chest", 1);
+
+ chestOpener = chestclicked;
+ if ( !players[chestclicked]->isLocalPlayer() && multiplayer == SERVER)
{
- item = (Item*) node->element;
- strcpy((char*)net_packet->data, "CITM"); //Chest item.
- SDLNet_Write32((Uint32)item->type, &net_packet->data[4]);
- SDLNet_Write32((Uint32)item->status, &net_packet->data[8]);
- SDLNet_Write32((Uint32)item->beatitude, &net_packet->data[12]);
- SDLNet_Write32((Uint32)item->count, &net_packet->data[16]);
- SDLNet_Write32((Uint32)item->appearance, &net_packet->data[20]);
- net_packet->data[24] = item->identified;
- net_packet->data[25] = 1; //forceNewStack ? 1 : 0;
- net_packet->data[26] = (Sint8)item->x;
- net_packet->data[27] = (Sint8)item->y;
+ //Send all of the items to the client.
+ strcpy((char*)net_packet->data, "CHST"); //Chest.
+ SDLNet_Write32((Uint32)getUID(), &net_packet->data[4]); //Give the client the UID.
+ net_packet->data[8] = chestVoidState != 0 ? 1 : 0;
net_packet->address.host = net_clients[chestclicked - 1].host;
net_packet->address.port = net_clients[chestclicked - 1].port;
- net_packet->len = 28;
+ net_packet->len = 9;
sendPacketSafe(net_sock, -1, net_packet, chestclicked - 1);
+ for (node = inventory->first; node != NULL; node = node->next)
+ {
+ item = (Item*) node->element;
+ strcpy((char*)net_packet->data, "CITM"); //Chest item.
+ SDLNet_Write32((Uint32)item->type, &net_packet->data[4]);
+ SDLNet_Write32((Uint32)item->status, &net_packet->data[8]);
+ SDLNet_Write32((Uint32)item->beatitude, &net_packet->data[12]);
+ SDLNet_Write32((Uint32)item->count, &net_packet->data[16]);
+ SDLNet_Write32((Uint32)item->appearance, &net_packet->data[20]);
+ net_packet->data[24] = item->identified;
+ net_packet->data[25] = 1; //forceNewStack ? 1 : 0;
+ net_packet->data[26] = (Sint8)item->x;
+ net_packet->data[27] = (Sint8)item->y;
+ net_packet->address.host = net_clients[chestclicked - 1].host;
+ net_packet->address.port = net_clients[chestclicked - 1].port;
+ net_packet->len = 28;
+ sendPacketSafe(net_sock, -1, net_packet, chestclicked - 1);
+ }
}
+ else
+ {
+ players[chestclicked]->openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM); // Reset the GUI to the inventory.
+ players[chestclicked]->GUI.activateModule(Player::GUI_t::MODULE_CHEST);
+ players[chestclicked]->inventoryUI.chestGUI.openChest(chestVoidState != 0);
+ }
+ chestStatus = 1; //Toggle chest open/closed.
}
- else
- {
- players[chestclicked]->openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM); // Reset the GUI to the inventory.
- players[chestclicked]->GUI.activateModule(Player::GUI_t::MODULE_CHEST);
- players[chestclicked]->inventoryUI.chestGUI.openChest();
- }
- chestStatus = 1; //Toggle chest open/closed.
}
else
{
@@ -1070,6 +1307,44 @@ void Entity::closeChestServer()
}
}
+Item* Entity::addItemToVoidChest(int player, Item* item, bool forceNewStack, Item* specificDestinationStack)
+{
+ if ( !item )
+ {
+ return nullptr;
+ }
+ if ( player < 0 || player >= MAXPLAYERS )
+ {
+ return nullptr;
+ }
+ if ( multiplayer == CLIENT )
+ {
+ if ( players[player]->isLocalPlayer() )
+ {
+ //Tell the server.
+ strcpy((char*)net_packet->data, "CITM");
+ net_packet->data[4] = player;
+ net_packet->address.host = net_server.host;
+ net_packet->address.port = net_server.port;
+ SDLNet_Write32((Uint32)item->type, &net_packet->data[5]);
+ SDLNet_Write32((Uint32)item->status, &net_packet->data[9]);
+ SDLNet_Write32((Uint32)item->beatitude, &net_packet->data[13]);
+ SDLNet_Write32((Uint32)item->count, &net_packet->data[17]);
+ SDLNet_Write32((Uint32)item->appearance, &net_packet->data[21]);
+ net_packet->data[25] = item->identified;
+ net_packet->data[26] = forceNewStack ? 1 : 0;
+ net_packet->data[27] = 1;
+ net_packet->len = 28;
+ sendPacketSafe(net_sock, -1, net_packet, 0);
+
+ return item;
+ }
+ return nullptr;
+ }
+
+ return addItemToVoidChestServer(player, item, forceNewStack, specificDestinationStack);
+}
+
Item* Entity::addItemToChest(Item* item, bool forceNewStack, Item* specificDestinationStack)
{
if (!item)
@@ -1095,7 +1370,8 @@ Item* Entity::addItemToChest(Item* item, bool forceNewStack, Item* specificDesti
SDLNet_Write32((Uint32)item->appearance, &net_packet->data[21]);
net_packet->data[25] = item->identified;
net_packet->data[26] = forceNewStack ? 1 : 0;
- net_packet->len = 27;
+ net_packet->data[27] = players[player]->inventoryUI.chestGUI.voidChest ? 1 : 0;
+ net_packet->len = 28;
sendPacketSafe(net_sock, -1, net_packet, 0);
return addItemToChestClientside(player, item, forceNewStack, specificDestinationStack);
@@ -1104,7 +1380,7 @@ Item* Entity::addItemToChest(Item* item, bool forceNewStack, Item* specificDesti
Item* item2 = NULL;
//Add the item to the chest's inventory.
- list_t* inventory = static_cast(children.first->element);
+ list_t* inventory = getChestInventoryList();
node_t* t_node = NULL;
if ( !forceNewStack )
@@ -1200,6 +1476,7 @@ Item* Entity::addItemToChestFromInventory(int player, Item* item, int amount, bo
}
}
item->identified = true;
+ Item::onItemIdentified(player, item);
return nullptr;
}
}
@@ -1357,7 +1634,9 @@ Item* Entity::getItemFromChest(Item* item, int amount, bool getInfoOnly)
SDLNet_Write32((Uint32)count, &net_packet->data[17]);
SDLNet_Write32((Uint32)item->appearance, &net_packet->data[21]);
net_packet->data[25] = item->identified;
- net_packet->len = 26;
+ net_packet->data[26] = 0;
+ net_packet->data[27] = players[player]->inventoryUI.chestGUI.voidChest ? 1 : 0;
+ net_packet->len = 28;
sendPacketSafe(net_sock, -1, net_packet, 0);
}
}
@@ -1371,7 +1650,7 @@ Item* Entity::getItemFromChest(Item* item, int amount, bool getInfoOnly)
{
return NULL;
}
- if ( item->node->list != children.first->element )
+ if ( item->node->list != getChestInventoryList() )
{
return NULL;
}
@@ -1420,6 +1699,29 @@ void closeChestClientside(const int player)
}
}
+list_t* Entity::getChestInventoryList()
+{
+ if ( multiplayer == CLIENT )
+ {
+ return nullptr;
+ }
+ if ( behavior == &::actChest )
+ {
+ if ( chestVoidState != 0 )
+ {
+ return &stats[0]->void_chest_inventory;
+ }
+ else
+ {
+ if ( children.first && children.first->element )
+ {
+ return (list_t*)children.first->element;
+ }
+ }
+ }
+ return nullptr;
+}
+
Item* addItemToChestClientside(const int player, Item* item, bool forceNewStack, Item* specificDestinationStack)
{
if (openedChest[player])
@@ -1465,6 +1767,188 @@ Item* addItemToChestClientside(const int player, Item* item, bool forceNewStack,
return nullptr;
}
+Item* Entity::addItemToVoidChestServer(int player, Item* item, bool forceNewStack, Item* specificDestinationStack)
+{
+ if ( !item )
+ {
+ return nullptr;
+ }
+
+ Item* item2 = NULL;
+ node_t* t_node = NULL;
+
+ //Add the item to the chest's inventory.
+ list_t* inventory = &stats[0]->void_chest_inventory;
+
+ if ( !inventory )
+ {
+ return nullptr;
+ }
+
+ bool voidChestInUse = false;
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( openedChest[i] && openedChest[i]->chestVoidState != 0 && i != player )
+ {
+ voidChestInUse = true;
+ break;
+ }
+ }
+ if ( voidChestInUse ) // someone already has a void chest open
+ {
+ if ( player >= 1 && player < MAXPLAYERS )
+ {
+ bool dropped = false;
+ if ( players[player]->entity )
+ {
+ auto item2 = newItem(item->type,
+ item->status,
+ item->beatitude,
+ item->count,
+ item->appearance,
+ item->identified,
+ &stats[player]->inventory);
+ dropped = dropItem(item2, player, true, true);
+ }
+
+ if ( !dropped )
+ {
+ Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
+ entity->flags[INVISIBLE] = true;
+ entity->flags[UPDATENEEDED] = true;
+ entity->x = players[player]->player_last_x;
+ entity->y = players[player]->player_last_y;
+ entity->sizex = 4;
+ entity->sizey = 4;
+ entity->yaw = local_rng.rand() % 360 * (PI / 180.0);
+ entity->vel_x = 0.0;
+ entity->vel_y = 0.0;
+ entity->vel_z = (-10 - local_rng.rand() % 20) * .01;
+ entity->flags[PASSABLE] = true;
+ entity->behavior = &actItem;
+ entity->skill[10] = item->type;
+ entity->skill[11] = item->status;
+ entity->skill[12] = item->beatitude;
+ entity->skill[13] = item->count;
+ entity->skill[14] = item->appearance;
+ entity->skill[15] = item->identified;
+ entity->parent = 0;
+ entity->itemOriginalOwner = 0;
+
+ playSoundPos(players[player]->player_last_x, players[player]->player_last_y, 47 + local_rng.rand() % 3, 64);
+ }
+ }
+ messagePlayer(player, MESSAGE_INVENTORY, Language::get(6565));
+ return nullptr;
+ }
+
+ int originalQty = item->count;
+ if ( !forceNewStack )
+ {
+ while ( item->count > 0 )
+ {
+ bool anyItemsInserted = false;
+
+ //If item's already in the chest, add it to a pre-existing stack.
+ for ( t_node = inventory->first; t_node != NULL; t_node = t_node->next )
+ {
+ item2 = (Item*)t_node->element;
+ if ( !specificDestinationStack )
+ {
+ if ( !itemCompare(item, item2, false) )
+ {
+ if ( item2->shouldItemStack(player) )
+ {
+ int stackAmount = std::max(0, item2->getMaxStackLimit(player) - item2->count);
+ int qty = std::max(0, std::min((int)item->count, stackAmount));
+ anyItemsInserted = qty > 0;
+ item2->count += qty;
+ item->count -= qty;
+ }
+ if ( item->count <= 0 )
+ {
+ return item2;
+ }
+ }
+ }
+ else
+ {
+ if ( specificDestinationStack == item2 && item2->shouldItemStack(player) )
+ {
+ int stackAmount = std::max(0, item2->getMaxStackLimit(player) - item2->count);
+ int qty = std::max(0, std::min((int)item->count, stackAmount));
+ anyItemsInserted = qty > 0;
+ item2->count += qty;
+ item->count -= qty;
+ if ( item->count <= 0 )
+ {
+ return item2;
+ }
+ }
+ }
+ }
+
+ if ( !anyItemsInserted )
+ {
+ break;
+ }
+ }
+ }
+
+ bool voidChestFull = list_Size(inventory) >= Player::Inventory_t::MAX_CHEST_X * Player::Inventory_t::MAX_CHEST_Y;
+ if ( voidChestFull ) // void chest is full
+ {
+ bool dropped = false;
+ if ( player >= 1 && player < MAXPLAYERS )
+ {
+ auto item2 = newItem(item->type,
+ item->status,
+ item->beatitude,
+ item->count,
+ item->appearance,
+ item->identified,
+ &stats[player]->inventory);
+ dropped = dropItem(item2, player, true, true);
+
+ if ( !dropped )
+ {
+ Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
+ entity->flags[INVISIBLE] = true;
+ entity->flags[UPDATENEEDED] = true;
+ entity->x = players[player]->player_last_x;
+ entity->y = players[player]->player_last_y;
+ entity->sizex = 4;
+ entity->sizey = 4;
+ entity->yaw = local_rng.rand() % 360 * (PI / 180.0);
+ entity->vel_x = 0.0;
+ entity->vel_y = 0.0;
+ entity->vel_z = (-10 - local_rng.rand() % 20) * .01;
+ entity->flags[PASSABLE] = true;
+ entity->behavior = &actItem;
+ entity->skill[10] = item->type;
+ entity->skill[11] = item->status;
+ entity->skill[12] = item->beatitude;
+ entity->skill[13] = item->count;
+ entity->skill[14] = item->appearance;
+ entity->skill[15] = item->identified;
+ entity->parent = 0;
+ entity->itemOriginalOwner = 0;
+
+ playSoundPos(players[player]->player_last_x, players[player]->player_last_y, 47 + local_rng.rand() % 3, 64);
+ }
+ }
+
+ messagePlayer(player, MESSAGE_INVENTORY, Language::get(6566));
+ return nullptr;
+ }
+
+ item->node = list_AddNodeLast(inventory);
+ item->node->element = item;
+ item->node->deconstructor = &defaultDeconstructor;
+
+ return item;
+}
+
Item* Entity::addItemToChestServer(Item* item, bool forceNewStack, Item* specificDestinationStack)
{
if (!item)
@@ -1476,7 +1960,7 @@ Item* Entity::addItemToChestServer(Item* item, bool forceNewStack, Item* specifi
node_t* t_node = NULL;
//Add the item to the chest's inventory.
- list_t* inventory = static_cast(children.first->element);
+ list_t* inventory = getChestInventoryList();
if (!inventory)
{
@@ -1514,6 +1998,63 @@ Item* Entity::addItemToChestServer(Item* item, bool forceNewStack, Item* specifi
return item;
}
+bool Entity::removeItemFromVoidChestServer(int player, Item* item, int count)
+{
+ if ( !item )
+ {
+ return false;
+ }
+
+ Item* item2 = NULL;
+ node_t* t_node = NULL;
+
+ list_t* inventory = &stats[0]->void_chest_inventory;
+ if ( !inventory )
+ {
+ return false;
+ }
+
+ node_t* nextnode = nullptr;
+ bool removedItems = false;
+ for ( t_node = inventory->first; t_node != NULL; t_node = nextnode )
+ {
+ nextnode = t_node->next;
+ item2 = (Item*)t_node->element;
+ if ( !item2 || !item2->node || item2->node->list != inventory )
+ {
+ return false;
+ }
+ if ( !itemCompare(item, item2, false, false) )
+ {
+ if ( count < item2->count )
+ {
+ //Grab only one item from the chest.
+ int oldcount = item2->count;
+ item2->count = oldcount - count;
+ if ( item2->count <= 0 )
+ {
+ list_RemoveNode(item2->node);
+ }
+ }
+ else if ( count == item2->count )
+ {
+ //Grab all items from the chest.
+ list_RemoveNode(item2->node);
+ }
+ else if ( count > item2->count )
+ {
+ count -= item2->count;
+ //Grab items that we can, and then look for more.
+ list_RemoveNode(item2->node);
+ removedItems = true;
+ continue;
+ }
+ return true;
+ }
+ }
+ return removedItems;
+}
+
bool Entity::removeItemFromChestServer(Item* item, int count)
{
if (!item)
@@ -1524,7 +2065,10 @@ bool Entity::removeItemFromChestServer(Item* item, int count)
Item* item2 = NULL;
node_t* t_node = NULL;
- list_t* inventory = static_cast(children.first->element);
+ Sint32 oldVoidChestState = chestVoidState;
+ chestVoidState = 0;
+ list_t* inventory = getChestInventoryList();
+ chestVoidState = oldVoidChestState;
if (!inventory)
{
return false;
@@ -1536,7 +2080,7 @@ bool Entity::removeItemFromChestServer(Item* item, int count)
{
nextnode = t_node->next;
item2 = (Item*) t_node->element;
- if (!item2 || !item2->node || item2->node->list != children.first->element)
+ if (!item2 || !item2->node || item2->node->list != inventory )
{
return false;
}
@@ -1582,8 +2126,10 @@ void Entity::lockChest()
chestLocked = 1;
}
-void Entity::chestHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster)
+void Entity::chestHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster, bool doSound)
{
+ updateEntityOldHPBeforeMagicHit(*this, magicProjectile);
+
if ( behavior == &actMonster )
{
Stat* stats = getStats();
@@ -1610,7 +2156,7 @@ void Entity::chestHandleDamageMagic(int damage, Entity &magicProjectile, Entity
}
if ( caster && oldHP > 0 )
{
- awardXP(caster, true, true);
+ caster->awardXP(this, true, true);
}
}
else
@@ -1657,6 +2203,11 @@ void Entity::chestHandleDamageMagic(int damage, Entity &magicProjectile, Entity
messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2520));
}
Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_CHESTS_DESTROYED, "chest", 1);
+
+ if ( chestOldHealth > 0 )
+ {
+ players[caster->skill[2]]->mechanics.incrementBreakableCounter(Player::PlayerMechanics_t::BreakableEvent::GBREAK_COMMON, this);
+ }
}
else
{
@@ -1674,5 +2225,8 @@ void Entity::chestHandleDamageMagic(int damage, Entity &magicProjectile, Entity
false, DamageGib::DMG_DEFAULT);
}
}
- playSoundEntity(this, 28, 128);
+ if ( doSound )
+ {
+ playSoundEntity(this, 28, 128);
+ }
}
diff --git a/src/actdoor.cpp b/src/actdoor.cpp
index 73a0f551c..811457a01 100644
--- a/src/actdoor.cpp
+++ b/src/actdoor.cpp
@@ -253,7 +253,7 @@ void actDoor(Entity* my)
}
bool insideEntity = false;
- if ( entity->behavior == &actDoor )
+ if ( entity->behavior == &actDoor || entity->behavior == &actIronDoor )
{
real_t oldx = entity->x;
real_t oldy = entity->y;
@@ -329,8 +329,13 @@ void actDoorFrame(Entity* my)
}
}
-void Entity::doorHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster)
+void Entity::doorHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster, bool messages, bool doSound)
{
+ if ( behavior == &::actIronDoor )
+ {
+ damage = 0;
+ }
+ updateEntityOldHPBeforeMagicHit(*this, magicProjectile);
doorHealth -= damage; //Decrease door health.
if ( caster )
{
@@ -338,30 +343,44 @@ void Entity::doorHandleDamageMagic(int damage, Entity &magicProjectile, Entity *
{
if ( doorHealth <= 0 )
{
- if ( magicProjectile.behavior == &actBomb )
+ if ( messages )
{
- messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3617), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(674));
+ if ( magicProjectile.behavior == &actBomb )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3617), items[magicProjectile.skill[21]].getIdentifiedName(),
+ behavior == &::actIronDoor ? Language::get(6414) : Language::get(674));
+ }
+ else
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(387));
+ }
}
- else
+ Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_DOOR_BROKEN, "door", 1);
+
+ if ( doorOldHealth > 0 )
{
- messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(387));
+ players[caster->skill[2]]->mechanics.incrementBreakableCounter(Player::PlayerMechanics_t::BreakableEvent::GBREAK_COMMON, this);
}
- Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_DOOR_BROKEN, "door", 1);
}
- else
+ else if ( damage > 0 )
{
- if ( magicProjectile.behavior == &actBomb )
- {
- messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(3618), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(674));
- }
- else
+ if ( messages )
{
- messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(378), Language::get(674));
+ if ( magicProjectile.behavior == &actBomb )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(3618), items[magicProjectile.skill[21]].getIdentifiedName(),
+ behavior == &::actIronDoor ? Language::get(6414) : Language::get(674));
+ }
+ else
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(378),
+ behavior == &::actIronDoor ? Language::get(6414) : Language::get(674));
+ }
}
}
- updateEnemyBar(caster, this, Language::get(674), doorHealth, doorMaxHealth,
- false, DamageGib::DMG_DEFAULT);
}
+ updateEnemyBar(caster, this, behavior == &::actIronDoor ? Language::get(6414) : Language::get(674), doorHealth, doorMaxHealth,
+ false, DamageGib::DMG_DEFAULT);
}
if ( !doorDir )
{
@@ -371,6 +390,321 @@ void Entity::doorHandleDamageMagic(int damage, Entity &magicProjectile, Entity *
{
doorSmacked = (magicProjectile.y < this->y);
}
+ if ( doSound )
+ {
+ playSoundEntity(this, 28, 128);
+ }
+}
+
+void actIronDoor(Entity* my)
+{
+ if ( my )
+ {
+ my->actIronDoor();
+ }
+}
+
+void Entity::actIronDoor()
+{
+ Entity* entity;
+ int i, c;
+
+ auto& rng = entity_rng ? *entity_rng : local_rng;
+
+ if ( !doorInit )
+ {
+ createWorldUITooltip();
+
+ doorInit = 1;
+ doorStartAng = yaw;
+ doorHealth = 100;
+ doorMaxHealth = doorHealth;
+ doorOldHealth = doorHealth;
+ doorPreventLockpickExploit = 1;
+ doorLockpickHealth = 50;
+ if ( doorForceLockedUnlocked == 2 )
+ {
+ doorLocked = 0; // force unlocked.
+ }
+ else if ( doorForceLockedUnlocked <= 1 )
+ {
+ doorLocked = 1;
+ doorPreventLockpickExploit = 0;
+ }
+ doorOldStatus = doorStatus;
+ scalex = 1.01;
+ scaley = 1.01;
+ scalez = 1.01;
+ flags[BURNABLE] = false;
+ }
+ else
+ {
+ if ( multiplayer != CLIENT )
+ {
+ doorOldHealth = doorHealth;
+
+ // door mortality :p
+ if ( doorHealth <= 0 )
+ {
+ for ( c = 0; c < 5 && false; c++ )
+ {
+ entity = spawnGib(this);
+ entity->flags[INVISIBLE] = false;
+ entity->sprite = 187; // Splinter.vox
+ entity->x = floor(x / 16) * 16 + 8;
+ entity->y = floor(y / 16) * 16 + 8;
+ entity->z = 0;
+ entity->z += -7 + local_rng.rand() % 14;
+ if ( !doorDir )
+ {
+ // horizontal door
+ entity->y += -4 + local_rng.rand() % 8;
+ if ( doorSmacked )
+ {
+ entity->yaw = PI;
+ }
+ else
+ {
+ entity->yaw = 0;
+ }
+ }
+ else
+ {
+ // vertical door
+ entity->x += -4 + local_rng.rand() % 8;
+ if ( doorSmacked )
+ {
+ entity->yaw = PI / 2;
+ }
+ else
+ {
+ entity->yaw = 3 * PI / 2;
+ }
+ }
+ entity->pitch = (local_rng.rand() % 360) * PI / 180.0;
+ entity->roll = (local_rng.rand() % 360) * PI / 180.0;
+ entity->vel_x = cos(entity->yaw) * (1.2 + (local_rng.rand() % 10) / 50.0);
+ entity->vel_y = sin(entity->yaw) * (1.2 + (local_rng.rand() % 10) / 50.0);
+ entity->vel_z = -.25;
+ entity->fskill[3] = 0.04;
+ serverSpawnGibForClient(entity);
+ }
+ playSoundEntity(this, 76, 64);
+ list_RemoveNode(mynode);
+ return;
+ }
- playSoundEntity(this, 28, 128);
+ if ( doorUnlockWhenPowered == 1 )
+ {
+ if ( circuit_status == CIRCUIT_ON )
+ {
+ if ( doorLocked == 1 )
+ {
+ doorLocked = 0;
+ playSoundEntity(this, 91, 64);
+ }
+ }
+ else if ( circuit_status == CIRCUIT_OFF )
+ {
+ if ( doorStatus == 0 ) // closed
+ {
+ if ( doorLocked == 0 )
+ {
+ doorLocked = 1;
+ playSoundEntity(this, 57, 64);
+ }
+ }
+ }
+ }
+
+ // using door
+ for ( i = 0; i < MAXPLAYERS; i++ )
+ {
+ if ( selectedEntity[i] == this || client_selected[i] == this )
+ {
+ if ( Player::getPlayerInteractEntity(i) && inrange[i] )
+ {
+ Entity* playerEntity = Player::getPlayerInteractEntity(i);
+ if ( !doorLocked ) // door unlocked
+ {
+ if ( !doorDir && !doorStatus )
+ {
+ // open door
+ doorStatus = 1 + (playerEntity->x > x);
+ playSoundEntity(this, 21, 96);
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6404));
+ Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DOOR_OPENED, "iron door", 1);
+ }
+ else if ( doorDir && !doorStatus )
+ {
+ // open door
+ doorStatus = 1 + (playerEntity->y < y);
+ playSoundEntity(this, 21, 96);
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6404));
+ Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DOOR_OPENED, "iron door", 1);
+ }
+ else
+ {
+ // close door
+ doorStatus = 0;
+ playSoundEntity(this, 22, 96);
+ if ( doorUnlockWhenPowered == 1 && circuit_status == CIRCUIT_OFF )
+ {
+ doorLocked = 1;
+ playSoundEntity(this, 57, 64);
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6406));
+ }
+ else
+ {
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6405));
+ }
+ Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DOOR_CLOSED, "iron door", 1);
+ }
+ }
+ else
+ {
+ // door locked
+ messagePlayer(i, MESSAGE_INTERACTION, Language::get(6407));
+ playSoundEntity(this, 152, 64);
+ }
+ }
+ }
+ }
+ }
+
+ // door swinging
+ static const real_t doorSpeed = 0.15;
+ if ( !doorStatus )
+ {
+ // closing door
+ if ( yaw > doorStartAng )
+ {
+ yaw = std::max(doorStartAng, yaw - 0.15);
+ }
+ else if ( yaw < doorStartAng )
+ {
+ yaw = std::min(doorStartAng, yaw + 0.15);
+ }
+ }
+ else
+ {
+ // opening door
+ if ( doorStatus == 1 )
+ {
+ if ( yaw > doorStartAng + PI / 2 )
+ {
+ yaw = std::max(doorStartAng + PI / 2, yaw - doorSpeed);
+ }
+ else if ( yaw < doorStartAng + PI / 2 )
+ {
+ yaw = std::min(doorStartAng + PI / 2, yaw + doorSpeed);
+ }
+ }
+ else if ( doorStatus == 2 )
+ {
+ if ( yaw > doorStartAng - PI / 2 )
+ {
+ yaw = std::max(doorStartAng - PI / 2, yaw - doorSpeed);
+ }
+ else if ( yaw < doorStartAng - PI / 2 )
+ {
+ yaw = std::min(doorStartAng - PI / 2, yaw + doorSpeed);
+ }
+ }
+ }
+
+ // setting collision
+ if ( yaw == doorStartAng && flags[PASSABLE] )
+ {
+ // don't set impassable if someone's inside, otherwise do
+ node_t* node;
+ bool somebodyinside = false;
+ std::vector entLists;
+ if ( multiplayer == CLIENT )
+ {
+ entLists.push_back(map.entities); // clients use old map.entities method
+ }
+ else
+ {
+ entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(this, 2);
+ }
+ real_t oldmyx = x;
+ real_t oldmyy = y;
+ x = (static_cast(x) >> 4) * 16.0 + 8.0; // door positioning isn't centred on tile so adjust
+ y = (static_cast(y) >> 4) * 16.0 + 8.0;
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !somebodyinside; ++it )
+ {
+ list_t* currentList = *it;
+ for ( node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity == this || (entity->flags[PASSABLE] && entity->behavior != &actDeathGhost)
+ || entity->behavior == &actDoorFrame )
+ {
+ continue;
+ }
+
+ bool insideEntity = false;
+ if ( entity->behavior == &actDoor || entity->behavior == &::actIronDoor )
+ {
+ real_t oldx = entity->x;
+ real_t oldy = entity->y;
+ entity->x = (static_cast(entity->x) >> 4) * 16.0 + 8.0; // door positioning isn't centred on tile so adjust
+ entity->y = (static_cast(entity->y) >> 4) * 16.0 + 8.0;
+ insideEntity = entityInsideEntity(this, entity);
+ entity->x = oldx;
+ entity->y = oldy;
+ }
+ else
+ {
+ insideEntity = entityInsideEntity(this, entity);
+ }
+
+ if ( insideEntity )
+ {
+ somebodyinside = true;
+ break;
+ }
+ }
+ }
+ x = oldmyx;
+ y = oldmyy;
+ if ( !somebodyinside )
+ {
+ focaly = 0;
+ if ( doorStartAng == 0 )
+ {
+ y -= 5;
+ }
+ else
+ {
+ x -= 5;
+ }
+ flags[PASSABLE] = false;
+ }
+ }
+ else if ( yaw != doorStartAng && !flags[PASSABLE] )
+ {
+ focaly = -5;
+ if ( doorStartAng == 0 )
+ {
+ y += 5;
+ }
+ else
+ {
+ x += 5;
+ }
+ flags[PASSABLE] = true;
+ }
+
+ // update for clients
+ if ( multiplayer == SERVER )
+ {
+ if ( doorOldStatus != doorStatus )
+ {
+ doorOldStatus = doorStatus;
+ serverUpdateEntitySkill(this, 3);
+ }
+ }
+ }
}
diff --git a/src/actflame.cpp b/src/actflame.cpp
index 1d6da76f2..f8e6ce784 100644
--- a/src/actflame.cpp
+++ b/src/actflame.cpp
@@ -83,6 +83,65 @@ void actFlame(Entity* my)
static ConsoleVariable cvar_flame_use_vismap("/flame_use_vismap", true);
+Entity* spawnFlameSprites(Entity* parentent, Sint32 sprite)
+{
+ if ( !parentent )
+ {
+ return nullptr;
+ }
+ /*if ( *cvar_flame_use_vismap && !intro )
+ {
+ if ( parentent->behavior != actPlayer
+ && parentent->behavior != actPlayerLimb
+ && !parentent->flags[OVERDRAW]
+ && !parentent->flags[GENIUS] )
+ {
+ int x = parentent->x / 16.0;
+ int y = parentent->y / 16.0;
+ if ( x >= 0 && x < map.width && y >= 0 && y < map.height )
+ {
+ bool anyVismap = false;
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( !client_disconnected[i] && players[i]->isLocalPlayer() )
+ {
+ if ( cameras[i].vismap && cameras[i].vismap[y + x * map.height] )
+ {
+ anyVismap = true;
+ break;
+ }
+ }
+ }
+ if ( !anyVismap )
+ {
+ return nullptr;
+ }
+ }
+ }
+ }*/
+
+ if ( Entity* fx = createParticleAestheticOrbit(parentent, sprite, 10, PARTICLE_EFFECT_FLAMES_BURNING) )
+ {
+ fx->flags[SPRITE] = true;
+ fx->flags[INVISIBLE] = true;
+ fx->x = parentent->x;
+ fx->y = parentent->y;
+ fx->z = parentent->z;
+ fx->scalex = 1.0;
+ fx->scaley = fx->scalex;
+ fx->scalez = fx->scalex;
+ fx->fskill[0] = fx->x;
+ fx->fskill[1] = fx->y;
+ fx->vel_z = -0.25;
+ fx->actmagicOrbitDist = 0;
+ fx->fskill[2] = parentent->yaw + (local_rng.rand() % 8) * PI / 4.0;
+ fx->yaw = fx->fskill[2];
+ fx->actmagicNoLight = 1;
+ return fx;
+ }
+ return nullptr;
+}
+
Entity* spawnFlame(Entity* parentent, Sint32 sprite )
{
if ( !parentent )
diff --git a/src/actgate.cpp b/src/actgate.cpp
index 41bf29d95..bf2156cc6 100644
--- a/src/actgate.cpp
+++ b/src/actgate.cpp
@@ -188,7 +188,7 @@ void Entity::actGate()
{
Entity* entity = (Entity*)node->element;
if ( entity == this || (entity->flags[PASSABLE] && entity->behavior != &actDeathGhost)
- || entity->behavior == &actDoorFrame )
+ || entity->behavior == &actDoorFrame || entity->behavior == &::actGate )
{
continue;
}
diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp
index 9298b5823..b5a4f803f 100644
--- a/src/actgeneral.cpp
+++ b/src/actgeneral.cpp
@@ -127,6 +127,91 @@ void actFurniture(Entity* my)
my->actFurniture();
}
+void Entity::furnitureHandleDamageMagic(int damage, Entity& magicProjectile, Entity* caster, bool messages, bool doSound)
+{
+ updateEntityOldHPBeforeMagicHit(*this, magicProjectile);
+ int oldHP = this->furnitureHealth;
+ this->furnitureHealth -= damage;
+ if ( caster )
+ {
+ if ( caster->behavior == &actPlayer )
+ {
+ bool destroyed = oldHP > 0 && this->furnitureHealth <= 0;
+ if ( destroyed )
+ {
+ gameModeManager.currentSession.challengeRun.updateKillEvent(this);
+ players[caster->skill[2]]->mechanics.incrementBreakableCounter(Player::PlayerMechanics_t::BreakableEvent::GBREAK_COMMON, this);
+ }
+ switch ( this->furnitureType )
+ {
+ case FURNITURE_CHAIR:
+ if ( destroyed && messages )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(388));
+ }
+ break;
+ case FURNITURE_TABLE:
+ if ( destroyed && messages )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(389));
+ }
+ break;
+ case FURNITURE_BED:
+ if ( destroyed && messages )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(2505));
+ }
+ break;
+ case FURNITURE_BUNKBED:
+ if ( destroyed && messages )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(2506));
+ }
+ break;
+ case FURNITURE_PODIUM:
+ if ( destroyed && messages )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(2507));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch ( this->furnitureType )
+ {
+ case FURNITURE_CHAIR:
+ updateEnemyBar(caster, this, Language::get(677), this->furnitureHealth, this->furnitureMaxHealth,
+ false, DamageGib::DMG_DEFAULT);
+ break;
+ case FURNITURE_TABLE:
+ updateEnemyBar(caster, this, Language::get(676), this->furnitureHealth, this->furnitureMaxHealth,
+ false, DamageGib::DMG_DEFAULT);
+ break;
+ case FURNITURE_BED:
+ updateEnemyBar(caster, this, Language::get(2505), this->furnitureHealth, this->furnitureMaxHealth,
+ false, DamageGib::DMG_DEFAULT);
+ break;
+ case FURNITURE_BUNKBED:
+ updateEnemyBar(caster, this, Language::get(2506), this->furnitureHealth, this->furnitureMaxHealth,
+ false, DamageGib::DMG_DEFAULT);
+ break;
+ case FURNITURE_PODIUM:
+ updateEnemyBar(caster, this, Language::get(2507), this->furnitureHealth, this->furnitureMaxHealth,
+ false, DamageGib::DMG_DEFAULT);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ( doSound )
+ {
+ playSoundEntity(this, 28, 128);
+ }
+}
+
void Entity::actFurniture()
{
@@ -589,6 +674,473 @@ void Entity::actPistonCam()
}
}
+int colliderGetSpellRange(Entity* my)
+{
+ if ( my->colliderSpellEvent % 1000 == 0 ) { return 0; }
+ int range = 32;
+ int effectType = my->colliderSpellEvent % 1000;
+ switch ( effectType )
+ {
+ case 3:
+ case 4:
+ case 7:
+ range = 128;
+ break;
+ default:
+ break;
+ }
+ return range;
+}
+
+void actColliderMushroomCap(Entity* my)
+{
+ Entity* parent = uidToEntity(my->parent);
+ if ( !parent )
+ {
+ Entity* gib = spawnGib(my);
+ gib->sprite = my->sprite;
+ gib->z = my->z;
+ gib->skill[5] = 1; // poof
+ list_RemoveNode(my->mynode);
+ return;
+ }
+
+ my->z = parent->z - 7.0 + 1.5;
+ my->pitch = (my->fskill[2]) * (PI / 24) * sin(my->fskill[1]);
+ my->roll = (my->fskill[2]) * (PI / 24) * sin(my->fskill[1] + PI / 2);
+
+ real_t bobScale = 0.08 * my->skill[0];
+ my->scalex = 1.0 + bobScale * sin(my->fskill[0]);
+ my->scaley = 1.0 + bobScale * sin(my->fskill[0]);
+ my->scalez = 1.0 - bobScale * sin(my->fskill[0]);
+
+ parent->scalex = 1.0 - bobScale * 0.25 * sin(my->fskill[0]);
+ parent->scaley = 1.0 - bobScale * 0.25 * sin(my->fskill[0]);
+ parent->scalez = 1.0 + bobScale * 0.25 * sin(my->fskill[0]);
+
+ int trigger = 0;
+ if ( parent->colliderSpellEventTrigger != 0 )
+ {
+ trigger = parent->colliderSpellEventTrigger;
+ parent->colliderSpellEventTrigger = 0;
+ }
+
+ auto& friendlyFire = my->skill[1];
+
+ if ( trigger > 0 )
+ {
+ real_t percent = (trigger % 100) / 100.0;
+ my->skill[0] = 4;
+
+ if ( percent < 0.5 )
+ {
+ playSoundEntityLocal(parent, 711 + local_rng.rand() % 3, 128);
+ }
+ else
+ {
+ playSoundEntityLocal(parent, 714 + local_rng.rand() % 3, 128);
+ }
+ my->fskill[0] = 0.0;
+ my->fskill[1] = 2 * PI * percent;
+ my->fskill[2] = 0.0;
+
+ if ( trigger >= 100 )
+ {
+ friendlyFire = 1;
+ /*if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
+ {
+ friendlyFire = 0;
+ }*/
+ }
+ else
+ {
+ friendlyFire = 0;
+ }
+ }
+
+ if ( my->skill[0] > 0 )
+ {
+ int range = colliderGetSpellRange(parent);
+ int effectType = parent->colliderSpellEvent % 1000;
+ if ( effectType == 3 || effectType == 4 || effectType == 7 )
+ {
+ range = 32;
+ }
+
+ auto cacheType = AOEIndicators_t::CACHE_MUSHROOM_1;
+ Uint32 color = makeColorRGB(0, 145, 16);
+ int gibSprite = 1885;
+ switch ( effectType )
+ {
+ case 4:
+ color = makeColorRGB(206, 162, 146);
+ cacheType = AOEIndicators_t::CACHE_MUSHROOM_2;
+ gibSprite = 1887;
+ break;
+ case 5:
+ color = makeColorRGB(116, 128, 180);
+ cacheType = AOEIndicators_t::CACHE_MUSHROOM_3;
+ gibSprite = 1888;
+ break;
+ case 2:
+ color = makeColorRGB(180, 116, 160);
+ cacheType = AOEIndicators_t::CACHE_MUSHROOM_4;
+ gibSprite = 1889;
+ break;
+ default:
+ break;
+ }
+
+ my->fskill[2] = std::min(1.0, std::max(0.05, my->fskill[2] * 1.15));
+ if ( my->skill[0] == 4 ) // circle first
+ {
+ if ( my->fskill[1] >= 2 * PI )
+ {
+ my->skill[0]--;
+
+ if ( (effectType == 3 || effectType == 4 || effectType == 7) && multiplayer != CLIENT )
+ {
+ if ( Entity* target = uidToEntity(parent->colliderSpellTarget) )
+ {
+ real_t tangent = atan2(target->y - parent->y, target->x - parent->x);
+
+ Entity* caster = parent;
+ if ( parent->colliderCreatedParent != 0 )
+ {
+ if ( Entity* ent = uidToEntity(parent->colliderCreatedParent) )
+ {
+ caster = ent;
+ }
+ }
+ if ( Entity* missile = castSpell(
+ caster ? caster->getUID() : 0,
+ getSpellFromID(
+ (effectType == 3 || effectType == 7) ? SPELL_SPORE_BOMB : SPELL_MYCELIUM_BOMB
+ ), false, true) )
+ {
+ missile->collisionIgnoreTargets.insert(parent->getUID());
+ missile->x = parent->x;
+ missile->y = parent->y;
+ missile->z = 6.0;
+ real_t vel = sqrt(pow(missile->vel_x, 2) + pow(missile->vel_y, 2));
+ missile->vel_z = -1.2;
+ missile->vel_x = vel * cos(tangent);
+ missile->vel_y = vel * sin(tangent);
+ }
+ }
+ }
+
+ for ( int i = 0; i < 8; ++i )
+ {
+ Entity* fx = createParticleAestheticOrbit(parent, gibSprite, 3 * TICKS_PER_SECOND, PARTICLE_EFFECT_MUSHROOM_SPELL);
+ fx->x = parent->x;
+ fx->y = parent->y;
+ fx->actmagicOrbitDist = range;
+ fx->yaw = parent->yaw + (i * PI / 4.0);
+ fx->pitch = -PI;
+ fx->fskill[4] = fx->yaw;
+
+ Entity* gib = spawnGib(parent);
+ gib->sprite = gibSprite;
+ gib->vel_x = 1.5 * cos(fx->yaw);
+ gib->vel_y = 1.5 * sin(fx->yaw);
+ gib->lightBonus = vec4{ 0.25f, 0.25f, 0.25f, 0.f };
+ }
+
+ if ( Entity* fx = createParticleAOEIndicator(parent, parent->x, parent->y, 0.0, TICKS_PER_SECOND * 2, range) )
+ {
+ //fx->actSpriteFollowUID = 0;
+ fx->actSpriteCheckParentExists = 0;
+ //fx->scalex = 0.8;
+ //fx->scaley = 0.8;
+ if ( auto indicator = AOEIndicators_t::getIndicator(fx->skill[10]) )
+ {
+ //indicator->arc = PI / 2;
+ indicator->indicatorColor = color;
+ indicator->loop = false;
+ indicator->gradient = 4;
+ indicator->framesPerTick = 2;
+ indicator->ticksPerUpdate = 1;
+ indicator->delayTicks = 0;
+ indicator->expireAlphaRate = 0.95;
+ indicator->cacheType = cacheType;
+ }
+ }
+ playSoundEntityLocal(parent, 169, 128);
+ playSoundEntityLocal(parent, 717 + local_rng.rand() % 3, 128);
+ }
+ }
+ else
+ {
+ my->fskill[0] += 0.25;
+ if ( my->fskill[0] >= 2 * PI )
+ {
+ my->fskill[0] -= 2 * PI;
+ my->skill[0]--;
+ if ( my->skill[0] == 1 )
+ {
+ auto& colliderData = EditorEntityData_t::colliderData[parent->colliderDamageTypes];
+ if ( (effectType == 3 || effectType == 4) &&
+ colliderData.name.find("_fragile") != std::string::npos )
+ {
+ if ( multiplayer != CLIENT )
+ {
+ parent->colliderCurrentHP = 0;
+ parent->colliderKillerUid = 0;
+ }
+ }
+ else
+ {
+ playSoundEntityLocal(parent, 717 + local_rng.rand() % 3, 128);
+ for ( int i = 0; i < 8; ++i )
+ {
+ Entity* fx = createParticleAestheticOrbit(parent, gibSprite, 3 * TICKS_PER_SECOND, PARTICLE_EFFECT_MUSHROOM_SPELL);
+ fx->x = parent->x;
+ fx->y = parent->y;
+ fx->actmagicOrbitDist = range;
+ fx->yaw = parent->yaw + (i * PI / 4.0) + PI / 8;
+ fx->pitch = -PI;
+ fx->fskill[4] = fx->yaw;
+
+ Entity* gib = spawnGib(parent);
+ gib->sprite = gibSprite;
+ gib->vel_x = 1.0 * cos(fx->yaw);
+ gib->vel_y = 1.0 * sin(fx->yaw);
+ gib->lightBonus = vec4{ 0.25f, 0.25f, 0.25f, 0.f };
+ }
+ }
+ }
+ else if ( friendlyFire == 0 )
+ {
+ auto& colliderData = EditorEntityData_t::colliderData[parent->colliderDamageTypes];
+ if ( !(effectType == 3 || effectType == 4) &&
+ colliderData.name.find("_fragile") != std::string::npos )
+ {
+ if ( multiplayer != CLIENT )
+ {
+ parent->colliderCurrentHP = 0;
+ parent->colliderKillerUid = 0;
+ }
+ }
+ }
+ }
+
+ if ( multiplayer != CLIENT )
+ {
+ Entity* caster = uidToEntity(parent->colliderCreatedParent);
+ std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1 + (range / 16));
+ for ( auto it : entLists )
+ {
+ node_t* node;
+ for ( node = it->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( !(entity->behavior == &actPlayer || entity->behavior == &actMonster) )
+ {
+ continue;
+ }
+ if ( !entity->monsterIsTargetable() )
+ {
+ continue;
+ }
+ if ( entityDist(my, entity) > (real_t)(range + 4.0) )
+ {
+ continue;
+ }
+ Stat* stats = entity->getStats();
+ if ( !stats ) { continue; }
+
+ if ( caster && caster->behavior == &actMonster )
+ {
+ if ( caster == entity )
+ {
+ continue;
+ }
+ if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
+ {
+ if ( caster->checkFriend(entity) && caster->friendlyFireProtection(entity) )
+ {
+ continue;
+ }
+ }
+ }
+ if ( caster && caster->behavior == &actPlayer )
+ {
+ if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
+ {
+ if ( caster->checkFriend(entity) && caster->friendlyFireProtection(entity) )
+ {
+ continue;
+ }
+ }
+ }
+
+ if ( entity->behavior == &actMonster
+ && stats->type == MYCONID )
+ {
+ continue;
+ }
+
+ auto props = getParticleEmitterHitProps(parent->getUID(), entity);
+ if ( !props )
+ {
+ continue;
+ }
+ if ( props->hits > 0 && (ticks - props->tick) < 50 )
+ {
+ continue;
+ }
+
+ real_t tangent = atan2(entity->y - parent->y, entity->x - parent->x);
+ bool oldPassable = entity->flags[PASSABLE];
+ entity->flags[PASSABLE] = false;
+ real_t d = lineTraceTarget(parent, parent->x, parent->y, tangent, (real_t)(range + 4.0), 0, false, entity);
+ entity->flags[PASSABLE] = oldPassable;
+ if ( hit.entity != entity )
+ {
+ continue;
+ }
+
+ int damage = 5;
+ bool alertTarget = true;
+ bool friendlyFireTarget = false;
+ if ( effectType == 6 || effectType == 7 ) // player casted
+ {
+ damage = getSpellDamageFromID(SPELL_MUSHROOM, caster, nullptr, my);
+
+ int bonusEffect = 0;
+ if ( parent && parent->colliderDropVariable > 0 )
+ {
+ bonusEffect = parent->colliderDropVariable;
+ }
+ if ( caster && caster->behavior == &actPlayer )
+ {
+ if ( Stat* casterStats = caster->getStats() )
+ {
+ if ( casterStats->type == MYCONID && casterStats->getEffectActive(EFF_GROWTH) >= 2 )
+ {
+ bonusEffect = std::max(bonusEffect, casterStats->getEffectActive(EFF_GROWTH) - 1);
+ }
+ }
+ }
+ damage += damage * (bonusEffect * 1.0);
+
+ if ( caster && caster->behavior == &actPlayer )
+ {
+ if ( caster->checkFriend(entity) && caster->friendlyFireProtection(entity) )
+ {
+ damage = 0;
+ alertTarget = false;
+ friendlyFireTarget = true;
+ }
+ }
+ else if ( achievementObserver.checkUidIsFromPlayer(parent->colliderCreatedParent) >= 0 )
+ {
+ if ( (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())
+ || entity->behavior == &actPlayer )
+ {
+ damage = 0;
+ alertTarget = false;
+ friendlyFireTarget = true;
+ }
+ }
+ }
+
+ if ( applyGenericMagicDamage(caster ? caster : parent, entity, caster ? *caster : *parent, SPELL_MUSHROOM, damage, alertTarget, true) )
+ {
+ stats->killer = KilledBy::MUSHROOM;
+ entity->setObituary(Language::get(6753));
+
+ props->hits++;
+ props->tick = ticks;
+ if ( effectType == 1 )
+ {
+ bool wasEffected = stats->getEffectActive(EFF_POISONED);
+ if ( entity->setEffect(EFF_POISONED, true, 3 * TICKS_PER_SECOND + 10, false) )
+ {
+ spawnMagicEffectParticles(entity->x, entity->y, entity->z, 944);
+ if ( caster )
+ {
+ stats->poisonKiller = caster->getUID();
+ }
+ }
+ }
+ else if ( effectType == 5 )
+ {
+ bool wasEffected = stats->getEffectActive(EFF_DUSTED);
+ if ( entity->setEffect(EFF_DUSTED, true, 10 * TICKS_PER_SECOND + 10, true) )
+ {
+ spawnMagicEffectParticles(entity->x, entity->y, entity->z, 944);
+ if ( !wasEffected )
+ {
+ messagePlayerColor(entity->isEntityPlayer(), MESSAGE_STATUS, makeColorRGB(255, 0, 0), Language::get(6752));
+ }
+ }
+ }
+ else if ( effectType == 2 || effectType == 3 || effectType == 4 || effectType == 6 || effectType == 7 )
+ {
+ spawnMagicEffectParticles(entity->x, entity->y, entity->z, 944);
+
+ if ( effectType == 3 || effectType == 6 || effectType == 7 )
+ {
+ if ( !friendlyFireTarget )
+ {
+ bool wasEffected = stats->getEffectActive(EFF_POISONED);
+ if ( entity->setEffect(EFF_POISONED, true, 3 * TICKS_PER_SECOND + 10, false) )
+ {
+ spawnMagicEffectParticles(entity->x, entity->y, entity->z, 944);
+ if ( caster )
+ {
+ stats->poisonKiller = caster->getUID();
+ }
+ }
+ }
+ }
+
+ if ( entity->setEffect(EFF_KNOCKBACK, true, 30, false) )
+ {
+ real_t pushbackMultiplier = 0.9;
+ real_t tangent = atan2(entity->y - parent->y, entity->x - parent->x);
+ if ( entity->behavior == &actPlayer )
+ {
+ if ( !players[entity->skill[2]]->isLocalPlayer() )
+ {
+ entity->monsterKnockbackVelocity = pushbackMultiplier;
+ entity->monsterKnockbackTangentDir = tangent;
+ serverUpdateEntityFSkill(entity, 11);
+ serverUpdateEntityFSkill(entity, 9);
+ }
+ else
+ {
+ entity->monsterKnockbackVelocity = pushbackMultiplier;
+ entity->monsterKnockbackTangentDir = tangent;
+ }
+ }
+ else if ( entity->behavior == &actMonster )
+ {
+ entity->vel_x = cos(tangent) * pushbackMultiplier;
+ entity->vel_y = sin(tangent) * pushbackMultiplier;
+ entity->monsterKnockbackVelocity = 0.01;
+ entity->monsterKnockbackUID = parent->colliderCreatedParent;
+ entity->monsterKnockbackTangentDir = tangent;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ my->fskill[2] *= 0.95;
+ }
+
+ my->fskill[1] += 0.125;
+ my->focalz = -1.5;
+}
+
bool Entity::isColliderShownAsWallOnMinimap() const
{
if ( !isDamageableCollider() ) { return false; }
@@ -645,6 +1197,13 @@ bool Entity::isColliderAttachableToBombs() const
return colliderDmgType.bombsAttach;
}
+bool Entity::isColliderPathableMonster(Monster type) const
+{
+ if ( !isDamageableCollider() ) { return false; }
+ auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes];
+ return colliderData.pathableMonsters.find(type) != colliderData.pathableMonsters.end();
+}
+
bool Entity::isDamageableCollider() const
{
return behavior == &actColliderDecoration && colliderMaxHP > 0;
@@ -674,6 +1233,7 @@ bool Entity::isColliderBreakableContainer() const
void Entity::colliderOnDestroy()
{
+ removeLightField();
if ( multiplayer == CLIENT ) { return; }
flags[PASSABLE] = true;
@@ -690,28 +1250,69 @@ void Entity::colliderOnDestroy()
}
}
- if ( colliderHideMonster != 0 )
+ auto find = EditorEntityData_t::colliderData.find(colliderDamageTypes);
+ if ( find != EditorEntityData_t::colliderData.end() )
{
- int type = colliderHideMonster % 1000;
- int numSpawns = type == BAT_SMALL ? 2 : 1;
- int successes = 0;
- for ( int i = 0; i < numSpawns; ++i )
+ if ( find->second.name == "mushroom_spell_casted" )
{
- auto monster = summonMonster((Monster)type, ((int)(x / 16)) * 16 + 8, ((int)(y / 16)) * 16 + 8);
- if ( monster )
+ for ( int i = 0; i < this->colliderDropVariable; ++i )
{
- monster->yaw = yaw;
- monster->lookAtEntity(*monster);
- monster->monsterLookDir = yaw;
- if ( Stat* stats = monster->getStats() )
+ if ( local_rng.rand() % 10 == 0 )
{
- stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1;
+ if ( Entity* ent = dropItemMonster(newItem(DUST_BALL, SERVICABLE, 0, 1, 0, true, nullptr), this, nullptr) )
+ {
+ ent->itemOriginalOwner = colliderCreatedParent;
+ ent->itemGerminateResult = 1;
+ }
+ }
+ else
+ {
+ if ( Entity* ent = dropItemMonster(newItem(FOOD_SHROOM, SERVICABLE, 0, 1, 0, true, nullptr), this, nullptr) )
+ {
+ ent->itemOriginalOwner = colliderCreatedParent;
+ ent->itemGerminateResult = 1;
+ }
+ }
+ }
+ Entity* parent = uidToEntity(this->colliderCreatedParent);
+ floorMagicCreateSpores(this, this->x, this->y, parent ? parent : this, 0, SPELL_SPORES);
+ }
+ else if ( find->second.name == "germinate_spell_casted" )
+ {
+ for ( int i = 0; i < this->colliderDropVariable; ++i )
+ {
+ if ( Entity* ent = dropItemMonster(newItem(FOOD_NUT, SERVICABLE, 0, 1, 0, true, nullptr), this, nullptr) )
+ {
+ ent->itemOriginalOwner = colliderCreatedParent;
+ ent->itemGerminateResult = 1;
+ }
+ }
+ }
+ }
+
+ if ( colliderHideMonster != 0 )
+ {
+ int type = colliderHideMonster % 1000;
+ int numSpawns = type == BAT_SMALL ? 2 : 1;
+ int successes = 0;
+ for ( int i = 0; i < numSpawns; ++i )
+ {
+ auto monster = summonMonster((Monster)type, ((int)(x / 16)) * 16 + 8, ((int)(y / 16)) * 16 + 8);
+ if ( monster )
+ {
+ monster->yaw = yaw;
+ monster->lookAtEntity(*monster);
+ monster->monsterLookDir = yaw;
+ if ( Stat* stats = monster->getStats() )
+ {
+ stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1;
if ( stats->type == GHOUL && currentlevel >= 15 )
{
strcpy(stats->name, "enslaved ghoul");
stats->setAttribute("special_npc", "enslaved ghoul");
}
- if ( stats->type == AUTOMATON )
+ stats->setAttribute("spawn_no_sleep", "1");
+ if ( stats->type == AUTOMATON && strcmp(map.filename, "automat.lmp") )
{
monster->monsterStoreType = 1; // damaged
}
@@ -731,8 +1332,16 @@ void Entity::colliderOnDestroy()
}
if ( successes == 1 )
{
- messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6234),
- getMonsterLocalizedName((Monster)type).c_str(), Language::get(getColliderLangName()));
+ if ( getColliderOnJumpLangEntry() == 6815 )
+ {
+ messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(getColliderOnJumpLangEntry()),
+ Language::get(getColliderLangName()));
+ }
+ else
+ {
+ messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(getColliderOnJumpLangEntry()),
+ getMonsterLocalizedName((Monster)type).c_str(), Language::get(getColliderLangName()));
+ }
}
else if ( successes > 1 )
{
@@ -758,6 +1367,7 @@ void Entity::colliderOnDestroy()
entity->goldBouncing = 0;
entity->z = 0.0 - (local_rng.rand() % 3);
entity->flags[INVISIBLE] = false;
+ entity->goldAmountBonus = entity->goldAmount;
if ( multiplayer == SERVER )
{
@@ -796,6 +1406,7 @@ void Entity::colliderOnDestroy()
ent->goldInContainer = 0;
ent->z = 0.0 - (local_rng.rand() % 3);
ent->flags[INVISIBLE] = false;
+ ent->goldAmountBonus = ent->goldAmount;
if ( multiplayer == SERVER )
{
@@ -889,6 +1500,13 @@ int Entity::getColliderOnBreakLangEntry() const
return colliderData.breakMessageLangEntry;
}
+int Entity::getColliderOnJumpLangEntry() const
+{
+ if ( !isDamageableCollider() ) { return 1; }
+ auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes];
+ return colliderData.colliderJumpLangEntry;
+}
+
int Entity::getColliderSfxOnHit() const
{
if ( !isDamageableCollider() ) { return 0; }
@@ -904,6 +1522,331 @@ int Entity::getColliderSfxOnBreak() const
return colliderData.sfxBreak[local_rng.rand() % colliderData.sfxBreak.size()];
}
+Entity* Entity::createBreakableCollider(int colliderDamageType, real_t _x, real_t _y, Entity* parent)
+{
+ if ( colliderDamageType == 0 )
+ {
+ return nullptr;
+ }
+ int x = static_cast(_x) >> 4;
+ int y = static_cast(_y) >> 4;
+ if ( !(x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1) )
+ {
+ return nullptr;
+ }
+ int mapIndex = (y)*MAPLAYERS + (x)*MAPLAYERS * map.height;
+ if ( !map.tiles[mapIndex] || swimmingtiles[map.tiles[mapIndex]] || lavatiles[map.tiles[mapIndex]] )
+ {
+ return nullptr;
+ }
+
+ auto find = EditorEntityData_t::colliderData.find(colliderDamageType);
+ if ( find == EditorEntityData_t::colliderData.end() )
+ {
+ return nullptr;
+ }
+
+
+ Entity* breakable = newEntity(-1, 1, map.entities, nullptr);
+ breakable->x = x * 16.0 + 8.0;
+ breakable->y = y * 16.0 + 8.0;
+ std::vector> coords =
+ {
+ {0, 1},
+ {0, -1},
+ {1, 0},
+ {1, 1},
+ {1, -1},
+ {-1, 0},
+ {-1, 1},
+ {-1, -1}
+ };
+
+ bool goodspot = false;
+ if ( checkObstacle(breakable->x, breakable->y, breakable, nullptr, true, true, true, false) )
+ {
+ while ( coords.size() > 0 )
+ {
+ int pick = local_rng.rand() % coords.size();
+ if ( checkObstacle(breakable->x + coords[pick].first * 16, breakable->y + coords[pick].second * 16, breakable, nullptr, true, true, true, false) )
+ {
+ coords.erase(coords.begin() + pick);
+ }
+ else
+ {
+ goodspot = true;
+ breakable->x += coords[pick].first * 16;
+ breakable->y += coords[pick].second * 16;
+ break;
+ }
+ }
+
+ if ( !goodspot )
+ {
+ list_RemoveNode(breakable->mynode);
+ return nullptr;
+ }
+ }
+
+ breakable->colliderDamageTypes = colliderDamageType;
+ breakable->colliderCreatedParent = parent ? parent->getUID() : 0;
+ breakable->colliderDecorationRotation = 2 * (local_rng.rand() % 3);
+ Entity::colliderAssignProperties(breakable, false, &map);
+
+ breakable->colliderSetServerSkillOnSpawned();
+ breakable->flags[UPDATENEEDED] = true;
+ breakable->flags[NOUPDATE] = false;
+ return breakable;
+}
+
+void Entity::colliderSetServerSkillOnSpawned()
+{
+ Sint32 val = (1 << 31);
+ val |= (Uint8)(25);
+ val |= (Uint8)(colliderDamageTypes) << 8;
+ val |= (Uint8)(colliderSpellEvent % 1000) << 16;
+ skill[2] = val;
+}
+
+void Entity::colliderAssignProperties(Entity* entity, bool mapGeneration, map_t* whichMap)
+{
+ auto& rng = mapGeneration ? map_rng : local_rng;
+
+ entity->seedEntityRNG(rng.getU32());
+
+ if ( mapGeneration || multiplayer != CLIENT )
+ {
+ int dir = entity->colliderDecorationRotation;
+ if ( dir == -1 )
+ {
+ dir = rng.rand() % 8;
+ entity->colliderDecorationRotation = dir;
+ }
+ entity->yaw = dir * (PI / 4);
+ }
+ /*static ConsoleVariable debugColliderType("/collider_type", 14);
+ entity->colliderDamageTypes = *debugColliderType;*/
+ auto find = EditorEntityData_t::colliderData.find(entity->colliderDamageTypes);
+ if ( find != EditorEntityData_t::colliderData.end() )
+ {
+ auto& data = find->second;
+ if ( data.hasOverride("dir_offset") )
+ {
+ if ( mapGeneration || multiplayer != CLIENT )
+ {
+ entity->yaw = ((entity->colliderDecorationRotation + data.getOverride("dir_offset")) * (PI / 4));
+ }
+ }
+ if ( data.hasOverride("model") )
+ {
+ entity->colliderDecorationModel = data.getOverride("model");
+ }
+ if ( data.hasOverride("height") )
+ {
+ entity->colliderDecorationHeightOffset = data.getOverride("height");
+ }
+ if ( entity->colliderDecorationRotation == 0 )
+ {
+ if ( data.hasOverride("east_x") )
+ {
+ entity->colliderDecorationXOffset = data.getOverride("east_x");
+ }
+ if ( data.hasOverride("east_y") )
+ {
+ entity->colliderDecorationYOffset = data.getOverride("east_y");
+ }
+ }
+ else if ( entity->colliderDecorationRotation == 2 )
+ {
+ if ( data.hasOverride("south_x") )
+ {
+ entity->colliderDecorationXOffset = data.getOverride("south_x");
+ }
+ if ( data.hasOverride("south_y") )
+ {
+ entity->colliderDecorationYOffset = data.getOverride("south_y");
+ }
+ }
+ else if ( entity->colliderDecorationRotation == 4 )
+ {
+ if ( data.hasOverride("west_x") )
+ {
+ entity->colliderDecorationXOffset = data.getOverride("west_x");
+ }
+ if ( data.hasOverride("west_y") )
+ {
+ entity->colliderDecorationYOffset = data.getOverride("west_y");
+ }
+ }
+ else if ( entity->colliderDecorationRotation == 6 )
+ {
+ if ( data.hasOverride("north_x") )
+ {
+ entity->colliderDecorationXOffset = data.getOverride("north_x");
+ }
+ if ( data.hasOverride("north_y") )
+ {
+ entity->colliderDecorationYOffset = data.getOverride("north_y");
+ }
+ }
+ if ( data.hasOverride("collision") )
+ {
+ entity->colliderHasCollision = data.getOverride("collision");
+ }
+ if ( data.hasOverride("collision_x") )
+ {
+ entity->colliderSizeX = data.getOverride("collision_x");
+ }
+ if ( data.hasOverride("collision_y") )
+ {
+ entity->colliderSizeY = data.getOverride("collision_y");
+ }
+ if ( data.hasOverride("hp") )
+ {
+ entity->colliderMaxHP = data.getOverride("hp");
+ }
+ if ( data.hasOverride("diggable") )
+ {
+ entity->colliderDiggable = data.getOverride("diggable");
+ }
+
+ if ( entity->colliderIsMapGenerated == 0 && (mapGeneration || multiplayer != CLIENT)
+ && entity->entity_rng )
+ {
+ static int lastSpellEvent = 0;
+ if ( data.spellTriggers.size() > 0 )
+ {
+ std::vector chances;
+ bool avoidLastSpell = false;
+ for ( auto s : data.spellTriggers )
+ {
+ chances.push_back(1);
+ if ( lastSpellEvent != 0 && s != lastSpellEvent )
+ {
+ avoidLastSpell = true;
+ }
+ }
+
+ if ( avoidLastSpell )
+ {
+ int unusedChances = 0;
+ for ( size_t i = 0; i < chances.size(); ++i )
+ {
+ if ( chances[i] != lastSpellEvent )
+ {
+ ++unusedChances;
+ }
+ }
+ if ( unusedChances > 0 )
+ {
+ for ( size_t i = 0; i < chances.size(); ++i )
+ {
+ if ( data.spellTriggers[i] == lastSpellEvent )
+ {
+ chances[i] = 0;
+ }
+ }
+ }
+ }
+
+ int pickIndex = entity->entity_rng->discrete(chances.data(), chances.size());
+ int picked = data.spellTriggers[pickIndex];
+ if ( picked > 0 )
+ {
+ if ( entity->entity_rng->rand() % 5 > 0 )
+ {
+ picked += 1000;
+ }
+ entity->colliderSpellEvent = picked;
+ lastSpellEvent = picked % 1000;
+ }
+ }
+ }
+ }
+
+ entity->sprite = entity->colliderDecorationModel;
+ entity->sizex = entity->colliderSizeX;
+ entity->sizey = entity->colliderSizeY;
+
+ if ( mapGeneration || multiplayer != CLIENT )
+ {
+ entity->x += entity->colliderDecorationXOffset * 0.25;
+ entity->y += entity->colliderDecorationYOffset * 0.25;
+ entity->z = 7.5 - entity->colliderDecorationHeightOffset * 0.25;
+ bool modifiedFocal = false;
+ if ( entity->x < 0.0 )
+ {
+ while ( entity->x < 0.0 )
+ {
+ entity->x += 16.0;
+ entity->focalx -= 16.0;
+ }
+ modifiedFocal = true;
+ }
+ if ( entity->y < 0 )
+ {
+ while ( entity->y < 0.0 )
+ {
+ entity->y += 16.0;
+ entity->focaly -= 16.0;
+ }
+ modifiedFocal = true;
+ }
+ if ( static_cast(entity->x) >= whichMap->width * 16 )
+ {
+ while ( static_cast(entity->x) >= whichMap->width * 16 )
+ {
+ entity->x -= 16.0;
+ entity->focalx += 16.0;
+ }
+ modifiedFocal = true;
+ }
+ if ( static_cast(entity->y) >= whichMap->height * 16 )
+ {
+ while ( static_cast(entity->y) >= whichMap->height * 16 )
+ {
+ entity->y -= 16.0;
+ entity->focaly += 16.0;
+ }
+ modifiedFocal = true;
+ }
+ if ( modifiedFocal )
+ {
+ real_t fx = entity->focalx;
+ real_t fy = entity->focaly;
+ entity->focalx = fx * cos(entity->yaw) - fy * cos(entity->yaw + PI / 2);
+ entity->focaly = -fx * sin(entity->yaw) + fy * sin(entity->yaw + PI / 2);
+ }
+ }
+
+ if ( mapGeneration )
+ {
+ entity->flags[PASSABLE] = entity->colliderHasCollision == 0;
+ }
+ else
+ {
+ entity->flags[PASSABLE] = true; // set later in act()
+ }
+ entity->flags[BLOCKSIGHT] = false;
+ entity->behavior = &actColliderDecoration;
+ entity->colliderCurrentHP = entity->colliderMaxHP;
+ entity->colliderOldHP = entity->colliderMaxHP;
+ if ( entity->isDamageableCollider() )
+ {
+ entity->flags[UNCLICKABLE] = false;
+ }
+ else
+ {
+ entity->flags[UNCLICKABLE] = true;
+ }
+ /*if ( multiplayer != CLIENT )
+ {
+ entity_uids--;
+ }
+ entity->setUID(-3);*/
+}
+
+std::set collidersToRaiseToHeight;
void actColliderDecoration(Entity* my)
{
if ( !my )
@@ -934,10 +1877,111 @@ void actColliderDecoration(Entity* my)
{
my->colliderHasCollision |= EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC;
}
+
+ if ( colliderData.name.find("mushroom_spell") != std::string::npos )
+ {
+ collidersToRaiseToHeight.insert(my->getUID());
+ my->z = 16.0;
+ Entity* entity = newEntity(my->sprite + 1, 1, map.entities, nullptr); //Sprite entity.
+ entity->x = my->x;
+ entity->y = my->y;
+ entity->z = my->z - 7.0;
+ entity->behavior = &actColliderMushroomCap;
+ entity->flags[PASSABLE] = true;
+ entity->flags[NOUPDATE] = true;
+ entity->flags[UPDATENEEDED] = false;
+ entity->flags[UNCLICKABLE] = true;
+ entity->yaw = my->yaw;
+ entity->parent = my->getUID();
+ if ( multiplayer != CLIENT )
+ {
+ entity_uids--;
+ }
+ entity->setUID(-3);
+ }
+ else if ( colliderData.name.find("germinate_spell") != std::string::npos )
+ {
+ collidersToRaiseToHeight.insert(my->getUID());
+ my->z = 16.0;
+ }
+ }
+ }
+
+ if ( collidersToRaiseToHeight.find(my->getUID()) != collidersToRaiseToHeight.end() )
+ {
+ // mushroom_spell
+ my->z = std::max(8.0, my->z - 0.25);
+ if ( my->z <= 8.0 )
+ {
+ collidersToRaiseToHeight.erase(my->getUID());
+
+ if ( multiplayer != CLIENT )
+ {
+ auto& colliderData = EditorEntityData_t::colliderData[my->colliderDamageTypes];
+ if ( colliderData.name.find("germinate_spell") != std::string::npos )
+ {
+ Entity* caster = my->colliderCreatedParent != 0 ? uidToEntity(my->colliderCreatedParent) : my;
+ createRadiusMagic(SPELL_HEAL_PULSE, caster,
+ my->x, my->y, 24, 10 * TICKS_PER_SECOND, nullptr);
+ }
+ }
+ }
+ if ( my->z > 8.5 )
+ {
+ if ( my->ticks % 2 == 0 ) {
+ const int x = my->x + local_rng.uniform(-3, 3);
+ const int y = my->y + local_rng.uniform(-3, 3);
+ auto poof = spawnPoof(x, y, 6, 0.33);
+ }
+ }
+ }
+
+ my->removeLightField();
+ if ( my->flags[BURNABLE] && my->flags[BURNING] )
+ {
+ my->light = addLight(my->x / 16, my->y / 16, "object_burning");
+ }
+
+ if ( (my->colliderHasCollision == 0) )
+ {
+ my->flags[PASSABLE] = true;
+ }
+ else if ( my->flags[PASSABLE] )
+ {
+ // check inside
+ std::vector entLists;
+ if ( multiplayer == CLIENT )
+ {
+ entLists.push_back(map.entities); // clients use old map.entities method
+ }
+ else
+ {
+ entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
+ }
+ bool somebodyinside = false;
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !somebodyinside; ++it )
+ {
+ list_t* currentList = *it;
+ for ( node_t* node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( !(entity->behavior == &actPlayer || entity->behavior == &actMonster) )
+ {
+ continue;
+ }
+ if ( entityInsideEntity(my, entity) )
+ {
+ somebodyinside = true;
+ break;
+ }
+ }
+ }
+ if ( !somebodyinside )
+ {
+ my->flags[PASSABLE] = false;
}
}
- my->flags[PASSABLE] = (my->colliderHasCollision == 0);
if ( multiplayer != CLIENT )
{
bool checkWallDeletion = false;
@@ -965,6 +2009,7 @@ void actColliderDecoration(Entity* my)
if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
{
//messagePlayer(0, MESSAGE_DEBUG, "[Collider]: Destroyed self at x: %d, y: %d", x, y);
+ my->removeLightField();
list_RemoveNode(my->mynode);
return;
}
@@ -980,6 +2025,35 @@ void actColliderDecoration(Entity* my)
my->createWorldUITooltip();
}
+ my->colliderTelepathy = 0;
+ if ( my->colliderContainedEntity != 0 )
+ {
+ Sint32 telepathy = 0;
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( players[i]->isLocalPlayer() )
+ {
+ if ( players[i]->entity && players[i]->entity->isBlind() )
+ {
+ if ( stats[i]->type == GNOME )
+ {
+ telepathy |= (1 << i);
+ }
+ }
+ }
+ }
+ if ( telepathy )
+ {
+ if ( Entity* containedEntity = uidToEntity(my->colliderContainedEntity) )
+ {
+ if ( containedEntity->behavior == &actGoldBag )
+ {
+ my->colliderTelepathy = telepathy;
+ }
+ }
+ }
+ }
+
if ( multiplayer != CLIENT )
{
auto& colliderData = EditorEntityData_t::colliderData[my->colliderDamageTypes];
@@ -995,10 +2069,165 @@ void actColliderDecoration(Entity* my)
}
}
+ auto prevOldHP = my->colliderOldHP;
my->colliderOldHP = my->colliderCurrentHP;
if ( my->colliderCurrentHP > 0 )
{
+ if ( my->colliderSpellEvent > 0 )
+ {
+ if ( my->colliderCreatedParent != 0 && my->ticks >= 10 * TICKS_PER_SECOND )
+ {
+ // destroy self
+ my->colliderCurrentHP = 0;
+ my->colliderKillerUid = 0;
+ }
+ else if ( my->colliderSpellEventCooldown > 0 )
+ {
+ my->colliderSpellEventCooldown--;
+ }
+ else if ( (my->colliderSpellEvent % 1000) != 8 && (my->colliderSpellEvent % 1000) != 9 )
+ {
+ Entity* found = nullptr;
+ bool rescan = false;
+ int effectType = my->colliderSpellEvent % 1000;
+ if ( prevOldHP != my->colliderCurrentHP )
+ {
+ my->colliderSpellEventCooldown = 4 * TICKS_PER_SECOND;
+ my->colliderSpellEventTrigger = 100 + 75 + local_rng.rand() % 21;
+ serverUpdateEntitySkill(my, 21);
+ if ( effectType == 3 || effectType == 4 || effectType == 7 )
+ {
+ rescan = true; // look for a random target
+ }
+ }
+
+ if ( (my->colliderSpellEventCooldown == 0 && (my->ticks % TICKS_PER_SECOND == 0 && my->colliderSpellEvent >= 1000)
+ || rescan) )
+ {
+ Entity* caster = (my->colliderCreatedParent != 0 ? uidToEntity(my->colliderCreatedParent) : nullptr);
+ bool targetNonPlayer = false;
+ if ( !caster && achievementObserver.checkUidIsFromPlayer(my->colliderCreatedParent) >= 0 )
+ {
+ targetNonPlayer = true;
+ }
+ int range = colliderGetSpellRange(my);
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1 + (range / 16));
+ std::vector entitiesInRange;
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !found; ++it )
+ {
+ list_t* currentList = *it;
+ node_t* node;
+ for ( node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( !entity || !(entity->behavior == &actPlayer || entity->behavior == &actMonster) ) { continue; }
+ if ( !entity->monsterIsTargetable() ) { continue; }
+ if ( caster )
+ {
+ if ( caster == entity
+ || (caster->checkFriend(entity)
+ && !(caster->behavior == &actMonster &&
+ (caster->monsterTarget == entity->getUID() // caster targeting this
+ || (entity->behavior == &actMonster && entity->monsterTarget == caster->getUID())))) ) // entity targeting caster
+ {
+ continue;
+ }
+ }
+ else
+ {
+ if ( targetNonPlayer )
+ {
+ if ( entity->behavior == &actMonster && !entity->monsterAllyGetPlayerLeader() )
+ {
+ // allowed to target
+ }
+ else
+ {
+ continue;
+ }
+ }
+ else if ( !(entity->behavior == &actPlayer || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) )
+ {
+ continue;
+ }
+ }
+ real_t targetDist = entityDist(entity, my);
+ if ( targetDist >= (range + 4.0) )
+ {
+ continue;
+ }
+ real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
+ real_t traceDist = range;
+ if ( Stat* entitystats = entity->getStats() )
+ {
+ int light = entity->entityLightAfterReductions(*entitystats, my);
+ if ( targetDist > light )
+ {
+ continue;
+ }
+ }
+ lineTraceTarget(my, my->x, my->y, tangent, traceDist, 0, false, entity);
+ if ( hit.entity == entity )
+ {
+ if ( effectType == 3 || effectType == 4 || effectType == 7 )
+ {
+ entitiesInRange.push_back(entity);
+ }
+ else
+ {
+ found = entity;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( effectType == 3 || effectType == 4 || effectType == 7 )
+ {
+ if ( entitiesInRange.size() > 0 )
+ {
+ if ( caster && caster->behavior == &actMonster && caster->monsterTarget != 0 )
+ {
+ for ( auto ent : entitiesInRange )
+ {
+ if ( ent->getUID() == caster->monsterTarget )
+ {
+ found = ent;
+ break;
+ }
+ }
+ }
+ if ( !found )
+ {
+ if ( caster )
+ {
+ for ( auto ent : entitiesInRange )
+ {
+ if ( ent->behavior == &actMonster && ent->monsterTarget == caster->getUID() )
+ {
+ found = ent;
+ break;
+ }
+ }
+ }
+ }
+ if ( !found )
+ {
+ found = entitiesInRange[local_rng.rand() % entitiesInRange.size()];
+ }
+ my->colliderSpellTarget = found->getUID();
+ }
+ }
+ }
+ if ( found )
+ {
+ my->colliderSpellEventCooldown = 4 * TICKS_PER_SECOND;
+ my->colliderSpellEventTrigger = 1 + local_rng.rand() % 75;
+ serverUpdateEntitySkill(my, 21);
+ }
+ }
+ }
if ( my->colliderHideMonster >= 1000 ) // summon a monster when player is near
{
Entity* found = nullptr;
@@ -1015,11 +2244,14 @@ void actColliderDecoration(Entity* my)
if ( entity && (entity->behavior == &actPlayer || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) )
{
real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
- lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity);
- if ( hit.entity == entity )
+ if ( entityDist(entity, my) < 40.0 )
{
- found = entity;
- break;
+ lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity);
+ if ( hit.entity == entity )
+ {
+ found = entity;
+ break;
+ }
}
}
}
@@ -1057,7 +2289,8 @@ void actColliderDecoration(Entity* my)
strcpy(stats->name, "enslaved ghoul");
stats->setAttribute("special_npc", "enslaved ghoul");
}
- if ( stats->type == AUTOMATON )
+ stats->setAttribute("spawn_no_sleep", "1");
+ if ( stats->type == AUTOMATON && strcmp(map.filename, "automat.lmp") )
{
monster->monsterStoreType = 1; // damaged
}
@@ -1074,8 +2307,16 @@ void actColliderDecoration(Entity* my)
}
if ( successes == 1 )
{
- messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6234),
- getMonsterLocalizedName((Monster)type).c_str(), Language::get(my->getColliderLangName()));
+ if ( my->getColliderOnJumpLangEntry() == 6815 )
+ {
+ messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(my->getColliderOnJumpLangEntry()),
+ Language::get(my->getColliderLangName()));
+ }
+ else
+ {
+ messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6234),
+ getMonsterLocalizedName((Monster)type).c_str(), Language::get(my->getColliderLangName()));
+ }
}
else if ( successes > 1 )
{
@@ -1108,8 +2349,9 @@ void actColliderDecoration(Entity* my)
}
}
-void Entity::colliderHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster)
+void Entity::colliderHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster, bool messages, bool doSound)
{
+ updateEntityOldHPBeforeMagicHit(*this, magicProjectile);
auto oldHP = colliderCurrentHP;
colliderCurrentHP -= damage; //Decrease object health.
if ( caster )
@@ -1124,34 +2366,42 @@ void Entity::colliderHandleDamageMagic(int damage, Entity &magicProjectile, Enti
{
if ( oldHP > 0 )
{
- if ( magicProjectile.behavior == &actBomb )
+ if ( messages )
{
- messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3617), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(getColliderLangName()));
- }
- else
- {
- messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(getColliderLangName()));
+ if ( magicProjectile.behavior == &actBomb )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3617), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(getColliderLangName()));
+ }
+ else
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(getColliderLangName()));
+ }
}
if ( isColliderWall() )
{
Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1);
}
+
+ players[caster->skill[2]]->mechanics.incrementBreakableCounter(Player::PlayerMechanics_t::BreakableEvent::GBREAK_COMMON, this);
}
}
else
{
- if ( magicProjectile.behavior == &actBomb )
- {
- messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(3618), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(getColliderLangName()));
- }
- else
+ if ( messages )
{
- messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(378), Language::get(getColliderLangName()));
+ if ( magicProjectile.behavior == &actBomb )
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(3618), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(getColliderLangName()));
+ }
+ else
+ {
+ messagePlayer(caster->skill[2], MESSAGE_COMBAT_BASIC, Language::get(378), Language::get(getColliderLangName()));
+ }
}
}
- updateEnemyBar(caster, this, Language::get(getColliderLangName()), colliderCurrentHP, colliderMaxHP,
- false, DamageGib::DMG_DEFAULT);
}
+ updateEnemyBar(caster, this, Language::get(getColliderLangName()), colliderCurrentHP, colliderMaxHP,
+ false, DamageGib::DMG_DEFAULT);
}
int sound = 28; //damage.ogg
@@ -1159,7 +2409,10 @@ void Entity::colliderHandleDamageMagic(int damage, Entity &magicProjectile, Enti
{
sound = getColliderSfxOnHit();
}
- playSoundEntity(this, sound, 64);
+ if ( doSound )
+ {
+ playSoundEntity(this, sound, 64);
+ }
}
void actFloorDecoration(Entity* my)
@@ -1182,6 +2435,68 @@ void actFloorDecoration(Entity* my)
return;
}
+ if ( my->floorDecorationDestroyIfNoWall >= 0 )
+ {
+ int x = static_cast(my->x) >> 4;
+ int y = static_cast(my->y) >> 4;
+ std::vector> coords;
+ switch ( my->floorDecorationDestroyIfNoWall )
+ {
+ case 0:
+ // east
+ coords.push_back(std::make_pair(x + 1, y));
+ break;
+ case 1:
+ // southeast
+ coords.push_back(std::make_pair(x + 1, y));
+ coords.push_back(std::make_pair(x, y + 1));
+ break;
+ case 2:
+ // south
+ coords.push_back(std::make_pair(x, y + 1));
+ break;
+ case 3:
+ // southwest
+ coords.push_back(std::make_pair(x, y + 1));
+ coords.push_back(std::make_pair(x - 1, y));
+ break;
+ case 4:
+ // west
+ coords.push_back(std::make_pair(x - 1, y));
+ break;
+ case 5:
+ // northwest
+ coords.push_back(std::make_pair(x, y - 1));
+ coords.push_back(std::make_pair(x - 1, y));
+ break;
+ case 6:
+ // north
+ coords.push_back(std::make_pair(x, y - 1));
+ break;
+ case 7:
+ // northeast
+ coords.push_back(std::make_pair(x, y - 1));
+ coords.push_back(std::make_pair(x + 1, y));
+ break;
+ default:
+ break;
+ }
+
+ for ( auto& pair : coords )
+ {
+ int x = pair.first;
+ int y = pair.second;
+ if ( x >= 0 && x < map.width && y >= 0 && y < map.height )
+ {
+ if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
+ {
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+ }
+ }
+
if ( my->floorDecorationInteractText1 == 0 )
{
// no text.
@@ -1231,26 +2546,71 @@ void actFloorDecoration(Entity* my)
if ( charIsWordSeparator(c) ) { break; }
key += c;
}
- if ( ScriptTextParser.allEntries.find(key) != ScriptTextParser.allEntries.end() )
+ auto find = ScriptTextParser.allEntries.find(key);
+ if ( find != ScriptTextParser.allEntries.end() )
{
- if ( players[i]->isLocalPlayer() )
+ if ( find->second.objectType == ScriptTextParser_t::OBJ_SIGN )
{
- if ( players[i]->isLocalPlayerAlive() )
+ if ( players[i]->isLocalPlayer() )
{
- players[i]->signGUI.openSign(key, my->getUID());
+ if ( players[i]->isLocalPlayerAlive() )
+ {
+ players[i]->signGUI.openSign(key, my->getUID());
+ }
+ }
+ else if ( multiplayer == SERVER && i > 0 && !client_disconnected[i] )
+ {
+ strcpy((char*)net_packet->data, "SIGN");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ strcpy((char*)(&net_packet->data[8]), key.c_str());
+ net_packet->address.host = net_clients[i - 1].host;
+ net_packet->address.port = net_clients[i - 1].port;
+ net_packet->len = 8 + strlen(key.c_str()) + 1;
+ sendPacketSafe(net_sock, -1, net_packet, i - 1);
+ }
+ return;
+ }
+ else if ( find->second.objectType == ScriptTextParser_t::OBJ_MESSAGE )
+ {
+ Uint8 r = 255;
+ Uint8 g = 255;
+ Uint8 b = 255;
+ for ( auto& var : find->second.variables )
+ {
+ if ( var.type == ScriptTextParser_t::VariableTypes::COLOR_R )
+ {
+ r = var.numericValue;
+ }
+ else if ( var.type == ScriptTextParser_t::VariableTypes::COLOR_G )
+ {
+ g = var.numericValue;
+ }
+ else if ( var.type == ScriptTextParser_t::VariableTypes::COLOR_B )
+ {
+ b = var.numericValue;
+ }
}
+ messagePlayerColor(i, MESSAGE_INSPECTION, makeColorRGB(r, g, b), find->second.formattedText.c_str());
+ return;
+ }
+ else if ( find->second.objectType == ScriptTextParser_t::OBJ_BUBBLE_SIGN )
+ {
+ players[i]->worldUI.worldTooltipDialogue.createDialogueTooltip(my->getUID(),
+ Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_SIGNPOST, find->second.formattedText.c_str());
+ return;
}
- else if ( multiplayer == SERVER && i > 0 && !client_disconnected[i] )
+ else if ( find->second.objectType == ScriptTextParser_t::OBJ_BUBBLE_GRAVE )
{
- strcpy((char*)net_packet->data, "SIGN");
- SDLNet_Write32(my->getUID(), &net_packet->data[4]);
- strcpy((char*)(&net_packet->data[8]), key.c_str());
- net_packet->address.host = net_clients[i - 1].host;
- net_packet->address.port = net_clients[i - 1].port;
- net_packet->len = 8 + strlen(key.c_str()) + 1;
- sendPacketSafe(net_sock, -1, net_packet, i - 1);
+ players[i]->worldUI.worldTooltipDialogue.createDialogueTooltip(my->getUID(),
+ Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_GRAVE, find->second.formattedText.c_str());
+ return;
+ }
+ else if ( find->second.objectType == ScriptTextParser_t::OBJ_BUBBLE_DIALOGUE )
+ {
+ players[i]->worldUI.worldTooltipDialogue.createDialogueTooltip(my->getUID(),
+ Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, find->second.formattedText.c_str());
+ return;
}
- return;
}
}
@@ -1418,6 +2778,18 @@ int TextSourceScript::textSourceProcessScriptTag(std::string& input, std::string
x2 = std::min(std::max(x1, x2), (int)map.width - 1);
y2 = std::min(std::max(y1, y2), (int)map.height - 1);
+ if ( findTag.compare("@explode=") == 0 )
+ {
+ int explosionSprite = 0;
+ size_t foundSeperator = tagValue.find(";");
+ if ( foundSeperator != std::string::npos )
+ {
+ std::string first_str = tagValue.substr(foundSeperator + 1, tagValue.length() - foundSeperator);
+ explosionSprite = std::stoi(first_str);
+ }
+ return ((x1 & 0xFF) << 16) + ((y1 & 0xFF) << 24) + ((explosionSprite & 0xFFFF) << 0);
+ }
+
return (x1 & 0xFF) + ((x2 & 0xFF) << 8) + ((y1 & 0xFF) << 16) + ((y2 & 0xFF) << 24);
}
else
@@ -1495,10 +2867,26 @@ int TextSourceScript::textSourceProcessScriptTag(std::string& input, std::string
{
return TO_ITEMS;
}
+ else if ( !tagValue.compare("gold") )
+ {
+ return TO_GOLD;
+ }
+ else if ( !tagValue.compare("bell") )
+ {
+ return TO_BELL;
+ }
else if ( !tagValue.compare("players") )
{
return TO_PLAYERS;
}
+ else if ( !tagValue.compare("breakable") )
+ {
+ return TO_BREAKABLE;
+ }
+ else if ( !tagValue.compare("collider") )
+ {
+ return TO_COLLIDER;
+ }
else
{
for ( int i = 0; i < NUMMONSTERS; ++i )
@@ -1529,6 +2917,10 @@ int TextSourceScript::textSourceProcessScriptTag(std::string& input, std::string
{
return TRIGGER_ATTACHED_VISIBLE;
}
+ else if ( !tagValue.compare("attached#interacted") )
+ {
+ return TRIGGER_ATTACHED_INTERACTED;
+ }
else if ( !tagValue.compare("always") )
{
return TRIGGER_ATTACHED_ALWAYS;
@@ -1538,6 +2930,17 @@ int TextSourceScript::textSourceProcessScriptTag(std::string& input, std::string
return k_ScriptError;
}
}
+ else if ( findTag.compare("@breakablespawn=") == 0 || findTag.compare("@breakablejumpspawn=") == 0 )
+ {
+ for ( int i = 0; i < NUMMONSTERS; ++i )
+ {
+ if ( !tagValue.compare(monstertypename[i]) )
+ {
+ return TO_NOTHING + i;
+ }
+ }
+ return k_ScriptError;
+ }
return std::stoi(tagValue);
}
@@ -1633,8 +3036,12 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
if ( (entity->behavior == &actMonster && attachTo == TO_MONSTERS)
|| (entity->behavior == &actPlayer && attachTo == TO_PLAYERS)
|| (entity->behavior == &actItem && attachTo == TO_ITEMS)
+ || (entity->behavior == &actGoldBag && attachTo == TO_GOLD)
+ || (entity->behavior == &actBell && attachTo == TO_BELL)
+ || (entity->isColliderBreakableContainer() && attachTo == TO_BREAKABLE)
+ || (entity->isDamageableCollider() && attachTo == TO_COLLIDER)
|| (entity->behavior == &actMonster
- && attachTo >= TO_NOTHING && attachTo <= TO_DUMMYBOT
+ && attachTo >= TO_NOTHING && attachTo < TO_MONSTER_MAX
&& entity->getRace() == (attachTo - TO_NOTHING)) )
{
// found our entity.
@@ -1716,6 +3123,10 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
{
result = CLASS_BARBARIAN;
}
+ if ( !enabledDLCPack3 && result >= CLASS_BARD && result <= CLASS_PALADIN )
+ {
+ result = CLASS_BARBARIAN;
+ }
std::vector applyToEntities;
if ( processOnAttachedEntity )
@@ -2150,6 +3561,10 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
}
if ( multiplayer == SERVER )
{
+ if ( entity->behavior == &actPlayer && entity->skill[2] > 0 )
+ {
+ serverUpdateEffects(entity->skill[2]);
+ }
entity->serverUpdateEffectsForEntity(true);
}
}
@@ -2177,6 +3592,10 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
}
if ( multiplayer == SERVER )
{
+ if ( entity->behavior == &actPlayer && entity->skill[2] > 0 )
+ {
+ serverUpdateEffects(entity->skill[2]);
+ }
entity->serverUpdateEffectsForEntity(true);
}
}
@@ -2329,6 +3748,75 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
}
}
}
+ else if ( (*it).find("@damage=") != std::string::npos )
+ {
+ int result = textSourceProcessScriptTag(input, "@damage=", src);
+ if ( result != k_ScriptError )
+ {
+ int damage = result;
+ if ( processOnAttachedEntity )
+ {
+ for ( auto entity : attachedEntities )
+ {
+ if ( entity->behavior == &actChest )
+ {
+ entity->chestHealth -= damage;
+ }
+ else if ( entity->behavior == &actMonster )
+ {
+ entity->modHP(-damage);
+ }
+ else if ( entity->behavior == &actDoor )
+ {
+ entity->doorHealth -= damage;
+ }
+ else if ( entity->behavior == &actFurniture )
+ {
+ entity->furnitureHealth -= damage;
+ }
+ else if ( entity->isDamageableCollider() )
+ {
+ entity->colliderCurrentHP -= damage;
+ }
+ }
+ }
+ }
+ }
+ else if ( (*it).find("@explode=") != std::string::npos )
+ {
+ int result = textSourceProcessScriptTag(input, "@explode=", src);
+ if ( result != k_ScriptError )
+ {
+ int explosionSprite = (result >> 0) & 0xFFFF;
+ int x1 = (result >> 16) & 0xFF;
+ int y1 = (result >> 24) & 0xFF;
+ if ( processOnAttachedEntity )
+ {
+ for ( auto entity : attachedEntities )
+ {
+ if ( explosionSprite > 0 )
+ {
+ spawnExplosionFromSprite(explosionSprite, entity->x, entity->y, -4 + local_rng.rand() % 8);
+ }
+ else
+ {
+ spawnExplosion(entity->x, entity->y, -4 + local_rng.rand() % 8);
+ }
+ }
+ }
+ else
+ {
+ if ( explosionSprite > 0 )
+ {
+ spawnExplosionFromSprite(explosionSprite, x1 * 16.0 + 8.0, y1 * 16.0 + 8.0, -4 + local_rng.rand() % 8);
+ }
+ else
+ {
+ spawnExplosion(x1 * 16.0 + 8.0, y1 * 16.0 + 8.0, -4 + local_rng.rand() % 8);
+ }
+ }
+ }
+ }
else if ( (*it).find("@nodropitems=") != std::string::npos )
{
int result = textSourceProcessScriptTag(input, "@nodropitems=", src);
@@ -2907,6 +4395,149 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input)
}
}
}
+ else if ( (*it).find("@addtobreakable=") != std::string::npos )
+ {
+ int result = textSourceProcessScriptTag(input, "@addtobreakable=", src);
+ if ( result != k_ScriptError )
+ {
+ int x1 = result & 0xFF;
+ int x2 = (result >> 8) & 0xFF;
+ int y1 = (result >> 16) & 0xFF;
+ int y2 = (result >> 24) & 0xFF;
+ std::vector applyToEntities;
+ if ( processOnAttachedEntity
+ && (textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_ITEMS
+ || textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_GOLD) )
+ {
+ for ( auto entity : attachedEntities )
+ {
+ if ( textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_ITEMS )
+ {
+ if ( entity->behavior == &actItem )
+ {
+ applyToEntities.push_back(entity);
+ }
+ }
+ else if ( textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_GOLD )
+ {
+ if ( entity->behavior == &actGoldBag )
+ {
+ applyToEntities.push_back(entity);
+ }
+ }
+ }
+ Entity* breakable = nullptr;
+ for ( node_t* node = map.entities->first; node; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity && entity->isColliderBreakableContainer() )
+ {
+ int findx = static_cast(entity->x) >> 4;
+ int findy = static_cast(entity->y) >> 4;
+ if ( findx >= x1 && findx <= x2 && findy >= y1 && findy <= y2 )
+ {
+ breakable = entity;
+ }
+ }
+ }
+ if ( breakable )
+ {
+ for ( auto entity : applyToEntities )
+ {
+ if ( entity->behavior == &actItem )
+ {
+ if ( breakable->colliderContainedEntity != 0 )
+ {
+ break; // only 1 item
+ }
+ entity->itemContainer = breakable->getUID();
+ }
+ else
+ {
+ entity->goldInContainer = breakable->getUID();
+ }
+ entity->flags[INVISIBLE] = true;
+ serverUpdateEntityFlag(entity, INVISIBLE);
+ breakable->colliderContainedEntity = entity->getUID();
+ }
+ }
+ }
+ else
+ {
+ // not implemented, needs to be attached to items.
+ }
+ }
+ }
+ else if ( (*it).find("@breakablespawn=") != std::string::npos )
+ {
+ int result = textSourceProcessScriptTag(input, "@breakablespawn=", src);
+ if ( result != k_ScriptError )
+ {
+ std::vector applyToEntities;
+ if ( processOnAttachedEntity && textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_BREAKABLE )
+ {
+ for ( auto entity : attachedEntities )
+ {
+ if ( entity->isColliderBreakableContainer() )
+ {
+ applyToEntities.push_back(entity);
+ }
+ }
+
+ if ( result >= TO_NOTHING && result < TO_MONSTER_MAX )
+ {
+ int monsterType = NOTHING + (result - TO_NOTHING);
+ for ( auto entity : applyToEntities )
+ {
+ if ( entity->colliderHideMonster != 0 )
+ {
+ continue;
+ }
+ entity->colliderHideMonster = monsterType;
+ }
+ }
+ }
+ else
+ {
+ // not implemented, needs to be attached to breakables.
+ }
+ }
+ }
+ else if ( (*it).find("@breakablejumpspawn=") != std::string::npos )
+ {
+ int result = textSourceProcessScriptTag(input, "@breakablejumpspawn=", src);
+ if ( result != k_ScriptError )
+ {
+ std::vector applyToEntities;
+ if ( processOnAttachedEntity && textSourceScript.getAttachedToEntityType(src.textSourceIsScript) == textSourceScript.TO_BREAKABLE )
+ {
+ for ( auto entity : attachedEntities )
+ {
+ if ( entity->isColliderBreakableContainer() )
+ {
+ applyToEntities.push_back(entity);
+ }
+ }
+
+ if ( result >= TO_NOTHING && result < TO_MONSTER_MAX )
+ {
+ int monsterType = NOTHING + (result - TO_NOTHING);
+ for ( auto entity : applyToEntities )
+ {
+ if ( entity->colliderHideMonster != 0 )
+ {
+ continue;
+ }
+ entity->colliderHideMonster = 1000 + monsterType;
+ }
+ }
+ }
+ else
+ {
+ // not implemented, needs to be attached to breakables.
+ }
+ }
+ }
else
{
for ( int i = 0; i < NUMSTATS; ++i )
@@ -3250,6 +4881,35 @@ void Entity::actTextSource()
}
else
{
+ if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_INTERACTED )
+ {
+ if ( textSourceScript.getAttachedToEntityType(textSourceIsScript) == textSourceScript.TO_BELL )
+ {
+ bool doEffect = false;
+ for ( node_t* node = children.first; node; node = node->next )
+ {
+ Uint32 entityUid = *((Uint32*)node->element);
+ Entity* child = uidToEntity(entityUid);
+ if ( child )
+ {
+ if ( child->behavior == &actBell )
+ {
+ if ( (child->skill[0] & 0xFF) // BELL_ACTIVE_TIMER countdown
+ || child->skill[10] > 0 ) // BELL_BULB_BROKEN
+ {
+ doEffect = true;
+ }
+ }
+ }
+ }
+ if ( doEffect )
+ {
+ textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
+ powered = true;
+ }
+ }
+ }
+
if ( textSourceScript.getTriggerType(textSourceIsScript) == textSourceScript.TRIGGER_ATTACHED_EXISTS )
{
textSourceScript.setScriptType(textSourceIsScript, textSourceScript.SCRIPT_ATTACHED_FIRED);
@@ -3494,6 +5154,7 @@ void TextSourceScript::playerClearInventory(bool clearStats)
spellcastingAnimationManager_deactivate(&cast_animation[clientnum]);
stats[clientnum]->freePlayerEquipment();
list_FreeAll(&stats[clientnum]->inventory);
+ list_FreeAll(&stats[clientnum]->void_chest_inventory);
players[clientnum]->shootmode = true;
players[clientnum]->inventoryUI.appraisal.timer = 0;
players[clientnum]->inventoryUI.appraisal.current_item = 0;
@@ -3550,6 +5211,52 @@ std::string TextSourceScript::getScriptFromEntity(Entity& src)
return buf;
}
+Entity* TextSourceScript::createScriptEntityInMapGen(int x, int y, const char* text)
+{
+ Entity* scriptEntity = newEntity(132, 1, map.entities, nullptr); // text script
+ setSpriteAttributes(scriptEntity, nullptr, nullptr);
+ scriptEntity->x = x * 16.0;
+ scriptEntity->y = y * 16.0;
+ scriptEntity->textSourceDelay = 1;
+
+ if ( text )
+ {
+ addScriptToTextSource(*scriptEntity, text);
+ }
+
+ return scriptEntity;
+}
+
+void TextSourceScript::addScriptToTextSource(Entity& src, const char* text)
+{
+ if ( !text ) { return; }
+
+ for ( int skill = 4; skill < 60; ++skill )
+ {
+ if ( skill == 28 ) { continue; }
+ src.skill[skill] = 0;
+ }
+
+ int skillnum = 4;
+ int len = strlen(text);
+ int encodeIndex = 0;
+ for ( int s = 0; s < len; ++s )
+ {
+ src.skill[skillnum] |= ((text[s]) << (encodeIndex * 8));
+ ++encodeIndex;
+ if ( encodeIndex == 4 )
+ {
+ encodeIndex = 0;
+ ++skillnum;
+ if ( skillnum == 28 ) { ++skillnum; }
+ }
+ if ( skillnum >= 60 )
+ {
+ break;
+ }
+ }
+}
+
void TextSourceScript::parseScriptInMapGeneration(Entity& src)
{
std::string script = getScriptFromEntity(src);
@@ -3620,8 +5327,12 @@ void TextSourceScript::parseScriptInMapGeneration(Entity& src)
if ( (entity->behavior == &actMonster && attachTo == TO_MONSTERS)
|| (entity->behavior == &actPlayer && attachTo == TO_PLAYERS)
|| (entity->behavior == &actItem && attachTo == TO_ITEMS)
+ || (entity->behavior == &actGoldBag && attachTo == TO_GOLD)
+ || (entity->behavior == &actBell && attachTo == TO_BELL)
+ || (entity->isColliderBreakableContainer() && attachTo == TO_BREAKABLE)
+ || (entity->isDamageableCollider() && attachTo == TO_COLLIDER)
|| (entity->behavior == &actMonster
- && attachTo >= TO_NOTHING && attachTo <= TO_DUMMYBOT
+ && attachTo >= TO_NOTHING && attachTo < TO_MONSTER_MAX
&& entity->getRace() == (attachTo - TO_NOTHING)) )
{
// found our entity.
@@ -3669,7 +5380,7 @@ void bellAttractMonsters(Entity* my)
&& myStats
&& entityDist(my, entity) > TOUCHRANGE )
{
- if ( !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN]
+ if ( !myStats->getEffectActive(EFF_DISTRACTED_COOLDOWN)
&& entity->monsterSetPathToLocation(my->x / 16, my->y / 16, 1,
GeneratePathTypes::GENERATE_PATH_DEFAULT, true) && entity->children.first )
{
@@ -3721,7 +5432,16 @@ int getBellDmgOnEntity(Entity* entity)
damage *= mult;
}
- if ( stats->helmet )
+ if ( entity->onEntityTrapHitSacredPath(nullptr) )
+ {
+ if ( entity->behavior == &actPlayer )
+ {
+ messagePlayerColor(entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0),
+ Language::get(6491));
+ }
+ playSoundEntity(entity, 166, 128);
+ }
+ else if ( stats->helmet )
{
bool shapeshifted = (entity->behavior == &actPlayer && entity->effectShapeshift != NOTHING);
@@ -3740,7 +5460,10 @@ int getBellDmgOnEntity(Entity* entity)
}
damage = 0;
}
- stats->helmet->status = BROKEN;
+ if ( !entity->spellEffectPreserveItem(stats->helmet) )
+ {
+ stats->helmet->status = BROKEN;
+ }
}
else if ( stats->helmet->type == HELM_MINING )
{
@@ -3766,9 +5489,12 @@ int getBellDmgOnEntity(Entity* entity)
}
}
damage *= mult;
- if ( stats->helmet->status > BROKEN )
+ if ( !entity->spellEffectPreserveItem(stats->helmet) )
{
- stats->helmet->status = (Status)((int)stats->helmet->status - 1);
+ if ( stats->helmet->status > BROKEN )
+ {
+ stats->helmet->status = (Status)((int)stats->helmet->status - 1);
+ }
}
}
@@ -3791,9 +5517,10 @@ int getBellDmgOnEntity(Entity* entity)
strcpy((char*)net_packet->data, "ARMR");
net_packet->data[4] = 0;
net_packet->data[5] = stats->helmet->status;
+ SDLNet_Write16((int)stats->helmet->type, &net_packet->data[6]);
net_packet->address.host = net_clients[player - 1].host;
net_packet->address.port = net_clients[player - 1].port;
- net_packet->len = 6;
+ net_packet->len = 8;
sendPacketSafe(net_sock, -1, net_packet, player - 1);
}
}
@@ -4233,8 +5960,8 @@ void actBell(Entity* my)
bellBreakBulb(my, true);
my->flags[BURNING] = false;
my->flags[INVISIBLE] = true;
- serverUpdateEntitySkill(my, INVISIBLE);
- serverUpdateEntitySkill(my, BURNING);
+ serverUpdateEntityFlag(my, INVISIBLE);
+ serverUpdateEntityFlag(my, BURNING);
if ( BELL_PULLED_TO_BREAK != 0 )
{
@@ -4254,9 +5981,11 @@ void actBell(Entity* my)
if ( my->flags[INVISIBLE] && my->flags[BURNING] )
{
my->flags[BURNING] = false;
+ my->flags[BURNABLE] = false;
if ( multiplayer != CLIENT )
{
- serverUpdateEntitySkill(my, BURNING);
+ serverUpdateEntityFlag(my, BURNING);
+ serverUpdateEntityFlag(my, BURNABLE);
}
}
@@ -4849,17 +6578,9 @@ void actBell(Entity* my)
{
int amount = 15;
entity->modHP(amount);
- entity->modMP(amount);
- if ( svFlags & SV_FLAG_HUNGER )
- {
- if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 )
- {
- Sint32 hungerPointPerMana = entity->playerInsectoidHungerValueOfManaPoint(*stats);
- stats->HUNGER += amount * hungerPointPerMana;
- stats->HUNGER = std::min(999, stats->HUNGER);
- serverUpdateHunger(entity->skill[2]);
- }
- }
+ int mpAmount = entity->modMP(amount);
+ entity->playerInsectoidIncrementHungerToMP(mpAmount);
+
playSoundEntity(entity, 168, 128);
spawnDamageGib(entity, NOTE_DOUBLE_EIGHTH, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true);
spawnMagicEffectParticles(entity->x, entity->y, entity->z, 169);
diff --git a/src/actgib.cpp b/src/actgib.cpp
index e56753fa1..eacce0b2b 100644
--- a/src/actgib.cpp
+++ b/src/actgib.cpp
@@ -19,6 +19,7 @@
#include "player.hpp"
#include "prng.hpp"
#include "ui/GameUI.hpp"
+#include "scores.hpp"
/*-------------------------------------------------------------------------------
@@ -33,11 +34,22 @@
#define GIB_VELY my->vel_y
#define GIB_VELZ my->vel_z
#define GIB_GRAVITY my->fskill[3]
+#define GIB_SHRINK my->fskill[4]
#define GIB_LIFESPAN my->skill[4]
-#define GIB_PLAYER my->skill[11]
#define GIB_POOF my->skill[5]
#define GIB_LIGHTING my->skill[6]
#define GIB_DMG_MISS my->skill[7]
+#define GIB_SWIRL my->fskill[5]
+#define GIB_OSC_H my->fskill[6]
+#define GIB_VEL_DECAY my->fskill[7]
+#define GIB_ORBIT_X my->fskill[8]
+#define GIB_ORBIT_Y my->fskill[9]
+#define GIB_FOCI_FLOAT1 my->fskill[10]
+#define GIB_FOCI_FLOAT2 my->fskill[11]
+#define GIB_FOCI_FLOAT3 my->fskill[12]
+#define GIB_DELAY_MOVE my->skill[8]
+#define GIB_HIT_GROUND my->skill[9]
+#define GIB_PLAYER my->skill[11]
void poof(Entity* my) {
if (GIB_POOF) {
@@ -58,7 +70,7 @@ void actGib(Entity* my)
// don't update gibs that have no velocity
if ( my->z == 8 && fabs(GIB_VELX) < .01 && fabs(GIB_VELY) < .01 )
{
- poof(my);
+ poof(my);
my->removeLightField();
list_RemoveNode(my->mynode);
return;
@@ -67,37 +79,106 @@ void actGib(Entity* my)
// remove gibs that have exceeded their life span
if ( my->ticks > GIB_LIFESPAN && GIB_LIFESPAN )
{
- poof(my);
- my->removeLightField();
- list_RemoveNode(my->mynode);
- return;
+ if ( GIB_SHRINK > 0.0001 )
+ {
+ my->scalex -= GIB_SHRINK;
+ my->scaley -= GIB_SHRINK;
+ my->scalez -= GIB_SHRINK;
+ if ( my->scalex < 0.0 )
+ {
+ poof(my);
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+ else
+ {
+ poof(my);
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
}
if ( my->flags[OVERDRAW]
- && players[clientnum] && players[clientnum]->entity && players[clientnum]->entity->skill[3] == 1 )
+ && players[clientnum] && players[clientnum]->entity && players[clientnum]->entity->skill[3] != 0 )
{
// debug cam, don't draw overdrawn.
my->flags[INVISIBLE] = true;
}
// horizontal motion
- my->yaw += sqrt(GIB_VELX * GIB_VELX + GIB_VELY * GIB_VELY) * .05;
- my->x += GIB_VELX;
- my->y += GIB_VELY;
- GIB_VELX = GIB_VELX * .95;
- GIB_VELY = GIB_VELY * .95;
+ if ( GIB_DELAY_MOVE > 0 )
+ {
+ --GIB_DELAY_MOVE;
+ if ( GIB_LIFESPAN )
+ {
+ ++GIB_LIFESPAN;
+ }
+ }
+ else
+ {
+ my->yaw += sqrt(GIB_VELX * GIB_VELX + GIB_VELY * GIB_VELY) * .05;
+
+ my->x += GIB_VELX;
+ my->y += GIB_VELY;
+ if ( GIB_VEL_DECAY > 0.001 )
+ {
+ GIB_VELX = GIB_VELX * GIB_VEL_DECAY;
+ GIB_VELY = GIB_VELY * GIB_VEL_DECAY;
+ }
+ else
+ {
+ GIB_VELX = GIB_VELX * .95;
+ GIB_VELY = GIB_VELY * .95;
+ }
+ }
if ( GIB_LIGHTING )
{
my->removeLightField();
}
+ if ( my->actGibHitGroundEvent == 1 )
+ {
+ if ( my->sprite == 245 && my->flags[SPRITE] )
+ {
+ if ( my->ticks % 5 == 0 )
+ {
+ spawnGreasePuddleSpawner(my->parent == 0 ? nullptr : uidToEntity(my->parent), my->x, my->y, 30 * TICKS_PER_SECOND);
+ }
+ if ( GIB_HIT_GROUND == 0 )
+ {
+ if ( Entity* fx = spawnMagicParticleCustom(my, 245, 1.0, 1.0) )
+ {
+ fx->ditheringDisabled = true;
+ real_t dir = atan2(GIB_VELY, GIB_VELX);
+ //dir += local_rng.rand() % 2 == 0 ? PI / 32 : -PI / 32;
+ real_t spd = sqrt(GIB_VELX * GIB_VELX + GIB_VELY * GIB_VELY);
+ fx->vel_x = spd * 0.05 * cos(dir);
+ fx->vel_y = spd * 0.05 * sin(dir);
+ fx->flags[BRIGHT] = true;
+ fx->pitch = PI / 2;
+ fx->roll = 0.0;
+ fx->yaw = dir;
+ fx->vel_z = 0.0;
+ fx->flags[SPRITE] = true;
+ }
+ }
+ }
+ }
+
// gravity
if ( my->z < 8 )
{
- GIB_VELZ += GIB_GRAVITY;
- my->z += GIB_VELZ;
- my->roll += 0.1;
+ if ( GIB_DELAY_MOVE == 0 )
+ {
+ GIB_VELZ += GIB_GRAVITY;
+ my->z += GIB_VELZ;
+
+ my->roll += 0.1;
+ }
if ( GIB_LIGHTING && my->flags[SPRITE] && my->sprite >= 180 && my->sprite <= 184 )
{
@@ -137,6 +218,17 @@ void actGib(Entity* my)
}
else
{
+ if ( GIB_HIT_GROUND == 0 )
+ {
+ GIB_HIT_GROUND = 1;
+ if ( my->sprite >= 1895 && my->sprite <= 1903 )
+ {
+ spawnMiscPuddle(my, my->x, my->y, my->sprite + 8);
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
GIB_VELZ = 0;
my->z = 8;
my->roll = PI / 2.0;
@@ -160,6 +252,590 @@ void actGib(Entity* my)
}
}
+void actFociGib(Entity* my)
+{
+ // remove gibs that have exceeded their life span
+ //if ( multiplayer != CLIENT )
+ {
+ if ( my->ticks > GIB_LIFESPAN && GIB_LIFESPAN )
+ {
+ if ( GIB_SHRINK > 0.0001 )
+ {
+ my->scalex -= GIB_SHRINK;
+ my->scaley -= GIB_SHRINK;
+ my->scalez -= GIB_SHRINK;
+ if ( my->scalex < 0.0 )
+ {
+ poof(my);
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+ else
+ {
+ poof(my);
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+ }
+
+//#ifdef USE_FMOD
+// if ( my->entity_sound )
+// {
+// bool isPlaying = false;
+// my->entity_sound->isPlaying(&isPlaying);
+// if ( isPlaying )
+// {
+// FMOD_VECTOR position;
+// position.x = (float)(my->x / (real_t)16.0);
+// position.y = (float)(0.0);
+// position.z = (float)(my->y / (real_t)16.0);
+// fmod_result = my->entity_sound->set3DAttributes(&position, nullptr);
+// }
+// }
+//#endif
+
+ Entity* parent = uidToEntity(my->parent);
+
+ // horizontal motion
+ //if ( multiplayer != CLIENT )
+ {
+ if ( GIB_DELAY_MOVE > 0 )
+ {
+ --GIB_DELAY_MOVE;
+ if ( GIB_LIFESPAN )
+ {
+ ++GIB_LIFESPAN;
+ }
+ }
+ else
+ {
+ my->focalx = 0.0;
+ my->focaly = 0.0;
+ bool tryHitEntity = false;
+
+ if ( GIB_SWIRL > 0.00001 )
+ {
+ my->yaw += GIB_SWIRL;
+ Uint32 vortexStart = TICKS_PER_SECOND / 8;
+ if ( my->ticks >= vortexStart )
+ {
+ real_t vortexTime = 0.5 * TICKS_PER_SECOND;
+ real_t ratio = 2.0 + 2.0 * sin((-PI / 2) + (PI)*std::min((Uint32)vortexTime, my->ticks - vortexStart) / vortexTime);
+ my->focalx += ratio * cos(my->yaw);
+ my->focaly += ratio * sin(my->yaw);
+ }
+ }
+ else
+ {
+ if ( my->actGibMagicParticle > 0 )
+ {
+ my->pitch = atan(my->vel_z / std::max(1.0, sqrt(GIB_VELX * GIB_VELX + GIB_VELY * GIB_VELY)));
+
+ if ( my->sprite == 2153 || my->sprite == 2155 )
+ {
+ if ( my->ticks % 4 == 0 )
+ {
+ my->roll += PI / 2;// (local_rng.rand() % 8)* PI / 2;
+ }
+ my->focalx = 4.0;
+ my->flags[INVISIBLE] = true;
+ }
+ else if ( my->sprite >= 233 && my->sprite <= 244 )
+ {
+ if ( my->ticks % 4 == 0 )
+ {
+ if ( my->sprite < 244 )
+ {
+ ++my->sprite;
+ }
+ }
+ }
+ else if ( my->sprite == 2154 )
+ {
+ my->x = GIB_ORBIT_X;
+ my->y = GIB_ORBIT_Y;
+
+ real_t scale = 2.0;
+ GIB_FOCI_FLOAT2 += 0.2;
+ real_t ratio = scale + scale * sin(GIB_FOCI_FLOAT2);
+
+ real_t dir = atan2(my->vel_y, my->vel_x);
+ my->x -= 2.0 * cos(dir) * sin(std::min(PI, GIB_FOCI_FLOAT2));
+ my->y -= 2.0 * sin(dir) * sin(std::min(PI, GIB_FOCI_FLOAT2));
+
+ if ( GIB_FOCI_FLOAT1 == 0 )
+ {
+ GIB_FOCI_FLOAT1 = -1 + ((local_rng.rand() % 2) ? 2 : 0);
+ }
+
+ my->x += 4.0 * GIB_FOCI_FLOAT1 * cos(dir + PI / 2) * sin(std::min(PI, GIB_FOCI_FLOAT2 * 0.4));
+ my->y += 4.0 * GIB_FOCI_FLOAT1 * sin(dir + PI / 2) * sin(std::min(PI, GIB_FOCI_FLOAT2 * 0.4));
+
+ if ( GIB_FOCI_FLOAT2 >= PI )
+ {
+ //GIB_ORBIT_X += my->vel_x;
+ //GIB_ORBIT_Y += my->vel_y;
+
+ real_t dist = clipMove(&GIB_ORBIT_X, &GIB_ORBIT_Y, my->vel_x, my->vel_y, my);
+ if ( multiplayer != CLIENT )
+ {
+ if ( dist != sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2)) )
+ {
+ if ( hit.entity )
+ {
+ tryHitEntity = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ /*if ( parent )
+ {
+ GIB_ORBIT_X = parent->x;
+ GIB_ORBIT_Y = parent->y;
+ }*/
+ }
+
+ GIB_FOCI_FLOAT3 += 0.3;
+ my->focalz = 0.25;
+ my->roll = fmod(GIB_FOCI_FLOAT3, 2 * PI);
+ }
+ }
+ }
+
+ if ( abs(GIB_OSC_H) > 0.00001 )
+ {
+ Uint32 vortexStart = 0;// TICKS_PER_SECOND / 8;
+ if ( my->ticks >= vortexStart )
+ {
+ //my->yaw = atan2(my->vel_y, my->vel_x);
+ real_t vortexTime = 0.2 * TICKS_PER_SECOND;
+ real_t magnitude = GIB_OSC_H;
+ real_t ratio = magnitude * sin((PI) * ((my->ticks - vortexStart) / vortexTime));
+ real_t tangent = 0.0;// my->yaw - atan2(my->vel_y, my->vel_x);
+ my->focalx += ratio * cos(tangent);
+ my->focaly += ratio * sin(tangent);
+ }
+ }
+
+ bool doMove = true;
+ if ( my->sprite == 2154 /*&& GIB_FOCI_FLOAT2 < PI*/ )
+ {
+ doMove = false;
+ }
+ else
+ {
+ real_t dist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
+ if ( multiplayer != CLIENT )
+ {
+ if ( dist != sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2)) )
+ {
+ if ( hit.entity )
+ {
+ tryHitEntity = true;
+ }
+ }
+ }
+ }
+
+ if ( multiplayer != CLIENT )
+ {
+ spell_t* spell = nullptr;
+ if ( my->children.first && my->children.first->element )
+ {
+ spell = (spell_t*)(my->children.first->element);
+ }
+
+ /*Entity* particle = spawnMagicParticle(my);
+ particle->sprite = 942;
+ particle->x = my->x + my->sizex;
+ particle->y = my->y + my->sizey;
+ particle->z = 0;
+
+ particle = spawnMagicParticle(my);
+ particle->sprite = 942;
+ particle->x = my->x - my->sizex;
+ particle->y = my->y + my->sizey;
+ particle->z = 0;
+
+ particle = spawnMagicParticle(my);
+ particle->sprite = 942;
+ particle->x = my->x + my->sizex;
+ particle->y = my->y - my->sizey;
+ particle->z = 0;
+
+ particle = spawnMagicParticle(my);
+ particle->sprite = 942;
+ particle->x = my->x - my->sizex;
+ particle->y = my->y - my->sizey;
+ particle->z = 0;*/
+
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && spell; ++it )
+ {
+ list_t* currentList = *it;
+ bool lastEntity = false;
+ for ( node_t* node = currentList->first;; )
+ {
+ Entity* entity = nullptr;
+ if ( node == nullptr ) // at the end of the list try the hit.entity from clipMove
+ {
+ lastEntity = true;
+ if ( tryHitEntity )
+ {
+ entity = hit.entity;
+ tryHitEntity = false;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ entity = (Entity*)node->element;
+ node = node->next;
+ }
+ if ( entity == parent || entity == my )
+ {
+ continue;
+ }
+
+ bool nonCreatureDamage = true;
+ if ( spell->ID == SPELL_FOCI_SNOW )
+ {
+ nonCreatureDamage = false;
+ }
+
+ if ( entity->behavior != &actMonster
+ && !entity->isInertMimic()
+ && entity->behavior != &actPlayer )
+ {
+ if ( nonCreatureDamage )
+ {
+ if ( entity->behavior != &actDoor
+ && entity->behavior != &::actIronDoor
+ && !(entity->isDamageableCollider() && entity->isColliderDamageableByMagic())
+ && entity->behavior != &::actChest
+ && entity->behavior != &::actFurniture )
+ {
+ if ( (spell->ID == SPELL_FOCI_FIRE || spell->ID == SPELL_BREATHE_FIRE) )
+ {
+ if ( entity->behavior == &actBell || entity->behavior == &actGreasePuddleSpawner )
+ {
+ if ( entityInsideEntity(entity, my) )
+ {
+ if ( entity->SetEntityOnFire(parent) && entity->flags[BURNING] )
+ {
+ if ( entity->behavior == &actBell )
+ {
+ if ( parent && parent->behavior == &actPlayer )
+ {
+ entity->skill[13] = parent->getUID(); // burning inflicted by for bell
+ if ( parent->behavior == &actPlayer )
+ {
+ messagePlayer(parent->skill[2], MESSAGE_INTERACTION, Language::get(6297));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ continue;
+ }
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ if ( !lastEntity && !entityInsideEntity(entity, my) )
+ {
+ continue;
+ }
+
+ auto hitprops = getParticleEmitterHitProps(my->getUID(), entity);
+ if ( hitprops )
+ {
+ if ( hitprops->hits > 0 )
+ {
+ continue;
+ }
+ }
+
+ if ( entity->getStats() )
+ {
+ if ( !entity->monsterIsTargetable(nonCreatureDamage ? true : false) )
+ {
+ if ( hitprops )
+ {
+ hitprops->hits++;
+ }
+ continue;
+ }
+
+ //if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
+ {
+ if ( parent && parent->checkFriend(entity) && parent->friendlyFireProtection(entity) )
+ {
+ if ( hitprops )
+ {
+ hitprops->hits++;
+ }
+ continue;
+ }
+ }
+ }
+ else if ( entity->flags[PASSABLE] )
+ {
+ if ( hitprops )
+ {
+ hitprops->hits++;
+ }
+ continue;
+ }
+
+ Uint32 targetUid = entity->getUID();
+ if ( Entity* entity = newEntity(my->sprite, 1, map.entities, nullptr) )
+ {
+ entity->behavior = &actMagicMissile;
+ entity->x = my->x;
+ entity->y = my->y;
+ entity->z = my->z;
+ entity->yaw = my->yaw;
+ entity->vel_x = cos(my->yaw);
+ entity->vel_y = sin(my->yaw);
+ entity->parent = parent ? parent->getUID() : 0;
+
+ entity->flags[INVISIBLE] = true;
+ entity->flags[PASSABLE] = true;
+ entity->flags[NOUPDATE] = true;
+ entity->flags[UPDATENEEDED] = false;
+ entity->flags[UNCLICKABLE] = true;
+
+ entity->skill[4] = 0; // life start
+ entity->skill[5] = 2; //lifetime
+ entity->actmagicSpray = 2;
+ entity->actmagicOrbitHitTargetUID4 = targetUid;
+ entity->actmagicReflectionCount = my->actmagicReflectionCount;
+ entity->actmagicSpellbookBonus = my->actmagicSpellbookBonus;
+ entity->actmagicFromSpellbook = my->actmagicFromSpellbook;
+
+ node_t* node = list_AddNodeFirst(&entity->children);
+ node->element = copySpell(spell);
+ ((spell_t*)node->element)->caster = parent ? parent->getUID() : 0;
+ node->deconstructor = &spellDeconstructor;
+ node->size = sizeof(spell_t);
+
+ --entity_uids;
+ entity->setUID(-3);
+ }
+
+ if ( hitprops )
+ {
+ hitprops->hits++;
+ }
+ }
+ }
+ }
+
+ if ( my->sprite == 2154 )
+ {
+ // no decay
+ }
+ else if ( GIB_VEL_DECAY > 0.001 )
+ {
+ GIB_VELX = GIB_VELX * GIB_VEL_DECAY;
+ GIB_VELY = GIB_VELY * GIB_VEL_DECAY;
+ }
+ else
+ {
+ GIB_VELX = GIB_VELX * .95;
+ GIB_VELY = GIB_VELY * .95;
+ }
+ }
+ }
+
+ if ( GIB_LIGHTING )
+ {
+ my->removeLightField();
+ }
+
+ if ( my->actGibMagicParticle > 0 )
+ {
+ if ( my->sprite == 2154 )
+ {
+ if ( GIB_FOCI_FLOAT2 < PI )
+ {
+ }
+ else
+ {
+ int sprite = my->actGibMagicParticle;
+ if ( local_rng.rand() % 2 == 0 )
+ {
+ sprite = 260;
+ }
+ if ( Entity* fx = spawnMagicParticleCustom(my, sprite, my->scalex, sprite == 260 ? 1.0 : 5.0) )
+ {
+ fx->flags[SPRITE] = true;// my->flags[SPRITE];
+ fx->ditheringDisabled = true;
+ fx->fskill[1] = 0.1; // decay size
+ fx->focalx = my->focalx;
+ fx->focaly = my->focaly;
+ fx->focalz = my->focalz;
+
+ real_t dir = atan2(my->vel_y, my->vel_x);
+ fx->x -= 4.0 * cos(dir);
+ fx->y -= 4.0 * sin(dir);
+ }
+ }
+ }
+ else if ( my->sprite == 2153 || my->sprite == 2155 )
+ {
+ /*if ( ticks % 4 == 0 )
+ {
+ if ( Entity* particle = spawnMagicParticleCustom(my, 1764, my->scalex * 1.0, 1) )
+ {
+ particle->z = 6.0;
+ particle->x = my->x;
+ particle->y = my->y;
+ particle->yaw = local_rng.rand() % 360 * PI / 180;
+ particle->vel_x = 0.05 * cos(particle->yaw);
+ particle->vel_y = 0.05 * sin(particle->yaw);
+ particle->ditheringDisabled = true;
+ }
+ }*/
+
+ if ( my->ticks % 2 == 0 )
+ {
+ if ( Entity* fx = spawnMagicParticleCustom(my, my->actGibMagicParticle, my->scalex, 10.0) )
+ {
+ fx->ditheringDisabled = true;
+ fx->fskill[1] = 0.05; // decay size
+ fx->focalx = my->focalx;
+ fx->focaly = my->focaly;
+ fx->focalz = my->focalz;
+
+ fx->roll = my->roll + PI / 2;
+ }
+ }
+ }
+ else if ( my->sprite == 2152 || my->sprite == 256 )
+ {
+ //if ( Entity* fx = spawnMagicParticleCustom(my, my->actGibMagicParticle, 1.0, 1.0) )
+ //{
+ // fx->flags[SPRITE] = my->flags[SPRITE];
+ // fx->ditheringDisabled = true;
+ // fx->fskill[1] = 0.05; // decay size
+ // fx->focalx = my->focalx;
+ // fx->focaly = my->focaly;
+ // fx->focalz = my->focalz;
+
+ // real_t dir = atan2(my->vel_y, my->vel_x);
+ // fx->x -= 2.0 * cos(dir);
+ // fx->y -= 2.0 * sin(dir);
+ //}
+ if ( Entity* fx = spawnMagicParticleCustom(my, 256, 1.0, 1.0) )
+ {
+ fx->flags[SPRITE] = true;
+ fx->ditheringDisabled = true;
+ fx->fskill[1] = 0.05; // decay size
+ fx->focalx = my->focalx;
+ fx->focaly = my->focaly;
+ fx->focalz = my->focalz;
+
+ real_t dir = atan2(my->vel_y, my->vel_x);
+ fx->x -= 2.0 * cos(dir);
+ fx->y -= 2.0 * sin(dir);
+ }
+ }
+ else if ( my->sprite == 2156 )
+ {
+ my->roll = fmod(my->roll + 0.1, 2 * PI);
+ if ( Entity* fx = spawnMagicParticleCustom(my, my->actGibMagicParticle, 1.0, 1.0) )
+ {
+ fx->flags[SPRITE] = my->flags[SPRITE];
+ fx->ditheringDisabled = true;
+ fx->fskill[1] = 0.05; // decay size
+ fx->focalx = my->focalx;
+ fx->focaly = my->focaly;
+ fx->focalz = my->focalz;
+
+ real_t dir = atan2(my->vel_y, my->vel_x);
+ fx->x -= 2.0 * cos(dir);
+ fx->y -= 2.0 * sin(dir);
+ }
+ }
+ else if ( my->sprite >= 233 && my->sprite <= 244 )
+ {
+ if ( my->ticks % 2 == 0 )
+ {
+ if ( Entity* fx = spawnMagicParticleCustom(my, my->actGibMagicParticle, 1.0, 1.0) )
+ {
+ fx->sprite = my->sprite;
+ fx->flags[SPRITE] = my->flags[SPRITE];
+ fx->ditheringDisabled = true;
+ fx->fskill[1] = 0.025; // decay size
+ fx->focalx = my->focalx;
+ fx->focaly = my->focaly;
+ fx->focalz = my->focalz;
+
+ real_t dir = atan2(my->vel_y, my->vel_x);
+ fx->x -= 2.0 * cos(dir);
+ fx->y -= 2.0 * sin(dir);
+ }
+ }
+ }
+ else
+ {
+ if ( my->ticks % 2 == 0 )
+ {
+ if ( Entity* fx = spawnMagicParticleCustom(my, my->actGibMagicParticle, 1.0, 1.0) )
+ {
+ fx->flags[SPRITE] = true;// = my->flags[SPRITE];
+ fx->ditheringDisabled = true;
+ fx->fskill[1] = 0.05; // decay size
+ fx->focalx = my->focalx;
+ fx->focaly = my->focaly;
+ fx->focalz = my->focalz;
+
+ real_t dir = atan2(my->vel_y, my->vel_x);
+ fx->x -= 2.0 * cos(dir);
+ fx->y -= 2.0 * sin(dir);
+ }
+ }
+ }
+ }
+
+ // gravity
+ if ( GIB_DELAY_MOVE == 0 )
+ {
+ GIB_VELZ += GIB_GRAVITY;
+ my->z += GIB_VELZ;
+ }
+
+ if ( GIB_LIGHTING )
+ {
+ my->removeLightField();
+ const char* lightname = nullptr;
+ if ( my->ticks > 0 && my->ticks % 12 == 0 && flickerLights )
+ {
+ my->light = addLight(my->x / 16, my->y / 16, magicLightColorForSprite(my, my->sprite, true));
+ }
+ else
+ {
+ my->light = addLight(my->x / 16, my->y / 16, magicLightColorForSprite(my, my->sprite, false));
+ }
+ }
+}
+
void actDamageGib(Entity* my)
{
my->flags[INVISIBLE] = !spawn_blood && !my->flags[SPRITE] && my->sprite == 5;
@@ -185,7 +861,7 @@ void actDamageGib(Entity* my)
GIB_VELX = GIB_VELX * .95;
GIB_VELY = GIB_VELY * .95;
- if ( my->skill[3] == DMG_WEAKER || my->skill[3] == DMG_WEAKEST || my->skill[3] == DMG_MISS )
+ if ( my->skill[3] == DMG_WEAKER || my->skill[3] == DMG_WEAKEST || my->skill[3] == DMG_MISS || my->skill[3] == DMG_GUARD )
{
real_t scale = 0.2;
if ( my->ticks > 10 )
@@ -198,6 +874,7 @@ void actDamageGib(Entity* my)
}
else if ( my->skill[3] == DMG_STRONGER
|| my->skill[3] == DMG_STRONGEST
+ || my->skill[3] == DMG_GUARD
|| my->skill[3] == DMG_MISS )
{
real_t scale = 0.2;
@@ -210,7 +887,7 @@ void actDamageGib(Entity* my)
{
scale *= anim[my->ticks] / 100.0;
}
- if ( my->skill[3] == DMG_MISS )
+ if ( my->skill[3] == DMG_MISS || my->skill[3] == DMG_GUARD )
{
scale = 0.2;
}
@@ -296,9 +973,9 @@ Entity* spawnGib(Entity* parentent, int customGibSprite)
if ( (parentstats = parentent->getStats()) != nullptr )
{
- if ( multiplayer == CLIENT )
+ if ( multiplayer == CLIENT && customGibSprite == -1 )
{
- printlog("[%s:%d spawnGib()] spawnGib() called on client, got clientstats. Probably bad?", __FILE__, __LINE__);
+ printlog("spawnGib() called on client, got clientstats. Probably bad?");
}
if ( customGibSprite != -1 )
@@ -351,7 +1028,7 @@ Entity* spawnGib(Entity* parentent, int customGibSprite)
gibsprite = 683;
break;
case 5:
- if (parentstats->HP > 0) {
+ if (parentstats->HP > 0 || parentstats->type == REVENANT_SKULL ) {
return nullptr;
}
gibsprite = 688;
@@ -405,31 +1082,326 @@ Entity* spawnGib(Entity* parentent, int customGibSprite)
return entity;
}
-Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int displayType, bool updateClients)
+Entity* spawnFociGib(real_t x, real_t y, real_t z, real_t dir, real_t velocityBonus, Uint32 parentUid, int sprite, Uint32 seed)
{
- if ( !parentent )
+ Entity* my = newEntity(sprite, 1, map.entities, nullptr); //Gib entity.
+ if ( !my )
{
return nullptr;
}
- Entity* entity = newEntity(displayType == DamageGibDisplayType::DMG_GIB_SPRITE ? dmgAmount : -1, 1, map.entities, nullptr);
- if ( !entity )
+ dir = fmod(dir, 2 * PI);
+
+ BaronyRNG rng;
+ rng.seedBytes(&seed, sizeof(Uint32));
+
+#ifndef NDEBUG
+ static ConsoleVariable cvar_foci_vel("/foci_vel", 1.f);
+ static ConsoleVariable cvar_foci_vel_decay("/foci_vel_decay", 0.95);
+ static ConsoleVariable cvar_foci_life("/foci_life", 1.f);
+ static ConsoleVariable cvar_foci_spread("/foci_spread", 1.f);
+ static ConsoleVariable cvar_foci_delay("/foci_delay", 0);
+ static ConsoleVariable cvar_foci_model("/foci_model", false);
+ static ConsoleVariable cvar_foci_scale("/foci_scale", 1.f);
+ static ConsoleVariable cvar_foci_shrink("/foci_shrink", 0.1);
+ static ConsoleVariable cvar_foci_osc_h("/foci_osc_h", 0.0);
+ static ConsoleVariable cvar_foci_swirl("/foci_swirl", 0.2);
+ static ConsoleVariable cvar_foci_particle("/foci_particle", 0);
+ static ConsoleVariable cvar_foci_invertz("/foci_invertz", false);
+ static ConsoleVariable cvar_foci_gravity("/foci_gravity", 1.0);
+ static ConsoleVariable cvar_foci_velz("/foci_velz", 1.0);
+
+ real_t foci_vel = *cvar_foci_vel;
+ real_t foci_vel_decay = *cvar_foci_vel_decay;
+ real_t foci_life = *cvar_foci_life;
+ real_t foci_spread = *cvar_foci_spread;
+ int foci_delay = *cvar_foci_delay;
+ bool foci_model = *cvar_foci_model;
+ real_t foci_scale = *cvar_foci_scale;
+ real_t foci_shrink = *cvar_foci_shrink;
+ real_t foci_osc_h = *cvar_foci_osc_h;
+ real_t foci_swirl = *cvar_foci_swirl;
+ int foci_particle = *cvar_foci_particle;
+ bool foci_invertz = *cvar_foci_invertz;
+ real_t foci_gravity = *cvar_foci_gravity;
+ real_t foci_velz = *cvar_foci_velz;
+#else
+ real_t foci_vel = 1.0;
+ real_t foci_vel_decay = 0.95;
+ real_t foci_life = 1.0;
+ real_t foci_spread = 1.0;
+ int foci_delay = 0;
+ bool foci_model = false;
+ real_t foci_scale = 1.0;
+ real_t foci_shrink = 0.1;
+ real_t foci_osc_h = 0.0;
+ real_t foci_swirl = 0.2;
+ int foci_particle = 0;
+ bool foci_invertz = false;
+ real_t foci_gravity = 1.0;
+ real_t foci_velz = 1.0;
+#endif
+
+ int sfx = 164;
+ int vol = 64;
+
+ my->x = x;
+ my->y = y;
+ my->z = z;
+ int lifetime = (0.4 * TICKS_PER_SECOND) + (rng.rand() % 5);
+ my->flags[INVISIBLE] = false;
+
+ if ( sprite == 2153 )
{
- return nullptr;
+ my->flags[INVISIBLE] = true;
+ foci_vel = 1.5;
+ foci_vel_decay = 0.95;
+ foci_life = 1.0;
+ foci_spread = 1.0;
+ foci_delay = 0;
+
+ sfx = 817 + local_rng.rand() % 2;
+ vol = 128;
+
+ foci_model = true;
+ foci_scale = 1.0;
+ foci_shrink = 0.1;
+ foci_osc_h = 0.0;
+ foci_swirl = 0.0;
+
+ foci_particle = sprite;
+ foci_invertz = false;
+ foci_gravity = 1.0;
+ foci_velz = 1.0;
}
- entity->x = parentent->x;
- entity->y = parentent->y;
- entity->z = parentent->z - 4;
- entity->parent = parentent->getUID();
- entity->sizex = 1;
- entity->sizey = 1;
- real_t vel = (local_rng.rand() % 10) / 20.f;
- entity->vel_z = -.5;
- if ( gibDmgType == DMG_STRONGER || gibDmgType == DMG_STRONGEST || gibDmgType == DMG_MISS )
+ else if ( sprite == 2154 )
{
- vel = 0.25;
- entity->vel_z = -.4;
- }
+ foci_vel = 1.5;
+ foci_vel_decay = 0.95;
+ foci_life = 1.0;
+ foci_spread = 0.25;
+ foci_delay = 0;
+
+ sfx = 814;
+ vol = 92;
+
+ foci_model = true;
+ foci_scale = 1.0;
+ foci_shrink = 0.2;
+ foci_osc_h = 0.0;
+ foci_swirl = 0.0;
+
+ foci_particle = 257;
+ foci_invertz = false;
+ foci_gravity = 0.0;
+ foci_velz = 0.0;
+
+ my->x += 6.0 * cos(dir);
+ my->y += 6.0 * sin(dir);
+ lifetime += 10;
+ }
+ else if ( sprite == 255 || (sprite >= 233 && sprite <= 244) )
+ {
+ foci_vel = 1.5;
+ foci_vel_decay = 0.95;
+ foci_life = 1.0;
+ foci_spread = 1.0;
+ foci_delay = 0;
+
+ foci_model = false;
+ foci_scale = 1.0;
+ foci_shrink = 0.1;
+ foci_osc_h = 1.0;
+ foci_swirl = 0.0;
+
+ foci_particle = sprite;
+ foci_invertz = false;
+ foci_gravity = 1.0;
+ foci_velz = 1.0;
+ }
+ else if ( sprite == 256 || sprite == 2152 )
+ {
+ foci_vel = 1.5;
+ foci_vel_decay = 0.95;
+ foci_life = 1.0;
+ foci_spread = 0.5;
+ foci_delay = 0;
+
+ sfx = 813;
+ vol = 128;
+
+ if ( sprite == 2152 )
+ {
+ foci_model = true;
+ }
+ else
+ {
+ foci_model = false;
+ }
+ foci_scale = 1.0;
+ foci_shrink = 0.1;
+ foci_osc_h = 0.0;
+ foci_swirl = 0.0;
+
+ foci_particle = sprite;
+ foci_invertz = false;
+ foci_gravity = -0.2;
+ foci_velz = -0.25;
+ }
+ else if ( sprite == 2156 )
+ {
+ foci_vel = 1.5;
+ foci_vel_decay = 0.95;
+ foci_life = 1.0;
+ foci_spread = 0.5;
+ foci_delay = 0;
+
+ sfx = 815;
+ vol = 128;
+
+ foci_model = true;
+ foci_scale = 1.0;
+ foci_shrink = 0.1;
+ foci_osc_h = 0.0;
+ foci_swirl = 0.0;
+
+ foci_particle = 2157;
+ foci_invertz = false;
+ foci_gravity = -0.2;
+ foci_velz = -0.25;
+ }
+
+ my->parent = parentUid;
+ my->behavior = &actFociGib;
+ my->ditheringDisabled = true;
+ my->flags[SPRITE] = !foci_model;
+ my->flags[PASSABLE] = true;
+ my->flags[NOUPDATE] = false;
+ my->flags[UPDATENEEDED] = false;
+ my->flags[UNCLICKABLE] = true;
+ my->flags[NOCLIP_CREATURES] = true;
+ //my->flags[BRIGHT] = true;
+ my->lightBonus = vec4_t{ 0.25f, 0.25f, 0.25f, 0.f };
+
+ my->sizex = 3;
+ my->sizey = 3;
+ real_t spread = 0.2 * foci_spread;
+ my->yaw = dir - spread + ((rng.rand() % 21) * (spread / 10));
+ my->pitch = 0.0; //(rng.rand() % 360)* PI / 180.0;
+ my->roll = 0.0; //(rng.rand() % 360)* PI / 180.0;
+ double vel = velocityBonus + foci_vel * (20 + (rng.rand() % 5)) / 10.f;
+ my->vel_x = vel * cos(my->yaw);
+ my->vel_y = vel * sin(my->yaw);
+
+ /*real_t movementDir = atan2(parent->vel_y, parent->vel_x);
+ real_t particleDir = fmod(my->yaw, 2 * PI);
+ real_t yawDiff = movementDir - particleDir;
+ while ( yawDiff > PI )
+ {
+ yawDiff -= 2 * PI;
+ }
+ while ( yawDiff <= -PI )
+ {
+ yawDiff += 2 * PI;
+ }
+ messagePlayer(0, MESSAGE_DEBUG, "%.2f", yawDiff);
+ if ( abs(yawDiff) <= PI )
+ {
+ entity->vel_x += parent->vel_x;
+ entity->vel_y += parent->vel_y;
+ }*/
+ my->vel_z = .6;
+ my->scalex = foci_scale;
+ my->scaley = foci_scale;
+ my->scalez = foci_scale;
+ real_t gravity = -0.035 - 0.002 * (rng.rand() % 6);
+ gravity *= foci_gravity;
+ my->vel_z *= foci_velz;
+ if ( foci_invertz )
+ {
+ gravity *= -1;
+ my->vel_z *= -0.5;
+ }
+
+ // swirl
+ if ( foci_swirl > 0.0001 )
+ {
+ //entity->yaw += (my->ticks % 10) * (2 * PI / 5);
+ lifetime += 5;
+ }
+ lifetime *= foci_life;
+
+ GIB_GRAVITY = gravity;
+ GIB_LIGHTING = 1;
+ GIB_LIFESPAN = lifetime;
+ GIB_DELAY_MOVE = foci_delay; // delay velx/y movement
+ GIB_SHRINK = foci_shrink * foci_scale; // shrink at end of life
+ GIB_SWIRL = foci_swirl;
+ GIB_OSC_H = foci_osc_h;// *((my->ticks % 2) ? 1 : -1);
+ GIB_VEL_DECAY = foci_vel_decay;
+ my->actGibMagicParticle = foci_particle;
+ GIB_ORBIT_X = my->x;
+ GIB_ORBIT_Y = my->y;
+
+ playSoundEntityLocal(my, sfx, vol);
+//#ifdef USE_FMOD
+// my->entity_sound = playSoundEntityLocal(my, sfx, vol);
+//#else
+//#endif
+
+ if ( multiplayer == SERVER )
+ {
+ for ( int c = 1; c < MAXPLAYERS; c++ )
+ {
+ if ( client_disconnected[c] || players[c]->isLocalPlayer() )
+ {
+ continue;
+ }
+ strcpy((char*)net_packet->data, "FOCI");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ SDLNet_Write16((Sint16)(x * 32), &net_packet->data[8]);
+ SDLNet_Write16((Sint16)(y * 32), &net_packet->data[10]);
+ SDLNet_Write16((Sint16)(z * 32), &net_packet->data[12]);
+ SDLNet_Write16((Sint16)(dir * 256), &net_packet->data[14]);
+ SDLNet_Write16((Sint16)(sprite), &net_packet->data[16]);
+ SDLNet_Write32(seed, &net_packet->data[18]);
+ SDLNet_Write16((Sint16)(velocityBonus * 256), &net_packet->data[22]);
+ net_packet->address.host = net_clients[c - 1].host;
+ net_packet->address.port = net_clients[c - 1].port;
+ net_packet->len = 24;
+ sendPacketSafe(net_sock, -1, net_packet, c - 1);
+ }
+ }
+
+ return my;
+}
+
+Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int displayType, bool updateClients)
+{
+ if ( gibDmgType == DMG_DETECT_MONSTER ) { return nullptr; }
+ if ( !parentent )
+ {
+ return nullptr;
+ }
+
+ Entity* entity = newEntity(displayType == DamageGibDisplayType::DMG_GIB_SPRITE ? dmgAmount : -1, 1, map.entities, nullptr);
+ if ( !entity )
+ {
+ return nullptr;
+ }
+ entity->x = parentent->x;
+ entity->y = parentent->y;
+ entity->z = parentent->z - 4;
+ entity->parent = parentent->getUID();
+ entity->sizex = 1;
+ entity->sizey = 1;
+ real_t vel = (local_rng.rand() % 10) / 20.f;
+ entity->vel_z = -.5;
+ if ( gibDmgType == DMG_STRONGER || gibDmgType == DMG_STRONGEST || gibDmgType == DMG_MISS || gibDmgType == DMG_GUARD )
+ {
+ vel = 0.25;
+ entity->vel_z = -.4;
+ }
if ( parentent->isDamageableCollider() )
{
entity->z -= 4;
@@ -438,6 +1410,38 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int
{
entity->z += 8.0;
}
+ if ( (parentent->behavior == &actMonster || parentent->behavior == &actDeathGhost) )
+ {
+ if ( parentent->behavior == &actDeathGhost )
+ {
+ if ( node_t* node = list_Node(&parentent->children, 2) )
+ {
+ if ( Entity* entity2 = (Entity*)node->element )
+ {
+ if ( Entity::getMonsterTypeFromSprite(entity2->sprite) == DUCK_SMALL )
+ {
+ if ( node_t* node = list_Node(&entity2->children, 2) )
+ {
+ if ( Entity* entity3 = (Entity*)node->element )
+ {
+ entity->z = entity3->z - 4;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if ( parentent->behavior == &actMonster && Entity::getMonsterTypeFromSprite(parentent->sprite) == DUCK_SMALL )
+ {
+ if ( node_t* node = list_Node(&parentent->children, 2) )
+ {
+ if ( Entity* entity2 = (Entity*)node->element )
+ {
+ entity->z = entity2->z - 4;
+ }
+ }
+ }
+ }
entity->yaw = (local_rng.rand() % 360) * PI / 180.0;
entity->vel_x = vel * cos(entity->yaw);
entity->vel_y = vel * sin(entity->yaw);
@@ -494,6 +1498,7 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int
case DMG_POISON:
break;
case DMG_MISS:
+ case DMG_GUARD:
break;
case DMG_HEAL:
color = hudColors.characterSheetGreen;
@@ -545,11 +1550,16 @@ Entity* spawnGibClient(Sint16 x, Sint16 y, Sint16 z, Sint16 sprite)
entity->pitch = (local_rng.rand() % 360) * PI / 180.0;
entity->roll = (local_rng.rand() % 360) * PI / 180.0;
vel = (local_rng.rand() % 10) / 10.f;
+ if ( sprite >= 1871 && sprite <= 1876 ) // earth sprite
+ {
+ vel *= 0.1;
+ }
entity->vel_x = vel * cos(entity->yaw);
entity->vel_y = vel * sin(entity->yaw);
entity->vel_z = -.5;
entity->fskill[3] = 0.04;
entity->behavior = &actGib;
+ entity->ditheringDisabled = true;
entity->flags[PASSABLE] = true;
entity->flags[NOUPDATE] = true;
entity->flags[UNCLICKABLE] = true;
@@ -578,7 +1588,8 @@ void serverSpawnGibForClient(Entity* gib)
SDLNet_Write16((Sint16)gib->y, &net_packet->data[6]);
SDLNet_Write16((Sint16)gib->z, &net_packet->data[8]);
SDLNet_Write16((Sint16)gib->sprite, &net_packet->data[10]);
- net_packet->data[12] = gib->flags[SPRITE];
+ net_packet->data[12] = gib->flags[SPRITE] ? 1 << 0 : 0;
+ net_packet->data[12] |= (gib->skill[5] == 1) ? 1 << 1 : 0; // poof
net_packet->address.host = net_clients[c - 1].host;
net_packet->address.port = net_clients[c - 1].port;
net_packet->len = 13;
@@ -586,3 +1597,1138 @@ void serverSpawnGibForClient(Entity* gib)
}
}
}
+
+void spawnGreasePuddleSpawner(Entity* caster, real_t x, real_t y, int duration)
+{
+ if ( multiplayer == CLIENT ) { return; }
+ int ox = x / 16;
+ int oy = y / 16;
+
+ if ( ox >= 0 && ox < map.width && oy >= 0 && oy < map.height )
+ {
+ int mapIndex = oy * MAPLAYERS + ox * MAPLAYERS * map.height;
+ if ( !map.tiles[mapIndex] )
+ {
+ return;
+ }
+ auto entLists = TileEntityList.getEntitiesWithinRadius(ox, oy, 0);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
+ {
+ list_t* currentList = *it;
+ for ( node_t* node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity->behavior == &actGreasePuddleSpawner && entity->skill[0] > 0 )
+ {
+ entity->skill[0] = std::max(entity->skill[0], duration);
+ return;
+ }
+ }
+ }
+ Entity* entity = newEntity(1786, 1, map.entities, nullptr); //Blood/gib entity.
+ real_t x = ox * 16.0 + 8.0;
+ real_t y = oy * 16.0 + 8.0;
+ entity->x = x;
+ entity->y = y;
+ entity->z = 7.5;
+ entity->parent = caster ? caster->getUID() : 0;
+ entity->behavior = &actGreasePuddleSpawner;
+ entity->sizex = 4;
+ entity->sizey = 4;
+ entity->skill[0] = duration;
+ entity->yaw = (local_rng.rand() % 360) * PI / 180.0;
+ entity->flags[UPDATENEEDED] = true;
+ entity->flags[PASSABLE] = true;
+ entity->flags[SPRITE] = true;
+ entity->flags[UNCLICKABLE] = true;
+ entity->flags[INVISIBLE] = true;
+ entity->flags[BURNABLE] = true;
+ TileEntityList.addEntity(*entity);
+ }
+}
+
+void spawnGreasePuddle(Entity* parent, real_t x, real_t y, int duration, int location)
+{
+ if ( !parent ) { return; }
+ int ox = x / 16;
+ int oy = y / 16;
+ if ( ox >= 0 && ox < map.width && oy >= 0 && oy < map.height )
+ {
+ Entity* entity = newEntity(1784, 1, map.entities, nullptr); //Blood/gib entity.
+ real_t x = ox * 16.0 + 8.0;
+ real_t y = oy * 16.0 + 8.0;
+
+ static const std::vector locations = {
+ 0 * PI / 4,
+ 5 * PI / 4,
+ 2 * PI / 4,
+ 3 * PI / 4,
+ 4 * PI / 4,
+ 7 * PI / 4,
+ 1 * PI / 4,
+ 6 * PI / 4,
+ };
+
+ x += 4.0 * cos(locations[location]) + 2.0 * (local_rng.rand() % 10) / 10.0;
+ y += 4.0 * sin(locations[location]) + 2.0 * (local_rng.rand() % 10) / 10.0;
+
+ entity->x = x;
+ entity->y = y;
+ entity->z = 8 + (local_rng.rand() % 20) / 100.0;
+ entity->parent = 0;
+ entity->sizex = 2;
+ entity->sizey = 2;
+ entity->behavior = &actGreasePuddle;
+ entity->parent = parent->getUID();
+ entity->yaw = (local_rng.rand() % 360) * PI / 180.0;
+ entity->flags[UPDATENEEDED] = false;
+ entity->flags[NOUPDATE] = true;
+ entity->flags[PASSABLE] = true;
+ entity->flags[UNCLICKABLE] = true;
+ if ( multiplayer != CLIENT )
+ {
+ --entity_uids;
+ }
+ entity->setUID(-3);
+ }
+}
+
+void actGreasePuddle(Entity* my)
+{
+ if ( my->ticks % 10 == 0 )
+ {
+ if ( my->parent == 0 || !uidToEntity(my->parent) )
+ {
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+}
+
+void actGreasePuddleSpawner(Entity* my)
+{
+ my->flags[INVISIBLE] = true;
+ int x = my->x / 16;
+ int y = my->y / 16;
+ if ( multiplayer != CLIENT )
+ {
+ if ( my->flags[BURNING] )
+ {
+ int player = achievementObserver.checkUidIsFromPlayer(my->parent);
+ if ( player >= 0 )
+ {
+ if ( achievementObserver.playerAchievements[player].hellsKitchen >= 0 )
+ {
+ achievementObserver.playerAchievements[player].hellsKitchen++;
+ if ( achievementObserver.playerAchievements[player].hellsKitchen >= 25 )
+ {
+ achievementObserver.playerAchievements[player].hellsKitchen = -1;
+ steamAchievementClient(player, "BARONY_ACH_HELLS_KITCHEN");
+ }
+ }
+ }
+ }
+
+ --my->skill[0];
+ if ( my->skill[0] <= 0 )
+ {
+ if ( x >= 0 && x < map.width && y >= 0 && y < map.height )
+ {
+ bool foundGrease = false;
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 0);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !foundGrease; ++it )
+ {
+ list_t* currentList = *it;
+ for ( node_t* node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity && entity->behavior == &actGreasePuddleSpawner && entity != my )
+ {
+ int x2 = entity->x / 16;
+ int y2 = entity->y / 16;
+ if ( x2 == x && y2 == y )
+ {
+ foundGrease = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !foundGrease )
+ {
+ if ( map.tileHasAttribute(x, y, 0, map_t::TILE_ATTRIBUTE_GREASE) )
+ {
+ map.tileAttributes[0 + (y * MAPLAYERS) + (x * MAPLAYERS * map.height)] &= ~map_t::TILE_ATTRIBUTE_GREASE;
+ serverUpdateMapTileFlag(x, y, 0, 0, map_t::TILE_ATTRIBUTE_GREASE);
+ }
+ }
+ }
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ if ( x >= 0 && x < map.width && y >= 0 && y < map.height )
+ {
+ if ( !map.tileHasAttribute(x, y, 0, map_t::TILE_ATTRIBUTE_GREASE) )
+ {
+ map.tileAttributes[0 + (y * MAPLAYERS) + (x * MAPLAYERS * map.height)] |= map_t::TILE_ATTRIBUTE_GREASE;
+ serverUpdateMapTileFlag(x, y, 0, map_t::TILE_ATTRIBUTE_GREASE, 0);
+ }
+
+ int mapIndex = y * MAPLAYERS + x * MAPLAYERS * map.height;
+ if ( lavatiles[map.tiles[mapIndex]] )
+ {
+ if ( !my->flags[BURNING] )
+ {
+ my->SetEntityOnFire(nullptr);
+ }
+ }
+
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 0);
+ Entity* parent = my->parent == 0 ? nullptr : uidToEntity(my->parent);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
+ {
+ list_t* currentList = *it;
+ for ( node_t* node = currentList->first; node != nullptr; node = node->next )
+ {
+ if ( Entity* entity = (Entity*)node->element )
+ {
+ if ( Stat* stats = entity->getStats() )
+ {
+ if ( entity->monsterIsTargetable() )
+ {
+ if ( !stats->getEffectActive(EFF_MAGIC_GREASE) || stats->EFFECTS_TIMERS[EFF_MAGIC_GREASE] < 1 * TICKS_PER_SECOND )
+ {
+ entity->setEffect(EFF_MAGIC_GREASE, true, std::max(5 * TICKS_PER_SECOND, stats->EFFECTS_TIMERS[EFF_MAGIC_GREASE]),
+ true);
+ }
+ }
+ }
+ if ( (entity->behavior == &actCampfire && entity->skill[3] > 0 )
+ || entity->behavior == &actTorch )
+ {
+ my->SetEntityOnFire(nullptr);
+ }
+ if ( my->flags[BURNING] )
+ {
+ if ( entity->flags[BURNABLE] && !entity->flags[BURNING] )
+ {
+ if ( Stat* stats = entity->getStats() )
+ {
+ if ( swimmingtiles[map.tiles[mapIndex]] && entity->behavior == &actPlayer && players[entity->skill[2]]->movement.isPlayerSwimming() )
+ {
+ continue;
+ }
+ if ( !entity->monsterIsTargetable(false) )
+ {
+ continue;
+ }
+ entity->SetEntityOnFire(parent);
+ if ( parent && parent->getStats() )
+ {
+ if ( entity->flags[BURNING] )
+ {
+ stats->burningInflictedBy = parent->getUID();
+
+ bool alertTarget = entity->monsterAlertBeforeHit(parent);
+
+ // alert the monster!
+ if ( entity->monsterState != MONSTER_STATE_ATTACK && (stats->type < LICH || stats->type >= SHOPKEEPER) )
+ {
+ if ( alertTarget )
+ {
+ entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH, true);
+ }
+ }
+
+ // alert other monsters too
+ /*if ( alertTarget )
+ {
+ entity->alertAlliesOnBeingHit(parent);
+ }*/
+ entity->updateEntityOnHit(parent, alertTarget);
+ }
+ }
+ }
+ else
+ {
+ if ( entity->behavior == &actDoor
+ || entity->behavior == &::actIronDoor
+ || entity->behavior == &actBell
+ || entity->behavior == &::actFurniture || entity->behavior == &::actChest
+ || (entity->isDamageableCollider() && entity->isColliderDamageableByMagic()) )
+ {
+ entity->SetEntityOnFire(parent);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( my->flags[BURNING] )
+ {
+ ++my->skill[6]; // burning spread counter
+ if ( my->skill[6] >= TICKS_PER_SECOND )
+ {
+ my->skill[6] = 0;
+
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
+ {
+ list_t* currentList = *it;
+ for ( node_t* node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity && entity->behavior == &actGreasePuddleSpawner && entity != my )
+ {
+ int x2 = entity->x / 16;
+ int y2 = entity->y / 16;
+ //if ( x2 == x || y2 == y ) // axis aligned only
+ {
+ if ( !entity->flags[BURNING] )
+ {
+ entity->flags[BURNING] = true;
+ entity->skill[5] = TICKS_PER_SECOND * 5;
+ serverUpdateEntityFlag(entity, BURNING);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( x >= 0 && x < map.width && y >= 0 && y < map.height )
+ {
+ if ( my->flags[BURNING] )
+ {
+ if ( !my->light )
+ {
+ my->light = addLight(my->x / 16, my->y / 16, "campfire");
+ }
+ }
+ else
+ {
+ my->removeLightField();
+ }
+ if ( my->skill[1] < 4 )
+ {
+ if ( map.tileHasAttribute(x, y, 0, map_t::TILE_ATTRIBUTE_GREASE) )
+ {
+ ++my->skill[1];
+ std::vector chances(8);
+ std::fill(chances.begin(), chances.end(), 1);
+ for ( int i = 0; i < 8; ++i )
+ {
+ if ( (my->skill[3] >> i) & 1 )
+ {
+ chances[i] = 0;
+ }
+ }
+ int pick = local_rng.discrete(chances.data(), chances.size());
+ my->skill[3] |= (1 << pick);
+ spawnGreasePuddle(my, my->x, my->y, 10 * TICKS_PER_SECOND, pick);
+ }
+ }
+ }
+}
+
+void actMiscPuddle(Entity* my)
+{
+ if ( !my ) { return; }
+
+ if ( my->skill[0] <= 0 )
+ {
+ if ( my->scalex <= 0.0 )
+ {
+ list_RemoveNode(my->mynode);
+ return;
+ }
+
+ my->scalex -= 0.05;
+ my->scalez = my->scalex;
+ }
+ else
+ {
+ --my->skill[0];
+
+ if ( my->scalex < my->fskill[0] )
+ {
+ real_t diff = std::max(0.01, (my->fskill[0] - my->scalex) / 10.0);
+ my->scalex = std::min(my->scalex + diff, my->fskill[0]);
+ }
+ my->scalez = my->scalex;
+ }
+}
+
+#define LEAF_IDLE_BOUNCE_TIME 50
+void actLeafParticle(Entity* my)
+{
+ Entity* parent = nullptr;
+
+ auto& particle_life = my->skill[0];
+ auto& anim_bounce = my->skill[1];
+ auto& anim_bounce_timer = my->skill[3];
+ auto& float_oscillate_dir = my->skill[4];
+ auto& anim_spin = my->skill[5];
+
+ auto& float_oscillate_amt = my->fskill[0];
+ auto& anim_bounce_fall = my->fskill[1];
+ auto& pos_x_center = my->fskill[4];
+ auto& pos_y_center = my->fskill[5];
+ auto& pos_z_start = my->fskill[6];
+ auto& pos_z_end = my->fskill[7];
+ auto& anim_bounce_rise_amt = my->fskill[8];
+ auto& rotation_offset = my->fskill[9];
+
+ if ( my->parent != 0 )
+ {
+ parent = uidToEntity(my->parent);
+ if ( !parent )
+ {
+ spawnPoof(my->x, my->y, my->z, 0.25);
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+ else
+ {
+ if ( particle_life <= 0 )
+ {
+ spawnPoof(my->x, my->y, my->z, 0.25);
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ --particle_life;
+ }
+
+ my->focalz = 2.0;
+
+ bool grounded = false;
+ bool noFloor = false;
+ int mapx = my->x / 16;
+ int mapy = my->y / 16;
+ if ( my->parent == 0 ) // check no floor
+ {
+ if ( mapx >= 0 && mapx < map.width && mapy >= 0 && mapy < map.height )
+ {
+ int mapIndex = mapy * MAPLAYERS + mapx * MAPLAYERS * map.height;
+ if ( !map.tiles[mapIndex] )
+ {
+ noFloor = true;
+ }
+ }
+ }
+
+ if ( my->z >= 5.5 && !noFloor )
+ {
+ grounded = true;
+
+ // reset to 0
+ if ( float_oscillate_amt < 0.0 )
+ {
+ float_oscillate_amt += 0.025;
+ float_oscillate_amt = std::min(float_oscillate_amt, 0.0);
+ }
+ else if ( float_oscillate_amt > 0.0 )
+ {
+ float_oscillate_amt -= 0.025;
+ float_oscillate_amt = std::max(float_oscillate_amt, 0.0);
+ }
+ }
+ else if ( float_oscillate_dir == 1 )
+ {
+ float_oscillate_amt += 0.025;
+ if ( float_oscillate_amt >= 1.0 )
+ {
+ float_oscillate_amt = 1.0;
+ if ( parent && parent->fskill[10] > 0.05 && (parent->skill[7] == 100 || parent->skill[8] >= 20) )
+ {
+ // spinning with at least 20 ticks left / strong spin, lock to 1 angle
+ }
+ else
+ {
+ float_oscillate_dir = 2;
+ }
+ }
+ }
+ else
+ {
+ float_oscillate_amt -= 0.025;
+ if ( float_oscillate_amt <= -1.0 )
+ {
+ float_oscillate_amt = -1.0;
+ if ( parent && parent->fskill[10] > 0.05 && (parent->skill[7] == 100 || parent->skill[8] >= 20) )
+ {
+ // spinning with at least 20 ticks left / strong spin, lock to 1 angle
+ }
+ else
+ {
+ float_oscillate_dir = 1;
+ }
+ }
+ }
+
+ if ( anim_bounce == 0 )
+ {
+ // rise up
+ anim_bounce_fall += std::max(0.01, (1.0 - anim_bounce_fall) / std::max(12.0, anim_bounce_rise_amt));
+ if ( anim_bounce_fall >= 1.0 )
+ {
+ anim_bounce_fall = 1.0;
+ if ( anim_spin != 0 )
+ {
+ // don't fall down
+ }
+ else
+ {
+ anim_bounce = 1;
+ }
+ }
+ }
+ else
+ {
+ anim_bounce_fall -= std::max(0.01, (anim_bounce_fall) / 10.0);
+ anim_bounce_fall = std::max(0.0, anim_bounce_fall);
+ }
+
+ real_t rate = (sin(float_oscillate_amt * PI / 2));
+ my->roll = (PI / 4) * rate;
+ /*if ( keystatus[SDLK_g] )
+ {
+ my->yaw += 0.05;
+ }*/
+ if ( parent )
+ {
+ pos_x_center = parent->x;
+ pos_y_center = parent->y;
+ my->yaw = parent->yaw + rotation_offset;
+
+ if ( parent->fskill[10] > 0.25 )
+ {
+ if ( anim_spin == 0 )
+ {
+ anim_spin = 1;
+
+ anim_bounce = 0;
+ anim_bounce_fall = 0.0;
+ pos_z_start = my->z;
+
+ real_t boost = 4.0 + 0.25 * (local_rng.rand() % 9); // 4-6
+ if ( parent->skill[7] != 100 )
+ {
+ anim_bounce_rise_amt = 12.0 + 0.25 * (local_rng.rand() % 13); // 12-15.0 random rise
+ boost /= 3;
+ pos_z_end = std::max(my->z - boost, -7.5) - pos_z_start;
+ }
+ else
+ {
+ anim_bounce_rise_amt = 12.0 + 0.25 * (local_rng.rand() % 13); // 12-15.0 random rise
+ anim_bounce_rise_amt *= 10.0;
+ real_t maxHeight = 0.0 + 5.0 * rotation_offset / (2 * PI);
+ if ( my->z - boost > maxHeight )
+ {
+ pos_z_end = std::min(my->z - boost, maxHeight) - pos_z_start;
+ }
+ else
+ {
+ pos_z_end = std::max(my->z - boost, maxHeight) - pos_z_start;
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( anim_spin == 1 )
+ {
+ anim_spin = 0;
+ }
+ }
+ }
+ real_t faceDir = my->yaw;
+ my->x = pos_x_center + 4.0 * cos(faceDir) - 2.0 * rate * cos(faceDir + PI / 2);
+ my->y = pos_y_center + 4.0 * sin(faceDir) - 2.0 * rate * sin(faceDir + PI / 2);
+ if ( anim_bounce == 0 )
+ {
+ // rise up
+ my->z = pos_z_start + pos_z_end * sin(anim_bounce_fall * PI / 2);
+ }
+ else
+ {
+ // fall down faster as anim_bounce_fall 1 - 0
+ my->vel_z = 0.1 * (1.0 - anim_bounce_fall) * abs(cos(float_oscillate_amt));
+ my->z += my->vel_z;
+ }
+
+ if ( noFloor )
+ {
+ // fall down
+ }
+ else
+ {
+ my->z = std::min(my->z, 5.5);
+ }
+
+ if ( !parent )
+ {
+ //if ( multiplayer != CLIENT )
+ {
+ //my->vel_x *= 0.8;
+ //my->vel_y *= 0.8;
+ //if ( abs(my->vel_x) > 0.01 || abs(my->vel_y) > 0.01 )
+ //{
+ // clipMove(&pos_x_center, &pos_y_center, my->vel_x, my->vel_y, my);
+ //}
+ if ( anim_bounce_timer == 0 )
+ {
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( players[i]->entity && entityInsideEntity(players[i]->entity, my) )
+ {
+ if ( abs(players[i]->entity->vel_x > 0.1) || abs(players[i]->entity->vel_y) > 0.1 )
+ {
+ anim_bounce_timer = LEAF_IDLE_BOUNCE_TIME;
+ real_t tangent = atan2(my->y - players[i]->entity->y, my->x - players[i]->entity->x);
+ my->vel_x = 1.5 * cos(tangent);
+ my->vel_y = 1.5 * sin(tangent);
+ float_oscillate_dir = 1 + local_rng.rand() % 2;
+ anim_bounce = 0;
+ anim_bounce_fall = 0.0;
+ pos_z_start = my->z;
+ anim_bounce_rise_amt = 12.0 + 0.25 * (local_rng.rand() % 13); // 12-15.0 random rise
+ real_t boost = 4.0 + 0.25 * (local_rng.rand() % 9); // 4-6
+ pos_z_end = std::max(my->z - boost, -7.5) - pos_z_start;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ --anim_bounce_timer;
+ }
+ }
+ }
+ else
+ {
+ if ( parent->skill[3] == LEAF_IDLE_BOUNCE_TIME - 1 )
+ {
+ anim_bounce = 0;
+ float_oscillate_dir = 1 + local_rng.rand() % 2;
+ anim_bounce_fall = 0.0;
+ pos_z_start = my->z;
+ real_t boost = 4.0 + 0.25 * (local_rng.rand() % 9); // 4-6
+ anim_bounce_rise_amt = 12.0 + 0.25 * (local_rng.rand() % 13); // 12-15.0
+ pos_z_end = std::max(my->z - boost, -7.5) - pos_z_start;
+ }
+ }
+}
+
+Entity* spawnLeafPile(real_t x, real_t y, bool trap)
+{
+ if ( multiplayer == CLIENT ) { return nullptr; }
+ if ( Entity* leaf = newEntity(1913, 1, map.entities, nullptr) )
+ {
+ leaf->x = x;
+ leaf->y = y;
+ leaf->z = 0.0;
+ leaf->yaw = map_rng.rand() % 360 * (PI / 180.0);
+ leaf->sizex = 4;
+ leaf->sizey = 4;
+ leaf->behavior = &actLeafPile;
+ leaf->skill[0] = 0;
+ leaf->skill[10] = 0; // not map gen
+ leaf->skill[11] = trap ? 0 : 1;
+ leaf->flags[NOCLIP_CREATURES] = true;
+ leaf->flags[UPDATENEEDED] = true;
+ leaf->flags[NOUPDATE] = false;
+ leaf->flags[PASSABLE] = true;
+ leaf->flags[UNCLICKABLE] = true;
+
+ int mapx = static_cast(x) / 16;
+ int mapy = static_cast(y) / 16;
+ int mapIndex = mapy * MAPLAYERS + mapx * MAPLAYERS * map.height;
+ if ( mapx > 0 && mapx < map.width && mapy > 0 && mapy < map.height )
+ {
+ if ( !map.tiles[mapIndex] || swimmingtiles[map.tiles[mapIndex]] || lavatiles[map.tiles[mapIndex]]
+ || map.tiles[OBSTACLELAYER + mapIndex] )
+ {
+ leaf->skill[0] = 4.0 * TICKS_PER_SECOND; // lifetime on wrong terrain
+ }
+ }
+ else
+ {
+ leaf->skill[0] = 4.0 * TICKS_PER_SECOND; // lifetime on wrong terrain
+ }
+ return leaf;
+ }
+ return nullptr;
+}
+
+void actLeafPile(Entity* my)
+{
+ if ( multiplayer != CLIENT )
+ {
+ if ( my->skill[0] > 0 )
+ {
+ --my->skill[0];
+ if ( my->skill[0] <= 0 )
+ {
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+ }
+
+ my->flags[INVISIBLE] = true;
+
+ if ( my->skill[1] == 0 )
+ {
+ my->skill[1] = 1;
+
+ real_t leafEndZ = -7.5;
+ if ( my->skill[10] == 1 )
+ {
+ // map gen
+ leafEndZ = 0.0 + local_rng.rand() % 3;
+ }
+ for ( int i = 0; i < 3; ++i )
+ {
+ Entity* leaf = newEntity(1912, 1, map.entities, nullptr); //Gib entity.
+ if ( leaf != NULL )
+ {
+ leaf->x = my->x;
+ leaf->y = my->y;
+ leaf->z = 5.0 - i * 0.5;
+ leaf->fskill[6] = leaf->z;
+ leaf->fskill[7] = leafEndZ - leaf->fskill[6];
+ leaf->vel_z = 0.0;
+ leaf->yaw = my->yaw + i * 2 * PI / 3;
+ leaf->sizex = 2;
+ leaf->sizey = 2;
+ leaf->scalex = 0.5;
+ leaf->scaley = 0.5;
+ leaf->scalez = 0.5;
+ leaf->fskill[4] = my->x;
+ leaf->fskill[5] = my->y;
+ leaf->fskill[9] = i * 2 * PI / 3;
+ leaf->parent = my->getUID();
+ leaf->behavior = &actLeafParticle;
+ leaf->flags[NOCLIP_CREATURES] = true;
+ leaf->flags[UPDATENEEDED] = false;
+ leaf->flags[NOUPDATE] = true;
+ leaf->flags[PASSABLE] = true;
+ leaf->flags[UNCLICKABLE] = true;
+ if ( multiplayer != CLIENT )
+ {
+ --entity_uids;
+ }
+ leaf->setUID(-3);
+ }
+ }
+ }
+
+ auto& spinStrength = my->skill[7];
+ auto& spinTimer = my->skill[8];
+
+ if ( multiplayer != CLIENT )
+ {
+ if ( my->skill[3] == 0 )
+ {
+ std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
+ for ( auto it : entLists )
+ {
+ if ( my->skill[3] != 0 )
+ {
+ break;
+ }
+ for ( node_t* node = it->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity->behavior == &actMonster || entity->behavior == &actPlayer )
+ {
+ if ( !entity->monsterIsTargetable() ) { continue; }
+ if ( abs(entity->vel_x > 0.1) || abs(entity->vel_y) > 0.1 )
+ {
+ if ( entityInsideEntity(entity, my) )
+ {
+ my->skill[3] = LEAF_IDLE_BOUNCE_TIME;
+ my->skill[4] = 100;
+ playSoundEntityLocal(my, 754 + local_rng.rand() % 2, 64);
+ entity->setEffect(EFF_NOISE_VISIBILITY, (Uint8)2, 2 * TICKS_PER_SECOND, false);
+ if ( multiplayer == SERVER )
+ {
+ for ( int c = 1; c < MAXPLAYERS; ++c ) // send to other players
+ {
+ if ( client_disconnected[c] || players[c]->isLocalPlayer() )
+ {
+ continue;
+ }
+ strcpy((char*)net_packet->data, "LEAF");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ net_packet->data[8] = 1;
+ net_packet->data[9] = (Uint8)my->skill[3];
+ net_packet->data[10] = (Uint8)my->skill[4];
+ net_packet->address.host = net_clients[c - 1].host;
+ net_packet->address.port = net_clients[c - 1].port;
+ net_packet->len = 11;
+ sendPacketSafe(net_sock, -1, net_packet, c - 1);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( my->fskill[10] < 0.05 )
+ {
+ my->vel_x *= 0.95;
+ my->vel_y *= 0.95;
+ }
+ else
+ {
+ my->vel_x *= 0.995;
+ my->vel_y *= 0.995;
+ }
+ if ( abs(my->vel_x) > 0.01 || abs(my->vel_y) > 0.01 )
+ {
+ real_t result = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
+ if ( result != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y) )
+ {
+ if ( spinStrength == 100 )
+ {
+ real_t bouncePenalty = 1.0;
+ if ( hit.side == HORIZONTAL )
+ {
+ my->vel_x = -my->vel_x * bouncePenalty;
+ }
+ else if ( hit.side == VERTICAL )
+ {
+ my->vel_y = -my->vel_y * bouncePenalty;
+ }
+ else if ( hit.side == 0 )
+ {
+ my->vel_x = -my->vel_y * bouncePenalty;
+ my->vel_y = -my->vel_x * bouncePenalty;
+ }
+ }
+ else
+ {
+ my->vel_x = 0.f;
+ my->vel_y = 0.f;
+ }
+ }
+ }
+ }
+
+ if ( my->skill[3] > 0 )
+ {
+ --my->skill[3];
+ }
+
+ if ( spinStrength > 0 )
+ {
+ real_t amt = spinStrength / 100.0;
+
+ my->fskill[10] += std::max(0.01, (amt - my->fskill[10]) / 10.0);
+ my->fskill[10] = std::min(amt, my->fskill[10]);
+ my->yaw = normaliseAngle2PI(my->yaw);
+
+ if ( spinStrength == 100 )
+ {
+ my->skill[5]++;
+ if ( my->skill[5] == 2 * TICKS_PER_SECOND && multiplayer != CLIENT )
+ {
+ CastSpellProps_t spellProps;
+ spellProps.caster_x = my->x;
+ spellProps.caster_y = my->y;
+ spellProps.target_x = my->x;
+ spellProps.target_y = my->y;
+ castSpell(my->getUID(), getSpellFromID(SPELL_SLAM), false, true, false, &spellProps);
+
+ std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
+ real_t dist = 10000.0;
+ Entity* closestEntity = nullptr;
+ for ( auto it : entLists )
+ {
+ for ( node_t* node = it->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity->behavior == &actMonster || entity->behavior == &actPlayer )
+ {
+ if ( !entity->monsterIsTargetable() ) { continue; }
+ if ( Stat* entityStats = entity->getStats() )
+ {
+ if ( entityStats->type == MYCONID || entityStats->type == DRYAD )
+ {
+ continue;
+ }
+ }
+ real_t newDist = entityDist(my, entity);
+ if ( newDist < dist && newDist < 64.0 )
+ {
+ real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
+ real_t d = lineTraceTarget(my, my->x, my->y, tangent, 64.0, 0, false, entity);
+ if ( hit.entity == entity )
+ {
+ closestEntity = entity;
+ dist = newDist;
+ }
+ }
+ }
+ }
+ }
+
+ if ( closestEntity )
+ {
+ real_t tangent = atan2(closestEntity->y - my->y, closestEntity->x - my->x);
+ my->vel_x = 0.75 * cos(tangent);
+ my->vel_y = 0.75 * sin(tangent);
+ }
+ }
+ }
+ }
+ else
+ {
+ my->skill[5] = 0;
+ my->fskill[10] -= std::max(0.01, my->fskill[10] / 100.0);
+ my->fskill[10] = std::max(0.0, my->fskill[10]);
+ }
+
+ my->yaw += 0.2 * (1 + sin(-PI / 2 + my->fskill[10] * PI / 2));
+ my->yaw = normaliseAngle2PI(my->yaw);
+
+ if ( my->skill[4] > 0 )
+ {
+ my->yaw += 0.025 * (my->skill[4]) / 100.0;
+ my->yaw = normaliseAngle2PI(my->yaw);
+ --my->skill[4];
+ }
+
+ if ( spinTimer > 0 )
+ {
+ --spinTimer;
+ if ( spinTimer == 0 )
+ {
+ spinStrength = 0;
+ }
+ }
+
+#ifdef USE_FMOD
+ bool isPlaying = false;
+ if ( my->entity_sound )
+ {
+ my->entity_sound->isPlaying(&isPlaying);
+ if ( isPlaying )
+ {
+ FMOD_VECTOR position;
+ position.x = (float)(my->x / (real_t)16.0);
+ position.y = (float)(0.0);
+ position.z = (float)(my->y / (real_t)16.0);
+ my->entity_sound->set3DAttributes(&position, nullptr);
+ }
+ }
+#endif
+
+ if ( multiplayer != CLIENT )
+ {
+ int spinEvent = 0;
+ if ( my->skill[6] == 0 )
+ {
+ my->skill[6] = TICKS_PER_SECOND * 5 + local_rng.rand() % (TICKS_PER_SECOND * 10);
+ }
+ else
+ {
+ if ( spinTimer == 0 )
+ {
+ --my->skill[6];
+ if ( my->skill[6] == 0 )
+ {
+ if ( my->skill[11] == 0 ) // trapped
+ {
+ spinEvent = local_rng.rand() % 8 == 0 ? 2 : 1;
+ }
+ else
+ {
+ spinEvent = 1;
+ }
+#ifdef USE_FMOD
+ bool isPlaying = false;
+ if ( my->entity_sound )
+ {
+ my->entity_sound->isPlaying(&isPlaying);
+ }
+ if ( !isPlaying )
+ {
+ my->entity_sound = playSoundEntityLocal(my, 752 + local_rng.rand() % 2, 128);
+ }
+#endif
+ }
+ }
+ }
+
+ if ( /*keystatus[SDLK_g] ||*/ spinEvent == 2 )
+ {
+ spinStrength = 100;
+ spinTimer = 225;
+
+ if ( local_rng.rand() % 2 == 0 )
+ {
+ std::vector dirs;
+ for ( int i = 0; i < 4; ++i )
+ {
+ if ( !checkObstacle(my->x + 16.0 * cos(i * PI / 2), my->y + 16.0 * sin(i * PI / 2), my, nullptr) )
+ {
+ dirs.push_back(i * PI / 2);
+ }
+ }
+ if ( dirs.size() > 0 )
+ {
+ real_t newDir = dirs[local_rng.rand() % dirs.size()];
+ my->vel_x = 0.5 * cos(newDir);
+ my->vel_y = 0.5 * sin(newDir);
+ }
+ else
+ {
+ real_t newDir = (local_rng.rand() % 4) * PI / 2;
+ my->vel_x = 0.5 * cos(newDir);
+ my->vel_y = 0.5 * sin(newDir);
+ }
+ }
+
+ if ( multiplayer == SERVER )
+ {
+ for ( int c = 1; c < MAXPLAYERS; ++c ) // send to other players
+ {
+ if ( client_disconnected[c] || players[c]->isLocalPlayer() )
+ {
+ continue;
+ }
+ strcpy((char*)net_packet->data, "LEAF");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ net_packet->data[8] = 2;
+ net_packet->data[9] = (Uint8)spinStrength;
+ net_packet->data[10] = (Uint8)spinTimer;
+ net_packet->address.host = net_clients[c - 1].host;
+ net_packet->address.port = net_clients[c - 1].port;
+ net_packet->len = 11;
+ sendPacketSafe(net_sock, -1, net_packet, c - 1);
+ }
+ }
+ }
+ if ( /*keystatus[SDLK_h] ||*/ spinEvent == 1 )
+ {
+ //keystatus[SDLK_h] = 0;
+ spinStrength = 75;
+ spinTimer = 50;
+
+ std::vector dirs;
+ for ( int i = 0; i < 4; ++i )
+ {
+ if ( !checkObstacle(my->x + 16.0 * cos(i * PI / 2), my->y + 16.0 * sin(i * PI / 2), my, nullptr) )
+ {
+ dirs.push_back(i * PI / 2);
+ }
+ }
+ if ( dirs.size() > 0 )
+ {
+ real_t newDir = dirs[local_rng.rand() % dirs.size()];
+ my->vel_x = 0.5 * cos(newDir);
+ my->vel_y = 0.5 * sin(newDir);
+ }
+ else
+ {
+ real_t newDir = (local_rng.rand() % 4) * PI / 2;
+ my->vel_x = 0.5 * cos(newDir);
+ my->vel_y = 0.5 * sin(newDir);
+ }
+
+ if ( multiplayer == SERVER )
+ {
+ for ( int c = 1; c < MAXPLAYERS; ++c ) // send to other players
+ {
+ if ( client_disconnected[c] || players[c]->isLocalPlayer() )
+ {
+ continue;
+ }
+ strcpy((char*)net_packet->data, "LEAF");
+ SDLNet_Write32(my->getUID(), &net_packet->data[4]);
+ net_packet->data[8] = 2;
+ net_packet->data[9] = (Uint8)spinStrength;
+ net_packet->data[10] = (Uint8)spinTimer;
+ net_packet->address.host = net_clients[c - 1].host;
+ net_packet->address.port = net_clients[c - 1].port;
+ net_packet->len = 11;
+ sendPacketSafe(net_sock, -1, net_packet, c - 1);
+ }
+ }
+ }
+ }
+}
+
+Entity* spawnMiscPuddle(Entity* parentent, real_t x, real_t y, int sprite, bool updateClients)
+{
+ if ( sprite == 0 )
+ {
+ return nullptr;
+ }
+ if ( parentent )
+ {
+ x = parentent->x;
+ y = parentent->y;
+ }
+
+ int mapx = static_cast(x) / 16;
+ int mapy = static_cast(y) / 16;
+ int mapIndex = mapy * MAPLAYERS + mapx * MAPLAYERS * map.height;
+ if ( mapx > 0 && mapx < map.width && mapy > 0 && mapy < map.height )
+ {
+ if ( !map.tiles[mapIndex] || map.tiles[OBSTACLELAYER + mapIndex] )
+ {
+ return nullptr;
+ }
+
+ Entity* puddle = newEntity(sprite, 1, map.entities, nullptr); //Gib entity.
+ if ( puddle != NULL )
+ {
+ puddle->x = x;
+ puddle->y = y;
+ puddle->z = 8.0 + (local_rng.rand() % 20) / 100.0;
+ puddle->sizex = 2;
+ puddle->sizey = 2;
+ puddle->behavior = &actMiscPuddle;
+ int randomScale = local_rng.rand() % 10;
+ puddle->fskill[0] = (100 - randomScale) / 100.f; // end scale
+
+ puddle->scalex = 0.0;
+ puddle->scalez = puddle->scalex;
+ puddle->skill[0] = TICKS_PER_SECOND * 3 + local_rng.rand() % (2 * TICKS_PER_SECOND);
+ puddle->yaw = (local_rng.rand() % 360) * PI / 180.0;
+ puddle->flags[UPDATENEEDED] = false;
+ puddle->flags[NOUPDATE] = true;
+ puddle->flags[PASSABLE] = true;
+ puddle->flags[UNCLICKABLE] = true;
+ if ( multiplayer != CLIENT )
+ {
+ --entity_uids;
+ }
+ puddle->setUID(-3);
+
+ if ( updateClients && multiplayer == SERVER )
+ {
+ serverSpawnMiscParticlesAtLocation(x, y, 0, PARTICLE_EFFECT_MISC_PUDDLE, sprite);
+ }
+ }
+ return puddle;
+ }
+
+ return nullptr;
+}
\ No newline at end of file
diff --git a/src/actgold.cpp b/src/actgold.cpp
index a8d504884..c341f7a5b 100644
--- a/src/actgold.cpp
+++ b/src/actgold.cpp
@@ -20,6 +20,7 @@
#include "prng.hpp"
#include "scores.hpp"
#include "collision.hpp"
+#include "mod_tools.hpp"
/*-------------------------------------------------------------------------------
@@ -33,6 +34,7 @@
void actGoldBag(Entity* my)
{
int i;
+ my->goldTelepathy = 0;
if ( my->ticks == 1 )
{
@@ -93,6 +95,20 @@ void actGoldBag(Entity* my)
}
#endif
+ for ( int i = 0; i < MAXPLAYERS; ++i )
+ {
+ if ( players[i]->isLocalPlayer() )
+ {
+ if ( players[i]->entity && players[i]->entity->isBlind() )
+ {
+ if ( stats[i]->type == GNOME )
+ {
+ my->goldTelepathy |= (1 << i);
+ }
+ }
+ }
+ }
+
// pick up gold
if ( multiplayer != CLIENT )
{
@@ -111,6 +127,11 @@ void actGoldBag(Entity* my)
{
playSoundEntity(players[i]->entity, 242 + local_rng.rand() % 4, 64 );
}
+ if ( stats[i]->type == GNOME )
+ {
+ my->goldAmount += my->goldAmountBonus;
+ my->goldAmountBonus = 0;
+ }
stats[i]->GOLD += my->goldAmount;
if ( multiplayer == SERVER && i > 0 && !players[i]->isLocalPlayer() )
{
@@ -133,6 +154,31 @@ void actGoldBag(Entity* my)
messagePlayer(i, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(484), my->goldAmount);
}
+ for ( int player = 0; player < MAXPLAYERS; ++player )
+ {
+ if ( players[player]->mechanics.donationRevealedOnFloor == my->getUID() )
+ {
+ messagePlayerColor(i, MESSAGE_HINT, makeColorRGB(255, 255, 0), Language::get(6943)); // you discovered a gift
+
+ for ( int player2 = 0; player2 < MAXPLAYERS; ++player2 ) // relay to other players
+ {
+ if ( player2 != i && !client_disconnected[player2] )
+ {
+ messagePlayerColor(player2, MESSAGE_HINT, makeColorRGB(255, 255, 0), Language::get(6944), stats[i]->name); // an ally discovered a gift
+ }
+ }
+
+ players[player]->mechanics.updateSustainedSpellEvent(SPELL_DONATION, 150.0, 1.0, nullptr);
+ break;
+ }
+ }
+
+ if ( my->goldDroppedByPlayer == 0 )
+ {
+ Compendium_t::Events_t::eventUpdateCodex(i, Compendium_t::CPDM_GOLD_COLLECTED, "gold", my->goldAmount);
+ Compendium_t::Events_t::eventUpdateCodex(i, Compendium_t::CPDM_GOLD_COLLECTED_RUN, "gold", my->goldAmount);
+ }
+
// remove gold entity
list_RemoveNode(my->mynode);
return;
@@ -150,6 +196,7 @@ void actGoldBag(Entity* my)
real_t groundheight = my->sprite == 1379 ? 7.75 : 6.25;
my->flags[BURNING] = false;
+ my->flags[NOCLIP_CREATURES] = true;
if ( my->goldBouncing == 0 )
{
diff --git a/src/acthudweapon.cpp b/src/acthudweapon.cpp
index 0e8a5848a..7df4dde1a 100644
--- a/src/acthudweapon.cpp
+++ b/src/acthudweapon.cpp
@@ -33,6 +33,54 @@
-------------------------------------------------------------------------------*/
+static ConsoleVariable cvar_hudweapon_yaw_ang("/hudweapon_yaw_ang", 0.0);
+static ConsoleVariable cvar_hudweapon_yaw_spd("/hudweapon_yaw_spd", 0.0);
+static ConsoleVariable cvar_hudweapon_pitch_ang("/hudweapon_pitch_ang", 0.0);
+static ConsoleVariable cvar_hudweapon_pitch_spd("/hudweapon_pitch_spd", 0.0);
+static ConsoleVariable cvar_hudweapon_roll_ang("/hudweapon_roll_ang", 0.0);
+static ConsoleVariable cvar_hudweapon_roll_spd("/hudweapon_roll_spd", 1.0);
+static ConsoleVariable cvar_hudweapon_x_ang("/hudweapon_x_ang", 2.0);
+static ConsoleVariable cvar_hudweapon_x_spd("/hudweapon_x_spd", 0.5);
+static ConsoleVariable cvar_hudweapon_y_ang("/hudweapon_y_ang", -4.0);
+static ConsoleVariable cvar_hudweapon_y_spd("/hudweapon_y_spd", 1.0);
+static ConsoleVariable cvar_hudweapon_z_ang("/hudweapon_z_ang", 0.0);
+static ConsoleVariable cvar_hudweapon_z_spd("/hudweapon_z_spd", 0.0);
+static ConsoleVariable cvar_hudweapon_timescale("/hudweapon_timescale", 0.5);
+static ConsoleVariable cvar_hudweapon_timescale2("/hudweapon_timescale2", 0.675);
+static ConsoleVariable cvar_hudweapon_timescale3("/hudweapon_timescale3", 1.0);
+//static ConsoleVariable cvar_claymore_toggle("/claymore_toggle", 0);
+void hudWeaponAnimateVariable(real_t& variable, real_t target, real_t speed)
+{
+ if ( variable < target )
+ {
+ variable = std::min(variable + speed * *cvar_hudweapon_timescale, target);
+ }
+ else if ( variable > target )
+ {
+ variable = std::max(variable - speed * *cvar_hudweapon_timescale, target);
+ }
+}
+
+struct HUDFlail_t
+{
+ real_t pitch = 0.0;
+ real_t roll = 0.0;
+ real_t yaw = 0.0;
+ real_t spin = 0.0;
+ real_t spin2 = 0.0;
+ real_t bounce = 0.0;
+ real_t rollSpin = 2 * PI;
+ real_t prevYaw = 0.0;
+ int spinState = 0;
+ bool needsInit = true;
+
+ static void reset(HUDFlail_t& my)
+ {
+ my = HUDFlail_t();
+ }
+};
+HUDFlail_t HUDFlail[MAXPLAYERS];
+
#define HUDARM_PLAYERNUM my->skill[11]
#define HUD_SHAPESHIFT_HIDE my->skill[12]
#define HUD_LASTSHAPESHIFT_FORM my->skill[13]
@@ -73,6 +121,9 @@ void actHudArm(Entity* my)
my->y = parent->y;
my->z = parent->z - 2.5;
+ my->mistformGLRender = players[HUDARM_PLAYERNUM]->entity->mistformGLRender > 0.9 ? players[HUDARM_PLAYERNUM]->entity->mistformGLRender
+ : 0.0;
+
Monster playerRace = players[HUDARM_PLAYERNUM]->entity->getMonsterFromPlayerRace(stats[HUDARM_PLAYERNUM]->playerRace);
int playerAppearance = stats[HUDARM_PLAYERNUM]->stat_appearance;
if ( players[HUDARM_PLAYERNUM]->entity->effectShapeshift != NOTHING )
@@ -157,6 +208,26 @@ void actHudArm(Entity* my)
{
my->sprite = 802;
}
+ else if ( stats[HUDARM_PLAYERNUM]->gloves->type == BONE_BRACERS )
+ {
+ my->sprite = 2107;
+ }
+ else if ( stats[HUDARM_PLAYERNUM]->gloves->type == BLACKIRON_GAUNTLETS )
+ {
+ my->sprite = 2109;
+ }
+ else if ( stats[HUDARM_PLAYERNUM]->gloves->type == SILVER_GAUNTLETS )
+ {
+ my->sprite = 2111;
+ }
+ else if ( stats[HUDARM_PLAYERNUM]->gloves->type == QUILTED_GLOVES )
+ {
+ my->sprite = 2113;
+ }
+ else if ( stats[HUDARM_PLAYERNUM]->gloves->type == CHAIN_GLOVES )
+ {
+ my->sprite = 2115;
+ }
if ( stats[HUDARM_PLAYERNUM]->weapon == nullptr )
{
my->scalex = 0.5f;
@@ -229,6 +300,28 @@ void actHudArm(Entity* my)
case RAT:
my->sprite = 859;
break;
+ case DRYAD:
+ my->sprite = 2320;
+ break;
+ case MYCONID:
+ my->sprite = 2328;
+ break;
+ case GREMLIN:
+ if ( stats[HUDARM_PLAYERNUM]->sex == FEMALE )
+ {
+ my->sprite = 2324;
+ }
+ else
+ {
+ my->sprite = 2326;
+ }
+ break;
+ case SALAMANDER:
+ my->sprite = 2330;
+ break;
+ case GNOME:
+ my->sprite = 2322;
+ break;
default:
my->sprite = 634;
break;
@@ -294,6 +387,8 @@ void actHudArm(Entity* my)
#define HUDWEAPON_BOW_HAS_QUIVER my->skill[9]
#define HUDWEAPON_BOW_FORCE_RELOAD my->skill[10]
#define HUDWEAPON_PLAYERNUM my->skill[11]
+#define HUDWEAPON_DELAY_TICK my->skill[14]
+#define HUDWEAPON_PARRY_TICK my->skill[15]
#define HUDWEAPON_MOVEX my->fskill[0]
#define HUDWEAPON_MOVEY my->fskill[1]
#define HUDWEAPON_MOVEZ my->fskill[2]
@@ -303,7 +398,6 @@ void actHudArm(Entity* my)
#define HUDWEAPON_OLDVIBRATEX my->fskill[6]
#define HUDWEAPON_OLDVIBRATEY my->fskill[7]
#define HUDWEAPON_OLDVIBRATEZ my->fskill[8]
-
enum CrossbowAnimState : int
{
CROSSBOW_ANIM_NONE,
@@ -369,6 +463,11 @@ void actHudWeapon(Entity* my)
return;
}
+ if ( HUDWEAPON_DELAY_TICK > 0 )
+ {
+ --HUDWEAPON_DELAY_TICK;
+ }
+
if ( multiplayer == CLIENT )
{
if ( stats[HUDWEAPON_PLAYERNUM]->HP <= 0 )
@@ -405,6 +504,9 @@ void actHudWeapon(Entity* my)
return;
}
+ my->mistformGLRender = players[HUDWEAPON_PLAYERNUM]->entity->mistformGLRender > 0.9 ? players[HUDWEAPON_PLAYERNUM]->entity->mistformGLRender
+ : 0.0;
+
// reduce throwGimpTimer (allows player to throw items again)
if ( throwGimpTimer > 0 )
{
@@ -484,6 +586,11 @@ void actHudWeapon(Entity* my)
}
}
+ if ( HUDWEAPON_CHOP >= 10 && HUDWEAPON_CHOP <= 12 ) // power strike charge
+ {
+ hideWeapon = true;
+ }
+
HUDWEAPON_HIDEWEAPON = hideWeapon;
HUDWEAPON_SHOOTING_RANGED_WEAPON = RANGED_ANIM_IDLE;
@@ -498,7 +605,7 @@ void actHudWeapon(Entity* my)
my->flags[INVISIBLE_DITHER] = false;
- if ( players[HUDWEAPON_PLAYERNUM]->entity->skill[3] == 1 ) // debug cam or player invisible
+ if ( players[HUDWEAPON_PLAYERNUM]->entity->skill[3] != 0 ) // debug cam or player invisible
{
my->flags[INVISIBLE] = true;
if (parent != nullptr)
@@ -580,7 +687,8 @@ void actHudWeapon(Entity* my)
if ( rangedweapon )
{
if ( stats[HUDWEAPON_PLAYERNUM]->weapon
- && stats[HUDWEAPON_PLAYERNUM]->weapon->type != CROSSBOW )
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != CROSSBOW
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != BLACKIRON_CROSSBOW )
{
my->sprite++;
}
@@ -588,7 +696,10 @@ void actHudWeapon(Entity* my)
}
}
}
- else if ( stats[HUDWEAPON_PLAYERNUM]->weapon && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) )
+ else if ( stats[HUDWEAPON_PLAYERNUM]->weapon
+ && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
{
HUDWEAPON_SHOOTING_RANGED_WEAPON = RANGED_ANIM_BEING_DRAWN;
if ( rangedWeaponUseQuiverOnAttack(stats[HUDWEAPON_PLAYERNUM]) )
@@ -624,6 +735,10 @@ void actHudWeapon(Entity* my)
{
my->sprite = 987;
}
+ else if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW )
+ {
+ my->sprite = 2260;
+ }
}
else if ( HUDWEAPON_CHOP == CROSSBOW_CHOP_RELOAD_ENDING )
{
@@ -695,12 +810,14 @@ void actHudWeapon(Entity* my)
bool swingweapon = false;
if ( players[HUDWEAPON_PLAYERNUM]->entity
- && input.binaryToggle("Attack")
+ && (input.binaryToggle("Attack")
+ || (cast_animation[HUDWEAPON_PLAYERNUM].spellWaitingAttackInput()
+ && inputs.hasController(HUDWEAPON_PLAYERNUM) && input.binaryToggle("Cast Spell")) )
&& shootmode
&& !gamePaused
&& players[HUDWEAPON_PLAYERNUM]->entity->isMobile()
- && !(input.binaryToggle("Defend") && stats[HUDWEAPON_PLAYERNUM]->defending)
- && HUDWEAPON_OVERCHARGE < MAXCHARGE )
+ && (!(input.binaryToggle("Defend") && stats[HUDWEAPON_PLAYERNUM]->defending) || cast_animation[HUDWEAPON_PLAYERNUM].spellWaitingAttackInput() )
+ && HUDWEAPON_OVERCHARGE < Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]) )
{
swingweapon = true;
}
@@ -721,8 +838,9 @@ void actHudWeapon(Entity* my)
bool thrownWeapon = stats[HUDWEAPON_PLAYERNUM]->weapon
&& (itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == THROWN || itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == GEM
- || stats[HUDWEAPON_PLAYERNUM]->weapon->type == FOOD_CREAMPIE);
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == FOOD_CREAMPIE || stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_DUCK);
bool castStrikeAnimation = (players[HUDWEAPON_PLAYERNUM]->entity->skill[9] == MONSTER_POSE_SPECIAL_WINDUP1);
+ bool flail = !hideWeapon && stats[HUDWEAPON_PLAYERNUM]->weapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == STEEL_FLAIL;
// weapon switch animation
if ( players[HUDWEAPON_PLAYERNUM]->hud.weaponSwitch )
@@ -730,7 +848,10 @@ void actHudWeapon(Entity* my)
players[HUDWEAPON_PLAYERNUM]->hud.weaponSwitch = false;
if ( !hideWeapon )
{
- if ( stats[HUDWEAPON_PLAYERNUM]->weapon && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) )
+ if ( stats[HUDWEAPON_PLAYERNUM]->weapon
+ && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
{
swingweapon = false;
HUDWEAPON_CHARGE = 0;
@@ -785,6 +906,13 @@ void actHudWeapon(Entity* my)
HUDWEAPON_CHOP = 0;
}
+ if ( flail )
+ {
+ swingweapon = false;
+ HUDWEAPON_CHOP = 0;
+ HUDWEAPON_CHARGE = 0;
+ HUDWEAPON_OVERCHARGE = 0;
+ }
if ( thrownWeapon && HUDWEAPON_CHOP > 3 )
{
// prevent thrown weapon rapid firing.
@@ -798,6 +926,7 @@ void actHudWeapon(Entity* my)
if ( rangedweapon && stats[HUDWEAPON_PLAYERNUM]->weapon
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != CROSSBOW
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != HEAVY_CROSSBOW
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != BLACKIRON_CROSSBOW
)
{
HUDWEAPON_BOW_FORCE_RELOAD = 1;
@@ -841,6 +970,7 @@ void actHudWeapon(Entity* my)
if ( rangedweapon && stats[HUDWEAPON_PLAYERNUM]->weapon
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != CROSSBOW
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != HEAVY_CROSSBOW
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != BLACKIRON_CROSSBOW
&& !hideWeapon )
{
bowFireRate = bowDrawBaseTicks * (rangedAttackGetSpeedModifier(stats[HUDWEAPON_PLAYERNUM]));
@@ -972,8 +1102,10 @@ void actHudWeapon(Entity* my)
}
}
- bool whip = stats[HUDWEAPON_PLAYERNUM]->weapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_WHIP;
+ bool whip = !hideWeapon && stats[HUDWEAPON_PLAYERNUM]->weapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_WHIP;
bool bearTrap = !hideWeapon && stats[HUDWEAPON_PLAYERNUM]->weapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_BEARTRAP;
+ bool rapier = stats[HUDWEAPON_PLAYERNUM]->weapon && !hideWeapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == RAPIER;
+ bool claymore = stats[HUDWEAPON_PLAYERNUM]->weapon && !hideWeapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == CLAYMORE_SWORD;
// main animation
if ( HUDWEAPON_CHOP == 0 )
@@ -985,8 +1117,21 @@ void actHudWeapon(Entity* my)
HUDWEAPON_CROSSBOW_RELOAD_ANIMATION = CROSSBOW_ANIM_NONE;
}
bool ignoreAttack = false;
- if ( swingweapon && throwGimpTimer > 0 && stats[HUDWEAPON_PLAYERNUM]->weapon &&
+ if ( !castStrikeAnimation && (cast_animation[HUDWEAPON_PLAYERNUM].spellWaitingAttackInput()
+ || cast_animation[HUDWEAPON_PLAYERNUM].spellIgnoreAttack()
+ || cast_animation[HUDWEAPON_PLAYERNUM].active
+ || cast_animation[HUDWEAPON_PLAYERNUM].active_spellbook )
+ )
+ {
+ ignoreAttack = true;
+ if ( cast_animation[HUDWEAPON_PLAYERNUM].spellWaitingAttackInput() )
+ {
+ cast_animation[HUDWEAPON_PLAYERNUM].executeAttackSpell(swingweapon);
+ }
+ }
+ else if ( swingweapon && throwGimpTimer > 0 && stats[HUDWEAPON_PLAYERNUM]->weapon && !hideWeapon &&
( stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW
|| itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == POTION
|| itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == GEM
|| itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == THROWN
@@ -998,6 +1143,7 @@ void actHudWeapon(Entity* my)
else if ( swingweapon && bowGimpTimer > 0 && rangedweapon && stats[HUDWEAPON_PLAYERNUM]->weapon
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != CROSSBOW
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != HEAVY_CROSSBOW
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != BLACKIRON_CROSSBOW
&& !hideWeapon )
{
ignoreAttack = true;
@@ -1069,7 +1215,11 @@ void actHudWeapon(Entity* my)
{
pickaxeGimpTimer = 40;
}
- if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == IRON_SPEAR || stats[HUDWEAPON_PLAYERNUM]->weapon->type == ARTIFACT_SPEAR )
+ if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == IRON_SPEAR
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == ARTIFACT_SPEAR
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BONE_SPEAR
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == SILVER_GLAIVE
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_TRIDENT )
{
HUDWEAPON_CHOP = 7; // spear lunges
}
@@ -1078,12 +1228,20 @@ void actHudWeapon(Entity* my)
HUDWEAPON_CHOP = 4;
pickaxeGimpTimer = 20;
}
+ else if ( rapier )
+ {
+ HUDWEAPON_CHOP = 7; // lunges
+ HUDWEAPON_DELAY_TICK = 0;
+ }
else if ( rangedweapon )
{
if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == SLING
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == SHORTBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BONE_SHORTBOW
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == ARTIFACT_BOW
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == LONGBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BRANCH_BOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BRANCH_BOW_INFECTED
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == COMPOUND_BOW )
{
if ( !stats[HUDWEAPON_PLAYERNUM]->defending && !throwGimpTimer )
@@ -1249,7 +1407,8 @@ void actHudWeapon(Entity* my)
// set delay before crossbow can fire again
throwGimpTimer = 40;
- if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW )
+ if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW )
{
throwGimpTimer *= rangedAttackGetSpeedModifier(stats[HUDWEAPON_PLAYERNUM]);
}
@@ -1377,7 +1536,8 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_CHOP = 7; // magicstaffs lunge
}
- else if ( itemCategory(item) == THROWN || itemCategory(item) == GEM || item->type == FOOD_CREAMPIE )
+ else if ( itemCategory(item) == THROWN || itemCategory(item) == GEM || item->type == FOOD_CREAMPIE
+ || item->type == TOOL_DUCK )
{
if ( !throwGimpTimer )
{
@@ -1394,8 +1554,12 @@ void actHudWeapon(Entity* my)
// keys and lockpicks
HUDWEAPON_MOVEX = 5;
HUDWEAPON_CHOP = 3;
+ if ( pickaxeGimpTimer < TICKS_PER_SECOND / 2 )
+ {
+ pickaxeGimpTimer = TICKS_PER_SECOND / 2; // fix for swapping weapon causing issues.
+ }
Entity* player = players[HUDWEAPON_PLAYERNUM]->entity;
- bool foundBomb = false;
+ bool foundPassableObject = false;
if ( stats[HUDWEAPON_PLAYERNUM]->weapon )
{
bool clickedOnGUI = false;
@@ -1411,14 +1575,15 @@ void actHudWeapon(Entity* my)
inputs.setMouse(HUDWEAPON_PLAYERNUM, Inputs::OX, tmpmousex);
inputs.setMouse(HUDWEAPON_PLAYERNUM, Inputs::OX, tmpmousey);
- if ( clickedOn && clickedOn->behavior == &actBomb && entityDist(clickedOn, players[HUDWEAPON_PLAYERNUM]->entity) < STRIKERANGE )
+ if ( clickedOn && (clickedOn->behavior == &actBomb || clickedOn->behavior == &actWallLock)
+ && entityDist(clickedOn, players[HUDWEAPON_PLAYERNUM]->entity) < STRIKERANGE )
{
// found something
stats[HUDWEAPON_PLAYERNUM]->weapon->apply(HUDWEAPON_PLAYERNUM, clickedOn);
- foundBomb = true;
+ foundPassableObject = true;
}
}
- if ( !foundBomb )
+ if ( !foundPassableObject )
{
real_t dist = lineTrace(player, player->x, player->y, player->yaw, STRIKERANGE, 0, false);
if ( hit.entity && stats[HUDWEAPON_PLAYERNUM]->weapon )
@@ -1449,6 +1614,10 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_MOVEX = 5;
HUDWEAPON_CHOP = 3;
+ if ( pickaxeGimpTimer < TICKS_PER_SECOND / 2 )
+ {
+ pickaxeGimpTimer = TICKS_PER_SECOND / 2; // fix for swapping weapon causing issues.
+ }
Entity* player = players[HUDWEAPON_PLAYERNUM]->entity;
lineTrace(player, player->x, player->y, player->yaw, STRIKERANGE, 0, false);
if ( hit.entity && stats[HUDWEAPON_PLAYERNUM]->weapon )
@@ -1484,9 +1653,14 @@ void actHudWeapon(Entity* my)
messagePlayer(HUDWEAPON_PLAYERNUM, MESSAGE_HINT, Language::get(3336));
}
throwGimpTimer = TICKS_PER_SECOND / 2;
+ if ( pickaxeGimpTimer < TICKS_PER_SECOND / 2 )
+ {
+ pickaxeGimpTimer = TICKS_PER_SECOND / 2; // fix for swapping weapon causing issues.
+ }
}
}
- else if ((itemCategory(item) == POTION || itemCategory(item) == GEM || itemCategory(item) == THROWN || item->type == FOOD_CREAMPIE )
+ else if ((itemCategory(item) == POTION || itemCategory(item) == GEM || itemCategory(item) == THROWN || item->type == FOOD_CREAMPIE
+ || item->type == TOOL_DUCK)
&& !throwGimpTimer)
{
if ( itemCategory(item) == THROWN )
@@ -1531,8 +1705,11 @@ void actHudWeapon(Entity* my)
if ( !hideWeapon &&
(stats[HUDWEAPON_PLAYERNUM]->weapon->type == SLING
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == SHORTBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BONE_SHORTBOW
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == ARTIFACT_BOW
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == LONGBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BRANCH_BOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BRANCH_BOW_INFECTED
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == COMPOUND_BOW) )
{
// not drawing bow anymore, reset.
@@ -1732,39 +1909,66 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_YAW = 0;
}
- HUDWEAPON_PITCH -= .1;
- if ( HUDWEAPON_PITCH < -PI / 4)
- {
- result = -PI / 4;
- HUDWEAPON_PITCH = result;
- }
HUDWEAPON_ROLL += .25;
if ( HUDWEAPON_ROLL > 0 )
{
HUDWEAPON_ROLL = 0;
}
int targetY = -2;
- if ( thrownWeapon )
- {
- targetY = -1;
- HUDWEAPON_MOVEY -= .25;
- HUDWEAPON_MOVEX -= .15;
- }
- else
+ int targetX = -1;
+ real_t targetPitch = -PI / 4;
+ if ( flail )
{
+ targetX = 2;
+ HUDWEAPON_MOVEX += .15;
HUDWEAPON_MOVEY -= .45;
- HUDWEAPON_MOVEX -= .35;
+ if ( HUDWEAPON_MOVEX > targetX )
+ {
+ HUDWEAPON_MOVEX = targetX;
+ }
+
+ targetPitch = -PI / 32;
+ HUDWEAPON_PITCH -= .1;
+ if ( HUDWEAPON_PITCH < targetPitch )
+ {
+ HUDWEAPON_PITCH = targetPitch;
+ }
}
- if ( HUDWEAPON_MOVEX < -1 )
+ else
{
- HUDWEAPON_MOVEX = -1;
+ HUDWEAPON_PITCH -= .1;
+ if ( HUDWEAPON_PITCH < targetPitch )
+ {
+ HUDWEAPON_PITCH = targetPitch;
+ }
+
+ if ( thrownWeapon )
+ {
+ targetY = -1;
+ HUDWEAPON_MOVEY -= .25;
+ HUDWEAPON_MOVEX -= .15;
+ }
+ else
+ {
+ HUDWEAPON_MOVEY -= .45;
+ HUDWEAPON_MOVEX -= .35;
+ }
+ if ( HUDWEAPON_MOVEX < targetX )
+ {
+ HUDWEAPON_MOVEX = targetX;
+ }
}
if ( HUDWEAPON_MOVEY < targetY )
{
HUDWEAPON_MOVEY = targetY;
}
int targetZ = -6;
- if ( whip )
+ if ( flail )
+ {
+ targetZ = -5;
+ HUDWEAPON_MOVEZ -= .65;
+ }
+ else if ( whip )
{
targetZ = -6;
HUDWEAPON_MOVEZ -= .32;
@@ -1782,50 +1986,88 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_MOVEZ -= .65;
}
+
+ bool parry = (claymore) && input.binaryToggle("Defend");
+
if ( HUDWEAPON_MOVEZ < targetZ )
{
HUDWEAPON_MOVEZ = targetZ;
- if ( HUDWEAPON_PITCH == result && HUDWEAPON_ROLL == 0 && HUDWEAPON_YAW == 0 && HUDWEAPON_MOVEX == -1 && HUDWEAPON_MOVEY == targetY )
+ if ( HUDWEAPON_PITCH == targetPitch && HUDWEAPON_ROLL == 0 && HUDWEAPON_YAW == 0 && HUDWEAPON_MOVEX == targetX && HUDWEAPON_MOVEY == targetY )
{
- if ( !swingweapon )
+ if ( claymore && (HUDWEAPON_OVERCHARGE > 0
+ /*|| (*cvar_claymore_toggle == 2 && HUDWEAPON_CHARGE >= Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]) / 2)*/) )
{
- HUDWEAPON_CHOP++;
- if ( !bearTrap )
+ /*if ( *cvar_claymore_toggle == 2 )
{
- players[HUDWEAPON_PLAYERNUM]->entity->attack(1, HUDWEAPON_CHARGE, nullptr);
- }
- if ( stats[HUDWEAPON_PLAYERNUM]->weapon
- && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) )
+ HUDWEAPON_CHARGE = Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]);
+ }*/
+ HUDWEAPON_CHOP = 4;
+ HUDWEAPON_OVERCHARGE = 0;
+ }
+ else if ( !swingweapon && flail && HUDWEAPON_OVERCHARGE == 0 )
+ {
+ HUDWEAPON_CHARGE = 0;
+ HUDWEAPON_OVERCHARGE = 0;
+ HUDWEAPON_CHOP = 24;
+ }
+ else if ( !swingweapon || parry )
+ {
+ HUDWEAPON_CHOP++;
+ if ( parry )
{
- throwGimpTimer = 40; // fix for swapping weapon to crossbow while charging.
+ input.consumeBinaryToggle("Defend");
+ HUDWEAPON_CHOP = 21;
+ pickaxeGimpTimer = 40;
+ HUDWEAPON_PARRY_TICK = my->ticks;
+ players[HUDWEAPON_PLAYERNUM]->entity->attack(MONSTER_POSE_PARRY, 35, nullptr);
+ HUDWEAPON_MOVEX = 0.0;
+ HUDWEAPON_MOVEY = -1;
+ HUDWEAPON_MOVEZ = -2;
+ HUDWEAPON_YAW = 2 * PI / 5;
+ HUDWEAPON_PITCH = 0.2;
+ HUDWEAPON_ROLL = -2 * PI / 5;
}
- else if ( (stats[HUDWEAPON_PLAYERNUM]->weapon
- && stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_PICKAXE) || whip )
+ else
{
- if ( pickaxeGimpTimer < 20 )
+ if ( !bearTrap )
{
- pickaxeGimpTimer = 20; // fix for swapping weapon from pickaxe causing issues.
+ players[HUDWEAPON_PLAYERNUM]->entity->attack(1, HUDWEAPON_CHARGE, nullptr);
+ }
+ if ( stats[HUDWEAPON_PLAYERNUM]->weapon
+ && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
+ {
+ throwGimpTimer = 40; // fix for swapping weapon to crossbow while charging.
+ }
+ else if ( (stats[HUDWEAPON_PLAYERNUM]->weapon
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_PICKAXE) || whip )
+ {
+ if ( pickaxeGimpTimer < 20 )
+ {
+ pickaxeGimpTimer = 20; // fix for swapping weapon from pickaxe causing issues.
+ }
}
- }
- if ( multiplayer == CLIENT && (thrownWeapon || (stats[HUDWEAPON_PLAYERNUM]->weapon && itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == POTION)) )
- {
- Item* item = stats[HUDWEAPON_PLAYERNUM]->weapon;
- if ( item )
+ if ( multiplayer == CLIENT && (thrownWeapon || (stats[HUDWEAPON_PLAYERNUM]->weapon && itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == POTION)) )
{
- item->count--;
- if ( item->count <= 0 )
+ Item* item = stats[HUDWEAPON_PLAYERNUM]->weapon;
+ if ( item )
{
- if ( item->node )
- {
- list_RemoveNode(item->node);
- }
- else
+ item->count--;
+ if ( item->count <= 0 )
{
- free(item);
+ if ( item->node )
+ {
+ list_RemoveNode(item->node);
+ }
+ else
+ {
+ free(item);
+ }
+ stats[HUDWEAPON_PLAYERNUM]->weapon = NULL;
}
- stats[HUDWEAPON_PLAYERNUM]->weapon = NULL;
}
}
}
@@ -1839,7 +2081,14 @@ void actHudWeapon(Entity* my)
}
else
{
- HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, MAXCHARGE);
+ if ( flail )
+ {
+ HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + (ticks % 4 == 0 ? 1 : 0), Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]));
+ }
+ else
+ {
+ HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]));
+ }
}
}
}
@@ -1886,6 +2135,30 @@ void actHudWeapon(Entity* my)
HUDWEAPON_CHOP++;
}
}
+ else if ( flail )
+ {
+ static ConsoleVariable cvar_anim_flail("/anim_flail", 1.0);
+ static ConsoleVariable cvar_anim_flail_x1("/anim_flail_x1", 0.3);
+ static ConsoleVariable cvar_anim_flail_x2("/anim_flail_x2", 8.0);
+ static ConsoleVariable cvar_anim_flail_x3("/anim_flail_x3", 0.0);
+ HUDWEAPON_PITCH += .55 * *cvar_anim_flail;
+ if ( HUDWEAPON_PITCH >= PI * *cvar_anim_flail_x1 )
+ {
+ HUDWEAPON_PITCH = PI * *cvar_anim_flail_x1;
+ }
+ HUDWEAPON_MOVEX += 1.5 * *cvar_anim_flail;
+ if ( HUDWEAPON_MOVEX > *cvar_anim_flail_x2 )
+ {
+ HUDWEAPON_MOVEX = *cvar_anim_flail_x2;
+ }
+ HUDWEAPON_MOVEZ += .8;
+
+ if ( HUDWEAPON_MOVEZ > 0.0 )
+ {
+ HUDWEAPON_MOVEZ = 0.0;
+ HUDWEAPON_CHOP++;
+ }
+ }
else
{
HUDWEAPON_PITCH += .75;
@@ -1923,11 +2196,27 @@ void actHudWeapon(Entity* my)
&& item->type != FOOD_CREAMPIE
&& !(item->type >= ARTIFACT_ORB_BLUE && item->type <= ARTIFACT_ORB_GREEN)
&& !(itemIsThrowableTinkerTool(item))
- && item->type != TOOL_WHIP )
+ && item->type != TOOL_DUCK
+ && item->type != RAPIER
+ && item->type != TOOL_WHIP
+ && item->type != STEEL_FLAIL )
{
if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type != TOOL_PICKAXE && itemCategory(item) != THROWN )
{
- HUDWEAPON_CHOP = 4;
+ if ( claymore )
+ {
+ HUDWEAPON_CHOP = 7;
+ HUDWEAPON_YAW = 3 * PI / 4;
+ HUDWEAPON_MOVEX = sin(HUDWEAPON_YAW) * 1;
+ HUDWEAPON_MOVEY = cos(HUDWEAPON_YAW) * -6;
+ HUDWEAPON_MOVEZ = -4;
+ HUDWEAPON_PITCH = 0.0;
+ HUDWEAPON_ROLL = - PI / 2;
+ }
+ else
+ {
+ HUDWEAPON_CHOP = 4;
+ }
}
else
{
@@ -1954,8 +2243,11 @@ void actHudWeapon(Entity* my)
{
if ( stats[HUDWEAPON_PLAYERNUM]->weapon->type == SLING
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == SHORTBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BONE_SHORTBOW
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == ARTIFACT_BOW
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == LONGBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BRANCH_BOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BRANCH_BOW_INFECTED
|| stats[HUDWEAPON_PLAYERNUM]->weapon->type == COMPOUND_BOW )
{
if (bowFire)
@@ -2047,7 +2339,9 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_MOVEY = 0;
}
+
HUDWEAPON_MOVEZ -= .35;
+
if ( HUDWEAPON_MOVEZ < 0 )
{
HUDWEAPON_MOVEZ = 0;
@@ -2082,6 +2376,22 @@ void actHudWeapon(Entity* my)
rateY = .55;
rateRoll = .35;
}
+ else if ( claymore )
+ {
+ real_t factor = 0.75;
+ rateY *= factor;
+ rateRoll *= factor;
+ HUDWEAPON_PITCH -= 0.25 * factor;
+ if ( HUDWEAPON_PITCH < targetPitch )
+ {
+ HUDWEAPON_PITCH = targetPitch;
+ }
+ HUDWEAPON_YAW -= 0.25;
+ if ( HUDWEAPON_YAW < 0.0 )
+ {
+ HUDWEAPON_YAW = 0.0;
+ }
+ }
else
{
HUDWEAPON_YAW = 0;
@@ -2113,7 +2423,7 @@ void actHudWeapon(Entity* my)
HUDWEAPON_ROLL = targetRoll;
if (HUDWEAPON_PITCH == targetPitch && HUDWEAPON_MOVEX == 0 && HUDWEAPON_MOVEY == targetY && HUDWEAPON_MOVEZ == targetZ)
{
- if (!swingweapon)
+ if (!swingweapon || claymore )
{
HUDWEAPON_CHOP++;
if ( !bearTrap )
@@ -2121,7 +2431,9 @@ void actHudWeapon(Entity* my)
players[HUDWEAPON_PLAYERNUM]->entity->attack(2, HUDWEAPON_CHARGE, nullptr);
}
if ( stats[HUDWEAPON_PLAYERNUM]->weapon
- && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) )
+ && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
{
throwGimpTimer = 40; // fix for swapping weapon to crossbow while charging.
}
@@ -2130,6 +2442,10 @@ void actHudWeapon(Entity* my)
if (players[HUDWEAPON_PLAYERNUM]->entity->skill[3] == 0) // debug cam OFF
{
camera_shakex += .07;
+ if ( claymore )
+ {
+ camera_shakex += .07;
+ }
}
if ( whip && pickaxeGimpTimer < 20 )
{
@@ -2138,7 +2454,7 @@ void actHudWeapon(Entity* my)
}
else
{
- HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, MAXCHARGE);
+ HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]));
}
}
}
@@ -2175,6 +2491,17 @@ void actHudWeapon(Entity* my)
HUDWEAPON_MOVEY += 10;
}
}
+ else if ( claymore )
+ {
+ HUDWEAPON_MOVEX = sin(HUDWEAPON_YAW) * 1;
+ HUDWEAPON_MOVEY = cos(HUDWEAPON_YAW) * -6;
+ HUDWEAPON_YAW += .25;
+ if ( HUDWEAPON_YAW > (3 * PI) / 4 )
+ {
+ HUDWEAPON_YAW = (3 * PI) / 4;
+ HUDWEAPON_CHOP++;
+ }
+ }
else
{
HUDWEAPON_MOVEX = sin(HUDWEAPON_YAW) * 1;
@@ -2195,6 +2522,11 @@ void actHudWeapon(Entity* my)
if ( stats[HUDWEAPON_PLAYERNUM]->weapon && !hideWeapon )
{
int weaponSkill = getWeaponSkill(stats[HUDWEAPON_PLAYERNUM]->weapon);
+ /*if ( claymore && *cvar_claymore_toggle == 1 )
+ {
+ HUDWEAPON_CHOP = 1;
+ }
+ else*/
if ( weaponSkill == PRO_SWORD || stats[HUDWEAPON_PLAYERNUM]->weapon->type == STEEL_HALBERD )
{
HUDWEAPON_CHOP = 7; // swords + halberds can stab
@@ -2267,6 +2599,7 @@ void actHudWeapon(Entity* my)
}
else if ( HUDWEAPON_CHOP == 7 ) // prepare for third swing
{
+ real_t pitchLimit = .2;
HUDWEAPON_MOVEX -= .35;
if ( HUDWEAPON_MOVEX < 0 )
{
@@ -2282,13 +2615,7 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_MOVEZ = -2;
}
- HUDWEAPON_YAW -= .15;
- if ( HUDWEAPON_YAW < 2 * PI / 5 )
- {
- result = 2 * PI / 5;
- HUDWEAPON_YAW = result;
- }
- real_t pitchLimit = .2;
+
if ( playerRace == SPIDER )
{
pitchLimit = -PI / 2;
@@ -2302,29 +2629,83 @@ void actHudWeapon(Entity* my)
{
HUDWEAPON_PITCH = pitchLimit;
}
+
+ HUDWEAPON_YAW -= .15;
+ if ( HUDWEAPON_YAW < 2 * PI / 5 )
+ {
+ result = 2 * PI / 5;
+ HUDWEAPON_YAW = result;
+ }
HUDWEAPON_ROLL -= .15;
+
+ bool parry = (rapier || claymore) && input.binaryToggle("Defend");
+
if (HUDWEAPON_ROLL < -2 * PI / 5)
{
HUDWEAPON_ROLL = -2 * PI / 5;
if (HUDWEAPON_PITCH == pitchLimit && HUDWEAPON_YAW == result
&& HUDWEAPON_MOVEX == 0 && HUDWEAPON_MOVEY == -1 && (HUDWEAPON_MOVEZ == -2))
{
- if (!swingweapon)
+ if ( claymore
+ && (HUDWEAPON_OVERCHARGE > 0
+ /*|| (*cvar_claymore_toggle == 2 && HUDWEAPON_CHARGE >= Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]) / 2)*/) )
+ {
+ /*if ( *cvar_claymore_toggle == 2 )
+ {
+ HUDWEAPON_CHARGE = Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]);
+ }*/
+ HUDWEAPON_CHOP = 4;
+ HUDWEAPON_OVERCHARGE = 0;
+ }
+ else if ( (!swingweapon || parry) && HUDWEAPON_DELAY_TICK == 0)
{
HUDWEAPON_CHOP++;
- if ( !bearTrap )
+ if ( parry )
+ {
+ input.consumeBinaryToggle("Defend");
+ HUDWEAPON_CHOP = 21;
+ pickaxeGimpTimer = 40;
+ HUDWEAPON_PARRY_TICK = my->ticks;
+ players[HUDWEAPON_PLAYERNUM]->entity->attack(MONSTER_POSE_PARRY, 35, nullptr);
+ }
+ else if ( rapier )
+ {
+ players[HUDWEAPON_PLAYERNUM]->entity->attack(3, HUDWEAPON_CHARGE, nullptr);
+ }
+ else if ( !bearTrap && !flail )
{
if ( stats[HUDWEAPON_PLAYERNUM]->weapon && hideWeapon )
{
players[HUDWEAPON_PLAYERNUM]->entity->attack(1, HUDWEAPON_CHARGE, nullptr);
}
+ else if ( !hideWeapon && stats[HUDWEAPON_PLAYERNUM]->weapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == MAGICSTAFF_SCEPTER )
+ {
+ int chargeAmount = HUDWEAPON_CHARGE;
+ if ( HUDWEAPON_OVERCHARGE >= (Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]) - 3) )
+ {
+ int staffCharge = stats[HUDWEAPON_PLAYERNUM]->weapon->appearance % MAGICSTAFF_SCEPTER_CHARGE_MAX;
+ if ( staffCharge > 0 )
+ {
+ int decrement = std::min(staffCharge, 5);
+ stats[HUDWEAPON_PLAYERNUM]->weapon->appearance -= decrement;
+ chargeAmount = 100;
+ }
+ else
+ {
+ chargeAmount = 99;
+ }
+ }
+ players[HUDWEAPON_PLAYERNUM]->entity->attack(3, chargeAmount, nullptr);
+ }
else
{
players[HUDWEAPON_PLAYERNUM]->entity->attack(3, HUDWEAPON_CHARGE, nullptr);
}
}
if ( stats[HUDWEAPON_PLAYERNUM]->weapon
- && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) )
+ && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
{
throwGimpTimer = 40; // fix for swapping weapon to crossbow while charging.
}
@@ -2338,7 +2719,7 @@ void actHudWeapon(Entity* my)
}
else
{
- HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, MAXCHARGE);
+ HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]));
}
}
}
@@ -2404,9 +2785,24 @@ void actHudWeapon(Entity* my)
}
else if ( !(stats[HUDWEAPON_PLAYERNUM]->weapon && stats[HUDWEAPON_PLAYERNUM]->weapon->type == TOOL_BEARTRAP) )
{
- if ( itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) != MAGICSTAFF
+ /*if ( claymore && *cvar_claymore_toggle == 1 )
+ {
+ HUDWEAPON_CHARGE = Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]);
+ HUDWEAPON_CHOP = 4;
+ HUDWEAPON_OVERCHARGE = 0;
+ }
+ else*/
+ if ( rapier )
+ {
+ HUDWEAPON_CHOP = 7;
+ }
+ else if ( stats[HUDWEAPON_PLAYERNUM]->weapon
+ && itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) != MAGICSTAFF
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != CRYSTAL_SPEAR
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != IRON_SPEAR
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != BLACKIRON_TRIDENT
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != BONE_SPEAR
+ && stats[HUDWEAPON_PLAYERNUM]->weapon->type != SILVER_GLAIVE
&& stats[HUDWEAPON_PLAYERNUM]->weapon->type != ARTIFACT_SPEAR )
{
HUDWEAPON_CHOP = 1;
@@ -2483,7 +2879,10 @@ void actHudWeapon(Entity* my)
}
if ( !bearTrap )
{
+ Item* tmp = stats[HUDWEAPON_PLAYERNUM]->weapon;
+ stats[HUDWEAPON_PLAYERNUM]->weapon = nullptr;
players[HUDWEAPON_PLAYERNUM]->entity->attack(PLAYER_POSE_GOLEM_SMASH, MAXCHARGE, nullptr);
+ stats[HUDWEAPON_PLAYERNUM]->weapon = tmp;
}
}
}
@@ -2533,6 +2932,7 @@ void actHudWeapon(Entity* my)
if ( HUDWEAPON_YAW == -.1 && HUDWEAPON_PITCH == 0 && HUDWEAPON_MOVEZ == 0 && HUDWEAPON_MOVEY == 0 && HUDWEAPON_MOVEX == 0 )
{
HUDWEAPON_CHOP = 0;
+ players[HUDWEAPON_PLAYERNUM]->hud.weaponSwitch = true;
}
}
}
@@ -2646,7 +3046,7 @@ void actHudWeapon(Entity* my)
}
else
{
- HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, MAXCHARGE);
+ HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]));
}
}
}
@@ -2717,7 +3117,14 @@ void actHudWeapon(Entity* my)
}
else if ( HUDWEAPON_MOVEX < 0 )
{
- HUDWEAPON_MOVEX = std::min(HUDWEAPON_MOVEX + .15, 0.0);
+ real_t reloadSpeed = 1.0;
+ if ( stats[HUDWEAPON_PLAYERNUM]->weapon
+ && (stats[HUDWEAPON_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDWEAPON_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
+ {
+ reloadSpeed += 5 * std::max(0.0, 1.0 - rangedAttackGetSpeedModifier(stats[HUDWEAPON_PLAYERNUM]));
+ }
+ HUDWEAPON_MOVEX = std::min(HUDWEAPON_MOVEX + .15 * reloadSpeed, 0.0);
if ( HUDWEAPON_MOVEX > -1 )
{
if ( HUDWEAPON_CROSSBOW_RELOAD_ANIMATION == CROSSBOW_ANIM_SHOOT
@@ -2993,7 +3400,7 @@ void actHudWeapon(Entity* my)
}
else
{
- HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, MAXCHARGE);
+ HUDWEAPON_CHARGE = std::min(HUDWEAPON_CHARGE + 1, Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]));
}
}
}
@@ -3049,24 +3456,207 @@ void actHudWeapon(Entity* my)
}
}
}
+ else if ( HUDWEAPON_CHOP == 21 ) // parry
+ {
+ //HUDWEAPON_MOVEX = std::min(static_cast(1.0), HUDWEAPON_MOVEX + .15); // forward/back
+ //HUDWEAPON_MOVEY = std::min(static_cast(1.0), HUDWEAPON_MOVEY + .25); // left/right
+ //HUDWEAPON_MOVEZ = std::min(static_cast(0.0), HUDWEAPON_MOVEZ + .05); // up/down
+ real_t spd = 1.0;
- if ( HUDWEAPON_CHARGE == MAXCHARGE || castStrikeAnimation
- || players[HUDWEAPON_PLAYERNUM]->entity->skill[9] == MONSTER_POSE_SPECIAL_WINDUP2
- || shakeRangedWeapon )
+ real_t z_ang = *cvar_hudweapon_z_ang;
+ real_t z_spd = *cvar_hudweapon_z_spd;
+ real_t roll_ang = *cvar_hudweapon_roll_ang;
+ if ( rapier )
+ {
+ z_ang -= 4.0;
+ z_spd = 1.0;
+ roll_ang += -1.0;
+ }
+
+ hudWeaponAnimateVariable(HUDWEAPON_MOVEX, *cvar_hudweapon_x_ang, *cvar_hudweapon_x_spd * spd);
+ hudWeaponAnimateVariable(HUDWEAPON_MOVEY, *cvar_hudweapon_y_ang, *cvar_hudweapon_y_spd * spd);
+ hudWeaponAnimateVariable(HUDWEAPON_MOVEZ, z_ang, z_spd * spd);
+ hudWeaponAnimateVariable(HUDWEAPON_PITCH, *cvar_hudweapon_pitch_ang, *cvar_hudweapon_pitch_spd * spd);
+ hudWeaponAnimateVariable(HUDWEAPON_ROLL, roll_ang, *cvar_hudweapon_roll_spd * spd);
+ hudWeaponAnimateVariable(HUDWEAPON_YAW, *cvar_hudweapon_yaw_ang, *cvar_hudweapon_yaw_spd * spd);
+ if ( (abs(HUDWEAPON_ROLL - roll_ang) < 0.001 || *cvar_hudweapon_roll_spd == 0.f)
+ && (abs(HUDWEAPON_PITCH - *cvar_hudweapon_pitch_ang) < 0.001 || *cvar_hudweapon_pitch_spd == 0.f)
+ && (abs(HUDWEAPON_YAW - *cvar_hudweapon_yaw_ang) < 0.001 || *cvar_hudweapon_yaw_spd == 0.f)
+ && (abs(HUDWEAPON_MOVEX - *cvar_hudweapon_x_ang) < 0.001 || *cvar_hudweapon_x_spd == 0.f)
+ && (abs(HUDWEAPON_MOVEY - *cvar_hudweapon_y_ang) < 0.001 || *cvar_hudweapon_y_spd == 0.f)
+ && (abs(HUDWEAPON_MOVEZ - z_ang) < 0.001 || z_spd == 0.f) )
+ {
+ HUDWEAPON_CHOP = 22;
+ }
+ }
+ else if ( HUDWEAPON_CHOP == 22 )
{
- if ( ticks % 5 == 0 && players[HUDWEAPON_PLAYERNUM]->entity->skill[9] == MONSTER_POSE_SPECIAL_WINDUP2 )
+ hudWeaponAnimateVariable(HUDWEAPON_MOVEX, 1.0, 0.15);
+ if ( abs(HUDWEAPON_MOVEX - 1.0) < 0.001 )
{
- camera_shakey += 6;
+ HUDWEAPON_CHOP = 23;
}
- if ( ticks % 2 == 0 )
+ }
+ else if ( HUDWEAPON_CHOP == 23 )
+ {
+ /*if ( swingweapon )
{
- if ( shakeRangedWeapon )
+ HUDWEAPON_CHOP = 7;
+ }
+ else*/
+ {
+ real_t scale = *cvar_hudweapon_timescale2;
+ real_t scaleRoll = *cvar_hudweapon_timescale3;
+ //hudWeaponAnimateVariable(HUDWEAPON_MOVEX, 0.0, 0.25 * scale);
+ //hudWeaponAnimateVariable(HUDWEAPON_MOVEY, 0.0, 1.0 * scale);
+ //hudWeaponAnimateVariable(HUDWEAPON_MOVEZ, 0.0, 0.25 * scale);
+ //hudWeaponAnimateVariable(HUDWEAPON_PITCH, 0.0, 0.1 * scale);
+ //hudWeaponAnimateVariable(HUDWEAPON_ROLL, 0.0, 0.025 * scaleRoll);
+ //hudWeaponAnimateVariable(HUDWEAPON_YAW, -.1, 0.2 * scale);
+ HUDWEAPON_MOVEX *= scale;
+ HUDWEAPON_MOVEY *= scale;
+ HUDWEAPON_MOVEZ *= scale;
+ HUDWEAPON_YAW = HUDWEAPON_YAW + (-.1 - HUDWEAPON_YAW) * (1.0 - scale);
+ HUDWEAPON_PITCH *= scale;
+ HUDWEAPON_ROLL *= scale;
+ if ( abs(HUDWEAPON_MOVEX) < 0.01 )
{
- HUDWEAPON_MOVEX -= HUDWEAPON_OLDVIBRATEX;
- HUDWEAPON_MOVEY -= HUDWEAPON_OLDVIBRATEY;
- HUDWEAPON_OLDVIBRATEX = (local_rng.rand() % 30 - 10) / 150.f;
- HUDWEAPON_OLDVIBRATEY = (local_rng.rand() % 30 - 10) / 150.f;
- HUDWEAPON_MOVEX += HUDWEAPON_OLDVIBRATEX;
+ HUDWEAPON_MOVEX = 0;
+ }
+ if ( abs(HUDWEAPON_MOVEY) < 0.01 )
+ {
+ HUDWEAPON_MOVEY = 0;
+ }
+ if ( abs(HUDWEAPON_MOVEZ) < 0.01 )
+ {
+ HUDWEAPON_MOVEZ = 0;
+ }
+ if ( abs(-.1 - HUDWEAPON_YAW) < 0.01 )
+ {
+ HUDWEAPON_YAW = -.1;
+ }
+ if ( abs(HUDWEAPON_PITCH) < 0.01 )
+ {
+ HUDWEAPON_PITCH = 0;
+ }
+ if ( abs(HUDWEAPON_ROLL) < 0.01 )
+ {
+ HUDWEAPON_ROLL = 0;
+ }
+ if ( (HUDWEAPON_YAW == -.1 && HUDWEAPON_ROLL == 0 && HUDWEAPON_PITCH == 0 && HUDWEAPON_MOVEZ == 0 && HUDWEAPON_MOVEY == 0 && HUDWEAPON_MOVEX == 0)
+ || ((my->ticks - HUDWEAPON_PARRY_TICK) > TICKS_PER_SECOND) )
+ {
+ //messagePlayer(0, MESSAGE_DEBUG, "Parry ticks: %d", my->ticks - HUDWEAPON_PARRY_TICK);
+
+ HUDWEAPON_YAW = -.1;
+ HUDWEAPON_ROLL = 0;
+ HUDWEAPON_PITCH = 0;
+ HUDWEAPON_MOVEX = 0;
+ HUDWEAPON_MOVEY = 0;
+ HUDWEAPON_MOVEZ = 0;
+
+ HUDWEAPON_PARRY_TICK = 0;
+ HUDWEAPON_CHARGE = 0;
+ HUDWEAPON_OVERCHARGE = 0;
+ if ( pickaxeGimpTimer > 2 )
+ {
+ pickaxeGimpTimer = 2;
+ }
+ if ( swingweapon && (rapier || claymore) )
+ {
+ /*if ( claymore && *cvar_claymore_toggle == 1)
+ {
+ HUDWEAPON_CHOP = 1;
+ }
+ else*/
+ {
+ HUDWEAPON_CHOP = 7;
+ }
+ }
+ else
+ {
+ HUDWEAPON_CHOP = 0;
+ }
+ }
+ }
+ }
+ else if ( HUDWEAPON_CHOP == 24 )
+ {
+ //static ConsoleVariable cvar_anim_flail2("/anim_flail2", 0.25);
+ real_t rate = 0.25;
+ if ( HUDWEAPON_MOVEX > 0 )
+ {
+ HUDWEAPON_MOVEX = std::max(HUDWEAPON_MOVEX - 1 * rate, 0.0);
+ }
+ else if ( HUDWEAPON_MOVEX < 0 )
+ {
+ HUDWEAPON_MOVEX = std::min(HUDWEAPON_MOVEX + 1 * rate, 0.0);
+ }
+ if ( HUDWEAPON_MOVEY > 0 )
+ {
+ HUDWEAPON_MOVEY = std::max(HUDWEAPON_MOVEY - 1 * rate, 0.0);
+ }
+ else if ( HUDWEAPON_MOVEY < 0 )
+ {
+ HUDWEAPON_MOVEY = std::min(HUDWEAPON_MOVEY + 1 * rate, 0.0);
+ }
+ if ( HUDWEAPON_MOVEZ > 0 )
+ {
+ HUDWEAPON_MOVEZ = std::max(HUDWEAPON_MOVEZ - 1 * rate, 0.0);
+ }
+ else if ( HUDWEAPON_MOVEZ < 0 )
+ {
+ HUDWEAPON_MOVEZ = std::min(HUDWEAPON_MOVEZ + 1 * rate, 0.0);
+ }
+ if ( HUDWEAPON_YAW > -.1 )
+ {
+ HUDWEAPON_YAW = std::max(HUDWEAPON_YAW - .1 * rate, -.1);
+ }
+ else if ( HUDWEAPON_YAW < -.1 )
+ {
+ HUDWEAPON_YAW = std::min(HUDWEAPON_YAW + .1 * rate, -.1);
+ }
+ if ( HUDWEAPON_PITCH > 0 )
+ {
+ HUDWEAPON_PITCH = std::max(HUDWEAPON_PITCH - .1 * rate, 0.0);
+ }
+ else if ( HUDWEAPON_PITCH < 0 )
+ {
+ HUDWEAPON_PITCH = std::min(HUDWEAPON_PITCH + .1 * rate, 0.0);
+ }
+ if ( HUDWEAPON_ROLL > 0 )
+ {
+ HUDWEAPON_ROLL = std::max(HUDWEAPON_ROLL - .1 * rate, 0.0);
+ }
+ else if ( HUDWEAPON_ROLL < 0 )
+ {
+ HUDWEAPON_ROLL = std::min(HUDWEAPON_ROLL + .1 * rate, 0.0);
+ }
+
+ if ( HUDWEAPON_MOVEX == 0 && HUDWEAPON_MOVEY == 0 && HUDWEAPON_MOVEZ == 0
+ && HUDWEAPON_YAW == -.1 && HUDWEAPON_PITCH == 0 && HUDWEAPON_ROLL == 0 )
+ {
+ HUDWEAPON_CHOP = 0;
+ }
+ }
+
+ if ( HUDWEAPON_CHARGE == Stat::getMaxAttackCharge(stats[HUDWEAPON_PLAYERNUM]) || castStrikeAnimation
+ || players[HUDWEAPON_PLAYERNUM]->entity->skill[9] == MONSTER_POSE_SPECIAL_WINDUP2
+ || shakeRangedWeapon )
+ {
+ if ( ticks % 5 == 0 && players[HUDWEAPON_PLAYERNUM]->entity->skill[9] == MONSTER_POSE_SPECIAL_WINDUP2 )
+ {
+ camera_shakey += 6;
+ }
+ if ( ticks % 2 == 0 )
+ {
+ if ( shakeRangedWeapon )
+ {
+ HUDWEAPON_MOVEX -= HUDWEAPON_OLDVIBRATEX;
+ HUDWEAPON_MOVEY -= HUDWEAPON_OLDVIBRATEY;
+ HUDWEAPON_OLDVIBRATEX = (local_rng.rand() % 30 - 10) / 150.f;
+ HUDWEAPON_OLDVIBRATEY = (local_rng.rand() % 30 - 10) / 150.f;
+ HUDWEAPON_MOVEX += HUDWEAPON_OLDVIBRATEX;
HUDWEAPON_MOVEY += HUDWEAPON_OLDVIBRATEY;
}
else
@@ -3097,7 +3687,7 @@ void actHudWeapon(Entity* my)
{
if ( ticks % 5 == 0 )
{
- Entity* entity = spawnGib(my);
+ Entity* entity = spawnGib(my, 16);
entity->flags[INVISIBLE] = false;
entity->flags[SPRITE] = true;
entity->flags[NOUPDATE] = true;
@@ -3118,12 +3708,18 @@ void actHudWeapon(Entity* my)
}
}
+ // init defaults
+ my->focalx = 0.0;
+ my->focaly = 0.0;
+ my->focalz = -4.0;
+
// move the weapon
if (players[HUDWEAPON_PLAYERNUM] == nullptr || players[HUDWEAPON_PLAYERNUM]->entity == nullptr)
{
return;
}
double defaultpitch = PI / 8.f;
+
if (stats[HUDWEAPON_PLAYERNUM]->weapon == nullptr || hideWeapon)
{
if ( playerRace == RAT )
@@ -3168,6 +3764,38 @@ void actHudWeapon(Entity* my)
{
my->z -= -2 * .5;
}
+ else if ( playerRace == GREMLIN )
+ {
+ my->z -= -1 * .5;
+ }
+ else if ( playerRace == GNOME )
+ {
+ my->z -= -2 * .5;
+ }
+ else if ( playerRace == DRYAD )
+ {
+ if ( players[HUDWEAPON_PLAYERNUM]->entity->z >= 1.5 )
+ {
+ my->z -= -3.0 * .5;
+ }
+ else
+ {
+ my->z -= -2.0 * .5;
+ }
+ }
+ else if ( playerRace == MYCONID )
+ {
+ my->z -= -1.0 * .5;
+ }
+
+ if ( stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MINIMISE) )
+ {
+ my->z += -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MINIMISE) & 0xF) * .5;
+ }
+ if ( stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) )
+ {
+ my->z -= -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) & 0xF) * .5;
+ }
}
else
{
@@ -3183,7 +3811,7 @@ void actHudWeapon(Entity* my)
{
defaultpitch = -PI / 8.f;
}
- if ( item->type == CROSSBOW || item->type == HEAVY_CROSSBOW )
+ if ( item->type == CROSSBOW || item->type == HEAVY_CROSSBOW || item->type == BLACKIRON_CROSSBOW )
{
my->x = 6 + HUDWEAPON_MOVEX;
my->y = 1.5 + HUDWEAPON_MOVEY;
@@ -3205,8 +3833,11 @@ void actHudWeapon(Entity* my)
}
else if ( item->type == SLING
|| item->type == SHORTBOW
+ || item->type == BONE_SHORTBOW
|| item->type == ARTIFACT_BOW
|| item->type == LONGBOW
+ || item->type == BRANCH_BOW
+ || item->type == BRANCH_BOW_INFECTED
|| item->type == COMPOUND_BOW )
{
my->x = 6 + HUDWEAPON_MOVEX;
@@ -3241,6 +3872,62 @@ void actHudWeapon(Entity* my)
}
my->roll = HUDWEAPON_ROLL;
}
+ else if ( item->type == BOLAS )
+ {
+ my->x = 6 + HUDWEAPON_MOVEX + 3 * (itemCategory(item) == POTION);
+ my->y = 3 + HUDWEAPON_MOVEY - 3 * (itemCategory(item) == POTION);
+ my->z = (cameras[HUDWEAPON_PLAYERNUM].z * .5 - players[HUDWEAPON_PLAYERNUM]->entity->z) + 7 + HUDWEAPON_MOVEZ - 3 * (itemCategory(item) == POTION);
+ my->yaw = HUDWEAPON_YAW - camera_shakex2;
+ my->pitch = defaultpitch + HUDWEAPON_PITCH - camera_shakey2 / 200.f;
+ my->roll = HUDWEAPON_ROLL + (PI / 2) * (itemCategory(item) == POTION);
+
+ static real_t bolasSpinVars[MAXPLAYERS] = { 0.0 };
+ static real_t bolasSpinVars2[MAXPLAYERS] = { 0.0 };
+ real_t& bolasSpinScale = bolasSpinVars[HUDWEAPON_PLAYERNUM];
+ real_t& bolasSpinYaw = bolasSpinVars2[HUDWEAPON_PLAYERNUM];
+ if ( HUDWEAPON_CHOP == 1 || HUDWEAPON_CHOP == 4 || HUDWEAPON_CHOP == 7 )
+ {
+ bolasSpinScale += 0.05;
+ if ( bolasSpinScale >= 1.0 )
+ {
+ bolasSpinScale = 1.0;
+ }
+ real_t prev = fmod(bolasSpinYaw, 2 * PI);
+ bolasSpinYaw += bolasSpinScale * -0.35;
+ real_t next = fmod(bolasSpinYaw, 2 * PI);
+ if ( (prev > -PI / 4 && next < -PI / 4)
+ || (prev > -5 * PI / 4 && next < -5 * PI / 4))
+ {
+ playSoundEntityLocal(players[HUDWEAPON_PLAYERNUM]->entity, 765 + local_rng.rand() % 2, 64);
+ }
+ }
+ else
+ {
+ bolasSpinScale *= 0.95;
+ if ( bolasSpinScale < 0.0 )
+ {
+ bolasSpinScale = 0.0;
+ }
+ if ( bolasSpinYaw < 0.0 )
+ {
+ bolasSpinYaw = fmod(bolasSpinYaw, 2 * PI); // restrict to -2PI
+ bolasSpinYaw *= 0.9; // return to 0
+ }
+ }
+
+
+ my->pitch *= (1.0 - bolasSpinScale); // keep less of original pitch as start spinning
+ my->pitch += bolasSpinScale * -(PI / 2 - PI / 32); // move to this pitch as more spinning
+
+ my->yaw += bolasSpinYaw;
+
+ my->y += 1 * std::min(bolasSpinScale, 1.0); // move right as max spin
+ my->z += -5;
+ my->z -= 3 * sin(HUDWEAPON_ROLL); // weaponswitch animation make rise from bottom
+ my->focalx = 0.0;
+ my->focaly = -0.125;
+ my->focalz = 2.25;
+ }
else
{
my->x = 6 + HUDWEAPON_MOVEX + 3 * (itemCategory(item) == POTION);
@@ -3249,6 +3936,11 @@ void actHudWeapon(Entity* my)
my->yaw = HUDWEAPON_YAW - camera_shakex2;
my->pitch = defaultpitch + HUDWEAPON_PITCH - camera_shakey2 / 200.f;
my->roll = HUDWEAPON_ROLL + (PI / 2) * (itemCategory(item) == POTION);
+ if ( flail )
+ {
+ my->roll += 0.05 * sin(HUDFlail[HUDWEAPON_PLAYERNUM].rollSpin);
+ my->pitch += -0.05 * cos(HUDFlail[HUDWEAPON_PLAYERNUM].rollSpin);
+ }
my->focalx = 0;
}
@@ -3267,6 +3959,38 @@ void actHudWeapon(Entity* my)
{
my->z -= -2 * .5;
}
+ else if ( playerRace == GREMLIN )
+ {
+ my->z -= -1 * .5;
+ }
+ else if ( playerRace == GNOME )
+ {
+ my->z -= -2 * .5;
+ }
+ else if ( playerRace == DRYAD )
+ {
+ if ( players[HUDWEAPON_PLAYERNUM]->entity->z >= 1.5 )
+ {
+ my->z -= -3.0 * .5;
+ }
+ else
+ {
+ my->z -= -2.0 * .5;
+ }
+ }
+ else if ( playerRace == MYCONID )
+ {
+ my->z -= -1.0 * .5;
+ }
+
+ if ( stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MINIMISE) )
+ {
+ my->z += -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MINIMISE) & 0xF) * .5;
+ }
+ if ( stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) )
+ {
+ my->z -= -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDWEAPON_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) & 0xF) * .5;
+ }
}
if ( !my->flags[OVERDRAW] )
{
@@ -3274,17 +3998,113 @@ void actHudWeapon(Entity* my)
my->y += 32;
my->z -= 3.5;
}
+
+ //Entity* particle = spawnMagicParticle(my);
+ //particle->flags[OVERDRAW] = true;
+ //particle->sprite = 942;
+ //particle->x = my->x;
+ //particle->y = my->y;
+ //particle->z = my->z;
+ //particle->focalx = limbs[HUMAN][22][0];
+ //particle->focaly = limbs[HUMAN][22][1];
+ //particle->focalz = limbs[HUMAN][22][2];
+
+ //static real_t p = 0.0;
+ //if ( keystatus[SDLK_F1] )
+ //{
+ // if ( keystatus[SDLK_LSHIFT] )
+ // {
+ // p -= 0.1;
+ // }
+ // else
+ // {
+ // p += 0.1;
+ // }
+ //}
+ //static real_t r = 0.0;
+ //if ( keystatus[SDLK_F2] )
+ //{
+ // if ( keystatus[SDLK_LSHIFT] )
+ // {
+ // r -= 0.1;
+ // }
+ // else
+ // {
+ // r += 0.1;
+ // }
+ //}
+ //static real_t y = 0.0;
+ //if ( keystatus[SDLK_F3] )
+ //{
+ // if ( keystatus[SDLK_LSHIFT] )
+ // {
+ // y -= 0.1;
+ // }
+ // else
+ // {
+ // y += 0.1;
+ // }
+ //}
+ //my->pitch += p;
+ //my->roll += r;
+ //my->yaw += y;
+
+ //particle->yaw = my->yaw;
+ //particle->pitch = my->pitch;
+ //particle->roll = my->roll;
+
+
+ //real_t focalx = limbs[HUMAN][20][0];
+ //real_t focaly = limbs[HUMAN][20][1];
+ //real_t focalz = (limbs[HUMAN][20][2] + my->focalz);
+
+ //// magic code to translate focals into pure coords (doesn't include focaly)
+ //real_t xoffset = focalz * sin(my->pitch + PI) * cos(my->roll) * cos(my->yaw);
+ //real_t yoffset = focalz * sin(my->pitch + PI) * cos(my->roll) * sin(my->yaw);
+ //xoffset += focalz * sin(my->roll + PI) * cos(my->yaw + PI / 2);
+ //yoffset += focalz * sin(my->roll + PI) * sin(my->yaw + PI / 2);
+
+ //xoffset += focalx * cos(my->yaw) * sin(my->pitch + PI / 2) + focaly * cos(my->yaw + PI / 2);
+ //yoffset += focalx * sin(my->yaw) * sin(my->pitch + PI / 2) + focaly * sin(my->yaw + PI / 2);
+
+ //real_t zoffset = focalz * cos(my->pitch) * cos(my->roll);
+ //zoffset += focalx * sin(my->pitch);
+ //particle->x += xoffset;
+ //particle->y += yoffset;
+ //particle->z += zoffset;
+ ////particle->flags[OVERDRAW] = true;
+
+ //// true particle left to opengl to translate position to check accuracy
+ //particle = spawnMagicParticle(my);
+ //particle->flags[OVERDRAW] = true;
+ //particle->sprite = 943;
+ //particle->x = my->x;
+ //particle->y = my->y;
+ //particle->z = my->z;
+ //particle->focalx = my->focalx + limbs[HUMAN][20][0];
+ //particle->focaly = my->focaly + limbs[HUMAN][20][1];
+ //particle->focalz = my->focalz + limbs[HUMAN][20][2];
+ //particle->yaw = my->yaw;
+ //particle->pitch = my->pitch;
+ //particle->roll = my->roll;
}
#define HUDSHIELD_DEFEND my->skill[0]
#define HUDSHIELD_SNEAKING my->skill[1]
#define HUDSHIELD_PLAYERNUM my->skill[2]
+#define HUDSHIELD_DEFEND_DELAY_TICK my->skill[3]
+#define HUDSHIELD_DEFEND_TIME my->skill[4]
#define HUDSHIELD_MOVEX my->fskill[0]
#define HUDSHIELD_MOVEY my->fskill[1]
#define HUDSHIELD_MOVEZ my->fskill[2]
#define HUDSHIELD_YAW my->fskill[3]
#define HUDSHIELD_PITCH my->fskill[4]
#define HUDSHIELD_ROLL my->fskill[5]
+#define HUDSHIELD_FOCI_SPIN my->fskill[6]
+#define HUDSHIELD_FOCI_EASE my->fskill[7]
+#define HUDSHIELD_FOCI_BOB my->fskill[8]
+#define HUDSHIELD_DUCK_CHARGE my->fskill[9]
+static ConsoleVariable cvar_hud_toggle_defend("/hud_toggle_defend", false);
void actHudShield(Entity* my)
{
@@ -3342,6 +4162,32 @@ void actHudShield(Entity* my)
}
}
+ my->mistformGLRender = 0.0;
+ if ( playerRace != SPIDER )
+ {
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender > 0.05 )
+ {
+ real_t modulus = fmod(players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender, 1.0);
+ if ( modulus >= 0.05 && modulus < 0.15 ) // force shield
+ {
+ my->mistformGLRender = 0.5;
+ }
+ else if ( modulus >= 0.15 && modulus < 0.25 ) // reflector shield
+ {
+ my->mistformGLRender = 0.6;
+ }
+ else
+ {
+ my->mistformGLRender = players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender;
+ }
+ }
+ }
+ else if ( playerRace == SPIDER )
+ {
+ my->mistformGLRender = players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender > 0.9 ? players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender
+ : 0.0;
+ }
+
bool hideShield = false;
if ( playerRace == RAT
|| playerRace == CREATURE_IMP
@@ -3352,6 +4198,8 @@ void actHudShield(Entity* my)
bool spellbook = false;
bool quiver = false;
+ bool foci = false;
+ bool duck = false;
if ( stats[HUDSHIELD_PLAYERNUM]->shield && itemCategory(stats[HUDSHIELD_PLAYERNUM]->shield) == SPELLBOOK )
{
spellbook = true;
@@ -3364,6 +4212,18 @@ void actHudShield(Entity* my)
{
quiver = true;
}
+ else if ( stats[HUDSHIELD_PLAYERNUM]->shield && itemTypeIsFoci(stats[HUDSHIELD_PLAYERNUM]->shield->type) )
+ {
+ foci = true;
+ if ( playerRace == CREATURE_IMP )
+ {
+ hideShield = false;
+ }
+ }
+ else if ( stats[HUDSHIELD_PLAYERNUM]->shield && stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_DUCK )
+ {
+ duck = true;
+ }
// when reverting form, render shield as invisible for 2 ticks as it's position needs to settle.
if ( HUD_LASTSHAPESHIFT_FORM != playerRace )
@@ -3376,7 +4236,7 @@ void actHudShield(Entity* my)
}
HUD_LASTSHAPESHIFT_FORM = playerRace;
- if ( players[HUDSHIELD_PLAYERNUM]->entity->skill[3] == 1 ) // debug cam or player invisible
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->skill[3] != 0 ) // debug cam or player invisible
{
my->flags[INVISIBLE] = true;
}
@@ -3451,7 +4311,7 @@ void actHudShield(Entity* my)
my->flags[INVISIBLE] = true;
my->flags[INVISIBLE_DITHER] = false;
}
- else if ( cast_animation[HUDSHIELD_PLAYERNUM].active )
+ else if ( cast_animation[HUDSHIELD_PLAYERNUM].hideShieldFromBasicCast() )
{
my->flags[INVISIBLE] = true;
my->flags[INVISIBLE_DITHER] = false;
@@ -3483,15 +4343,24 @@ void actHudShield(Entity* my)
{
allowDefend = false;
}
+ else if ( players[HUDSHIELD_PLAYERNUM]->hud.weapon->skill[6] == 0 // hideWeapon == false
+ && stats[HUDSHIELD_PLAYERNUM]->weapon /*&& stats[HUDSHIELD_PLAYERNUM]->weapon->type == RAPIER*/
+ && (players[HUDSHIELD_PLAYERNUM]->hud.weapon->skill[0] == 21
+ || players[HUDSHIELD_PLAYERNUM]->hud.weapon->skill[0] == 22) )
+ {
+ allowDefend = false;
+ }
}
if ( players[HUDSHIELD_PLAYERNUM] && players[HUDSHIELD_PLAYERNUM]->entity
&& allowDefend
&& players[HUDSHIELD_PLAYERNUM]->entity->isMobile()
- && !cast_animation[HUDSHIELD_PLAYERNUM].active
+ && !cast_animation[HUDSHIELD_PLAYERNUM].hideShieldFromBasicCast()
&& !cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook
- && (!spellbook || (spellbook && hideShield)) )
+ && (!spellbook || (spellbook && (hideShield || playerRace == SPIDER)))
+ && HUDSHIELD_DEFEND_DELAY_TICK == 0
+ && !((foci || duck) && !hideShield && players[HUDSHIELD_PLAYERNUM]->hud.shieldSwitch) )
{
if ( stats[HUDSHIELD_PLAYERNUM]->shield )
{
@@ -3501,6 +4370,10 @@ void actHudShield(Entity* my)
{
defending = true;
}
+ else if ( *cvar_hud_toggle_defend )
+ {
+ defending = true;
+ }
}
wouldBeDefending = true;
}
@@ -3511,6 +4384,11 @@ void actHudShield(Entity* my)
}
}
+ if ( HUDSHIELD_DEFEND_DELAY_TICK > 0 )
+ {
+ --HUDSHIELD_DEFEND_DELAY_TICK;
+ }
+
if ( stats[HUDSHIELD_PLAYERNUM]->shield && itemTypeIsQuiver(stats[HUDSHIELD_PLAYERNUM]->shield->type) )
{
// can't defend with quivers.
@@ -3522,8 +4400,25 @@ void actHudShield(Entity* my)
|| playerRace == TROLL
|| playerRace == SPIDER )
{
- defending = false;
- wouldBeDefending = false;
+ if ( !(playerRace == CREATURE_IMP && foci) )
+ {
+ defending = false;
+ wouldBeDefending = false;
+ }
+ }
+
+ if ( defending )
+ {
+ if ( foci
+ || (stats[HUDSHIELD_PLAYERNUM]->shield && itemTypeIsInstrument(stats[HUDSHIELD_PLAYERNUM]->shield->type))
+ || duck )
+ {
+ if ( players[HUDSHIELD_PLAYERNUM]->messageZone.logWindow || players[HUDSHIELD_PLAYERNUM]->minimap.mapWindow
+ || FollowerMenu[HUDSHIELD_PLAYERNUM].followerMenuIsOpen() || CalloutMenu[HUDSHIELD_PLAYERNUM].calloutMenuIsOpen() )
+ {
+ defending = false;
+ }
+ }
}
bool dropShield = false;
@@ -3537,10 +4432,12 @@ void actHudShield(Entity* my)
if (defending)
{
stats[HUDSHIELD_PLAYERNUM]->defending = true;
+ HUDSHIELD_DEFEND_TIME++;
}
else
{
stats[HUDSHIELD_PLAYERNUM]->defending = false;
+ HUDSHIELD_DEFEND_TIME = 0;
}
if ( sneaking && (!defending && !wouldBeDefending) )
{
@@ -3550,9 +4447,10 @@ void actHudShield(Entity* my)
{
stats[HUDSHIELD_PLAYERNUM]->sneaking = false;
}
+
if (multiplayer == CLIENT)
{
- if (HUDSHIELD_DEFEND != defending || ticks % 120 == 0)
+ if ((HUDSHIELD_DEFEND > 0 ? true : false) != defending || ticks % 120 == 0)
{
strcpy((char*)net_packet->data, "SHLD");
net_packet->data[4] = HUDSHIELD_PLAYERNUM;
@@ -3573,7 +4471,7 @@ void actHudShield(Entity* my)
sendPacketSafe(net_sock, -1, net_packet, 0);
}
}
- HUDSHIELD_DEFEND = defending;
+ HUDSHIELD_DEFEND = (defending == false) ? 0 : (HUDSHIELD_DEFEND + 1);
HUDSHIELD_SNEAKING = sneaking;
if ( dropShield )
@@ -3583,7 +4481,9 @@ void actHudShield(Entity* my)
return;
}
- bool crossbow = (stats[HUDSHIELD_PLAYERNUM]->weapon && (stats[HUDSHIELD_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDSHIELD_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) );
+ bool crossbow = (stats[HUDSHIELD_PLAYERNUM]->weapon
+ && (stats[HUDSHIELD_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDSHIELD_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDSHIELD_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) );
bool doCrossbowReloadAnimation = false;
bool doBowReload = false;
@@ -3616,6 +4516,10 @@ void actHudShield(Entity* my)
}
}
+ if ( (foci || duck) && !hideShield )
+ {
+ HUDSHIELD_DEFEND_DELAY_TICK = 10;
+ }
if ( !spellbook )
{
players[HUDSHIELD_PLAYERNUM]->hud.shieldSwitch = false;
@@ -3646,7 +4550,10 @@ void actHudShield(Entity* my)
// main animation
if ( defending || cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook )
{
- if ( !spellbook )
+ if ( duck )
+ {
+ }
+ else if ( !spellbook )
{
if ( HUDSHIELD_MOVEY < 3 )
{
@@ -3674,7 +4581,8 @@ void actHudShield(Entity* my)
}
if ( stats[HUDSHIELD_PLAYERNUM]->shield )
{
- if ( stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_TORCH || stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_CRYSTALSHARD )
+ if ( stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_TORCH
+ || stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_CRYSTALSHARD )
{
if ( HUDSHIELD_MOVEX < 1.5 )
{
@@ -3693,6 +4601,25 @@ void actHudShield(Entity* my)
}
}
}
+ else if ( itemTypeIsFoci(stats[HUDSHIELD_PLAYERNUM]->shield->type) )
+ {
+ if ( HUDSHIELD_MOVEX < 1.5 )
+ {
+ HUDSHIELD_MOVEX += .5;
+ if ( HUDSHIELD_MOVEX > 1.5 )
+ {
+ HUDSHIELD_MOVEX = 1.5;
+ }
+ }
+ /*if ( HUDSHIELD_ROLL < PI / 5 )
+ {
+ HUDSHIELD_ROLL += .15;
+ if ( HUDSHIELD_ROLL > PI / 5 )
+ {
+ HUDSHIELD_ROLL = PI / 5;
+ }
+ }*/
+ }
}
}
else
@@ -3713,7 +4640,9 @@ void actHudShield(Entity* my)
}
else if ( !hideShield && quiver && hudweapon && rangedWeaponUseQuiverOnAttack(stats[HUDSHIELD_PLAYERNUM])
&& hudweapon->skill[7] != RANGED_ANIM_IDLE
- && (!crossbow || (crossbow && crossbowReloadAnimation && hudweapon->skill[8] != CROSSBOW_ANIM_RELOAD_START)) )
+ && (!crossbow
+ || (crossbow && crossbowReloadAnimation && hudweapon->skill[8] != CROSSBOW_ANIM_RELOAD_START
+ && !cast_animation[HUDSHIELD_PLAYERNUM].spellWaitingAttackInput() && !cast_animation[HUDSHIELD_PLAYERNUM].spellIgnoreAttack()) ))
{
// skill[7] == 1 is hudweapon bow drawing, skill[8] is the crossbow reload animation state.
if ( hudweapon->skill[7] == RANGED_ANIM_FIRED && (!crossbow || (crossbow && hudweapon->skill[8] == CROSSBOW_ANIM_SHOOT)) )
@@ -3975,6 +4904,119 @@ void actHudShield(Entity* my)
my->yaw += PI / 2 - HUDSHIELD_YAW / 4;
my->focalz -= 0.5;
}
+ else if ( my->sprite == items[TOOL_FRYING_PAN].fpindex && !hideShield )
+ {
+ my->roll += PI / 8;
+ }
+ else if ( duck && !hideShield )
+ {
+ my->z += -1;
+ my->yaw += (PI / 3) + -HUDSHIELD_YAW;
+ if ( defending && HUDSHIELD_DEFEND_TIME > 0 )
+ {
+ HUDSHIELD_DUCK_CHARGE += 1.0;
+ }
+
+ real_t chargeAnim = std::min(1.0, HUDSHIELD_DUCK_CHARGE / 10.0);
+
+ if ( HUDSHIELD_DUCK_CHARGE >= 1.0 )
+ {
+ my->pitch += (-PI / 16) * chargeAnim;
+ my->y += (3 - 3) * chargeAnim;
+ my->x += 2 * chargeAnim;
+ //my->yaw += (PI / 3) * chargeAnim;
+ my->z += -1 * chargeAnim;
+
+ if ( ticks % 2 && HUDSHIELD_DUCK_CHARGE >= 30.0 )
+ {
+ my->x += (local_rng.rand() % 30 - 10) / 80.f;
+ my->y += (local_rng.rand() % 30 - 10) / 80.f;
+ my->z += (local_rng.rand() % 30 - 10) / 80.f;
+ }
+ }
+
+ if ( HUDSHIELD_DUCK_CHARGE >= 60.0 || (!defending && HUDSHIELD_DUCK_CHARGE >= 30.0) )
+ {
+ if ( playerThrowDuck(HUDSHIELD_PLAYERNUM, stats[HUDSHIELD_PLAYERNUM]->shield, std::min(50, (int)HUDSHIELD_DUCK_CHARGE)) )
+ {
+ my->flags[INVISIBLE] = true;
+ my->flags[INVISIBLE_DITHER] = false;
+ return;
+ }
+ }
+ }
+ else if ( foci && !hideShield )
+ {
+ //my->yaw += PI / 2 - HUDSHIELD_YAW / 4;
+ my->focalz -= 1.5;
+ if ( defending )
+ {
+ int rate = 20;
+ int chargeTimeInit = (float)(TICKS_PER_SECOND / 4);
+ chargeTimeInit *= getSpellPropertyFromID(spell_t::SPELLPROP_MODIFIED_FOCI_CAST_TIME, getSpellIDFromFoci(stats[HUDSHIELD_PLAYERNUM]->shield->type),
+ nullptr, stats[HUDSHIELD_PLAYERNUM], nullptr);
+ chargeTimeInit = std::max(TICKS_PER_SECOND, chargeTimeInit + TICKS_PER_SECOND);
+ if ( HUDSHIELD_DEFEND_TIME < chargeTimeInit )
+ {
+ rate = 10;
+ if ( (chargeTimeInit - HUDSHIELD_DEFEND_TIME) > TICKS_PER_SECOND )
+ {
+ HUDSHIELD_FOCI_SPIN += 0.3;
+ }
+ else
+ {
+ HUDSHIELD_FOCI_SPIN += 0.15 + 0.25 * (chargeTimeInit - HUDSHIELD_DEFEND_TIME) / (real_t)TICKS_PER_SECOND;
+ }
+
+ }
+ else
+ {
+ HUDSHIELD_FOCI_SPIN += 0.15;
+ }
+ if ( my->ticks % rate == 0 )
+ {
+ Entity* entity = spawnGib(my, 16);
+ entity->flags[INVISIBLE] = false;
+ entity->flags[SPRITE] = true;
+ entity->flags[NOUPDATE] = true;
+ entity->flags[UPDATENEEDED] = false;
+ entity->flags[OVERDRAW] = true;
+ entity->lightBonus = vec4(0.2f, 0.2f, 0.2f, 0.f);
+ entity->z = std::max(my->z, entity->z);
+ entity->z -= 2.0;
+ //entity->sizex = 1; //MAKE 'EM SMALL PLEASE!
+ //entity->sizey = 1;
+ entity->scalex = 0.25f; //MAKE 'EM SMALL PLEASE!
+ entity->scaley = 0.25f;
+ entity->scalez = 0.25f;
+ entity->sprite = 16; //TODO: Originally. 22. 16 -- spark sprite instead?
+ entity->yaw = ((local_rng.rand() % 6) * 60) * PI / 180.0;
+ entity->pitch = (local_rng.rand() % 360) * PI / 180.0;
+ entity->roll = (local_rng.rand() % 360) * PI / 180.0;
+ entity->vel_x = cos(entity->yaw) * .1;
+ entity->vel_y = sin(entity->yaw) * .1;
+ entity->vel_z = -.15;
+ entity->fskill[3] = 0.01;
+ entity->skill[11] = HUDSHIELD_PLAYERNUM;
+ }
+ HUDSHIELD_FOCI_EASE = std::min(1.0, HUDSHIELD_FOCI_EASE + 0.05);
+ HUDSHIELD_FOCI_BOB += 0.05;
+ my->z += 0.5 * sin(HUDSHIELD_FOCI_BOB);
+ }
+ my->yaw += HUDSHIELD_FOCI_SPIN * sin(HUDSHIELD_FOCI_EASE * PI / 2);
+ }
+
+ if ( !foci || hideShield || !defending )
+ {
+ HUDSHIELD_FOCI_SPIN = 0.0;
+ HUDSHIELD_FOCI_EASE = 0.0;
+ HUDSHIELD_FOCI_BOB = 0.0;
+ }
+
+ if ( !duck || hideShield || !defending )
+ {
+ HUDSHIELD_DUCK_CHARGE = 0.0;
+ }
Entity*& hudarm = players[HUDSHIELD_PLAYERNUM]->hud.arm;
if ( playerRace == SPIDER && hudarm && players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.at(0) )
@@ -4001,6 +5043,38 @@ void actHudShield(Entity* my)
{
my->z -= -2 * .5;
}
+ else if ( playerRace == GREMLIN )
+ {
+ my->z -= -1 * .5;
+ }
+ else if ( playerRace == GNOME )
+ {
+ my->z -= -2 * .5;
+ }
+ else if ( playerRace == DRYAD )
+ {
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->z >= 1.5 )
+ {
+ my->z -= -3.0 * .5;
+ }
+ else
+ {
+ my->z -= -2.0 * .5;
+ }
+ }
+ else if ( playerRace == MYCONID )
+ {
+ my->z -= -1.0 * .5;
+ }
+
+ if ( stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MINIMISE) )
+ {
+ my->z += -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MINIMISE) & 0xF) * .5;
+ }
+ if ( stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) )
+ {
+ my->z -= -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) & 0xF) * .5;
+ }
// torch/lantern flames
if ( playerRace == TROLL || playerRace == SPIDER || playerRace == CREATURE_IMP || playerRace == RAT )
@@ -4010,53 +5084,326 @@ void actHudShield(Entity* my)
}
if (stats[HUDSHIELD_PLAYERNUM]->shield
&& !swimming
- && players[HUDSHIELD_PLAYERNUM]->entity->skill[3] == 0
- && !cast_animation[HUDSHIELD_PLAYERNUM].active
+ && !cast_animation[HUDSHIELD_PLAYERNUM].hideShieldFromBasicCast()
&& !cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook
&& !players[HUDSHIELD_PLAYERNUM]->hud.shieldSwitch)
{
if (itemCategory(stats[HUDSHIELD_PLAYERNUM]->shield) == TOOL)
{
- if (stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_TORCH)
- {
- if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 )
- {
- if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->skill[3] == 0 )
+ {
+ if (stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_TORCH)
+ {
+ if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 )
+ {
+ if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ {
+ entity->flags[OVERDRAW] = true;
+ entity->z -= 2.5 * cos(HUDSHIELD_ROLL);
+ entity->y += 2.5 * sin(HUDSHIELD_ROLL);
+ entity->skill[11] = HUDSHIELD_PLAYERNUM;
+ }
+ }
+ }
+ else if ( stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_CRYSTALSHARD )
+ {
+ if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 )
{
+ /*Entity* entity = spawnFlame(my, SPRITE_CRYSTALFLAME);
entity->flags[OVERDRAW] = true;
entity->z -= 2.5 * cos(HUDSHIELD_ROLL);
entity->y += 2.5 * sin(HUDSHIELD_ROLL);
- entity->skill[11] = HUDSHIELD_PLAYERNUM;
+ entity->skill[11] = HUDSHIELD_PLAYERNUM;*/
}
- }
- }
- else if ( stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_CRYSTALSHARD )
- {
- if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 )
- {
- /*Entity* entity = spawnFlame(my, SPRITE_CRYSTALFLAME);
- entity->flags[OVERDRAW] = true;
- entity->z -= 2.5 * cos(HUDSHIELD_ROLL);
- entity->y += 2.5 * sin(HUDSHIELD_ROLL);
- entity->skill[11] = HUDSHIELD_PLAYERNUM;*/
- }
- }
- else if (stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_LANTERN)
- {
- if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 )
- {
- if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ }
+ else if (stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_LANTERN)
+ {
+ if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 )
{
- entity->flags[OVERDRAW] = true;
- entity->skill[11] = HUDSHIELD_PLAYERNUM;
- entity->z += 1;
+ if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) )
+ {
+ entity->flags[OVERDRAW] = true;
+ entity->skill[11] = HUDSHIELD_PLAYERNUM;
+ entity->z += 1;
+ }
}
- }
- }
+ }
+ }
+
+ /*if ( multiplayer != CLIENT )
+ {
+ if ( stats[HUDSHIELD_PLAYERNUM]->shield->type == TOOL_FOCI_FIRE )
+ {
+ static ConsoleVariable cvar_foci_charge_init("/foci_charge_init", 1.f);
+ int chargeTimeInit = (float)(TICKS_PER_SECOND / 4) * *cvar_foci_charge_init;
+ if ( HUDSHIELD_DEFEND >= chargeTimeInit )
+ {
+ static ConsoleVariable cvar_foci_charge("/foci_charge", 1.f);
+ int chargeTime = (float)(TICKS_PER_SECOND / 4) * *cvar_foci_charge;
+ if ( (HUDSHIELD_DEFEND - chargeTimeInit) % chargeTime == 0 )
+ {
+ auto spell = getSpellFromID(SPELL_FOCI_FIRE);
+ int mpcost = getCostOfSpell(spell, players[HUDSHIELD_PLAYERNUM]->entity);
+ if ( mpcost <= stats[HUDSHIELD_PLAYERNUM]->MP )
+ {
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->safeConsumeMP(mpcost) )
+ {
+ castSpell(players[HUDSHIELD_PLAYERNUM]->entity->getUID(), spell, false, false);
+ }
+ }
+ }
+ }
+ }
+ }*/
}
}
}
+#define HUDADDITIONAL_PLAYERNUM my->skill[2]
+void actHudAdditional2(Entity* my)
+{
+ my->flags[UNCLICKABLE] = true;
+
+ auto& camera_shakex2 = cameravars[HUDADDITIONAL_PLAYERNUM].shakex2;
+ auto& camera_shakey2 = cameravars[HUDADDITIONAL_PLAYERNUM].shakey2;
+ auto& hudFlail = HUDFlail[HUDADDITIONAL_PLAYERNUM];
+
+ my->flags[INVISIBLE_DITHER] = false;
+
+ // isn't active during intro/menu sequence
+ if ( intro == true )
+ {
+ my->flags[INVISIBLE] = true;
+ hudFlail.needsInit = true;
+ return;
+ }
+ else
+ {
+ if ( multiplayer == CLIENT )
+ {
+ if ( stats[HUDADDITIONAL_PLAYERNUM]->HP <= 0 )
+ {
+ my->flags[INVISIBLE] = true;
+ my->flags[INVISIBLE_DITHER] = false;
+ hudFlail.needsInit = true;
+ return;
+ }
+ }
+
+ // this entity only exists so long as the player exists
+ if ( players[HUDADDITIONAL_PLAYERNUM] == nullptr || players[HUDADDITIONAL_PLAYERNUM]->entity == nullptr || !players[HUDADDITIONAL_PLAYERNUM]->hud.weapon )
+ {
+ list_RemoveNode(my->mynode);
+ hudFlail.needsInit = true;
+ return;
+ }
+
+ if ( !(stats[HUDADDITIONAL_PLAYERNUM]->weapon && stats[HUDADDITIONAL_PLAYERNUM]->weapon->type == STEEL_FLAIL) )
+ {
+ my->flags[INVISIBLE] = true;
+ my->flags[INVISIBLE_DITHER] = false;
+ hudFlail.needsInit = true;
+ return;
+ }
+ }
+
+ my->flags[INVISIBLE] = players[HUDADDITIONAL_PLAYERNUM]->hud.weapon->flags[INVISIBLE];
+ my->flags[INVISIBLE_DITHER] = players[HUDADDITIONAL_PLAYERNUM]->hud.weapon->flags[INVISIBLE_DITHER];
+
+ auto& weaponLimb = players[HUDADDITIONAL_PLAYERNUM]->hud.weapon;
+ if ( (my->flags[INVISIBLE] && !my->flags[INVISIBLE_DITHER]) || weaponLimb->skill[6] != 0 ) // HUDWEAPON_HIDEWEAPON
+ {
+ hudFlail.needsInit = true;
+ return;
+ }
+
+ if ( hudFlail.needsInit )
+ {
+ HUDFlail_t::reset(hudFlail);
+ hudFlail.needsInit = false;
+ }
+
+ my->mistformGLRender = weaponLimb->mistformGLRender;
+ my->sprite = 1922;
+ //my->flags[OVERDRAW] = false;
+ my->x = weaponLimb->x;
+ my->y = weaponLimb->y;
+ my->z = weaponLimb->z;
+ my->focalx = 0;
+ my->focaly = 0;
+ my->focalz = 4;
+
+ /*static ConsoleVariable cvar_bounce("/bounce", 1.f);
+ if ( keystatus[SDLK_F1] )
+ {
+ keystatus[SDLK_F1] = 0;
+ bounce = *cvar_bounce * ((local_rng.rand() % 2) ? 1 : -1);
+ }
+ static ConsoleCommand ccmd_bounce_start("/bounce_start", "",
+ [](int argc, const char* argv[]) {
+ if ( argc >= 2 )
+ {
+ real_t var = atoi(argv[1]);
+ bounce = var / 100.0;
+ }
+ });*/
+ auto& bounce = hudFlail.bounce;
+ auto& prevYaw = hudFlail.prevYaw;
+
+ if ( players[HUDADDITIONAL_PLAYERNUM]->entity )
+ {
+ real_t newYaw = atan2(players[HUDADDITIONAL_PLAYERNUM]->entity->vel_y, players[HUDADDITIONAL_PLAYERNUM]->entity->vel_x);
+ newYaw = fmod(newYaw, 2 * PI);
+ prevYaw = fmod(prevYaw, 2 * PI);
+ int diff = static_cast((newYaw - prevYaw) * 180.0 / PI) % 360;
+ if ( diff < 0 )
+ {
+ diff += 360;
+ }
+ if ( diff > 180 )
+ {
+ diff -= 360;
+ }
+ // move dir to bounce effect
+
+ real_t mult = 1.0;
+ {
+ int diff = static_cast((newYaw - fmod(players[HUDADDITIONAL_PLAYERNUM]->entity->yaw, 2 * PI)) * 180.0 / PI) % 360;
+ if ( diff < 0 )
+ {
+ diff += 360;
+ }
+ if ( diff > 180 )
+ {
+ diff -= 360;
+ }
+ if ( abs(diff) > 90 ) // backwards
+ {
+ mult *= -1;
+ }
+ }
+
+ if ( diff > 0 )
+ {
+ bounce += 0.05f * mult;
+ }
+ else if ( diff < 0 )
+ {
+ bounce += -0.05f * mult;
+ }
+ prevYaw = newYaw;
+ }
+
+ static ConsoleVariable cvar_anim_flail_damp("/anim_flail_damp", 2.f);
+ static ConsoleVariable cvar_anim_flail_mass("/anim_flail_mass", 5.f);
+ static ConsoleVariable cvar_anim_flail_spring("/anim_flail_spring", 5.f);
+ static ConsoleVariable cvar_anim_flail_mult("/anim_flail_mult", .1f);
+ // spring motion
+ {
+ real_t accel = (-*cvar_anim_flail_damp * bounce - *cvar_anim_flail_spring * hudFlail.roll) / *cvar_anim_flail_mass;
+ bounce += accel * *cvar_anim_flail_mult;
+ hudFlail.roll += bounce * *cvar_anim_flail_mult;
+ }
+
+ auto& spin = hudFlail.spin;
+ auto& spin2 = hudFlail.spin2;
+ auto& rollSpin = hudFlail.rollSpin;
+ auto& spinState = hudFlail.spinState;
+
+ if ( weaponLimb->skill[0] == 1 ) // HUDWEAPON_CHOP
+ {
+ spinState = 1;
+ spin += std::max(0.01, (1.0 - spin) / 20);
+ spin = std::min(1.0, spin);
+
+ real_t prevRollSpin = rollSpin;
+ rollSpin += spin / 3;
+
+ if ( weaponLimb->skill[0] == 1 )
+ {
+ if ( fmod(prevRollSpin, 2 * PI) > PI && fmod(rollSpin, 2 * PI) < PI ) // crossing the 0.0 angle boundary after first swing
+ {
+ players[HUDADDITIONAL_PLAYERNUM]->entity->attack(MONSTER_POSE_FLAIL_SWING, 0, nullptr);
+ }
+ }
+ }
+ else
+ {
+ if ( spinState == 1 )
+ {
+ rollSpin = 2 * PI - 0.01; // immediately jump to end of circle
+ if ( fmod(rollSpin, 2 * PI) >= PI )
+ {
+ spinState = 2;
+ static ConsoleVariable cvar_anim_flail_atk_spin("/anim_flail_atk_spin", 2.5);
+ spin2 = 1.0;
+ spin = *cvar_anim_flail_atk_spin;
+
+ if ( weaponLimb->skill[0] == 2 )
+ {
+ bounce = 1.f;
+ }
+ else
+ {
+ bounce = -0.5f;
+ }
+ }
+ else
+ {
+ spin += std::max(0.01, (1.0 - spin) / 20);
+ spin = std::min(1.0, spin);
+ rollSpin += spin / 3;
+ }
+ }
+
+ if ( spinState != 1 )
+ {
+ spin -= std::max(0.01, (spin) / 20);
+ spin = std::max(0.0, spin);
+ if ( rollSpin > 2 * PI + 0.001 )
+ {
+ rollSpin = fmod(rollSpin, 2 * PI);
+ }
+ rollSpin += std::max(0.01, (2 * PI - rollSpin) / 10);
+ rollSpin = std::min(2 * PI, rollSpin);
+ if ( rollSpin >= 2 * PI )
+ {
+ spinState = 0;
+ }
+ }
+ }
+
+ spin2 -= std::max(0.01, (spin2) / 20);
+ spin2 = std::max(0.0, spin2);
+
+ hudFlail.pitch = -(4 * PI / 8) * spin;
+
+ float basePitch = 0.15;
+ my->pitch = hudFlail.pitch - (1.0 - spin) * (PI * basePitch);
+ my->roll = hudFlail.roll + rollSpin;
+ my->yaw = hudFlail.yaw + sin(spin2 * PI / 2);
+
+ // target focal x/y/z relative to weapon's position
+ real_t focalx = 1.5 + weaponLimb->focalx;
+ real_t focaly = 0.0 + weaponLimb->focaly;
+ real_t focalz = -0.9 + weaponLimb->focalz;
+
+ // magic code to translate focals into pure coords (doesn't include focaly)
+ real_t xoffset = focalz * sin(weaponLimb->pitch + PI) * cos(weaponLimb->roll) * cos(weaponLimb->yaw);
+ real_t yoffset = focalz * sin(weaponLimb->pitch + PI) * cos(weaponLimb->roll) * sin(weaponLimb->yaw);
+ xoffset += focalz * sin(weaponLimb->roll + PI) * cos(weaponLimb->yaw + PI / 2);
+ yoffset += focalz * sin(weaponLimb->roll + PI) * sin(weaponLimb->yaw + PI / 2);
+
+ xoffset += focalx * cos(weaponLimb->yaw) * sin(weaponLimb->pitch + PI / 2) + focaly * cos(weaponLimb->yaw + PI / 2);
+ yoffset += focalx * sin(weaponLimb->yaw) * sin(weaponLimb->pitch + PI / 2) + focaly * sin(weaponLimb->yaw + PI / 2);
+
+ real_t zoffset = focalz * cos(weaponLimb->pitch) * cos(weaponLimb->roll);
+ zoffset += focalx * sin(weaponLimb->pitch);
+ my->x += xoffset;
+ my->y += yoffset;
+ my->z += zoffset;
+}
+
void actHudAdditional(Entity* my)
{
bool spellbook = false;
@@ -4096,6 +5443,24 @@ void actHudAdditional(Entity* my)
return;
}
+ my->mistformGLRender = 0.0;
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender > 0.05 )
+ {
+ real_t modulus = fmod(players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender, 1.0);
+ if ( modulus >= 0.05 && modulus < 0.15 ) // force shield
+ {
+ my->mistformGLRender = 0.5;
+ }
+ else if ( modulus >= 0.15 && modulus < 0.25 ) // reflector shield
+ {
+ my->mistformGLRender = 0.6;
+ }
+ else
+ {
+ my->mistformGLRender = players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender;
+ }
+ }
+
if ( !spellbook )
{
my->flags[INVISIBLE] = true;
@@ -4161,7 +5526,7 @@ void actHudAdditional(Entity* my)
return;
}
- if ( cast_animation[HUDSHIELD_PLAYERNUM].active )
+ if ( cast_animation[HUDSHIELD_PLAYERNUM].hideShieldFromBasicCast() )
{
my->flags[INVISIBLE] = true;
my->flags[INVISIBLE_DITHER] = false;
@@ -4299,6 +5664,38 @@ void actHudAdditional(Entity* my)
{
my->z -= -2 * .5;
}
+ else if ( stats[HUDSHIELD_PLAYERNUM]->type == GREMLIN )
+ {
+ my->z -= -1 * .5;
+ }
+ else if ( stats[HUDSHIELD_PLAYERNUM]->type == GNOME )
+ {
+ my->z -= -2 * .5;
+ }
+ else if ( stats[HUDSHIELD_PLAYERNUM]->type == DRYAD )
+ {
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->z >= 1.5 )
+ {
+ my->z -= -3.0 * .5;
+ }
+ else
+ {
+ my->z -= -2.0 * .5;
+ }
+ }
+ else if ( stats[HUDSHIELD_PLAYERNUM]->type == MYCONID )
+ {
+ my->z -= -1.0 * .5;
+ }
+
+ if ( stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MINIMISE) )
+ {
+ my->z += -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MINIMISE) & 0xF) * .5;
+ }
+ if ( stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) )
+ {
+ my->z -= -Player::PlayerMovement_t::minimiseMaximiseCameraZ * (stats[HUDSHIELD_PLAYERNUM]->getEffectActive(EFF_MAXIMISE) & 0xF) * .5;
+ }
}
void actHudArrowModel(Entity* my)
@@ -4307,7 +5704,10 @@ void actHudArrowModel(Entity* my)
bool crossbow = false;
if ( stats[HUDSHIELD_PLAYERNUM]->weapon
&& (stats[HUDSHIELD_PLAYERNUM]->weapon->type == SHORTBOW
+ || stats[HUDSHIELD_PLAYERNUM]->weapon->type == BONE_SHORTBOW
|| stats[HUDSHIELD_PLAYERNUM]->weapon->type == LONGBOW
+ || stats[HUDSHIELD_PLAYERNUM]->weapon->type == BRANCH_BOW
+ || stats[HUDSHIELD_PLAYERNUM]->weapon->type == BRANCH_BOW_INFECTED
|| stats[HUDSHIELD_PLAYERNUM]->weapon->type == ARTIFACT_BOW
|| stats[HUDSHIELD_PLAYERNUM]->weapon->type == COMPOUND_BOW )
)
@@ -4315,7 +5715,9 @@ void actHudArrowModel(Entity* my)
bow = true;
}
else if ( stats[HUDSHIELD_PLAYERNUM]->weapon
- && (stats[HUDSHIELD_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDSHIELD_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) )
+ && (stats[HUDSHIELD_PLAYERNUM]->weapon->type == CROSSBOW
+ || stats[HUDSHIELD_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW
+ || stats[HUDSHIELD_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW) )
{
crossbow = true;
}
@@ -4349,7 +5751,27 @@ void actHudArrowModel(Entity* my)
return;
}
- if ( (!crossbow && !bow) || cast_animation[HUDSHIELD_PLAYERNUM].active || cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook )
+ my->mistformGLRender = 0.0;
+ if ( players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender > 0.05 )
+ {
+ real_t modulus = fmod(players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender, 1.0);
+ if ( modulus >= 0.05 && modulus < 0.15 ) // force shield
+ {
+ my->mistformGLRender = 0.5;
+ }
+ else if ( modulus >= 0.15 && modulus < 0.25 ) // reflector shield
+ {
+ my->mistformGLRender = 0.6;
+ }
+ else
+ {
+ my->mistformGLRender = players[HUDSHIELD_PLAYERNUM]->entity->mistformGLRender;
+ }
+ }
+
+ if ( (!crossbow && !bow)
+ || cast_animation[HUDSHIELD_PLAYERNUM].hideShieldFromBasicCast()
+ || cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook )
{
my->flags[INVISIBLE] = true;
return;
@@ -4383,6 +5805,14 @@ void actHudArrowModel(Entity* my)
return;
}
}
+ if ( stats[HUDSHIELD_PLAYERNUM]->weapon->type == BLACKIRON_CROSSBOW )
+ {
+ if ( hudweapon->sprite != items[BLACKIRON_CROSSBOW].fpindex )
+ {
+ my->flags[INVISIBLE] = true;
+ return;
+ }
+ }
}
else if ( hudweapon->skill[6] != 0
|| hudweapon->skill[7] != RANGED_ANIM_FIRED ) // skill[6] is hiding weapon, skill[7] is shooting something
@@ -4437,6 +5867,12 @@ void actHudArrowModel(Entity* my)
case QUIVER_HUNTING:
my->sprite = 941;
break;
+ case QUIVER_BONE:
+ my->sprite = 2302;
+ break;
+ case QUIVER_BLACKIRON:
+ my->sprite = 2303;
+ break;
default:
break;
}
@@ -4467,6 +5903,12 @@ void actHudArrowModel(Entity* my)
my->focaly += 0.25;
my->focalz += -0.25;
}
+ else if ( hudweapon->sprite == items[BLACKIRON_CROSSBOW].fpindex )
+ {
+ my->focalx += 3.5;
+ my->focaly += 0.25;
+ my->focalz += -0.25;
+ }
else if ( hudweapon->sprite == items[HEAVY_CROSSBOW].fpindex || hudweapon->sprite == items[HEAVY_CROSSBOW].fpindex - 1 )
{
my->focalx += 4.5;
diff --git a/src/actitem.cpp b/src/actitem.cpp
index d2da9f33e..f77b3fe19 100644
--- a/src/actitem.cpp
+++ b/src/actitem.cpp
@@ -47,6 +47,321 @@
#define ITEM_SPLOOSHED my->skill[27]
#define ITEM_WATERBOB my->fskill[2]
+bool itemProcessReturnItemEffect(Entity* my, bool fallingIntoVoid)
+{
+ if ( Entity* returnToParent = uidToEntity(my->itemReturnUID) )
+ {
+ int returnTime = std::max(10, std::max(getSpellDamageSecondaryFromID(SPELL_RETURN_ITEMS, returnToParent, nullptr, returnToParent, 0.0, false),
+ getSpellDamageFromID(SPELL_RETURN_ITEMS, returnToParent, nullptr, returnToParent, 0.0, false)));
+ if ( fallingIntoVoid || (my->ticks >= returnTime && returnToParent->behavior == &actPlayer) )
+ {
+ int cost = std::max(1, getSpellEffectDurationSecondaryFromID(SPELL_RETURN_ITEMS, returnToParent, nullptr, returnToParent));
+ if ( cost > 0 && !returnToParent->safeConsumeMP(cost) )
+ {
+ Stat* returnStats = returnToParent->getStats();
+ if ( returnStats && returnStats->MP > 0 )
+ {
+ returnToParent->modMP(-returnStats->MP);
+ }
+ if ( spell_t* sustainSpell = returnToParent->getActiveMagicEffect(SPELL_RETURN_ITEMS) )
+ {
+ sustainSpell->sustain = false;
+ }
+ playSoundEntity(returnToParent, 163, 128);
+ if ( Item* item = newItemFromEntity(my, true) )
+ {
+ messagePlayerColor(returnToParent->skill[2], MESSAGE_COMBAT, makeColorRGB(255, 0, 0), Language::get(6813), item->getName());
+ free(item);
+ }
+ my->itemReturnUID = 0;
+ }
+ else
+ {
+ int i = returnToParent->skill[2];
+ Item* item2 = newItemFromEntity(my);
+ if ( item2 )
+ {
+ int pickedUpCount = item2->count;
+ Item* item = itemPickup(i, item2);
+ if ( item )
+ {
+ if ( players[i]->isLocalPlayer() )
+ {
+ // item is the new inventory stack for server, free the picked up items
+ free(item2);
+ int oldcount = item->count;
+ item->count = pickedUpCount;
+ messagePlayer(i, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(3746), item->getName());
+ item->count = oldcount;
+ }
+ else
+ {
+ messagePlayer(i, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(3746), item->getName());
+ free(item); // item is the picked up items (item == item2)
+ }
+
+ if ( returnToParent->behavior == &actPlayer )
+ {
+ if ( auto spell = getSpellFromID(SPELL_RETURN_ITEMS) )
+ {
+ players[returnToParent->skill[2]]->mechanics.sustainedSpellIncrementMP(cost, spell->skillID);
+ }
+ players[returnToParent->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_RETURN_ITEMS, 10.0, 1.0, nullptr);
+ }
+
+ spawnMagicEffectParticles(my->x, my->y, my->z, 170);
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void onItemPickedUp(Entity& who, Uint32 itemUid)
+{
+ for ( int player = 0; player < MAXPLAYERS; ++player )
+ {
+ if ( players[player]->mechanics.donationRevealedOnFloor == itemUid )
+ {
+ if ( who.behavior == &actPlayer )
+ {
+ messagePlayerColor(who.skill[2], MESSAGE_HINT, makeColorRGB(255, 255, 0), Language::get(6943)); // you discovered a gift
+
+ for ( int player2 = 0; player2 < MAXPLAYERS; ++player2 ) // relay to other players
+ {
+ if ( player2 != who.skill[2] && !client_disconnected[player2] )
+ {
+ messagePlayerColor(player2, MESSAGE_HINT, makeColorRGB(255, 255, 0), Language::get(6944), stats[who.skill[2]]->name); // an ally discovered a gift
+ }
+ }
+ }
+ else if ( who.behavior == &actMonster && who.monsterAllyIndex >= 0 && who.monsterAllyIndex < MAXPLAYERS && who.getStats() )
+ {
+ std::string allyName = who.getStats()->name;
+ if ( allyName == "" )
+ {
+ allyName = getMonsterLocalizedName(who.getStats()->type);
+ }
+
+ messagePlayerColor(who.monsterAllyIndex, MESSAGE_HINT, makeColorRGB(255, 255, 0), Language::get(6944), allyName.c_str()); // your ally %s discovered a gift
+
+ for ( int player2 = 0; player2 < MAXPLAYERS; ++player2 ) // relay to other players
+ {
+ if ( player2 != who.monsterAllyIndex && !client_disconnected[player2] )
+ {
+ messagePlayerColor(player2, MESSAGE_HINT, makeColorRGB(255, 255, 0), Language::get(6945), stats[who.monsterAllyIndex]->name, allyName.c_str()); // %s's ally %s discovered a gift
+ }
+ }
+ }
+
+ players[player]->mechanics.updateSustainedSpellEvent(SPELL_DONATION, 150.0, 1.0, nullptr);
+ break;
+ }
+ }
+}
+
+bool entityWantsJewel(int tier, Entity& entity, Stat& stats, bool checkTypeOnly)
+{
+ int req = -1;
+ switch ( stats.type )
+ {
+ case GNOME:
+ case GOBLIN:
+ req = 1;
+ break;
+ case HUMAN:
+ case GREMLIN:
+ case SUCCUBUS:
+ case GOATMAN:
+ req = 2;
+ break;
+ case KOBOLD:
+ case INSECTOID:
+ case DRYAD:
+ case MYCONID:
+ case BUGBEAR:
+ case INCUBUS:
+ req = 3;
+ break;
+ case VAMPIRE:
+ case SALAMANDER:
+ req = 4;
+ break;
+ //case TROLL,
+ //case AUTOMATON,
+ default:
+ break;
+ }
+
+ if ( req < 0 )
+ {
+ return false;
+ }
+
+ if ( entity.behavior != &actMonster ) { return false; }
+ if ( !entity.monsterIsTargetable() || !entity.isMobile() ) { return false; }
+ if ( entity.isBossMonster() ) { return false; }
+ if ( entity.monsterAllyGetPlayerLeader() ) { return false; }
+ if ( stats.type == INCUBUS && !strncmp(stats.name, "inner demon", strlen("inner demon")) ) { return false; }
+ //if ( stats.leader_uid != 0 && uidToEntity(stats.leader_uid) ) { return false; }
+
+ if ( checkTypeOnly )
+ {
+ return req >= 0;
+ }
+ else if ( req >= 0 )
+ {
+ if ( (std::max(1, tier) * 5) >= currentlevel || tier == EXCELLENT )
+ {
+ return true;
+ }
+ //return tier >= req;
+ }
+
+ return false;
+}
+
+bool jewelItemRecruit(Entity* parent, Entity* entity, int itemStatus, const char** msg)
+{
+ if ( !(entity && parent) )
+ {
+ return false;
+ }
+
+ if ( parent->behavior != &actPlayer )
+ {
+ return false;
+ }
+
+ Stat* entitystats = entity->getStats();
+ if ( !entitystats )
+ {
+ return false;
+ }
+
+ int allowedFollowers = std::min(8, std::max(4, 2 * (stats[parent->skill[2]]->getModifiedProficiency(PRO_LEADERSHIP) / 20)));
+ int numFollowers = 0;
+ for ( node_t* node = stats[parent->skill[2]]->FOLLOWERS.first; node; node = node->next )
+ {
+ Entity* follower = nullptr;
+ if ( (Uint32*)node->element )
+ {
+ follower = uidToEntity(*((Uint32*)node->element));
+ }
+ if ( follower )
+ {
+ Stat* followerStats = follower->getStats();
+ if ( followerStats )
+ {
+ if ( !(followerStats->type == SENTRYBOT || followerStats->type == GYROBOT
+ || followerStats->type == SPELLBOT || followerStats->type == DUMMYBOT) )
+ {
+ ++numFollowers;
+ }
+ }
+ }
+ }
+
+ if ( numFollowers >= allowedFollowers )
+ {
+ if ( allowedFollowers >= 8 )
+ {
+ if ( msg )
+ {
+ *msg = Language::get(3482);
+ }
+ }
+ else
+ {
+ if ( msg )
+ {
+ *msg = Language::get(3480);
+ }
+ }
+ return false;
+ }
+ else if ( forceFollower(*parent, *entity) )
+ {
+ Entity* fx = createRadiusMagic(SPELL_FORGE_JEWEL, entity, entity->x, entity->y, 16, 2 * TICKS_PER_SECOND, entity);
+ spawnMagicEffectParticles(entity->x, entity->y, entity->z, 2410);
+ playSoundEntity(entity, 167, 64);
+
+ if ( parent->behavior == &actPlayer )
+ {
+ magicOnSpellCastEvent(parent, parent, entity, SPELL_FORGE_JEWEL, spell_t::SPELL_LEVEL_EVENT_DEFAULT, 1);
+ messagePlayerMonsterEvent(parent->skill[2], makeColorRGB(0, 255, 0), *entitystats, Language::get(6954), Language::get(6955), MSG_COMBAT);
+ Compendium_t::Events_t::eventUpdateMonster(parent->skill[2], Compendium_t::CPDM_RECRUITED, entity, 1);
+
+ if ( stats[parent->skill[2]]->playerRace == RACE_GNOME && stats[parent->skill[2]]->stat_appearance == 0 )
+ {
+ steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_MERCENARY_ARMY, STEAM_STAT_INT, 1);
+ }
+
+ if ( itemStatus == DECREPIT )
+ {
+ Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_JEWEL_RECRUIT_DECREPIT, GEM_JEWEL, 1);
+ }
+ else if ( itemStatus == WORN )
+ {
+ Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_JEWEL_RECRUIT_WORN, GEM_JEWEL, 1);
+ }
+ else if ( itemStatus == SERVICABLE )
+ {
+ Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_JEWEL_RECRUIT_SERVICABLE, GEM_JEWEL, 1);
+ }
+ else if ( itemStatus == EXCELLENT )
+ {
+ Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_JEWEL_RECRUIT_EXCELLENT, GEM_JEWEL, 1);
+ }
+ Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_RECRUITED, GEM_JEWEL, 1);
+
+ if ( entitystats->type == HUMAN && entitystats->getAttribute("special_npc") == "merlin" )
+ {
+ Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_MERLINS, "magicians guild", 1);
+ }
+ entity->monsterAllyIndex = parent->skill[2];
+ if ( multiplayer == SERVER )
+ {
+ serverUpdateEntitySkill(entity, 42); // update monsterAllyIndex for clients.
+ }
+ }
+
+ if ( entity->monsterTarget == parent->getUID() )
+ {
+ entity->monsterReleaseAttackTarget();
+ }
+ entity->setEffect(EFF_CONFUSED, false, 0, true);
+
+ // change the color of the hit entity.
+ entity->flags[USERFLAG2] = true;
+ serverUpdateEntityFlag(entity, USERFLAG2);
+ if ( monsterChangesColorWhenAlly(entitystats) )
+ {
+ int bodypart = 0;
+ for ( node_t* node = (entity)->children.first; node != nullptr; node = node->next )
+ {
+ if ( bodypart >= LIMB_HUMANOID_TORSO )
+ {
+ Entity* tmp = (Entity*)node->element;
+ if ( tmp )
+ {
+ tmp->flags[USERFLAG2] = true;
+ //serverUpdateEntityFlag(tmp, USERFLAG2);
+ }
+ }
+ ++bodypart;
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
void actItem(Entity* my)
{
Item* item;
@@ -67,6 +382,25 @@ void actItem(Entity* my)
my->new_z -= ITEM_WATERBOB;
ITEM_WATERBOB = 0.0;
+ if ( my->sprite >= items[TOOL_DUCK].index && my->sprite < items[TOOL_DUCK].index + 4 )
+ {
+ if ( multiplayer == CLIENT )
+ {
+ my->flags[INVISIBLE] = true;
+ }
+ else
+ {
+ Item* item = newItemFromEntity(my, true);
+ if ( item )
+ {
+ item->applyDuck(my->parent, my->x, my->y, nullptr, false);
+ }
+ free(item);
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ }
+
if ( multiplayer == CLIENT )
{
my->flags[NOUPDATE] = true;
@@ -168,6 +502,20 @@ void actItem(Entity* my)
my->flags[INVISIBLE] = false;
}
item = newItemFromEntity(my, true);
+ /*if ( currentlevel > 0 && my->ticks == 1 )
+ {
+ if ( items[item->type].category != TOME_SPELL && items[item->type].category != SPELLBOOK )
+ {
+ if ( items[item->type].level < 0 )
+ {
+ if ( item->type != KEY_IRON && item->type != KEY_BRONZE && item->type != KEY_GOLD
+ && item->type != KEY_SILVER )
+ {
+ printlog("%s", items[item->type].getIdentifiedName());
+ }
+ }
+ }
+ }*/
my->sprite = itemModel(item);
free(item);
}
@@ -182,6 +530,78 @@ void actItem(Entity* my)
// pick up item
if (multiplayer != CLIENT)
{
+ if ( my->skill[10] == GEM_JEWEL )
+ {
+ if ( Entity* parent = uidToEntity(my->parent) )
+ {
+ if ( parent && parent->behavior == &actPlayer )
+ {
+ int tier = my->skill[11];
+ auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
+ for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
+ {
+ list_t* currentList = *it;
+ node_t* node;
+ for ( node = currentList->first; node != nullptr; node = node->next )
+ {
+ Entity* entity = (Entity*)node->element;
+ if ( entity && entity->behavior == &actMonster )
+ {
+ if ( Stat* entitystats = entity->getStats() )
+ {
+ auto hitProps = getParticleEmitterHitProps(my->getUID(), entity);
+ if ( hitProps->hits > 0 || (ticks - hitProps->tick) < TICKS_PER_SECOND )
+ {
+ continue;
+ }
+ if ( entity->getUID() % 10 == ticks % 10 )
+ {
+ hitProps->tick = ticks;
+ if ( entityWantsJewel(tier, *entity, *entitystats, true) )
+ {
+ if ( entityDist(my, entity) < 16.0 )
+ {
+ hitProps->hits++;
+ if ( entityWantsJewel(tier, *entity, *entitystats, false) )
+ {
+ const char* msg = nullptr;
+ if ( jewelItemRecruit(parent, entity, tier, &msg) )
+ {
+ my->clearMonsterInteract();
+ my->removeLightField();
+ list_RemoveNode(my->mynode);
+ return;
+ }
+ else
+ {
+ if ( msg )
+ {
+ auto hitProps = getParticleEmitterHitProps(my->getUID(), parent);
+ if ( hitProps && hitProps->hits == 0 )
+ {
+ ++hitProps->hits;
+ messagePlayer(parent->isEntityPlayer(), MESSAGE_HINT, msg);
+ }
+ }
+ }
+ }
+ else
+ {
+ spawnFloatingSpriteMisc(134, entity->x + (-4 + local_rng.rand() % 9) + cos(entity->yaw) * 2,
+ entity->y + (-4 + local_rng.rand() % 9) + sin(entity->yaw) * 2, entity->z + local_rng.rand() % 4);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Uint32 myUid = my->getUID();
if ( my->isInteractWithMonster() )
{
Entity* monsterInteracting = uidToEntity(my->interactedByMonster);
@@ -243,6 +663,7 @@ void actItem(Entity* my)
copyOfItem = nullptr;
if ( pickedUpItem && monsterInteracting->monsterAllyIndex >= 0 )
{
+ onItemPickedUp(*monsterInteracting, myUid);
FollowerMenu[monsterInteracting->monsterAllyIndex].entityToInteractWith = nullptr; // in lieu of my->clearMonsterInteract, my might have been deleted.
return;
}
@@ -252,6 +673,7 @@ void actItem(Entity* my)
{
if ( monsterInteracting->monsterConsumeFoodEntity(my, monsterInteracting->getStats()) && monsterInteracting->monsterAllyIndex >= 0 )
{
+ onItemPickedUp(*monsterInteracting, myUid);
FollowerMenu[monsterInteracting->monsterAllyIndex].entityToInteractWith = nullptr; // in lieu of my->clearMonsterInteract, my might have been deleted.
return;
}
@@ -260,6 +682,7 @@ void actItem(Entity* my)
{
if ( monsterInteracting->monsterAddNearbyItemToInventory(monsterInteracting->getStats(), 24, 9, my) && monsterInteracting->monsterAllyIndex >= 0 )
{
+ onItemPickedUp(*monsterInteracting, myUid);
FollowerMenu[monsterInteracting->monsterAllyIndex].entityToInteractWith = nullptr; // in lieu of my->clearMonsterInteract, my might have been deleted.
return;
}
@@ -270,6 +693,15 @@ void actItem(Entity* my)
}
my->clearMonsterInteract();
}
+
+ if ( my->itemReturnUID != 0 )
+ {
+ if ( itemProcessReturnItemEffect(my, false) )
+ {
+ return;
+ }
+ }
+
for ( i = 0; i < MAXPLAYERS; i++)
{
if ( selectedEntity[i] == my || client_selected[i] == my )
@@ -324,6 +756,27 @@ void actItem(Entity* my)
{
steamAchievementClient(i, "BARONY_ACH_REPOSSESSION");
}
+ if ( my->itemGerminateResult == 1 )
+ {
+ if ( item2->type == FOOD_NUT || item2->type == FOOD_SHROOM )
+ {
+ if ( achievementObserver.checkUidIsFromPlayer(my->itemOriginalOwner) >= 0 )
+ {
+ int owner = achievementObserver.checkUidIsFromPlayer(my->itemOriginalOwner);
+ achievementObserver.playerAchievements[owner].eatMe++;
+ }
+ }
+ }
+ if ( GenericGUI[i].isItemRation(item2->type)
+ && stats[i]->HUNGER <= getEntityHungerInterval(i, nullptr, stats[i], HUNGER_INTERVAL_HUNGRY) )
+ {
+ if ( achievementObserver.checkUidIsFromPlayer(my->itemOriginalOwner) >= 0
+ && achievementObserver.checkUidIsFromPlayer(my->itemOriginalOwner) != i )
+ {
+ steamAchievementClient(i, "BARONY_ACH_SECOND_BREAKFAST");
+ }
+ }
+
//messagePlayer(i, "old owner: %d", item2->ownerUid);
if (item2)
{
@@ -341,6 +794,7 @@ void actItem(Entity* my)
if ( salvaged )
{
+ onItemPickedUp(*players[i]->entity, myUid);
free(item2);
my->removeLightField();
list_RemoveNode(my->mynode);
@@ -396,6 +850,7 @@ void actItem(Entity* my)
}
free(item); // item is the picked up items (item == item2)
}
+ onItemPickedUp(*players[i]->entity, myUid);
my->removeLightField();
list_RemoveNode(my->mynode);
return;
@@ -464,10 +919,78 @@ void actItem(Entity* my)
my->light = addLight(my->x / 16, my->y / 16, "lootbag_white");
}
break;
+ case 2409: // jewel
+ if ( !my->light )
+ {
+ my->light = addLight(my->x / 16, my->y / 16, "jewel_yellow");
+ }
+ break;
default:
break;
}
+ bool levitating = false;
+ Entity* leader = nullptr;
+ if ( my->itemFollowUID != 0 )
+ {
+ if ( multiplayer != CLIENT )
+ {
+ if ( leader = uidToEntity(my->itemFollowUID) )
+ {
+ Stat* leaderStats = leader->getStats();
+ real_t dist = entityDist(leader, my);
+
+ real_t maxDist = std::max(16, std::min(getSpellDamageSecondaryFromID(SPELL_ATTRACT_ITEMS, leader, nullptr, leader),
+ getSpellDamageFromID(SPELL_ATTRACT_ITEMS, leader, nullptr, leader)));
+ if ( dist > maxDist + 4.0 || (leaderStats && !leaderStats->getEffectActive(EFF_ATTRACT_ITEMS)) )
+ {
+ my->itemFollowUID = 0;
+ serverUpdateEntitySkill(my, 30);
+ leader = nullptr;
+ }
+
+ if ( leader )
+ {
+ levitating = true;
+ const real_t followDist = 12.0;
+ if ( dist > 12.0 )
+ {
+ real_t tangent = atan2(leader->y - my->y, leader->x - my->x);
+ my->vel_x = cos(tangent) * ((dist - followDist) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
+ my->vel_y = sin(tangent) * ((dist - followDist) / MAGICLIGHTBALL_DIVIDE_CONSTANT);
+ my->vel_x = (my->vel_x < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_x : MAGIC_LIGHTBALL_SPEEDLIMIT;
+ my->vel_y = (my->vel_y < MAGIC_LIGHTBALL_SPEEDLIMIT) ? my->vel_y : MAGIC_LIGHTBALL_SPEEDLIMIT;
+ }
+ my->flags[UPDATENEEDED] = true;
+ my->flags[NOUPDATE] = false;
+ }
+ }
+ }
+ else
+ {
+ levitating = true;
+ my->itemNotMoving = 0;
+ my->itemNotMovingClient = 0;
+ my->flags[UPDATENEEDED] = true;
+ my->flags[NOUPDATE] = false;
+ }
+ }
+
+ if ( my->sprite == items[GEM_JEWEL].index )
+ {
+ if ( my->ticks % 25 == 0 || my->ticks % 40 == 0 )
+ {
+ if ( Entity* fx = spawnMagicParticleCustom(my, 2410, 1.0, 1.0) )
+ {
+ fx->vel_z = -0.2;
+ fx->yaw = (local_rng.rand() % 360) * PI / 180.0;
+ fx->x = my->x + 3.0 * cos(fx->yaw);
+ fx->y = my->y + 3.0 * sin(fx->yaw);
+ fx->focalz = 0.25;
+ }
+ }
+ }
+
if ( my->itemNotMoving )
{
switch ( my->sprite )
@@ -541,7 +1064,26 @@ void actItem(Entity* my)
my->flags[BURNING] = false;
- if ( my->z < groundheight )
+ if ( levitating )
+ {
+ /*ITEM_VELZ += 0.04;
+ ITEM_VELZ = std::min(0.0, ITEM_VELZ);
+ my->z += ITEM_VELZ;*/
+ my->z = my->itemLevitateStartZ * my->itemLevitate;
+ my->z = std::min(groundheight - 0.1, my->z);
+ my->z = std::max(my->z, -7.5);
+ my->vel_z = 0.0;
+
+ real_t diff = std::max(0.025, my->itemLevitate / 10.0);
+ my->itemLevitate = std::max(0.0, my->itemLevitate - diff);
+
+ my->yaw += PI / (TICKS_PER_SECOND * 10);
+ my->new_yaw = my->yaw;
+ ITEM_WATERBOB = sin(((ticks % (TICKS_PER_SECOND * 2)) / ((real_t)TICKS_PER_SECOND * 2.0)) * (2.0 * PI)) * 0.5;
+ my->z += ITEM_WATERBOB;
+ my->new_z = my->z;
+ }
+ else if ( my->z < groundheight )
{
// fall
// chakram and shuriken lie flat, needs to use sprites for client
@@ -596,7 +1138,7 @@ void actItem(Entity* my)
if ( my->x >= 0 && my->y >= 0 && my->x < map.width << 4 && my->y < map.height << 4 )
{
const int tile = map.tiles[(int)(my->y / 16) * MAPLAYERS + (int)(my->x / 16) * MAPLAYERS * map.height];
- if ( tile || (my->sprite >= 610 && my->sprite <= 613) || (my->sprite >= 1206 && my->sprite <= 1209) )
+ if ( tile || (my->sprite >= 610 && my->sprite <= 613) || (my->sprite >= 1206 && my->sprite <= 1210) )
{
onground = true;
if (!isArtifact && tile >= 64 && tile < 72) { // landing on lava
@@ -617,6 +1159,7 @@ void actItem(Entity* my)
if ( splash )
{
playSoundEntity(my, 136, 64);
+ createWaterSplash(my->x, my->y, 30);
}
}
}
@@ -722,21 +1265,29 @@ void actItem(Entity* my)
Compendium_t::Events_t::eventUpdateWorld(playerOwner, Compendium_t::CPDM_PITS_ITEMS_LOST, "pits", 1);
if ( ITEM_TYPE >= 0 && ITEM_TYPE < NUMITEMS )
{
- Compendium_t::Events_t::eventUpdateWorld(playerOwner, Compendium_t::CPDM_PITS_ITEMS_VALUE_LOST, "pits", items[ITEM_TYPE].value);
+ Compendium_t::Events_t::eventUpdateWorld(playerOwner, Compendium_t::CPDM_PITS_ITEMS_VALUE_LOST, "pits", items[ITEM_TYPE].gold_value);
}
}
}
+ if ( my->itemReturnUID != 0 )
+ {
+ if ( itemProcessReturnItemEffect(my, true) )
+ {
+ return;
+ }
+ }
}
if ( ITEM_TYPE == ARTIFACT_MACE && my->parent != 0 )
{
steamAchievementEntity(uidToEntity(my->parent), "BARONY_ACH_STFU");
}
+ my->removeLightField();
list_RemoveNode(my->mynode);
return;
}
// don't perform unneeded computations on items that have basically no velocity
- if (!overWater && onground &&
+ if (!overWater && onground && !levitating &&
my->z > groundheight - .0001 && my->z < groundheight + .0001 &&
fabs(ITEM_VELX) < 0.02 && fabs(ITEM_VELY) < 0.02)
{
@@ -769,6 +1320,15 @@ void actItem(Entity* my)
{
double result = clipMove(&my->x, &my->y, ITEM_VELX, ITEM_VELY, my);
my->yaw += result * .05;
+
+ if ( multiplayer != CLIENT )
+ {
+ if ( levitating && leader && leader->behavior == &actPlayer )
+ {
+ players[leader->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_ATTRACT_ITEMS, result, 0.025, nullptr);
+ }
+ }
+
if ( result != sqrt( ITEM_VELX * ITEM_VELX + ITEM_VELY * ITEM_VELY ) )
{
if ( !hit.side )
@@ -786,6 +1346,54 @@ void actItem(Entity* my)
}
}
}
+
ITEM_VELX = ITEM_VELX * .925;
ITEM_VELY = ITEM_VELY * .925;
}
+
+static Uint32 lastAttractTick = 0;
+void Entity::attractItem(Entity& itemEntity)
+{
+ if ( itemEntity.itemFollowUID != getUID() && itemEntity.z < 16.0 && itemEntity.ticks > TICKS_PER_SECOND )
+ {
+ if ( lastAttractTick != ::ticks )
+ {
+ spawnMagicEffectParticles(itemEntity.x, itemEntity.y, itemEntity.z, 170);
+ lastAttractTick = ::ticks;
+ }
+ itemEntity.itemFollowUID = getUID();
+
+ itemEntity.flags[USERFLAG1] = false;
+ itemEntity.itemNotMoving = 0;
+ itemEntity.itemNotMovingClient = 0;
+ itemEntity.z = std::max(itemEntity.z - 0.1, 0.0);
+ itemEntity.vel_z = -0.75;
+ itemEntity.itemLevitate = 1.0;
+ itemEntity.itemLevitateStartZ = itemEntity.z;
+ itemEntity.flags[UPDATENEEDED] = true;
+ itemEntity.flags[NOUPDATE] = false;
+ if ( multiplayer == SERVER )
+ {
+ for ( int c = 1; c < MAXPLAYERS; c++ )
+ {
+ if ( client_disconnected[c] || players[c]->isLocalPlayer() )
+ {
+ continue;
+ }
+ strcpy((char*)net_packet->data, "ATTI");
+ SDLNet_Write32(itemEntity.getUID(), &net_packet->data[4]);
+ SDLNet_Write16((Sint16)(itemEntity.x * 32), &net_packet->data[8]);
+ SDLNet_Write16((Sint16)(itemEntity.y * 32), &net_packet->data[10]);
+ SDLNet_Write16((Sint16)(itemEntity.z * 32), &net_packet->data[12]);
+ SDLNet_Write16((Sint16)(itemEntity.vel_x * 32), &net_packet->data[14]);
+ SDLNet_Write16((Sint16)(itemEntity.vel_y * 32), &net_packet->data[16]);
+ SDLNet_Write16((Sint16)(itemEntity.vel_z * 32), &net_packet->data[18]);
+ SDLNet_Write32(getUID(), & net_packet->data[20]);
+ net_packet->address.host = net_clients[c - 1].host;
+ net_packet->address.port = net_clients[c - 1].port;
+ net_packet->len = 24;
+ sendPacketSafe(net_sock, -1, net_packet, c - 1);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/actladder.cpp b/src/actladder.cpp
index 39d5bcfd4..72006c51c 100644
--- a/src/actladder.cpp
+++ b/src/actladder.cpp
@@ -286,7 +286,7 @@ void actPortal(Entity* my)
&& !entity->monsterAllyGetPlayerLeader() )
{
Stat* stats = entity->getStats();
- if ( stats && MonsterData_t::nameMatchesSpecialNPCName(*stats, "bram kindly") )
+ if ( stats && (stats->getAttribute("special_npc") == "bram kindly") )
{
bossAlive = true;
}
@@ -616,6 +616,10 @@ void actWinningPortal(Entity* my)
switch ( race ) {
default:
case RACE_HUMAN:
+ case RACE_GNOME:
+ case RACE_DRYAD:
+ case RACE_MYCONID:
+ case RACE_SALAMANDER:
MainMenu::beginFade(MainMenu::FadeDestination::ClassicEndingHuman);
break;
case RACE_AUTOMATON:
@@ -630,6 +634,7 @@ void actWinningPortal(Entity* my)
case RACE_VAMPIRE:
case RACE_SUCCUBUS:
case RACE_INCUBUS:
+ case RACE_GREMLIN:
MainMenu::beginFade(MainMenu::FadeDestination::ClassicEndingEvil);
break;
}
@@ -644,6 +649,10 @@ void actWinningPortal(Entity* my)
switch ( race ) {
default:
case RACE_HUMAN:
+ case RACE_GNOME:
+ case RACE_DRYAD:
+ case RACE_MYCONID:
+ case RACE_SALAMANDER:
MainMenu::beginFade(MainMenu::FadeDestination::ClassicBaphometEndingHuman);
break;
case RACE_AUTOMATON:
@@ -658,6 +667,7 @@ void actWinningPortal(Entity* my)
case RACE_VAMPIRE:
case RACE_SUCCUBUS:
case RACE_INCUBUS:
+ case RACE_GREMLIN:
MainMenu::beginFade(MainMenu::FadeDestination::ClassicBaphometEndingEvil);
break;
}
@@ -845,6 +855,10 @@ void Entity::actExpansionEndGamePortal()
switch ( race ) {
default:
case RACE_HUMAN:
+ case RACE_GNOME:
+ case RACE_DRYAD:
+ case RACE_MYCONID:
+ case RACE_SALAMANDER:
MainMenu::beginFade(MainMenu::FadeDestination::EndingHuman);
break;
case RACE_AUTOMATON:
@@ -859,6 +873,7 @@ void Entity::actExpansionEndGamePortal()
case RACE_VAMPIRE:
case RACE_SUCCUBUS:
case RACE_INCUBUS:
+ case RACE_GREMLIN:
MainMenu::beginFade(MainMenu::FadeDestination::EndingEvil);
break;
}
@@ -1055,6 +1070,7 @@ void Entity::actMidGamePortal()
switch ( race ) { // herx midpoint
default:
case RACE_HUMAN:
+ case RACE_GNOME:
MainMenu::beginFade(MainMenu::FadeDestination::HerxMidpointHuman);
break;
case RACE_AUTOMATON:
@@ -1063,12 +1079,16 @@ void Entity::actMidGamePortal()
case RACE_GOATMAN:
case RACE_GOBLIN:
case RACE_INSECTOID:
+ case RACE_DRYAD:
+ case RACE_MYCONID:
+ case RACE_SALAMANDER:
MainMenu::beginFade(MainMenu::FadeDestination::HerxMidpointBeast);
break;
case RACE_SKELETON:
case RACE_VAMPIRE:
case RACE_SUCCUBUS:
case RACE_INCUBUS:
+ case RACE_GREMLIN:
MainMenu::beginFade(MainMenu::FadeDestination::HerxMidpointEvil);
break;
}
@@ -1077,6 +1097,7 @@ void Entity::actMidGamePortal()
switch ( race ) {
default:
case RACE_HUMAN:
+ case RACE_GNOME:
MainMenu::beginFade(MainMenu::FadeDestination::BaphometMidpointHuman);
break;
case RACE_AUTOMATON:
@@ -1085,12 +1106,16 @@ void Entity::actMidGamePortal()
case RACE_GOATMAN:
case RACE_GOBLIN:
case RACE_INSECTOID:
+ case RACE_DRYAD:
+ case RACE_MYCONID:
+ case RACE_SALAMANDER:
MainMenu::beginFade(MainMenu::FadeDestination::BaphometMidpointBeast);
break;
case RACE_SKELETON:
case RACE_VAMPIRE:
case RACE_SUCCUBUS:
case RACE_INCUBUS:
+ case RACE_GREMLIN:
MainMenu::beginFade(MainMenu::FadeDestination::BaphometMidpointEvil);
break;
}
diff --git a/src/actmagictrap.cpp b/src/actmagictrap.cpp
index 7c89057f7..a35907e20 100644
--- a/src/actmagictrap.cpp
+++ b/src/actmagictrap.cpp
@@ -44,33 +44,44 @@ void actMagicTrapCeiling(Entity* my)
void Entity::actMagicTrapCeiling()
{
-#ifdef USE_FMOD
- if ( spellTrapAmbience == 0 )
+ if ( actTrapSabotaged == 0 )
{
+#ifdef USE_FMOD
+ if ( spellTrapAmbience == 0 )
+ {
+ spellTrapAmbience--;
+ stopEntitySound();
+ entity_sound = playSoundEntityLocal(this, 149, 16);
+ }
+ if ( entity_sound )
+ {
+ bool playing = false;
+ entity_sound->isPlaying(&playing);
+ if ( !playing )
+ {
+ entity_sound = nullptr;
+ }
+ }
+#else
spellTrapAmbience--;
- stopEntitySound();
- entity_sound = playSoundEntityLocal(this, 149, 16);
- }
- if ( entity_sound )
- {
- bool playing = false;
- entity_sound->isPlaying(&playing);
- if ( !playing )
+ if ( spellTrapAmbience <= 0 )
{
- entity_sound = nullptr;
+ spellTrapAmbience = TICKS_PER_SECOND * 30;
+ playSoundEntityLocal(this, 149, 16);
}
+#endif
}
-#else
- spellTrapAmbience--;
- if ( spellTrapAmbience <= 0 )
+ else
{
- spellTrapAmbience = TICKS_PER_SECOND * 30;
- playSoundEntityLocal(this, 149, 16);
- }
+#ifdef USE_FMOD
+ stopEntitySound();
#endif
+ return;
+ }
if ( multiplayer == CLIENT )
{
+ flags[NOUPDATE] = true;
return;
}
if ( circuit_status != CIRCUIT_ON )
@@ -166,7 +177,7 @@ void Entity::actMagicTrapCeiling()
entity->x = x;
entity->y = y;
entity->z = ceilingModel->z - 2;
- double missile_speed = 4 * ((double)(((spellElement_t*)(getSpellFromID(spellTrapType)->elements.first->element))->mana) / ((spellElement_t*)(getSpellFromID(spellTrapType)->elements.first->element))->overload_multiplier);
+ double missile_speed = 4.0;
entity->vel_x = 0.0;
entity->vel_y = 0.0;
entity->vel_z = 0.5 * (missile_speed);
@@ -239,8 +250,15 @@ void actMagicTrap(Entity* my)
return;
}
+ if ( my->actTrapSabotaged > 0 )
+ {
+ my->removeLightField();
+ return;
+ }
+
if ( multiplayer == CLIENT )
{
+ my->flags[NOUPDATE] = true;
return;
}
@@ -284,7 +302,7 @@ void actMagicTrap(Entity* my)
entity->y = my->y + y;
entity->z = my->z;
entity->yaw = oldir * (PI / 2.f);
- double missile_speed = 4 * ((double)(((spellElement_t*)(getSpellFromID(MAGICTRAP_SPELL)->elements.first->element))->mana) / ((spellElement_t*)(getSpellFromID(MAGICTRAP_SPELL)->elements.first->element))->overload_multiplier);
+ double missile_speed = 4.0;
entity->vel_x = cos(entity->yaw) * (missile_speed);
entity->vel_y = sin(entity->yaw) * (missile_speed);
}
@@ -605,7 +623,7 @@ void daedalusShrineInteract(Entity* my, Entity* touched)
if ( touched && touched->getStats() )
{
Stat* myStats = touched->getStats();
- if ( myStats->EFFECTS[EFF_SLOW] )
+ if ( myStats->getEffectActive(EFF_SLOW) )
{
touched->setEffect(EFF_SLOW, false, 0, true);
}
@@ -742,7 +760,7 @@ void Entity::actDaedalusShrine()
if ( touched && touched->getStats() )
{
Stat* myStats = touched->getStats();
- if ( myStats->EFFECTS[EFF_SLOW] )
+ if ( myStats->getEffectActive(EFF_SLOW) )
{
touched->setEffect(EFF_SLOW, false, 0, true);
}
@@ -1072,7 +1090,10 @@ void Entity::actAssistShrine()
}
else if ( multiplayer == SINGLE || playernum == 0 )
{
- GenericGUI[playernum].assistShrineGUI.closeAssistShrine();
+ if ( playernum >= 0 && playernum < MAXPLAYERS )
+ {
+ GenericGUI[playernum].assistShrineGUI.closeAssistShrine();
+ }
}
}
}
diff --git a/src/actmonster.cpp b/src/actmonster.cpp
index 83cb5c78b..02c039090 100644
--- a/src/actmonster.cpp
+++ b/src/actmonster.cpp
@@ -30,104 +30,134 @@
#include "ui/MainMenu.hpp"
#include "menu.hpp"
-float limbs[NUMMONSTERS][20][3];
+float limbs[NUMMONSTERS][30][3];
// determines which monsters fight which
bool swornenemies[NUMMONSTERS][NUMMONSTERS] =
{
-// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B
-// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U
-// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING
- { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1 }, // HUMAN
- { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // RAT
- { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GOBLIN
- { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1 }, // SLIME
- { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // TROLL
- { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1 }, // BAT_SMALL
- { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SPIDER
- { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // GHOUL
- { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SKELETON
- { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SCORPION
- { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // IMP
- { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // CRAB
- { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GNOME
- { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1 }, // DEMON
- { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SUCCUBUS
- { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // MIMIC
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // LICH
- { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // MINOTAUR
- { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // DEVIL
- { 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER
- { 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // KOBOLD
- { 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SCARAB
- { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // CRYSTALGOLEM
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // INCUBUS
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // VAMPIRE
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SHADOW
- { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // COCKATRICE
- { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // INSECTOID
- { 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // GOATMAN
- { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // AUTOMATON
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // LICH_ICE
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // LICH_FIRE
- { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SENTRYBOT
- { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SPELLBOT
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUMMYBOT
- { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 } // BUGBEAR
-// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B
-// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U
-// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G
+// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B D M S G R M A U U U U U U U U
+// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U D M S G E I D N N N N N N N N
+// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G D M S G V N O 1 2 3 4 5 6 7 8
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING
+ { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // HUMAN
+ { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT
+ { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GOBLIN
+ { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SLIME
+ { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL
+ { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL
+ { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER
+ { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GHOUL
+ { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SKELETON
+ { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCORPION
+ { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // IMP
+ { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRAB
+ { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GNOME
+ { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DEMON
+ { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SUCCUBUS
+ { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MIMIC
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // LICH
+ { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINOTAUR
+ { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DEVIL
+ { 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER
+ { 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // KOBOLD
+ { 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCARAB
+ { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRYSTALGOLEM
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // INCUBUS
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // VAMPIRE
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHADOW
+ { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // COCKATRICE
+ { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // INSECTOID
+ { 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GOATMAN
+ { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // AUTOMATON
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // LICH_ICE
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // LICH_FIRE
+ { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SENTRYBOT
+ { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPELLBOT
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUMMYBOT
+ { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BUGBEAR
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DRYAD
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MYCONID
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SALAMANDER
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GREMLIN
+ { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // REVENANT_SKULL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINIMIMIC
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MONSTER_ADORCISED_WEAPON
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // FLAME_ELEMENTAL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // HOLOGRAM
+ { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MOTH_SMALL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // EARTH_ELEMENTAL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUCK_SMALL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MONSTER_UNUSED_6
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MONSTER_UNUSED_7
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // MONSTER_UNUSED_8
+// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B D M S G R M A F H M E D U U U
+// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U D M S G E I D L O T R U N N N
+// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G D M S G V N O M L H F C 6 7 8
};
// determines which monsters come to the aid of other monsters
bool monsterally[NUMMONSTERS][NUMMONSTERS] =
{
-// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B
-// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U
-// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // HUMAN
- { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT
- { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // GOBLIN
- { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // SLIME
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL
- { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL
- { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER
- { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, // GHOUL
- { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SKELETON
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCORPION
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // IMP
- { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRAB
- { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GNOME
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // DEMON
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SUCCUBUS
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MIMIC
- { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // LICH
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINOTAUR
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // DEVIL
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER
- { 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // KOBOLD
- { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCARAB
- { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRYSTALGOLEM
- { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // INCUBUS
- { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // VAMPIRE
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // SHADOW
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // COCKATRICE
- { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // INSECTOID
- { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // GOATMAN
- { 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // AUTOMATON
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // LICH_ICE
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // LICH_FIRE
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // SENTRYBOT
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // SPELLBOT
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // GYROBOT
- { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // DUMMYBOT
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // BUGBEAR
-// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B
-// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U
-// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G
+// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B D M S G R M A U U U U U U U U
+// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U D M S G E I D N N N N N N N N
+// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G D M S G V N O 1 2 3 4 5 6 7 8
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // HUMAN
+ { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT
+ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GOBLIN
+ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SLIME
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL
+ { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL
+ { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER
+ { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GHOUL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SKELETON
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCORPION
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // IMP
+ { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRAB
+ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GNOME
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DEMON
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SUCCUBUS
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MIMIC
+ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // LICH
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINOTAUR
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DEVIL
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER
+ { 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // KOBOLD
+ { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCARAB
+ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRYSTALGOLEM
+ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // INCUBUS
+ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // VAMPIRE
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHADOW
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // COCKATRICE
+ { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // INSECTOID
+ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GOATMAN
+ { 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // AUTOMATON
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // LICH_ICE
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // LICH_FIRE
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SENTRYBOT
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPELLBOT
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT
+ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUMMYBOT
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BUGBEAR
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DRYAD
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MYCONID
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SALAMANDER
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GREMLIN
+ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // REVENANT_SKULL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINIMIMIC
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MONSTER_ADORCISED_WEAPON
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // FLAME_ELEMENTAL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // HOLOGRAM
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // MOTH_SMALL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, // EARTH_ELEMENTAL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUCK_SMALL
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MONSTER_UNUSED_6
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MONSTER_UNUSED_7
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // MONSTER_UNUSED_8
+// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B D M S G R M A F H M E D U U U
+// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U D M S G E I D L O T R U N N N
+// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G D M S G V N O M L H F C 6 7 8
};
// monster sight ranges
@@ -170,18 +200,49 @@ double sightranges[NUMMONSTERS] =
192, // SPELLBOT
256, // GYROBOT
32, // DUMMYBOT
- 128 // BUGBEAR
+ 128, // BUGBEAR
+ 256, // DRYAD
+ 256, // MYCONID
+ 256, // SALAMANDER
+ 256, // GREMLIN
+ 192, // REVENANT_SKULL
+ 256, // MINIMIMIC
+ 64, // ADORCISED_WEAPON
+ 128, // FLAME_ELEMENTAL
+ 256, // HOLOGRAM
+ 128, // MOTH_SMALL
+ 128, // EARTH_ELEMENTAL
+ 256, // DUCK_SMALL
+ 256, // MONSTER_UNUSED_6
+ 256, // MONSTER_UNUSED_7
+ 256 // MONSTER_UNUSED_8
};
int monsterGlobalAnimationMultiplier = 10;
int monsterGlobalAttackTimeMultiplier = 1;
-std::string getMonsterLocalizedName(Monster creature)
+std::string getMonsterLocalizedName(Monster creature, Stat* optionalStats)
{
if ( creature == BUGBEAR )
{
return Language::get(6256);
}
+ else if ( creature == MOTH_SMALL && optionalStats && MonsterData_t::nameMatchesSpecialNPCName(*optionalStats, "fire sprite") )
+ {
+ return optionalStats->name;
+ }
+ else if ( creature >= DRYAD && creature <= GREMLIN )
+ {
+ return Language::get(6358 + (creature - DRYAD));
+ }
+ else if ( creature >= REVENANT_SKULL && creature <= MINIMIMIC )
+ {
+ return Language::get(6568 + (creature - REVENANT_SKULL));
+ }
+ else if ( creature >= MONSTER_ADORCISED_WEAPON && creature <= MONSTER_UNUSED_8 )
+ {
+ return Language::get(6580 + (creature - MONSTER_ADORCISED_WEAPON));
+ }
else if ( creature < KOBOLD )
{
if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) {
@@ -203,6 +264,18 @@ std::string getMonsterLocalizedPlural(Monster creature)
{
return Language::get(6257);
}
+ else if ( creature >= DRYAD && creature <= GREMLIN )
+ {
+ return Language::get(6362 + (creature - DRYAD));
+ }
+ else if ( creature >= REVENANT_SKULL && creature <= MINIMIMIC )
+ {
+ return Language::get(6570 + (creature - REVENANT_SKULL));
+ }
+ else if ( creature >= MONSTER_ADORCISED_WEAPON && creature <= MONSTER_UNUSED_8 )
+ {
+ return Language::get(6589 + (creature - MONSTER_ADORCISED_WEAPON));
+ }
if ( creature < KOBOLD )
{
if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter))) {
@@ -223,6 +296,18 @@ std::string getMonsterLocalizedInjury(Monster creature)
{
return Language::get(6258);
}
+ else if ( creature >= DRYAD && creature <= GREMLIN )
+ {
+ return Language::get(6366 + (creature - DRYAD));
+ }
+ else if ( creature >= REVENANT_SKULL && creature <= MINIMIMIC )
+ {
+ return Language::get(6572 + (creature - REVENANT_SKULL));
+ }
+ else if ( creature >= MONSTER_ADORCISED_WEAPON && creature <= MONSTER_UNUSED_8 )
+ {
+ return Language::get(6598 + (creature - MONSTER_ADORCISED_WEAPON));
+ }
if ( creature < KOBOLD )
{
if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) {
@@ -330,11 +415,11 @@ bool ShopkeeperPlayerHostility_t::isPlayerEnemy(const int player)
bool ShopkeeperPlayerHostility_t::playerRaceCheckHostility(const int player, const Monster type) const
{
if ( player < 0 || player >= MAXPLAYERS ) { return false; }
- if ( type != HUMAN && type != AUTOMATON )
+ if ( type != HUMAN && type != AUTOMATON && type != DRYAD && type != MYCONID && type != SALAMANDER && type != GNOME )
{
if ( stats[player] && stats[player]->mask && stats[player]->mask->type == MONOCLE )
{
- if ( !stats[player]->EFFECTS[EFF_SHAPESHIFT] && !(players[player]->entity && players[player]->entity->isInvisible()) )
+ if ( !stats[player]->getEffectActive(EFF_SHAPESHIFT) && !(players[player]->entity && players[player]->entity->isInvisible()) )
{
return true;
}
@@ -497,6 +582,10 @@ void ShopkeeperPlayerHostility_t::setWantedLevel(ShopkeeperPlayerHostility_t::Pl
}
real_t monsterVisionRange = sightranges[SHOPKEEPER];
+ if ( players[i]->mechanics.ensemblePlaying >= 0 )
+ {
+ monsterVisionRange = std::max(monsterVisionRange, 5 * 16.0);
+ }
int light = players[i]->entity->entityLightAfterReductions(*stats[i], shopkeeper);
double targetdist = sqrt(pow(shopkeeper->x - players[i]->entity->x, 2) + pow(shopkeeper->y - players[i]->entity->y, 2));
@@ -629,6 +718,37 @@ void ShopkeeperPlayerHostility_t::serverSendClientUpdate(const bool force)
ShopkeeperPlayerHostility_t ShopkeeperPlayerHostility;
+bool Entity::monsterAlertBeforeHit(Entity* attacker)
+{
+ if ( !attacker )
+ {
+ return false;
+ }
+
+ bool alertTarget = true;
+ if ( attacker->behavior == &actMonster && attacker->monsterAllyIndex != -1 )
+ {
+ if ( behavior == &actMonster && monsterAllyIndex != -1 )
+ {
+ // if a player ally + hit another ally, don't aggro back
+ alertTarget = false;
+ }
+ }
+
+ if ( Stat* myStats = getStats() )
+ {
+ if ( myStats->getEffectActive(EFF_NUMBING_BOLT) )
+ {
+ alertTarget = false;
+ spawnFloatingSpriteMisc(134, x + (-4 + local_rng.rand() % 9) + cos(yaw) * 2,
+ y + (-4 + local_rng.rand() % 9) + sin(yaw) * 2, z + local_rng.rand() % 4);
+ setEffect(EFF_NUMBING_BOLT, false, 0, false);
+ }
+ }
+
+ return alertTarget;
+}
+
void Entity::updateEntityOnHit(Entity* attacker, bool alertTarget)
{
if ( !attacker ) return;
@@ -636,6 +756,38 @@ void Entity::updateEntityOnHit(Entity* attacker, bool alertTarget)
if ( Stat* myStats = getStats() )
{
+ if ( Uint8 effectStrength = myStats->getEffectActive(EFF_PENANCE) )
+ {
+ if ( attacker )
+ {
+ if ( attacker->behavior == &actPlayer || attacker->monsterAllyGetPlayerLeader() )
+ {
+ if ( effectStrength >= 1 && effectStrength < 1 + MAXPLAYERS )
+ {
+ setEffect(EFF_PENANCE, false, 0, true);
+ }
+ }
+ }
+ }
+ if ( myStats->getEffectActive(EFF_SEEK_CREATURE) )
+ {
+ setEffect(EFF_SEEK_CREATURE, false, 0, true);
+ }
+ if ( Uint8 effectStrength = myStats->getEffectActive(EFF_COMMAND) )
+ {
+ if ( attacker && (attacker->behavior == &actPlayer || attacker->monsterAllyGetPlayerLeader()) )
+ {
+ if ( effectStrength >= 1 && effectStrength < 1 + MAXPLAYERS )
+ {
+ players[effectStrength - 1]->mechanics.targetsRefuseCompel.insert(this->getUID());
+ }
+ setEffect(EFF_COMMAND, false, 0, true);
+ }
+ }
+ /*if ( myStats->getEffectActive(EFF_HEALING_WORD) )
+ {
+ setEffect(EFF_HEALING_WORD, false, 0, false);
+ }*/
if ( myStats->type == SHOPKEEPER )
{
if ( alertTarget )
@@ -959,6 +1111,11 @@ void MonsterAllyFormation_t::updateFormation(Uint32 leaderUid, Uint32 monsterUpd
}
}
+ if ( leader->behavior == &actPlayer && players[leader->skill[2]]->ghost.isActive() && players[leader->skill[2]]->ghost.isSpiritGhost() )
+ {
+ leader = players[leader->skill[2]]->ghost.my;
+ }
+
size_t formationIndex = 0;
for ( auto& unit : leaderUnits.meleeUnits )
{
@@ -1004,7 +1161,10 @@ void MonsterAllyFormation_t::updateFormation(Uint32 leaderUid, Uint32 monsterUpd
ally->x = x;
ally->y = y;
double tangent = atan2(leader->y - ally->y, leader->x - ally->x);
+ bool oldPassable = leader->flags[PASSABLE]; // hack to linetrace ghosts
+ leader->flags[PASSABLE] = false;
lineTraceTarget(ally, ally->x, ally->y, tangent, 128, 0, false, leader);
+ leader->flags[PASSABLE] = oldPassable;
if ( hit.entity == leader )
{
found = true;
@@ -1062,7 +1222,10 @@ void MonsterAllyFormation_t::updateFormation(Uint32 leaderUid, Uint32 monsterUpd
ally->x = x;
ally->y = y;
double tangent = atan2(leader->y - ally->y, leader->x - ally->x);
+ bool oldPassable = leader->flags[PASSABLE]; // hack to linetrace ghosts
+ leader->flags[PASSABLE] = false;
lineTraceTarget(ally, ally->x, ally->y, tangent, 128, 0, false, leader);
+ leader->flags[PASSABLE] = oldPassable;
if ( hit.entity == leader )
{
found = true;
@@ -1109,6 +1272,15 @@ Entity* summonMonster(Monster creature, long x, long y, bool forceLocation)
// small poof
auto poof = spawnPoof(entity->x, entity->y, 4, 0.5);
}
+ else if ( creature == MOTH_SMALL || creature == FLAME_ELEMENTAL || creature == DUCK_SMALL )
+ {
+ // small poof
+ auto poof = spawnPoof(entity->x, entity->y, -6, 0.5);
+ }
+ else if ( creature == EARTH_ELEMENTAL )
+ {
+ // no poof
+ }
else {
(void)spawnPoof(entity->x, entity->y, 0, 1.0);
}
@@ -1175,6 +1347,10 @@ Entity* summonMonsterNoSmoke(Monster creature, long x, long y, bool forceLocatio
{
myStats->setAttribute("slime_type", "terrain_spawn_override");
}
+ if ( myStats->type == EARTH_ELEMENTAL )
+ {
+ myStats->setAttribute("earth_elemental_spawn", "terrain_spawn_override");
+ }
}
// Find a free tile next to the source and then spawn it there.
@@ -1245,6 +1421,10 @@ Entity* summonMonsterNoSmoke(Monster creature, long x, long y, bool forceLocatio
{
myStats->attributes.erase("slime_type");
}
+ if ( myStats->type == EARTH_ELEMENTAL )
+ {
+ myStats->attributes.erase("earth_elemental_spawn");
+ }
}
nummonsters++;
@@ -1378,6 +1558,18 @@ int getMonsterInteractGreeting(Stat& myStats)
{
return 6259;
}
+ else if ( myStats.type >= REVENANT_SKULL && myStats.type <= MINIMIMIC )
+ {
+ return 6574 + (myStats.type - REVENANT_SKULL);
+ }
+ else if ( myStats.type >= MONSTER_ADORCISED_WEAPON && myStats.type <= MONSTER_UNUSED_8 )
+ {
+ return (6607 + (myStats.type - MONSTER_ADORCISED_WEAPON));
+ }
+ else if ( myStats.type >= DRYAD && myStats.type <= GREMLIN )
+ {
+ return 6370 + (myStats.type - DRYAD);
+ }
else if ( myStats.type < BUGBEAR )
{
return 4262 + myStats.type;
@@ -1467,6 +1659,11 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
return false;
}
+ if ( race == INCUBUS && myStats->getAttribute("special_npc") == "johann" )
+ {
+ return false;
+ }
+
bool canAlly = false;
bool roseEvent = false;
if ( skillCapstoneUnlocked(monsterclicked, PRO_LEADERSHIP) )
@@ -1518,7 +1715,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
}
else if ( stats[monsterclicked]->type == VAMPIRE )
{
- if ( race == VAMPIRE && !MonsterData_t::nameMatchesSpecialNPCName(*myStats, "bram kindly") )
+ if ( race == VAMPIRE && !(myStats->getAttribute("special_npc") == "bram kindly") )
{
canAlly = true;
}
@@ -1529,7 +1726,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
{
canAlly = true;
}
- else if ( race == HUMAN && (myStats->EFFECTS[EFF_DRUNK] || myStats->EFFECTS[EFF_CONFUSED])
+ else if ( race == HUMAN && (myStats->getEffectActive(EFF_DRUNK) || myStats->getEffectActive(EFF_CONFUSED))
&& stats[monsterclicked]->type != INCUBUS )
{
canAlly = true;
@@ -1537,9 +1734,9 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
{
steamAchievementClient(monsterclicked, "BARONY_ACH_TEMPTRESS");
}
- if ( myStats->EFFECTS[EFF_CONFUSED] )
+ if ( myStats->getEffectActive(EFF_CONFUSED) )
{
- my->setEffect(EFF_CONFUSED, false, 0, false);
+ my->setEffect(EFF_CONFUSED, false, 0, true);
}
}
}
@@ -1592,6 +1789,34 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
canAlly = true;
}
}
+ else if ( stats[monsterclicked]->type == DRYAD )
+ {
+ if ( race == DRYAD )
+ {
+ canAlly = true;
+ }
+ }
+ else if ( stats[monsterclicked]->type == MYCONID )
+ {
+ if ( race == MYCONID )
+ {
+ canAlly = true;
+ }
+ }
+ else if ( stats[monsterclicked]->type == SALAMANDER )
+ {
+ if ( race == SALAMANDER )
+ {
+ canAlly = true;
+ }
+ }
+ else if ( stats[monsterclicked]->type == GREMLIN )
+ {
+ if ( race == GREMLIN )
+ {
+ canAlly = true;
+ }
+ }
else if ( stats[monsterclicked]->type == CREATURE_IMP )
{
if ( race == CREATURE_IMP && !(!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
@@ -1599,7 +1824,9 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
canAlly = true; // non-boss imps
}
}
- if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE )
+ if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE
+ || (myStats->getEffectActive(EFF_PENANCE) >= 1
+ && myStats->getEffectActive(EFF_PENANCE) < 1 + MAXPLAYERS) )
{
canAlly = true;
}
@@ -1623,7 +1850,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
bool tryAlly = my->checkFriend(players[monsterclicked]->entity);
if ( stats[monsterclicked]->type == SUCCUBUS )
{
- if ( race == HUMAN && (myStats->EFFECTS[EFF_DRUNK] || myStats->EFFECTS[EFF_CONFUSED]) )
+ if ( race == HUMAN && (myStats->getEffectActive(EFF_DRUNK) || myStats->getEffectActive(EFF_CONFUSED)) )
{
tryAlly = true;
}
@@ -1690,7 +1917,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
}
else if ( stats[monsterclicked]->type == VAMPIRE )
{
- if ( race == VAMPIRE && !MonsterData_t::nameMatchesSpecialNPCName(*myStats, "bram kindly") )
+ if ( race == VAMPIRE && !(myStats->getAttribute("special_npc") == "bram kindly") )
{
canAlly = true;
}
@@ -1701,7 +1928,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
{
canAlly = true;
}
- else if ( race == HUMAN && (myStats->EFFECTS[EFF_DRUNK] || myStats->EFFECTS[EFF_CONFUSED])
+ else if ( race == HUMAN && (myStats->getEffectActive(EFF_DRUNK) || myStats->getEffectActive(EFF_CONFUSED))
&& stats[monsterclicked]->type != INCUBUS )
{
canAlly = true;
@@ -1709,9 +1936,9 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
{
steamAchievementClient(monsterclicked, "BARONY_ACH_TEMPTRESS");
}
- if ( myStats->EFFECTS[EFF_CONFUSED] )
+ if ( myStats->getEffectActive(EFF_CONFUSED) )
{
- my->setEffect(EFF_CONFUSED, false, 0, false);
+ my->setEffect(EFF_CONFUSED, false, 0, true);
}
}
}
@@ -1764,6 +1991,34 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
canAlly = true;
}
}
+ else if ( stats[monsterclicked]->type == DRYAD )
+ {
+ if ( race == DRYAD )
+ {
+ canAlly = true;
+ }
+ }
+ else if ( stats[monsterclicked]->type == MYCONID )
+ {
+ if ( race == MYCONID )
+ {
+ canAlly = true;
+ }
+ }
+ else if ( stats[monsterclicked]->type == SALAMANDER )
+ {
+ if ( race == SALAMANDER )
+ {
+ canAlly = true;
+ }
+ }
+ else if ( stats[monsterclicked]->type == GREMLIN )
+ {
+ if ( race == GREMLIN )
+ {
+ canAlly = true;
+ }
+ }
else if ( stats[monsterclicked]->type == CREATURE_IMP )
{
if ( race == CREATURE_IMP && !(!strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9)) )
@@ -1775,6 +2030,12 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
{
canAlly = true;
}
+ if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_RECRUITABLE
+ || (myStats->getEffectActive(EFF_PENANCE) >= 1
+ && myStats->getEffectActive(EFF_PENANCE) < 1 + MAXPLAYERS) )
+ {
+ canAlly = true;
+ }
if ( stats[monsterclicked]->mask && stats[monsterclicked]->mask->type == MASK_MOUTH_ROSE
&& players[monsterclicked]->entity->effectShapeshift == NOTHING )
{
@@ -1900,6 +2161,10 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64],
my->monsterState = MONSTER_STATE_WAIT; // be ready to follow
myStats->leader_uid = players[monsterclicked]->entity->getUID();
my->monsterAllyIndex = monsterclicked;
+ if ( myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
+ {
+ myStats->monsterForceAllegiance = Stat::MONSTER_FORCE_ALLEGIANCE_NONE;
+ }
if ( multiplayer == SERVER )
{
serverUpdateEntitySkill(my, 42); // update monsterAllyIndex for clients.
@@ -2288,11 +2553,13 @@ void sentrybotPickSpotNoise(Entity* my, Stat* myStats)
{
doSpecialNoise = true;
}
+ break;
case 1:
if ( my->ticks % 60 >= 20 && my->ticks % 60 < 40 )
{
doSpecialNoise = true;
}
+ break;
case 2:
if ( my->ticks % 60 >= 40 )
{
@@ -2354,6 +2621,21 @@ void monsterAnimate(Entity* my, Stat* myStats, double dist)
case MIMIC: mimicAnimate(my, myStats, dist); break;
case BAT_SMALL: batAnimate(my, myStats, dist); break;
case BUGBEAR: bugbearMoveBodyparts(my, myStats, dist); break;
+ case DRYAD: monsterDMoveBodyparts(my, myStats, dist); break;
+ case MYCONID: monsterMMoveBodyparts(my, myStats, dist); break;
+ case SALAMANDER: monsterSMoveBodyparts(my, myStats, dist); break;
+ case GREMLIN: monsterGMoveBodyparts(my, myStats, dist); break;
+ case REVENANT_SKULL: revenantSkullAnimate(my, myStats, dist); break;
+ case MINIMIMIC: mimicAnimate(my, myStats, dist); break;
+ case MONSTER_ADORCISED_WEAPON: revenantSkullAnimate(my, myStats, dist); break;
+ case FLAME_ELEMENTAL: revenantSkullAnimate(my, myStats, dist); break;
+ case HOLOGRAM: hologramAnimate(my, myStats, dist); break;
+ case MOTH_SMALL: mothAnimate(my, myStats, dist); break;
+ case EARTH_ELEMENTAL: earthElementalAnimate(my, myStats, dist); break;
+ case DUCK_SMALL: duckAnimate(my, myStats, dist); break;
+ case MONSTER_UNUSED_6: break;
+ case MONSTER_UNUSED_7: break;
+ case MONSTER_UNUSED_8: break;
default:
break;
}
@@ -2371,7 +2653,7 @@ void actMonster(Entity* my)
return;
}
- int x, y, c, i;
+ int x, y;
double dist, dist2;
list_t* path;
node_t* node, *node2;
@@ -2449,6 +2731,21 @@ void actMonster(Entity* my)
case MIMIC: initMimic(my, nullptr); break;
case BAT_SMALL: initBat(my, nullptr); break;
case BUGBEAR: initBugbear(my, nullptr); break;
+ case DRYAD: initMonsterD(my, nullptr); break;
+ case MYCONID: initMonsterM(my, nullptr); break;
+ case SALAMANDER: initMonsterS(my, nullptr); break;
+ case GREMLIN: initMonsterG(my, nullptr); break;
+ case REVENANT_SKULL: initRevenantSkull(my, nullptr); break;
+ case MINIMIMIC: initMiniMimic(my, nullptr); break;
+ case MONSTER_ADORCISED_WEAPON: initAdorcisedWeapon(my, nullptr); break;
+ case FLAME_ELEMENTAL: initFlameElemental(my, nullptr); break;
+ case HOLOGRAM: initHologram(my, nullptr); break;
+ case MOTH_SMALL: initMoth(my, nullptr); break;
+ case EARTH_ELEMENTAL: initEarthElemental(my, nullptr); break;
+ case DUCK_SMALL: initDuck(my, nullptr); break;
+ case MONSTER_UNUSED_6: break;
+ case MONSTER_UNUSED_7: break;
+ case MONSTER_UNUSED_8: break;
default: printlog("Unknown monster, can't init!"); break;
}
}
@@ -2544,6 +2841,21 @@ void actMonster(Entity* my)
case MIMIC: initMimic(my, myStats); break;
case BAT_SMALL: initBat(my, myStats); break;
case BUGBEAR: initBugbear(my, myStats); break;
+ case DRYAD: initMonsterD(my, myStats); break;
+ case MYCONID: initMonsterM(my, myStats); break;
+ case SALAMANDER: initMonsterS(my, myStats); break;
+ case GREMLIN: initMonsterG(my, myStats); break;
+ case REVENANT_SKULL: initRevenantSkull(my, myStats); break;
+ case MONSTER_ADORCISED_WEAPON: initAdorcisedWeapon(my, myStats); break;
+ case FLAME_ELEMENTAL: initFlameElemental(my, myStats); break;
+ case HOLOGRAM: initHologram(my, myStats); break;
+ case MOTH_SMALL: initMoth(my, myStats); break;
+ case EARTH_ELEMENTAL: initEarthElemental(my, myStats); break;
+ case DUCK_SMALL: initDuck(my, myStats); break;
+ case MONSTER_UNUSED_6: break;
+ case MONSTER_UNUSED_7: break;
+ case MONSTER_UNUSED_8: break;
+ case MINIMIMIC: initMiniMimic(my, myStats); break;
default: break; //This should never be reached.
}
}
@@ -2563,6 +2875,10 @@ void actMonster(Entity* my)
}
my->monsterLookTime = local_rng.rand() % 120;
my->monsterMoveTime = local_rng.rand() % 10;
+ if ( my->monsterCanTradeWith(-1) )
+ {
+ my->createPathBoundariesNPC();
+ }
MONSTER_SOUND = NULL;
if ( MONSTER_NUMBER == -1 )
{
@@ -2929,7 +3245,7 @@ void actMonster(Entity* my)
}
if ( myStats->type == LICH_FIRE )
{
- if ( !myStats->EFFECTS[EFF_VAMPIRICAURA] )
+ if ( !myStats->getEffectActive(EFF_VAMPIRICAURA) )
{
if ( (lichAlly && lichAlly->monsterState != MONSTER_STATE_LICH_CASTSPELLS)
|| my->monsterLichAllyStatus == LICH_ALLY_DEAD
@@ -3308,6 +3624,63 @@ void actMonster(Entity* my)
&& my->monsterState != MONSTER_STATE_LICHFIRE_DIE
&& my->monsterState != MONSTER_STATE_LICHICE_DIE )
{
+ Uint8 cursedFlesh = myStats->getEffectActive(EFF_CURSE_FLESH);
+ if ( cursedFlesh & (1 << 7) )
+ {
+ int player = -1;
+ if ( (cursedFlesh & 0x7F) > 0 && (cursedFlesh & 0x7F) < MAXPLAYERS + 1 )
+ {
+ player = (cursedFlesh & 0x7F) - 1;
+ }
+ if ( Entity* monster = spellEffectPolymorph(my, player >= 0 ? players[player]->entity : nullptr, true, 0, SKELETON) )
+ {
+ // old entity was removed
+ if ( Stat* monsterStats = monster->getStats() )
+ {
+ monster->setEffect(EFF_STUNNED, true, 20, false);
+
+ Entity* commanderEntity = player >= 0 ? players[player]->entity : nullptr;
+
+ int duration = getSpellEffectDurationSecondaryFromID(SPELL_REVENANT_CURSE, commanderEntity, nullptr, commanderEntity);
+ monsterStats->setAttribute("revenant_skeleton", std::to_string(duration));
+ monsterStats->MISC_FLAGS[STAT_FLAG_MONSTER_DISABLE_HC_SCALING] = 1;
+ strcpy(monsterStats->name, Language::get(6807));
+ real_t ratio = getSpellDamageFromID(SPELL_REVENANT_CURSE, commanderEntity, nullptr, commanderEntity) / 100.0;
+ real_t maxratio = getSpellDamageSecondaryFromID(SPELL_REVENANT_CURSE, commanderEntity, nullptr, commanderEntity) / 100.0;
+ ratio = std::min(ratio, maxratio);
+
+ monsterStats->MP *= ratio;
+ monsterStats->MAXHP *= ratio;
+ monsterStats->MAXMP *= ratio;
+ monsterStats->STR *= ratio;
+ monsterStats->DEX *= ratio;
+ monsterStats->CON *= ratio;
+ monsterStats->INT *= ratio;
+ monsterStats->PER *= ratio;
+ monsterStats->LVL *= ratio;
+ monsterStats->LVL = std::max(1, monsterStats->LVL);
+ monsterStats->GOLD *= ratio;
+
+ monsterStats->HP = monsterStats->MAXHP * ratio;
+ monsterStats->OLDHP = monsterStats->HP;
+
+ if ( player >= 0 )
+ {
+ messagePlayer(player, MESSAGE_STATUS, Language::get(6808), Language::get(6807));
+ }
+
+ playSoundEntity(monster, 167, 128);
+ createParticleDropRising(monster, 2354, 1.f);
+ serverSpawnMiscParticles(monster, PARTICLE_EFFECT_RISING_DROP, 2354);
+ }
+ return;
+ }
+ else
+ {
+ cursedFlesh &= ~(1 << 7); // revert to revenant skull
+ }
+ }
+
//TODO: Refactor die function.
// drop all equipment
entity = dropItemMonster(myStats->helmet, my, myStats);
@@ -3400,13 +3773,13 @@ void actMonster(Entity* my)
{
nextnode = node->next;
Item* item = (Item*)node->element;
- for ( c = item->count; c > 0; c-- )
+ for ( int c = item->count; c > 0; c-- )
{
bool wasDroppable = item->isDroppable;
if ( myStats->type == SHOPKEEPER )
{
auto& rng = my->entity_rng ? *my->entity_rng : local_rng;
- if ( rng.rand() % 2 )
+ if ( rng.rand() % 2 && !items[item->type].hasAttribute("UNVOIDABLE") )
{
item->isDroppable = false; // sometimes don't drop inventory
}
@@ -3442,6 +3815,7 @@ void actMonster(Entity* my)
}
}
}
+
bool wasQuiver = itemTypeIsQuiver(item->type);
entity = dropItemMonster(item, my, myStats); // returns nullptr on "undroppables"
if ( entity )
@@ -3461,7 +3835,7 @@ void actMonster(Entity* my)
// broadcast my player allies about my death
int playerFollower = MAXPLAYERS;
- for (c = 0; c < MAXPLAYERS; c++)
+ for ( int c = 0; c < MAXPLAYERS; c++)
{
if (players[c] && players[c]->entity)
{
@@ -3505,7 +3879,19 @@ void actMonster(Entity* my)
}
bool skipObituary = false;
- if ( my->monsterAllySummonRank != 0 && myStats->MP > 0 )
+ if ( myStats->getAttribute("skip_obituary") != "" )
+ {
+ skipObituary = true;
+ }
+ else if ( my->monsterAllySummonRank != 0 && myStats->MP > 0 )
+ {
+ skipObituary = true;
+ }
+ else if ( myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "" )
+ {
+ skipObituary = true;
+ }
+ else if ( myStats->type == HOLOGRAM )
{
skipObituary = true;
}
@@ -3551,6 +3937,14 @@ void actMonster(Entity* my)
}
#endif
myStats = my->getStats();
+
+ real_t deathLocationX = my->x;
+ real_t deathLocationY = my->y;
+ if ( cursedFlesh > 0 )
+ {
+ my->flags[PASSABLE] = true;
+ }
+
switch ( myStats->type )
{
case HUMAN:
@@ -3600,9 +3994,9 @@ void actMonster(Entity* my)
MONSTER_ATTACKTIME = 0;
serverUpdateEntitySkill(my, 8);
serverUpdateEntitySkill(my, 9);
- for ( c = 0; c < NUMEFFECTS; ++c )
+ for ( int c = 0; c < NUMEFFECTS; ++c )
{
- myStats->EFFECTS[c] = false;
+ myStats->clearEffect(c);
myStats->EFFECTS_TIMERS[c] = 0;
}
break;
@@ -3622,9 +4016,9 @@ void actMonster(Entity* my)
serverUpdateEntitySkill(my, 8);
serverUpdateEntitySkill(my, 9);
serverUpdateEntitySkill(my, 10);
- for ( c = 0; c < NUMEFFECTS; ++c )
+ for ( int c = 0; c < NUMEFFECTS; ++c )
{
- myStats->EFFECTS[c] = false;
+ myStats->clearEffect(c);
myStats->EFFECTS_TIMERS[c] = 0;
}
break;
@@ -3667,9 +4061,9 @@ void actMonster(Entity* my)
serverUpdateEntitySkill(my, 8);
serverUpdateEntitySkill(my, 9);
serverUpdateEntitySkill(my, 0);
- for ( c = 0; c < NUMEFFECTS; ++c )
+ for ( int c = 0; c < NUMEFFECTS; ++c )
{
- myStats->EFFECTS[c] = false;
+ myStats->clearEffect(c);
myStats->EFFECTS_TIMERS[c] = 0;
}
break;
@@ -3682,9 +4076,9 @@ void actMonster(Entity* my)
serverUpdateEntitySkill(my, 8);
serverUpdateEntitySkill(my, 9);
serverUpdateEntitySkill(my, 0);
- for ( c = 0; c < NUMEFFECTS; ++c )
+ for ( int c = 0; c < NUMEFFECTS; ++c )
{
- myStats->EFFECTS[c] = false;
+ myStats->clearEffect(c);
myStats->EFFECTS_TIMERS[c] = 0;
}
break;
@@ -3707,9 +4101,117 @@ void actMonster(Entity* my)
case BUGBEAR:
bugbearDie(my);
break;
+ case DRYAD:
+ monsterDDie(my);
+ break;
+ case MYCONID:
+ monsterMDie(my);
+ break;
+ case SALAMANDER:
+ monsterSDie(my);
+ break;
+ case GREMLIN:
+ monsterGDie(my);
+ break;
+ case REVENANT_SKULL:
+ revenantSkullDie(my);
+ break;
+ case MINIMIMIC:
+ miniMimicDie(my);
+ break;
+ case MONSTER_ADORCISED_WEAPON:
+ adorcisedWeaponDie(my);
+ break;
+ case FLAME_ELEMENTAL:
+ flameElementalDie(my);
+ break;
+ case HOLOGRAM:
+ hologramDie(my);
+ break;
+ case MOTH_SMALL:
+ mothDie(my);
+ break;
+ case EARTH_ELEMENTAL:
+ earthElementalDie(my);
+ break;
+ case DUCK_SMALL:
+ duckDie(my);
+ break;
+ case MONSTER_UNUSED_6:
+ break;
+ case MONSTER_UNUSED_7:
+ break;
+ case MONSTER_UNUSED_8:
+ break;
default:
break; //This should never be reached.
}
+
+ if ( cursedFlesh > 0 )
+ {
+ real_t x = floor(deathLocationX / 16) * 16 + 8.0;
+ real_t y = floor(deathLocationY / 16) * 16 + 8.0;
+
+
+ Entity* monster = summonMonster(REVENANT_SKULL, x, y, false);
+ if ( monster )
+ {
+ if ( Stat* monsterStats = monster->getStats() )
+ {
+ monster->setEffect(EFF_STUNNED, true, 20, false);
+ int player = -1;
+ if ( cursedFlesh > 0 && cursedFlesh < MAXPLAYERS + 1 )
+ {
+ player = cursedFlesh - 1;
+ }
+
+ Entity* commanderEntity = player >= 0 ? players[player]->entity : nullptr;
+
+ int duration = getSpellEffectDurationSecondaryFromID(SPELL_CURSE_FLESH, commanderEntity, nullptr, commanderEntity);
+ monsterStats->setAttribute("revenant_skull", std::to_string(duration));
+ monsterStats->MISC_FLAGS[STAT_FLAG_MONSTER_DISABLE_HC_SCALING] = 1;
+ int lvl = getSpellDamageFromID(SPELL_CURSE_FLESH, commanderEntity, nullptr, commanderEntity);
+ int maxlvl = getSpellDamageSecondaryFromID(SPELL_CURSE_FLESH, commanderEntity, nullptr, commanderEntity);
+ lvl = std::min(lvl, maxlvl);
+ monsterStats->LVL = lvl;
+ if ( player >= 0 && players[player]->entity && forceFollower(*players[player]->entity, *monster) )
+ {
+ messagePlayer(player, MESSAGE_STATUS, Language::get(6808), getMonsterLocalizedName(monsterStats->type).c_str());
+ monster->monsterAllyIndex = player;
+ if ( multiplayer == SERVER )
+ {
+ serverUpdateEntitySkill(monster, 42); // update monsterAllyIndex for clients.
+ }
+
+ // change the color of the hit entity.
+ Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_RECRUITED, monster, 1);
+ monster->flags[USERFLAG2] = true;
+ serverUpdateEntityFlag(monster, USERFLAG2);
+ if ( monsterChangesColorWhenAlly(monsterStats) )
+ {
+ int bodypart = 0;
+ for ( node_t* node = monster->children.first; node != nullptr; node = node->next )
+ {
+ if ( bodypart >= LIMB_HUMANOID_TORSO )
+ {
+ Entity* tmp = (Entity*)node->element;
+ if ( tmp )
+ {
+ tmp->flags[USERFLAG2] = true;
+ //serverUpdateEntityFlag(tmp, USERFLAG2);
+ }
+ }
+ ++bodypart;
+ }
+ }
+ }
+
+ playSoundEntity(monster, 167, 128);
+ createParticleDropRising(monster, 2354, 1.f);
+ serverSpawnMiscParticles(monster, PARTICLE_EFFECT_RISING_DROP, 2354);
+ }
+ }
+ }
return;
}
@@ -3840,51 +4342,7 @@ void actMonster(Entity* my)
}
// calculate weight
- Sint32 weight = 0;
- if ( myStats->helmet != NULL )
- {
- weight += myStats->helmet->getWeight();
- }
- if ( myStats->breastplate != NULL )
- {
- weight += myStats->breastplate->getWeight();
- }
- if ( myStats->gloves != NULL )
- {
- weight += myStats->gloves->getWeight();
- }
- if ( myStats->shoes != NULL )
- {
- weight += myStats->shoes->getWeight();
- }
- if ( myStats->shield != NULL )
- {
- weight += myStats->shield->getWeight();
- }
- if ( myStats->weapon != NULL )
- {
- weight += myStats->weapon->getWeight();
- }
- if ( myStats->cloak != NULL )
- {
- weight += myStats->cloak->getWeight();
- }
- if ( myStats->amulet != NULL )
- {
- weight += myStats->amulet->getWeight();
- }
- if ( myStats->ring != NULL )
- {
- weight += myStats->ring->getWeight();
- }
- if ( myStats->mask != NULL )
- {
- weight += myStats->mask->getWeight();
- }
- weight += myStats->getGoldWeight();
- weight /= 2; // on monsters weight shouldn't matter so much
- double weightratio = (1000 + my->getSTR() * 100 - weight) / (double)(1000 + my->getSTR() * 100);
- weightratio = fmin(fmax(0, weightratio), 1);
+ real_t weightratio = my->monsterGetWeightRatio();
// determine if I have a ranged weapon or not
hasrangedweapon = my->hasRangedWeapon();
@@ -3943,6 +4401,21 @@ void actMonster(Entity* my)
case DUMMYBOT:
case MIMIC:
case BUGBEAR:
+ case DRYAD:
+ case MYCONID:
+ case SALAMANDER:
+ case GREMLIN:
+ case REVENANT_SKULL:
+ case MONSTER_ADORCISED_WEAPON:
+ case MINIMIMIC:
+ case COCKATRICE:
+ case HOLOGRAM:
+ case MOTH_SMALL:
+ case EARTH_ELEMENTAL:
+ case DUCK_SMALL:
+ case MONSTER_UNUSED_6:
+ case MONSTER_UNUSED_7:
+ case MONSTER_UNUSED_8:
handleinvisible = false;
break;
default:
@@ -3951,7 +4424,7 @@ void actMonster(Entity* my)
if ( handleinvisible )
{
//TODO: Should this use isInvisible()?
- if ( myStats->EFFECTS[EFF_INVISIBLE] )
+ if ( myStats->getEffectActive(EFF_INVISIBLE) )
{
my->flags[INVISIBLE] = true;
for ( node = list_Node(&my->children, 2); node != NULL; node = node->next )
@@ -3989,7 +4462,7 @@ void actMonster(Entity* my)
snprintf(namesays, 63, Language::get(1302), myStats->name);
}
int monsterclicked = -1;
- for (i = 0; i < MAXPLAYERS; i++)
+ for ( int i = 0; i < MAXPLAYERS; i++)
{
if ( selectedEntity[i] == my || client_selected[i] == my )
{
@@ -4013,12 +4486,12 @@ void actMonster(Entity* my)
if ( my->isInertMimic() )
{
// wake up
- if ( myStats->EFFECTS[EFF_MIMIC_LOCKED] )
+ if ( myStats->getEffectActive(EFF_MIMIC_LOCKED) )
{
messagePlayer(monsterclicked, MESSAGE_INTERACTION, Language::get(462));
playSoundEntity(my, 152, 64);
}
- else if ( my->disturbMimic(players[monsterclicked]->entity, false, false) )
+ else if ( my->disturbMimic(players[monsterclicked]->entity, false, true) )
{
messagePlayer(monsterclicked, MESSAGE_INTERACTION, Language::get(6081));
}
@@ -4034,7 +4507,7 @@ void actMonster(Entity* my)
}
else
{
- if (my->monsterTarget == players[monsterclicked]->entity->getUID() && my->monsterState != 4)
+ if (my->monsterTarget == players[monsterclicked]->entity->getUID() && my->monsterState != MONSTER_STATE_TALK)
{
// angry at the player, "En Guarde!"
for ( int c = 0; c < MAXPLAYERS; ++c )
@@ -4116,7 +4589,7 @@ void actMonster(Entity* my)
else
{
// handle followers/trading
- if ( myStats->type != SHOPKEEPER )
+ if ( myStats->type != SHOPKEEPER && !my->monsterCanTradeWith(-1) )
{
if ( myStats->MISC_FLAGS[STAT_FLAG_NPC] == 0 )
{
@@ -4276,7 +4749,7 @@ void actMonster(Entity* my)
// shopkeepers start trading
startTradingServer(my, monsterclicked);
if ( stats[monsterclicked] && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->stat_appearance == 0
- && stats[monsterclicked]->playerRace == RACE_AUTOMATON )
+ && stats[monsterclicked]->playerRace == RACE_AUTOMATON && myStats->type == SHOPKEEPER )
{
achievementObserver.updatePlayerAchievement(monsterclicked, AchievementObserver::Achievement::BARONY_ACH_REAL_BOY,
AchievementObserver::AchievementEvent::REAL_BOY_SHOP);
@@ -4344,7 +4817,15 @@ void actMonster(Entity* my)
}
else
{
- my->modHP(-9999);
+ my->setHP(0);
+ }
+ }
+
+ if ( myStats->getEffectActive(EFF_SEEK_CREATURE) )
+ {
+ if ( !(my->monsterState == MONSTER_STATE_HUNT || my->monsterState == MONSTER_STATE_PATH) )
+ {
+ my->setEffect(EFF_SEEK_CREATURE, false, 0, false);
}
}
@@ -4407,7 +4888,7 @@ void actMonster(Entity* my)
}
// being bumped by someone friendly
- std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
+ std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it )
{
list_t* currentList = *it;
@@ -4418,7 +4899,19 @@ void actMonster(Entity* my)
{
continue;
}
- if ( entity->behavior != &actMonster && entity->behavior != &actPlayer && entity->behavior != &actDoorFrame )
+
+ if ( entity->behavior == &actChest && myStats->type == EARTH_ELEMENTAL )
+ {
+ // check for spawning on chest
+ }
+ else if ( entity->behavior != &actMonster && entity->behavior != &actPlayer && entity->behavior != &actDoorFrame
+ && !(entity->behavior == &actColliderDecoration
+ && ((entity->flags[PASSABLE] && entity->colliderHasCollision != 0)
+ || (entity->isColliderPathableMonster(myStats->type))) ))
+ {
+ continue;
+ }
+ if ( entity->sprite == 1822 ) /* fire sprite */
{
continue;
}
@@ -4435,23 +4928,49 @@ void actMonster(Entity* my)
my->sizey = sizey;
if ( entityInside && entity->getRace() != GYROBOT )
{
- if ( entity->behavior != &actDoorFrame )
+ if ( entity->behavior == &actColliderDecoration )
{
double tangent = atan2(my->y - entity->y, my->x - entity->x);
MONSTER_VELX = cos(tangent) * .1;
MONSTER_VELY = sin(tangent) * .1;
}
- else if ( entity->behavior == &actDoorFrame &&
- entity->flags[INVISIBLE] )
+ else if ( entity->behavior != &actDoorFrame )
{
- int mapx = (int)floor(entity->x / 16);
- int mapy = (int)floor(entity->y / 16);
- if ( entity->yaw >= -0.1 && entity->yaw <= 0.1 )
+ bool skip = false;
+ if ( myStats->type == MONSTER_ADORCISED_WEAPON || entity->getRace() == MONSTER_ADORCISED_WEAPON )
{
- // east/west doorway
- if ( my->y < floor(entity->y / 16) * 16 + 8 )
+ if ( myStats->getEffectActive(EFF_KNOCKBACK) )
{
- bool slide = true;
+ skip = true;
+ }
+ else if ( entity->getStats() && entity->getStats()->getEffectActive(EFF_KNOCKBACK) )
+ {
+ skip = true;
+ }
+ }
+ if ( (myStats->type == EARTH_ELEMENTAL && (my->flags[PASSABLE] || myStats->getEffectActive(EFF_KNOCKBACK)) )
+ || (entity->flags[PASSABLE] && entity->getRace() == EARTH_ELEMENTAL) )
+ {
+ skip = true;
+ }
+ if ( !skip )
+ {
+ double tangent = atan2(my->y - entity->y, my->x - entity->x);
+ MONSTER_VELX = cos(tangent) * .1;
+ MONSTER_VELY = sin(tangent) * .1;
+ }
+ }
+ else if ( entity->behavior == &actDoorFrame &&
+ entity->flags[INVISIBLE] )
+ {
+ int mapx = (int)floor(entity->x / 16);
+ int mapy = (int)floor(entity->y / 16);
+ if ( entity->yaw >= -0.1 && entity->yaw <= 0.1 )
+ {
+ // east/west doorway
+ if ( my->y < floor(entity->y / 16) * 16 + 8 )
+ {
+ bool slide = true;
if ( mapy - 1 > 0 )
{
int index = (mapy - 1) * MAPLAYERS + (mapx) * MAPLAYERS * map.height;
@@ -4600,7 +5119,7 @@ void actMonster(Entity* my)
--my->monsterAllySpecialCooldown;
}
- if ( myStats->type == AUTOMATON )
+ if ( myStats->type == AUTOMATON && !my->monsterCanTradeWith(-1) )
{
my->automatonRecycleItem();
}
@@ -4613,23 +5132,33 @@ void actMonster(Entity* my)
}
}
- if ( myStats->EFFECTS[EFF_PACIFY] || myStats->EFFECTS[EFF_FEAR] )
+ if ( myStats->getEffectActive(EFF_PACIFY) || myStats->getEffectActive(EFF_FEAR) )
{
my->monsterHitTime = HITRATE / 2; // stop this incrementing to HITRATE but leave monster ready to strike shortly after.
}
if ( my->monsterDefend != MONSTER_DEFEND_NONE )
{
- if ( my->monsterState != MONSTER_STATE_ATTACK
- || myStats->shield == nullptr )
+ if ( myStats->type == EARTH_ELEMENTAL )
{
- myStats->defending = false;
- my->monsterDefend = 0;
- serverUpdateEntitySkill(my, 47);
+ if ( my->monsterAttack == 0 )
+ {
+ myStats->defending = true;
+ }
}
- else if ( my->monsterAttack == 0 )
+ else
{
- myStats->defending = true;
+ if ( my->monsterState != MONSTER_STATE_ATTACK
+ || myStats->shield == nullptr )
+ {
+ myStats->defending = false;
+ my->monsterDefend = 0;
+ serverUpdateEntitySkill(my, 47);
+ }
+ else if ( my->monsterAttack == 0 )
+ {
+ myStats->defending = true;
+ }
}
}
else
@@ -4637,6 +5166,25 @@ void actMonster(Entity* my)
myStats->defending = false;
}
+ if ( myStats->getEffectActive(EFF_SPIN) )
+ {
+ my->monsterLookTime = 1;
+ my->monsterLookDir += PI / 16;
+ }
+ if ( myStats->getEffectActive(EFF_DISORIENTED) == 2 )
+ {
+ if ( myStats->EFFECTS_TIMERS[EFF_DISORIENTED] == 1 )
+ {
+ my->monsterLookTime = 1;
+ my->monsterLookDir += PI;
+ }
+ }
+
+ if ( my->monsterState == MONSTER_STATE_WAIT )
+ {
+ myStats->monsterRangedAccuracy.accuracy = 0.0;
+ }
+
/*if ( myStats->defending )
{
messagePlayer(0, "defending!");
@@ -4673,12 +5221,39 @@ void actMonster(Entity* my)
// my->getUID(), ticks, state_string.c_str(), my->monsterAttack, my->monsterHitTime, MONSTER_ATTACKTIME, devilstate, devilacted, my->monsterSpecialTimer); //Debug message.
//}
+ int linetraceTargetEnemyFlags = LINETRACE_ATK_CHECK_FRIENDLYFIRE;
+
//Begin state machine
if ( my->monsterState == MONSTER_STATE_WAIT ) //Begin wait state
{
//my->monsterTarget = -1; //TODO: Setting it to -1 = Bug? -1 may not work properly for cases such as: if ( !my->monsterTarget )
- my->monsterReleaseAttackTarget();
- if ( !myStats->EFFECTS[EFF_KNOCKBACK] )
+ if ( myStats->type == DUCK_SMALL )
+ {
+ Entity* target = uidToEntity(my->monsterTarget);
+ if ( target && entityDist(target, my) < 64.0 )
+ {
+ // don't release, try path again
+ if ( myReflex )
+ {
+ my->monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH);
+ my->lookAtEntity(*target);
+ if ( previousMonsterState != my->monsterState )
+ {
+ serverUpdateEntitySkill(my, 0);
+ }
+ return;
+ }
+ }
+ else
+ {
+ my->monsterReleaseAttackTarget();
+ }
+ }
+ else
+ {
+ my->monsterReleaseAttackTarget();
+ }
+ if ( !myStats->getEffectActive(EFF_KNOCKBACK) )
{
MONSTER_VELX = 0;
MONSTER_VELY = 0;
@@ -4686,19 +5261,35 @@ void actMonster(Entity* my)
else
{
// do knockback movement
- my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir, weightratio);
+ my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir + PI, weightratio);
if ( abs(MONSTER_VELX) > 0.01 || abs(MONSTER_VELY) > 0.01 )
{
dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
my->handleKnockbackDamage(*myStats, hit.entity);
}
}
- if ( myReflex && !myStats->EFFECTS[EFF_DISORIENTED] && !isIllusionTaunt )
+
+ Entity* scaryEntity = nullptr;
+ if ( myStats->getEffectActive(EFF_FEAR) && my->monsterFearfulOfUid != 0
+ && !myStats->getEffectActive(EFF_DISORIENTED)
+ && !myStats->getEffectActive(EFF_KNOCKBACK) && !isIllusionTaunt )
+ {
+ scaryEntity = uidToEntity(my->monsterFearfulOfUid);
+ if ( scaryEntity && scaryEntity->behavior == &actRadiusMagic )
+ {
+ real_t tangent = atan2(scaryEntity->y - my->y, scaryEntity->x - my->x);
+ MONSTER_VELX = 0.25 * cos(tangent + PI);
+ MONSTER_VELY = 0.25 * sin(tangent + PI);
+ my->lookAtEntity(*scaryEntity);
+ clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
+ }
+ }
+
+ if ( myReflex && !myStats->getEffectActive(EFF_DISORIENTED) && !isIllusionTaunt )
{
- if ( myStats->EFFECTS[EFF_FEAR] && my->monsterFearfulOfUid != 0 )
+ if ( myStats->getEffectActive(EFF_FEAR) && my->monsterFearfulOfUid != 0 )
{
- Entity* scaryEntity = uidToEntity(my->monsterFearfulOfUid);
- if ( scaryEntity )
+ if ( scaryEntity && scaryEntity->behavior != &actRadiusMagic )
{
my->monsterAcquireAttackTarget(*scaryEntity, MONSTER_STATE_PATH);
my->lookAtEntity(*scaryEntity);
@@ -4735,7 +5326,8 @@ void actMonster(Entity* my)
// skip if light level is too low and distance is too high
int light = entity->entityLightAfterReductions(*hitstats, my);
- if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
+ if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE
+ || myStats->type == LICH_ICE || myStats->type == SHADOW || myStats->type == MONSTER_ADORCISED_WEAPON )
{
//See invisible.
light = 1000;
@@ -4747,10 +5339,14 @@ void actMonster(Entity* my)
double targetdist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
real_t monsterVisionRange = sightranges[myStats->type];
- if ( hitstats->type == DUMMYBOT )
+ if ( hitstats->type == DUMMYBOT || hitstats->type == HOLOGRAM )
{
monsterVisionRange = std::max(monsterVisionRange, 96.0);
}
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.ensemblePlaying >= 0 )
+ {
+ monsterVisionRange = std::max(monsterVisionRange, 5 * 16.0);
+ }
if ( targetdist > monsterVisionRange )
{
@@ -4760,7 +5356,7 @@ void actMonster(Entity* my)
{
if ( !(myStats->leader_uid == entity->getUID())
&& !(hitstats->leader_uid == my->getUID())
- && !(my->monsterAllyGetPlayerLeader() && entity->behavior == &actPlayer) )
+ && !((my->monsterAllyGetPlayerLeader() || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0) && entity->behavior == &actPlayer) )
{
if ( !levitating )
{
@@ -4771,15 +5367,33 @@ void actMonster(Entity* my)
lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
}
if ( hit.entity == entity )
+ {
+ if ( entity->behavior == &actPlayer && entity->isInvisible() )
+ {
+ real_t dist = sqrt(pow(entity->vel_x, 2) + pow(entity->vel_y, 2));
+ if ( dist > 0.05 )
+ {
+ players[entity->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_INVISIBILITY, 1.0, 1.0, my);
+ }
+ }
if ( local_rng.rand() % 100 == 0 )
{
- entity->increaseSkill(PRO_STEALTH);
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.allowedRaiseStealthAgainstEntity(*my) )
+ {
+ entity->increaseSkill(PRO_STEALTH);
+ players[entity->skill[2]]->mechanics.enemyRaisedStealthAgainst[my->getUID()]++;
+ }
}
+ }
}
continue;
}
bool visiontest = false;
- if ( hitstats->type == DUMMYBOT || myStats->type == SENTRYBOT || myStats->type == SPELLBOT
+ if ( (myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "") )
+ {
+ visiontest = true;
+ }
+ else if ( hitstats->type == DUMMYBOT || hitstats->type == HOLOGRAM || myStats->type == SENTRYBOT || myStats->type == SPELLBOT
|| (ringConflictHolder && ringConflictHolder == entity) )
{
if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
@@ -4811,11 +5425,11 @@ void actMonster(Entity* my)
}
else
{
- lineTrace(my, my->x, my->y, tangent, monsterVisionRange, LINETRACE_IGNORE_ENTITIES, false);
+ lineTrace(my, my->x, my->y, tangent, monsterVisionRange, LINETRACE_IGNORE_ENTITIES | linetraceTargetEnemyFlags, false);
}
if ( !hit.entity )
{
- lineTrace(my, my->x, my->y, tangent, TOUCHRANGE, 0, false);
+ lineTrace(my, my->x, my->y, tangent, TOUCHRANGE, linetraceTargetEnemyFlags, false);
}
if ( hit.entity == entity )
{
@@ -4878,7 +5492,7 @@ void actMonster(Entity* my)
if ( !entity->checkFriend(attackTarget) )
{
tangent = atan2( entity->y - my->y, entity->x - my->x );
- lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
+ lineTrace(my, my->x, my->y, tangent, monsterVisionRange, linetraceTargetEnemyFlags, false);
if ( hit.entity == entity )
{
entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH);
@@ -4973,9 +5587,9 @@ void actMonster(Entity* my)
// follow the leader :)
if ( myStats->leader_uid != 0
&& my->monsterAllyState == ALLY_STATE_DEFAULT
- && !myStats->EFFECTS[EFF_FEAR]
- && !myStats->EFFECTS[EFF_DISORIENTED]
- && !myStats->EFFECTS[EFF_ROOTED]
+ && !myStats->getEffectActive(EFF_FEAR)
+ && !myStats->getEffectActive(EFF_DISORIENTED)
+ && !myStats->getEffectActive(EFF_ROOTED)
&& !isIllusionTaunt
&& !monsterIsImmobileTurret(my, myStats)
&& my->getUID() % TICKS_PER_SECOND == ticks % monsterAllyFormations.getFollowerChaseLeaderInterval(*my, *myStats)
@@ -4984,10 +5598,25 @@ void actMonster(Entity* my)
Entity* leader = uidToEntity(myStats->leader_uid);
if ( leader )
{
+ Uint32 leaderUid = leader->getUID();
+
+ if ( leader && leader->behavior == &actPlayer && players[leader->skill[2]]->ghost.isActive() && players[leader->skill[2]]->ghost.isSpiritGhost() )
+ {
+ leader = players[leader->skill[2]]->ghost.my;
+ }
+
real_t followx = leader->x;
real_t followy = leader->y;
- if ( myStats->type == GYROBOT )
+
+ int followDist = WAIT_FOLLOWDIST;
+ if ( myStats->type == GYROBOT || myStats->type == DUCK_SMALL
+ || (myStats->type == MOTH_SMALL && myStats->getAttribute("fire_sprite") != "") )
{
+ if ( myStats->type == MOTH_SMALL || myStats->type == DUCK_SMALL )
+ {
+ followDist /= 2;
+ }
+
// follow ahead of the leader.
real_t startx = leader->x;
real_t starty = leader->y;
@@ -5028,7 +5657,7 @@ void actMonster(Entity* my)
// hit a wall.
break;
}
- if ( sqrt(pow(leader->x - previousx, 2) + pow(leader->y - previousy, 2)) > WAIT_FOLLOWDIST )
+ if ( sqrt(pow(leader->x - previousx, 2) + pow(leader->y - previousy, 2)) > followDist )
{
break;
}
@@ -5039,7 +5668,7 @@ void actMonster(Entity* my)
}
double dist = sqrt(pow(my->x - followx, 2) + pow(my->y - followy, 2));
- if ( dist > WAIT_FOLLOWDIST )
+ if ( dist > followDist )
{
bool doFollow = true;
if ( my->monsterTarget != 0 )
@@ -5051,7 +5680,8 @@ void actMonster(Entity* my)
{
my->monsterReleaseAttackTarget();
std::pair followPos;
- if ( myStats->type == GYROBOT )
+ if ( myStats->type == GYROBOT || myStats->type == DUCK_SMALL
+ || (myStats->type == MOTH_SMALL && myStats->getAttribute("fire_sprite") != "") )
{
if ( my->monsterSetPathToLocation(static_cast(followx) / 16, static_cast(followy) / 16, 0,
GeneratePathTypes::GENERATE_PATH_ALLY_FOLLOW) )
@@ -5060,7 +5690,7 @@ void actMonster(Entity* my)
my->monsterState = MONSTER_STATE_HUNT; // hunt state
}
}
- else if ( monsterAllyFormations.getFollowLocation(my->getUID(), leader->getUID(), followPos) )
+ else if ( monsterAllyFormations.getFollowLocation(my->getUID(), leaderUid, followPos) )
{
if ( my->monsterSetPathToLocation(followPos.first, followPos.second, 1,
GeneratePathTypes::GENERATE_PATH_ALLY_FOLLOW) )
@@ -5079,10 +5709,14 @@ void actMonster(Entity* my)
return;
}
}
- else if ( myStats->type != GYROBOT )
+ else if ( !(myStats->type == GYROBOT || myStats->type == DUCK_SMALL
+ || (myStats->type == MOTH_SMALL && myStats->getAttribute("fire_sprite") != "")) )
{
tangent = atan2( leader->y - my->y, leader->x - my->x );
+ bool oldPassable = leader->flags[PASSABLE]; // hack for ghosts
+ leader->flags[PASSABLE] = false;
lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, true);
+ leader->flags[PASSABLE] = oldPassable;
if ( hit.entity != leader )
{
bool doFollow = true;
@@ -5094,7 +5728,7 @@ void actMonster(Entity* my)
{
my->monsterReleaseAttackTarget();
std::pair followPos;
- if ( monsterAllyFormations.getFollowLocation(my->getUID(), leader->getUID(), followPos) )
+ if ( monsterAllyFormations.getFollowLocation(my->getUID(), leaderUid, followPos) )
{
if ( my->monsterSetPathToLocation(followPos.first, followPos.second, 1,
GeneratePathTypes::GENERATE_PATH_ALLY_FOLLOW) )
@@ -5128,7 +5762,7 @@ void actMonster(Entity* my)
my->monsterLookTime = 0;
my->monsterMoveTime--;
if ( myStats->type != GHOUL && (myStats->type != SPIDER || (myStats->type == SPIDER && my->monsterAllyGetPlayerLeader()))
- && !myStats->EFFECTS[EFF_FEAR] && !isIllusionTaunt )
+ && !myStats->getEffectActive(EFF_FEAR) && !isIllusionTaunt )
{
if ( monsterIsImmobileTurret(my, myStats) )
{
@@ -5150,7 +5784,12 @@ void actMonster(Entity* my)
my->monsterLookDir = (local_rng.rand() % 360) * PI / 180;
}
}
- if ( !myStats->EFFECTS[EFF_FEAR] && my->monsterTarget == 0 && my->monsterState == MONSTER_STATE_WAIT && my->monsterAllyGetPlayerLeader() )
+ if ( !myStats->getEffectActive(EFF_FEAR)
+ && my->monsterTarget == 0
+ && my->monsterState == MONSTER_STATE_WAIT
+ && (my->monsterAllyGetPlayerLeader()
+ || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0
+ || (myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "")) )
{
// allies should try intelligently scan for enemies in radius.
if ( monsterIsImmobileTurret(my, myStats) && myStats->LVL < 5 )
@@ -5160,6 +5799,10 @@ void actMonster(Entity* my)
else
{
real_t dist = sightranges[myStats->type];
+ if ( myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "" )
+ {
+ dist = 40;
+ }
for ( node = map.creatures->first; node != nullptr; node = node->next )
{
Entity* target = (Entity*)node->element;
@@ -5170,7 +5813,7 @@ void actMonster(Entity* my)
if ( dist < sightranges[myStats->type] && dist <= oldDist )
{
double tangent = atan2(target->y - my->y, target->x - my->x);
- lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
+ lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], linetraceTargetEnemyFlags, false);
if ( hit.entity == target )
{
//my->monsterLookTime = 1;
@@ -5252,9 +5895,9 @@ void actMonster(Entity* my)
}
if ( my->monsterMoveTime == 0
&& (uidToEntity(myStats->leader_uid) == NULL || my->monsterAllyState == ALLY_STATE_DEFEND)
- && !myStats->EFFECTS[EFF_FEAR]
- && !myStats->EFFECTS[EFF_DISORIENTED]
- && !myStats->EFFECTS[EFF_ROOTED]
+ && !myStats->getEffectActive(EFF_FEAR)
+ && !myStats->getEffectActive(EFF_DISORIENTED)
+ && !myStats->getEffectActive(EFF_ROOTED)
&& !isIllusionTaunt
&& !(monsterIsImmobileTurret(my, myStats))
&& myStats->type != DEVIL )
@@ -5281,14 +5924,19 @@ void actMonster(Entity* my)
searchLimitX = 7;
searchLimitY = 7;
}
+ else if ( !strcmp(map.name, "Citadel") )
+ {
+ searchLimitX = 7;
+ searchLimitY = 7;
+ }
int lowerX = std::max(0, centerX - searchLimitX); // assigned upper/lower x coords from entity start position.
int upperX = std::min(centerX + searchLimitX, map.width);
int lowerY = std::max(0, centerY - searchLimitY); // assigned upper/lower y coords from entity start position.
int upperY = std::min(centerY + searchLimitY, map.height);
//messagePlayer(0, "my x: %d, my y: %d, rangex: (%d-%d), rangey: (%d-%d)", centerX, centerY, lowerX, upperX, lowerY, upperY);
-
- if ( myStats->type != SHOPKEEPER && (myStats->MISC_FLAGS[STAT_FLAG_NPC] == 0 && my->monsterAllyState == ALLY_STATE_DEFAULT) )
+ if ( myStats->type != SHOPKEEPER && !my->monsterCanTradeWith(-1)
+ && (myStats->MISC_FLAGS[STAT_FLAG_NPC] == 0 && my->monsterAllyState == ALLY_STATE_DEFAULT) )
{
for ( x = lowerX; x < upperX; x++ )
{
@@ -5415,7 +6063,7 @@ void actMonster(Entity* my)
if ( myStats->type == SHOPKEEPER && strncmp(map.name, "Mages Guild", 11) )
{
// shopkeepers hold a grudge against players
- for ( c = 0; c < MAXPLAYERS; ++c )
+ for ( int c = 0; c < MAXPLAYERS; ++c )
{
if ( players[c] && players[c]->entity )
{
@@ -5432,7 +6080,8 @@ void actMonster(Entity* my)
{
// skip if light level is too low and distance is too high
int light = entity->entityLightAfterReductions(*hitstats, my);
- if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
+ if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE
+ || myStats->type == SHADOW || myStats->type == MONSTER_ADORCISED_WEAPON )
{
//See invisible.
light = 1000;
@@ -5444,11 +6093,15 @@ void actMonster(Entity* my)
double targetdist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
real_t monsterVisionRange = sightranges[myStats->type];
- if ( hitstats && hitstats->type == DUMMYBOT )
+ if ( hitstats && (hitstats->type == DUMMYBOT || hitstats->type == HOLOGRAM) )
{
monsterVisionRange = std::max(monsterVisionRange, 96.0);
}
- if ( myStats->EFFECTS[EFF_FEAR] )
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.ensemblePlaying >= 0 )
+ {
+ monsterVisionRange = std::max(monsterVisionRange, 5 * 16.0);
+ }
+ if ( myStats->getEffectActive(EFF_FEAR) )
{
targetdist = 0.0; // so we can always see our scary target.
}
@@ -5456,7 +6109,7 @@ void actMonster(Entity* my)
if ( targetdist > monsterVisionRange )
{
// if target has left my sight, decide whether or not to path or retreat (stay put).
- if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
+ if ( my->shouldRetreat(*myStats) && !myStats->getEffectActive(EFF_FEAR) )
{
my->monsterMoveTime = 0;
my->monsterState = MONSTER_STATE_WAIT; // wait state
@@ -5472,7 +6125,7 @@ void actMonster(Entity* my)
{
if ( !(myStats->leader_uid == entity->getUID())
&& !(hitstats->leader_uid == my->getUID())
- && !(my->monsterAllyGetPlayerLeader() && entity->behavior == &actPlayer) )
+ && !((my->monsterAllyGetPlayerLeader() || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0) && entity->behavior == &actPlayer) )
{
tangent = atan2( my->monsterTargetY - my->y, my->monsterTargetX - my->x );
if ( !levitating )
@@ -5485,15 +6138,27 @@ void actMonster(Entity* my)
}
if ( hit.entity == entity )
{
+ if ( entity->behavior == &actPlayer && entity->isInvisible() )
+ {
+ real_t dist = sqrt(pow(entity->vel_x, 2) + pow(entity->vel_y, 2));
+ if ( dist > 0.05 )
+ {
+ players[entity->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_INVISIBILITY, 1.0, 1.0, my);
+ }
+ }
if ( local_rng.rand() % 100 == 0 )
{
- entity->increaseSkill(PRO_STEALTH);
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.allowedRaiseStealthAgainstEntity(*my) )
+ {
+ entity->increaseSkill(PRO_STEALTH);
+ players[entity->skill[2]]->mechanics.enemyRaisedStealthAgainst[my->getUID()]++;
+ }
}
}
}
// if target is within sight range but light level is too low and out of melee range.
// decide whether or not to path or retreat (stay put).
- if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
+ if ( my->shouldRetreat(*myStats) && !myStats->getEffectActive(EFF_FEAR) )
{
my->monsterMoveTime = 0;
my->monsterState = MONSTER_STATE_WAIT; // wait state
@@ -5505,7 +6170,7 @@ void actMonster(Entity* my)
}
else
{
- if ( myStats->EFFECTS[EFF_FEAR] )
+ if ( myStats->getEffectActive(EFF_FEAR) )
{
myReflex = false; // don't determine if you lost sight of the scary monster.
}
@@ -5537,7 +6202,7 @@ void actMonster(Entity* my)
{
if ( hasrangedweapon )
{
- dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
+ dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, linetraceTargetEnemyFlags, false);
if ( hit.entity == entity )
{
Entity* ohitentity = hit.entity;
@@ -5548,7 +6213,7 @@ void actMonster(Entity* my)
real_t my1 = my->y + 2.5 * sin(tangent + PI / 2);
real_t mx1 = my->x + 2.5 * cos(tangent + PI / 2);
real_t tangent1 = atan2(my->monsterTargetY - my1, my->monsterTargetX - mx1);
- dist = lineTraceTarget(my, mx1, my1, tangent1, monsterVisionRange, 0, false, entity);
+ dist = lineTraceTarget(my, mx1, my1, tangent1, monsterVisionRange, linetraceTargetEnemyFlags, false, entity);
hitentity1 = hit.entity;
}
Entity* hitentity2 = nullptr;
@@ -5556,7 +6221,7 @@ void actMonster(Entity* my)
real_t my2 = my->y - 2.5 * sin(tangent + PI / 2);
real_t mx2 = my->x - 2.5 * cos(tangent + PI / 2);
real_t tangent2 = atan2(my->monsterTargetY - my2, my->monsterTargetX - mx2);
- dist = lineTraceTarget(my, mx2, my2, tangent2, monsterVisionRange, 0, false, entity);
+ dist = lineTraceTarget(my, mx2, my2, tangent2, monsterVisionRange, linetraceTargetEnemyFlags, false, entity);
hitentity2 = hit.entity;
}
if ( hitentity1 && hitentity2 )
@@ -5572,12 +6237,12 @@ void actMonster(Entity* my)
}
else
{
- dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, true);
+ dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, linetraceTargetEnemyFlags, true);
}
}
else
{
- dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, 0, false);
+ dist = lineTrace(my, my->x, my->y, tangent, monsterVisionRange, linetraceTargetEnemyFlags, false);
}
}
else
@@ -5589,7 +6254,7 @@ void actMonster(Entity* my)
{
// if I currently lost sight of my target in a straight line in front of me
// decide whether or not to path or retreat (stay put).
- if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
+ if ( my->shouldRetreat(*myStats) && !myStats->getEffectActive(EFF_FEAR) )
{
my->monsterMoveTime = 0;
my->monsterState = MONSTER_STATE_WAIT; // wait state
@@ -5635,7 +6300,7 @@ void actMonster(Entity* my)
}
Entity* tempHitEntity = hit.entity;
- if ( lineTrace(my, my->x, my->y, tangent2, TOUCHRANGE, 1, false) < TOUCHRANGE )
+ if ( lineTrace(my, my->x, my->y, tangent2, TOUCHRANGE, LINETRACE_IGNORE_ENTITIES | linetraceTargetEnemyFlags, false) < TOUCHRANGE )
{
MONSTER_FLIPPEDANGLE = (MONSTER_FLIPPEDANGLE < 5) * 10;
goAgain++;
@@ -5659,7 +6324,7 @@ void actMonster(Entity* my)
int myDex = my->monsterGetDexterityForMovement();
real_t maxVelX = cos(tangent2) * .045 * (myDex + 10) * weightratio;
real_t maxVelY = sin(tangent2) * .045 * (myDex + 10) * weightratio;
- if ( !myStats->EFFECTS[EFF_KNOCKBACK] )
+ if ( !myStats->getEffectActive(EFF_KNOCKBACK) )
{
MONSTER_VELX = maxVelX;
MONSTER_VELY = maxVelY;
@@ -5672,7 +6337,7 @@ void actMonster(Entity* my)
if ( effectiveDistance < rangedWeaponDistance )
{
// shorter range xbows etc should advance at a little less than the extremity.
- rangedWeaponDistance = effectiveDistance - 10;
+ rangedWeaponDistance = std::max(STRIKERANGE, effectiveDistance - 10);
}
if ( myStats->weapon && myStats->weapon->type == SPELLBOOK_DASH )
{
@@ -5685,14 +6350,37 @@ void actMonster(Entity* my)
{
chaseRange = 20;
}
+ if ( myStats->type == DUCK_SMALL )
+ {
+ chaseRange = 32;
+ }
+ if ( myStats->weapon && (myStats->weapon->type == TOOL_WHIP || myStats->weapon->type == STEEL_FLAIL) )
+ {
+ chaseRange = TOUCHRANGE;
+ }
- if ( monsterIsImmobileTurret(my, myStats) || myStats->EFFECTS[EFF_ROOTED] )
+ if ( monsterIsImmobileTurret(my, myStats) || myStats->getEffectActive(EFF_ROOTED) )
{
// this is just so that the monster rotates. it doesn't actually move
- MONSTER_VELX = maxVelX * 0.01;
- MONSTER_VELY = maxVelY * 0.01;
+ if ( !myStats->getEffectActive(EFF_KNOCKBACK) )
+ {
+ MONSTER_VELX = maxVelX * 0.01;
+ MONSTER_VELY = maxVelY * 0.01;
+ }
+ else
+ {
+ my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir + PI, weightratio);
+ bool flag = my->flags[PASSABLE];
+ if ( (myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "") )
+ {
+ my->flags[PASSABLE] = true; // hack to zoom through stuff
+ }
+ dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
+ my->handleKnockbackDamage(*myStats, hit.entity);
+ my->flags[PASSABLE] = flag;
+ }
}
- else if ( !myStats->EFFECTS[EFF_KNOCKBACK] &&
+ else if ( !myStats->getEffectActive(EFF_KNOCKBACK) &&
((dist > chaseRange && !hasrangedweapon && !my->shouldRetreat(*myStats))
|| (hasrangedweapon && dist > rangedWeaponDistance)) )
{
@@ -5732,7 +6420,7 @@ void actMonster(Entity* my)
}
if ( hit.entity != NULL )
{
- if ( hit.entity->behavior == &actDoor )
+ if ( hit.entity->behavior == &actDoor || hit.entity->behavior == &actIronDoor )
{
// opens the door if unlocked and monster can do it
if ( !hit.entity->doorLocked && my->getINT() > -2 )
@@ -5748,6 +6436,42 @@ void actMonster(Entity* my)
playSoundEntity(hit.entity, 21, 96);
}
}
+ else if ( hit.entity->behavior == &actIronDoor )
+ {
+ if ( myStats->type == MINOTAUR )
+ {
+ hit.entity->doorHealth = 0; // minotaurs smash doors instantly
+ my->monsterAttack = my->getAttackPose(); // random attack motion
+ my->monsterAttackTime = 0;
+ my->monsterHitTime = 0;
+
+ playSoundEntity(hit.entity, 28, 64);
+ if ( hit.entity->doorHealth <= 0 )
+ {
+ // set direction of splinters
+ if ( !hit.entity->doorDir )
+ {
+ hit.entity->doorSmacked = (my->x > hit.entity->x);
+ }
+ else
+ {
+ hit.entity->doorSmacked = (my->y < hit.entity->y);
+ }
+ }
+ }
+ else
+ {
+ if ( my->shouldRetreat(*myStats) && !myStats->getEffectActive(EFF_FEAR) )
+ {
+ my->monsterMoveTime = 0;
+ my->monsterState = MONSTER_STATE_WAIT; // wait state
+ }
+ else
+ {
+ my->monsterState = MONSTER_STATE_PATH; // path state
+ }
+ }
+ }
else
{
// can't open door, so break it down
@@ -5767,8 +6491,8 @@ void actMonster(Entity* my)
{
hit.entity->doorHealth = 0; // minotaurs smash doors instantly
}
- updateEnemyBar(my, hit.entity, Language::get(674), hit.entity->skill[4], hit.entity->skill[9],
- false, DamageGib::DMG_DEFAULT);
+ updateEnemyBar(my, hit.entity, hit.entity->behavior == &actIronDoor ? Language::get(6414) : Language::get(674),
+ hit.entity->doorHealth, hit.entity->doorMaxHealth, false, DamageGib::DMG_DEFAULT);
playSoundEntity(hit.entity, 28, 64);
if ( hit.entity->doorHealth <= 0 )
{
@@ -5853,7 +6577,7 @@ void actMonster(Entity* my)
}
else
{
- if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
+ if ( my->shouldRetreat(*myStats) && !myStats->getEffectActive(EFF_FEAR) )
{
my->monsterMoveTime = 0;
my->monsterState = MONSTER_STATE_WAIT; // wait state
@@ -5866,7 +6590,7 @@ void actMonster(Entity* my)
}
else
{
- if ( my->shouldRetreat(*myStats) && !myStats->EFFECTS[EFF_FEAR] )
+ if ( my->shouldRetreat(*myStats) && !myStats->getEffectActive(EFF_FEAR) )
{
my->monsterMoveTime = 0;
my->monsterState = MONSTER_STATE_WAIT; // wait state
@@ -5923,7 +6647,7 @@ void actMonster(Entity* my)
int myDex = my->monsterGetDexterityForMovement();
real_t maxVelX = cos(tangent2) * .045 * (myDex + 10) * weightratio * -.5;
real_t maxVelY = sin(tangent2) * .045 * (myDex + 10) * weightratio * -.5;
- if ( myStats->EFFECTS[EFF_KNOCKBACK] )
+ if ( myStats->getEffectActive(EFF_KNOCKBACK) )
{
my->monsterHandleKnockbackVelocity(tangent2, weightratio);
}
@@ -6042,7 +6766,7 @@ void actMonster(Entity* my)
// override if we're strafing, keep facing the target
dir = my->yaw - atan2(-tempVelY, -tempVelX);
}
- else if ( myStats->EFFECTS[EFF_KNOCKBACK] )
+ else if ( myStats->getEffectActive(EFF_KNOCKBACK) )
{
// in knockback, the velocitys change sign from negative/positive or positive/negative.
// this makes monsters moonwalk if the direction to rotate is assumed the same.
@@ -6081,6 +6805,12 @@ void actMonster(Entity* my)
{
dir = my->yaw - atan2( MONSTER_VELY, MONSTER_VELX );
}
+
+ if ( myStats->getEffectActive(EFF_SPIN) )
+ {
+ dir += my->monsterLookDir;
+ }
+
while ( dir >= PI )
{
dir -= PI * 2;
@@ -6145,8 +6875,7 @@ void actMonster(Entity* my)
{
if ( !MONSTER_ATTACK )
{
- int c;
- for ( c = 0; c < MAXPLAYERS; c++ )
+ for ( int c = 0; c < MAXPLAYERS; c++ )
{
playSoundPlayer(c, 204, 64);
}
@@ -6218,7 +6947,7 @@ void actMonster(Entity* my)
tangent = atan2(entity->y - my->y, entity->x - my->x);
my->yaw = tangent;
(void)castSpell(my->getUID(), &spell_fireball, true, false);
- for ( c = 0; c < MAXPLAYERS; ++c )
+ for ( int c = 0; c < MAXPLAYERS; ++c )
{
if ( players[c] && players[c]->entity && entity != players[c]->entity )
{
@@ -6284,6 +7013,12 @@ void actMonster(Entity* my)
my->monsterMoveTime = 0;
return;
}
+ else if ( myStats->type == HOLOGRAM )
+ {
+ my->monsterState = MONSTER_STATE_WAIT;
+ my->monsterMoveTime = 0;
+ return;
+ }
else if ( monsterIsImmobileTurret(my, myStats) )
{
my->monsterState = MONSTER_STATE_WAIT;
@@ -6428,7 +7163,8 @@ void actMonster(Entity* my)
// skip if light level is too low and distance is too high
int light = entity->entityLightAfterReductions(*hitstats, my);
- if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW )
+ if ( (myStats->type >= LICH && myStats->type < KOBOLD) || myStats->type == LICH_FIRE || myStats->type == LICH_ICE || myStats->type == SHADOW
+ || myStats->type == MONSTER_ADORCISED_WEAPON )
{
//See invisible.
light = 1000;
@@ -6436,10 +7172,14 @@ void actMonster(Entity* my)
double targetdist = sqrt( pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2) );
real_t monsterVisionRange = sightranges[myStats->type];
- if ( hitstats->type == DUMMYBOT )
+ if ( hitstats->type == DUMMYBOT || hitstats->type == HOLOGRAM )
{
monsterVisionRange = std::max(monsterVisionRange, 96.0);
}
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.ensemblePlaying >= 0 )
+ {
+ monsterVisionRange = std::max(monsterVisionRange, 5 * 16.0);
+ }
if ( targetdist > monsterVisionRange )
{
@@ -6449,7 +7189,7 @@ void actMonster(Entity* my)
{
if ( !(myStats->leader_uid == entity->getUID())
&& !(hitstats->leader_uid == my->getUID())
- && !(my->monsterAllyGetPlayerLeader() && entity->behavior == &actPlayer) )
+ && !((my->monsterAllyGetPlayerLeader() || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0) && entity->behavior == &actPlayer) )
{
if ( !levitating )
{
@@ -6461,16 +7201,32 @@ void actMonster(Entity* my)
}
if ( hit.entity == entity )
{
+ if ( entity->behavior == &actPlayer && entity->isInvisible() )
+ {
+ real_t dist = sqrt(pow(entity->vel_x, 2) + pow(entity->vel_y, 2));
+ if ( dist > 0.05 )
+ {
+ players[entity->skill[2]]->mechanics.updateSustainedSpellEvent(SPELL_INVISIBILITY, 1.0, 1.0, my);
+ }
+ }
if ( local_rng.rand() % 100 == 0 )
{
- entity->increaseSkill(PRO_STEALTH);
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.allowedRaiseStealthAgainstEntity(*my) )
+ {
+ entity->increaseSkill(PRO_STEALTH);
+ players[entity->skill[2]]->mechanics.enemyRaisedStealthAgainst[my->getUID()]++;
+ }
}
}
}
continue;
}
bool visiontest = false;
- if ( hitstats->type == DUMMYBOT || myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
+ if ( (myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "") )
+ {
+ visiontest = true;
+ }
+ else if ( hitstats->type == DUMMYBOT || hitstats->type == HOLOGRAM || myStats->type == SENTRYBOT || myStats->type == SPELLBOT )
{
if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
{
@@ -6479,7 +7235,7 @@ void actMonster(Entity* my)
}
else if ( myStats->type != SPIDER )
{
- if ( my->monsterAllyGetPlayerLeader() )
+ if ( my->monsterAllyGetPlayerLeader() || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0 )
{
if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 )
{
@@ -6503,16 +7259,16 @@ void actMonster(Entity* my)
}
if ( visiontest ) // vision cone
{
- lineTrace(my, my->x + 1, my->y, tangent, monsterVisionRange, 0, (levitating == false));
+ lineTrace(my, my->x + 1, my->y, tangent, monsterVisionRange, linetraceTargetEnemyFlags, (levitating == false));
if ( hit.entity == entity )
{
- lineTrace(my, my->x - 1, my->y, tangent, monsterVisionRange, 0, (levitating == false));
+ lineTrace(my, my->x - 1, my->y, tangent, monsterVisionRange, linetraceTargetEnemyFlags, (levitating == false));
if ( hit.entity == entity )
{
- lineTrace(my, my->x, my->y + 1, tangent, monsterVisionRange, 0, (levitating == false));
+ lineTrace(my, my->x, my->y + 1, tangent, monsterVisionRange, linetraceTargetEnemyFlags, (levitating == false));
if ( hit.entity == entity )
{
- lineTrace(my, my->x, my->y - 1, tangent, monsterVisionRange, 0, (levitating == false));
+ lineTrace(my, my->x, my->y - 1, tangent, monsterVisionRange, linetraceTargetEnemyFlags, (levitating == false));
if ( hit.entity == entity )
{
Entity& attackTarget = *hit.entity;
@@ -6551,6 +7307,11 @@ void actMonster(Entity* my)
//messagePlayer(0, "hunt -> attack, %d", my->monsterHitTime);
my->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_ATTACK);
+ if ( myStats->type == DUCK_SMALL )
+ {
+ my->monsterHitTime = 0;
+ }
+
if ( MONSTER_SOUND == NULL )
{
if ( myStats->type != MINOTAUR )
@@ -6569,8 +7330,7 @@ void actMonster(Entity* my)
}
else
{
- int c;
- for ( c = 0; c < MAXPLAYERS; c++ )
+ for ( int c = 0; c < MAXPLAYERS; c++ )
{
if ( c == 0 )
{
@@ -6586,7 +7346,7 @@ void actMonster(Entity* my)
if ( entity != nullptr )
{
- if ( entity->behavior == &actPlayer && myStats->type != DUMMYBOT )
+ if ( entity->behavior == &actPlayer && myStats->type != DUMMYBOT && myStats->type != DUCK_SMALL )
{
assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off
assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN;
@@ -6626,8 +7386,8 @@ void actMonster(Entity* my)
if (shouldHuntPlayer)
{
double distToPlayer = 0;
- int c, playerToChase = -1;
- for (c = 0; c < MAXPLAYERS; c++)
+ int playerToChase = -1;
+ for ( int c = 0; c < MAXPLAYERS; c++)
{
if (players[c] && players[c]->entity)
{
@@ -6710,17 +7470,29 @@ void actMonster(Entity* my)
&& myStats->leader_uid != 0
&& my->monsterAllyState == ALLY_STATE_DEFAULT
&& !monsterIsImmobileTurret(my, myStats)
- && !myStats->EFFECTS[EFF_ROOTED]
+ && !myStats->getEffectActive(EFF_ROOTED)
&& my->getUID() % TICKS_PER_SECOND == ticks % monsterAllyFormations.getFollowerChaseLeaderInterval(*my, *myStats)
)
{
Entity* leader = uidToEntity(myStats->leader_uid);
if ( leader )
{
+ Uint32 leaderUid = leader->getUID();
+ if ( leader && leader->behavior == &actPlayer && players[leader->skill[2]]->ghost.isActive() && players[leader->skill[2]]->ghost.isSpiritGhost() )
+ {
+ leader = players[leader->skill[2]]->ghost.my;
+ }
+ int followDist = HUNT_FOLLOWDIST;
real_t followx = leader->x;
real_t followy = leader->y;
- if ( myStats->type == GYROBOT )
+ if ( myStats->type == GYROBOT || myStats->type == DUCK_SMALL
+ || (myStats->type == MOTH_SMALL && myStats->getAttribute("fire_sprite") != "") )
{
+ if ( myStats->type == MOTH_SMALL || myStats->type == DUCK_SMALL )
+ {
+ followDist /= 2;
+ }
+
// follow ahead of the leader.
real_t startx = leader->x;
real_t starty = leader->y;
@@ -6760,7 +7532,7 @@ void actMonster(Entity* my)
{
break;
}
- if ( sqrt(pow(leader->x - previousx, 2) + pow(leader->y - previousy, 2)) > HUNT_FOLLOWDIST )
+ if ( sqrt(pow(leader->x - previousx, 2) + pow(leader->y - previousy, 2)) > followDist )
{
break;
}
@@ -6770,7 +7542,7 @@ void actMonster(Entity* my)
//createParticleFollowerCommand(previousx, previousy, 0, 175); debug particle
}
double dist = sqrt(pow(my->x - followx, 2) + pow(my->y - followy, 2));
- if ( dist > HUNT_FOLLOWDIST )
+ if ( dist > followDist )
{
bool doFollow = true;
if ( my->monsterTarget != 0 )
@@ -6780,7 +7552,8 @@ void actMonster(Entity* my)
if ( doFollow )
{
- if ( myStats->type == GYROBOT )
+ if ( myStats->type == GYROBOT || myStats->type == DUCK_SMALL
+ || (myStats->type == MOTH_SMALL && myStats->getAttribute("fire_sprite") != "") )
{
my->monsterSetPathToLocation(static_cast(followx) / 16, static_cast(followy) / 16, 0,
GeneratePathTypes::GENERATE_PATH_ALLY_FOLLOW2);
@@ -6788,7 +7561,7 @@ void actMonster(Entity* my)
else
{
std::pair followPos;
- if ( monsterAllyFormations.getFollowLocation(my->getUID(), leader->getUID(), followPos) )
+ if ( monsterAllyFormations.getFollowLocation(my->getUID(), leaderUid, followPos) )
{
my->monsterSetPathToLocation(followPos.first, followPos.second, 2,
GeneratePathTypes::GENERATE_PATH_ALLY_FOLLOW2);
@@ -6802,7 +7575,8 @@ void actMonster(Entity* my)
return;
}
}
- else if ( myStats->type != GYROBOT )
+ else if ( !(myStats->type == GYROBOT || myStats->type == DUCK_SMALL
+ || (myStats->type == MOTH_SMALL && myStats->getAttribute("fire_sprite") != "")) )
{
bool doFollow = true;
if ( my->monsterTarget != 0 )
@@ -6814,12 +7588,15 @@ void actMonster(Entity* my)
{
double tangent = atan2( leader->y - my->y, leader->x - my->x );
Entity* ohitentity = hit.entity;
+ bool oldPassable = leader->flags[PASSABLE]; // hack for ghosts
+ leader->flags[PASSABLE] = false;
lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, true);
+ leader->flags[PASSABLE] = oldPassable;
if ( hit.entity != leader )
{
my->monsterReleaseAttackTarget();
std::pair followPos;
- if ( monsterAllyFormations.getFollowLocation(my->getUID(), leader->getUID(), followPos) )
+ if ( monsterAllyFormations.getFollowLocation(my->getUID(), leaderUid, followPos) )
{
if ( my->monsterSetPathToLocation(followPos.first, followPos.second, 1,
GeneratePathTypes::GENERATE_PATH_ALLY_FOLLOW) )
@@ -6857,7 +7634,7 @@ void actMonster(Entity* my)
if ( my->children.first->element != NULL )
{
path = (list_t*)my->children.first->element;
- if ( path->first != NULL && !myStats->EFFECTS[EFF_ROOTED] )
+ if ( path->first != NULL && !myStats->getEffectActive(EFF_ROOTED) )
{
auto pathnode = (pathnode_t*)path->first->element;
dist = sqrt( pow(pathnode->y * 16 + 8 - my->y, 2) + pow(pathnode->x * 16 + 8 - my->x, 2) );
@@ -6877,8 +7654,7 @@ void actMonster(Entity* my)
}
else
{
- int c;
- for ( c = 0; c < MAXPLAYERS; c++ )
+ for ( int c = 0; c < MAXPLAYERS; c++ )
{
if ( c == 0 )
{
@@ -6898,13 +7674,23 @@ void actMonster(Entity* my)
// move monster
tangent = atan2( pathnode->y * 16 + 8 - my->y, pathnode->x * 16 + 8 - my->x );
int myDex = my->getDEX();
- if ( my->monsterAllyGetPlayerLeader() )
+ if ( my->monsterAllyGetPlayerLeader() || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0 )
{
+ if ( myStats->type == DUCK_SMALL )
+ {
+ if ( entity )
+ {
+ if ( entityDist(my, entity) > TOUCHRANGE )
+ {
+ myDex += 2;
+ }
+ }
+ }
myDex = std::min(myDex, MONSTER_ALLY_DEXTERITY_SPEED_CAP);
}
real_t maxVelX = cos(tangent) * .045 * (myDex + 10) * weightratio;
real_t maxVelY = sin(tangent) * .045 * (myDex + 10) * weightratio;
- if ( myStats->EFFECTS[EFF_KNOCKBACK] )
+ if ( myStats->getEffectActive(EFF_KNOCKBACK) )
{
my->monsterHandleKnockbackVelocity(tangent, weightratio);
}
@@ -6917,7 +7703,7 @@ void actMonster(Entity* my)
my->handleKnockbackDamage(*myStats, hit.entity);
if ( hit.entity != NULL )
{
- if ( hit.entity->behavior == &actDoor )
+ if ( hit.entity->behavior == &actDoor || hit.entity->behavior == &actIronDoor )
{
// opens the door if unlocked and monster can do it
if ( !hit.entity->doorLocked && my->getINT() > -2 )
@@ -6933,6 +7719,40 @@ void actMonster(Entity* my)
playSoundEntity(hit.entity, 21, 96);
}
}
+ else if ( hit.entity->behavior == &actIronDoor )
+ {
+ if ( myStats->type == MINOTAUR )
+ {
+ hit.entity->doorHealth = 0; // minotaurs smash doors instantly
+ my->monsterAttack = my->getAttackPose(); // random attack motion
+ my->monsterAttackTime = 0;
+ my->monsterHitTime = 0;
+
+ playSoundEntity(hit.entity, 28, 64);
+ if ( hit.entity->doorHealth <= 0 )
+ {
+ // set direction of splinters
+ if ( !hit.entity->doorDir )
+ {
+ hit.entity->doorSmacked = (my->x > hit.entity->x);
+ }
+ else
+ {
+ hit.entity->doorSmacked = (my->y < hit.entity->y);
+ }
+ }
+ }
+ else
+ {
+ ++my->monsterPathCount;
+ if ( my->monsterPathCount > 50 )
+ {
+ my->monsterPathCount = 0;
+ //messagePlayer(0, MESSAGE_DEBUG, "remaking path!");
+ my->monsterMoveBackwardsAndPath(true);
+ }
+ }
+ }
else
{
// can't open door, so break it down
@@ -6952,7 +7772,8 @@ void actMonster(Entity* my)
{
hit.entity->doorHealth = 0; // minotaurs smash doors instantly
}
- updateEnemyBar(my, hit.entity, Language::get(674), hit.entity->skill[4], hit.entity->skill[9],
+ updateEnemyBar(my, hit.entity, hit.entity->behavior == &actIronDoor ? Language::get(6414) : Language::get(674),
+ hit.entity->doorHealth, hit.entity->doorMaxHealth,
false, DamageGib::DMG_DEFAULT);
playSoundEntity(hit.entity, 28, 64);
if ( hit.entity->doorHealth <= 0 )
@@ -7104,6 +7925,10 @@ void actMonster(Entity* my)
// melee 240ms
my->monsterHitTime = std::max(HITRATE - 12, my->monsterHitTime);
}
+ /*if ( myStats->type == DUCK_SMALL )
+ {
+ my->monsterHitTime = 0;
+ }*/
//messagePlayer(0, "bump1 -> attack, %d", my->monsterHitTime);
}
else if ( yourStats )
@@ -7170,6 +7995,10 @@ void actMonster(Entity* my)
}
//messagePlayer(0, "bump2 -> attack, %d", my->monsterHitTime);
my->monsterAcquireAttackTarget(attackTarget, MONSTER_STATE_ATTACK);
+ /*if ( myStats->type == DUCK_SMALL )
+ {
+ my->monsterHitTime = 0;
+ }*/
}
else
{
@@ -7213,7 +8042,7 @@ void actMonster(Entity* my)
}
// rotate monster
- if ( myStats->EFFECTS[EFF_KNOCKBACK] )
+ if ( myStats->getEffectActive(EFF_KNOCKBACK) )
{
// in knockback, the velocitys change sign from negative/positive or positive/negative.
// this makes monsters moonwalk if the direction to rotate is assumed the same.
@@ -7247,6 +8076,11 @@ void actMonster(Entity* my)
{
dir = my->yaw - atan2(MONSTER_VELY, MONSTER_VELX);
}
+
+ if ( myStats->getEffectActive(EFF_SPIN) )
+ {
+ dir += my->monsterLookDir;
+ }
while ( dir >= PI )
{
dir -= PI * 2;
@@ -7277,7 +8111,40 @@ void actMonster(Entity* my)
messagePlayer(0, "[SHADOW] No path #1: Resetting to wait state.");
}*/
}
- my->monsterState = MONSTER_STATE_WAIT; // no path, return to wait state
+
+ if ( path->first != NULL && myStats->getEffectActive(EFF_ROOTED) )
+ {
+ // keep path while rooted
+ }
+ else
+ {
+ my->monsterState = MONSTER_STATE_WAIT; // no path, return to wait state
+
+ if ( target && ((target->getStats() && target->getStats()->type == DUCK_SMALL) || target->behavior == &actDeathGhost)
+ && myStats->getEffectActive(EFF_DISORIENTED) )
+ {
+ if ( entityDist(my, target) < STRIKERANGE )
+ {
+ if ( target->getStats() && target->getStats()->type == DUCK_SMALL )
+ {
+ if ( target->monsterAttack == 0 )
+ {
+ if ( local_rng.rand() % 4 == 0 )
+ {
+ // dodge anim
+ target->attack(local_rng.rand() % 2 ? MONSTER_POSE_MELEE_WINDUP2 : MONSTER_POSE_MELEE_WINDUP3, 0, nullptr);
+ }
+ }
+ }
+
+ my->monsterAttack = my->getAttackPose(); // random attack motion
+ my->monsterAttackTime = 0;
+ my->monsterHitTime = std::max(my->monsterHitTime, HITRATE / 4);
+ serverUpdateEntitySkill(my, 9);
+ serverUpdateEntitySkill(my, 8);
+ }
+ }
+ }
if ( !target && myStats->type == MIMIC )
{
mimicResetIdle(my);
@@ -7339,7 +8206,7 @@ void actMonster(Entity* my)
if ( dist < sightranges[myStats->type] && dist <= oldDist )
{
double tangent = atan2(target->y - my->y, target->x - my->x);
- lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
+ lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], linetraceTargetEnemyFlags, false);
if ( hit.entity == target )
{
my->monsterLookTime = 1;
@@ -7366,7 +8233,7 @@ void actMonster(Entity* my)
serverUpdateEntitySkill(my, 43); // update monsterAllyState
}
}
- else if ( !target && my->monsterAllyGetPlayerLeader() )
+ else if ( !target && (my->monsterAllyGetPlayerLeader() || achievementObserver.checkUidIsFromPlayer(myStats->leader_uid) >= 0) )
{
// scan for enemies after reaching move point.
real_t dist = sightranges[myStats->type];
@@ -7380,7 +8247,7 @@ void actMonster(Entity* my)
if ( dist < sightranges[myStats->type] && dist <= oldDist )
{
double tangent = atan2(target->y - my->y, target->x - my->x);
- lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
+ lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], linetraceTargetEnemyFlags, false);
if ( hit.entity == target )
{
my->monsterLookTime = 1;
@@ -7492,7 +8359,7 @@ void actMonster(Entity* my)
if ( dist < sightranges[myStats->type] && dist <= oldDist )
{
double tangent = atan2(target->y - my->y, target->x - my->x);
- lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], 0, false);
+ lineTrace(my, my->x, my->y, tangent, sightranges[myStats->type], linetraceTargetEnemyFlags, false);
if ( hit.entity == target )
{
my->monsterLookTime = 1;
@@ -7550,6 +8417,11 @@ void actMonster(Entity* my)
{
dir += PI * 2;
}
+
+ if ( myStats->getEffectActive(EFF_SPIN) )
+ {
+ dir += my->monsterLookDir;
+ }
my->yaw -= dir / 2;
while ( my->yaw < 0 )
{
@@ -7720,8 +8592,7 @@ void actMonster(Entity* my)
{
serverUpdateEntitySkill(my, 8);
serverUpdateEntitySkill(my, 9);
- int c;
- for ( c = 0; c < MAXPLAYERS; c++ )
+ for ( int c = 0; c < MAXPLAYERS; c++ )
{
playSoundPlayer(c, 186, 128);
}
@@ -8584,6 +9455,11 @@ void actMonster(Entity* my)
{
dir += PI * 2;
}
+
+ if ( myStats->getEffectActive(EFF_SPIN) )
+ {
+ dir += my->monsterLookDir;
+ }
my->yaw -= dir / 64;
while ( my->yaw < 0 )
{
@@ -8731,7 +9607,7 @@ void actMonster(Entity* my)
{
real_t distToPlayer = 0.f;
int playerToChase = -1;
- for ( c = 0; c < MAXPLAYERS; c++ )
+ for ( int c = 0; c < MAXPLAYERS; c++ )
{
if ( players[c] && players[c]->entity )
{
@@ -8862,7 +9738,7 @@ void actMonster(Entity* my)
{
real_t distToPlayer = 0.f;
int playerToChase = -1;
- for ( c = 0; c < MAXPLAYERS; c++ )
+ for ( int c = 0; c < MAXPLAYERS; c++ )
{
if ( players[c] && players[c]->entity )
{
@@ -9013,6 +9889,10 @@ void actMonster(Entity* my)
bool visiontest = false;
real_t monsterVisionRange = 40.0; //sightranges[myStats->type];
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.ensemblePlaying >= 0 )
+ {
+ monsterVisionRange = std::max(monsterVisionRange, 5 * 16.0);
+ }
int sizex = my->sizex;
int sizey = my->sizey;
my->sizex = std::max(my->sizex, 4); // override size temporarily
@@ -9087,7 +9967,7 @@ void actMonster(Entity* my)
}
}
}
- else if ( myStats && myStats->type == MIMIC && myStats->EFFECTS[EFF_MIMIC_LOCKED] && !my->isInertMimic() )
+ else if ( myStats && myStats->type == MIMIC && myStats->getEffectActive(EFF_MIMIC_LOCKED) && !my->isInertMimic() )
{
my->monsterHitTime++;
if ( my->monsterHitTime >= HITRATE )
@@ -9106,7 +9986,7 @@ void actMonster(Entity* my)
}
else if ( myStats && myStats->type == MIMIC && !my->isInertMimic() )
{
- if ( myStats->EFFECTS[EFF_ASLEEP] || myStats->EFFECTS[EFF_PARALYZED] )
+ if ( myStats->getEffectActive(EFF_ASLEEP) || myStats->getEffectActive(EFF_PARALYZED) )
{
if ( my->monsterSpecialState != MIMIC_STATUS_IMMOBILE )
{
@@ -9153,7 +10033,11 @@ void actMonster(Entity* my)
double targetdist = sqrt(pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2));
real_t monsterVisionRange = 24.0; //sightranges[myStats->type];
-
+ if ( entity->behavior == &actPlayer && players[entity->skill[2]]->mechanics.ensemblePlaying >= 0 )
+ {
+ monsterVisionRange = std::max(monsterVisionRange, 3 * 16.0);
+ }
+
if ( targetdist > monsterVisionRange )
{
continue;
@@ -9179,7 +10063,7 @@ void actMonster(Entity* my)
{
// charge state
Entity* attackTarget = hit.entity;
- if ( my->disturbMimic(attackTarget, false, true) )
+ if ( my->disturbMimic(attackTarget, false, monsterVisionRange <= 25.0 ) )
{
my->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_ATTACK);
@@ -9205,9 +10089,124 @@ void actMonster(Entity* my)
}
}
}
- if ( myStats->EFFECTS[EFF_KNOCKBACK] )
+
+ if ( myStats && myStats->type == DUCK_SMALL )
+ {
+ if ( my->monsterSpecialState == DUCK_DIVE )
+ {
+ my->monsterReleaseAttackTarget();
+ real_t centerx = (int)floor(my->x / 16) * 16.0 + 8.0;
+ real_t centery = (int)floor(my->y / 16) * 16.0 + 8.0;
+
+ // center on tile
+ real_t tangent = atan2(centery - my->y, centerx - my->x);
+ MONSTER_VELX = 0.1 * cos(tangent);
+ MONSTER_VELY = 0.1 * sin(tangent);
+ clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
+ MONSTER_VELX = 0.0;
+ MONSTER_VELY = 0.0;
+ }
+ else if ( my->monsterSpecialState == DUCK_RETURN )
+ {
+ my->monsterReleaseAttackTarget();
+ Entity* leader = uidToEntity(myStats->leader_uid);
+ if ( !leader || myStats->getAttribute("duck_run") == "1" )
+ {
+ int dir = my->getUID() % 4;
+ int mapx = 1;
+ int mapy = 1;
+ switch ( dir )
+ {
+ case 0:
+ mapx = 1;
+ mapy = 1;
+ break;
+ case 1:
+ mapx = 1;
+ mapy = map.height - 2;
+ break;
+ case 2:
+ mapx = map.width - 2;
+ mapy = 1;
+ break;
+ case 3:
+ mapx = map.width - 2;
+ mapy = map.height - 2;
+ break;
+ default:
+ break;
+ }
+
+ real_t tangent = atan2((mapy * 16.0 + 8.0) - my->y, (mapx * 16.0 + 8.0) - my->x);
+ my->monsterLookDir = tangent;
+ MONSTER_VELX = cos(tangent);
+ MONSTER_VELY = sin(tangent);
+ clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
+ if ( static_cast(my->x / 16) == mapx && static_cast(my->y / 16) == mapy )
+ {
+ playSoundEntity(my, 794 + local_rng.rand() % 2, 128);
+ playSoundEntity(my, 789 + local_rng.rand() % 5, 128);
+ my->setHP(0);
+ return;
+ }
+ }
+ else if ( leader )
+ {
+ real_t tangent = atan2(leader->y - my->y, leader->x - my->x);
+ my->monsterLookDir = tangent;
+ MONSTER_VELX = cos(tangent);
+ MONSTER_VELY = sin(tangent);
+ clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
+ if ( entityInsideEntity(my, leader) )
+ {
+ if ( Stat* leaderStats = leader->getStats() )
+ {
+ if ( leader->behavior == &actPlayer )
+ {
+ int player = leader->skill[2];
+ int appearance = 0;
+ if ( myStats->getAttribute("duck_type") != "" )
+ {
+ appearance = std::stoi(myStats->getAttribute("duck_type"));
+ }
+ int bless = 0;
+ if ( myStats->getAttribute("duck_bless") != "" )
+ {
+ bless = std::stoi(myStats->getAttribute("duck_bless"));
+ }
+ Item* item2 = newItem(TOOL_DUCK, EXCELLENT, bless, 1, appearance, true, nullptr);
+ Item* item = itemPickup(player, item2);
+ if ( item )
+ {
+ if ( players[player]->isLocalPlayer() )
+ {
+ // item is the new inventory stack for server, free the picked up items
+ free(item2);
+ int oldcount = item->count;
+ item->count = 1;
+ messagePlayer(player, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(504), item->description());
+ item->count = oldcount;
+ }
+ else
+ {
+ messagePlayer(player, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(504), item->description());
+ free(item); // item is the picked up items (item == item2)
+ }
+ }
+ }
+ }
+ playSoundEntity(my, 794 + local_rng.rand() % 2, 128);
+ playSoundEntity(my, 789 + local_rng.rand() % 5, 128);
+ my->setHP(0);
+ return;
+ }
+ }
+ }
+ my->monsterRotate();
+ }
+ else if ( myStats->getEffectActive(EFF_KNOCKBACK) )
{
- my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir, weightratio);
+ my->monsterHandleKnockbackVelocity(my->monsterKnockbackTangentDir + PI, weightratio);
if ( abs(MONSTER_VELX) > 0.01 || abs(MONSTER_VELY) > 0.01 )
{
dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my);
@@ -9221,6 +10220,8 @@ void actMonster(Entity* my)
}
}
+ my->processEntityWind();
+
if ( previousMonsterState != my->monsterState )
{
serverUpdateEntitySkill(my, 0);
@@ -9229,7 +10230,7 @@ void actMonster(Entity* my)
serverUpdateEntitySkill(my, 1); // update monsterTarget for player leaders.
}
- if ( myStats->type == SHOPKEEPER && previousMonsterState == MONSTER_STATE_TALK )
+ if ( previousMonsterState == MONSTER_STATE_TALK && (myStats->type == SHOPKEEPER || my->monsterCanTradeWith(-1)) )
{
for ( int i = 0; i < MAXPLAYERS; ++i )
{
@@ -9281,9 +10282,46 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
}
+ if ( myStats->type == DUCK_SMALL )
+ {
+ if ( dist < 48 )
+ {
+ ++monsterHitTime;
+ if ( monsterHitTime >= HITRATE )
+ {
+ bool anyTarget = duckAreaQuck(this);
+ if ( anyTarget )
+ {
+ monsterHitTime = 0;
+
+ if ( Entity* target = uidToEntity(monsterTarget) )
+ {
+ if ( monsterSetPathToLocation(target->x / 16, target->y / 16, 1,
+ GeneratePathTypes::GENERATE_PATH_TO_HUNT_MONSTER_TARGET, true, false) )
+ {
+ monsterState = MONSTER_STATE_HUNT; // hunt state
+ this->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND / 2, false);
+ }
+ }
+ else
+ {
+ if ( monsterSetPathToLocation(this->x / 16, this->y / 16, 1,
+ GeneratePathTypes::GENERATE_PATH_TO_HUNT_MONSTER_TARGET, true, false) )
+ {
+ monsterState = MONSTER_STATE_HUNT; // hunt state
+ this->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND / 2, false);
+ }
+ }
+ }
+ }
+ }
+ return;
+ }
+
//TODO: I don't like this function getting called every frame. Find a better place to put it.
chooseWeapon(target, dist);
- bool hasrangedweapon = this->hasRangedWeapon();
+ bool hasrangedweapon = this->hasRangedWeapon(true);
+
bool lichRangeCheckOverride = false;
if ( myStats->type == SLIME )
{
@@ -9292,7 +10330,52 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
lichRangeCheckOverride = true;
}
}
- if ( myStats->type == LICH_FIRE)
+ if ( myStats->type == REVENANT_SKULL
+ || myStats->type == MONSTER_ADORCISED_WEAPON
+ || myStats->type == FLAME_ELEMENTAL
+ || myStats->type == EARTH_ELEMENTAL )
+ {
+ if ( monsterSpecialState == SKULL_CAST )
+ {
+ lichRangeCheckOverride = true;
+ }
+
+ if ( (myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("spirit_weapon") != "") )
+ {
+ lichRangeCheckOverride = true;
+ }
+ }
+ else if ( myStats->type == MOTH_SMALL )
+ {
+ if ( monsterSpecialState == MOTH_CAST )
+ {
+ lichRangeCheckOverride = true;
+ }
+ }
+ else if ( myStats->type == DRYAD )
+ {
+ if ( (monsterSpecialState >= MONSTER_D_SPECIAL_CAST1
+ && monsterSpecialState <= MONSTER_D_SPECIAL_CAST3) )
+ {
+ lichRangeCheckOverride = true;
+ }
+ }
+ else if ( myStats->type == MYCONID )
+ {
+ if ( (monsterSpecialState >= MONSTER_M_SPECIAL_CAST1
+ && monsterSpecialState <= MONSTER_M_SPECIAL_CAST3) )
+ {
+ lichRangeCheckOverride = true;
+ }
+ }
+ else if ( myStats->type == GREMLIN )
+ {
+ if ( monsterSpecialState == MONSTER_G_SPECIAL_CAST1 )
+ {
+ lichRangeCheckOverride = true;
+ }
+ }
+ else if ( myStats->type == LICH_FIRE)
{
if ( monsterLichFireMeleeSeq == LICH_ATK_BASICSPELL_SINGLE )
{
@@ -9315,7 +10398,7 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
monsterStrafeDirection = -1 + ((local_rng.rand() % 2 == 0) ? 2 : 0);
}
}
- else if ( myStats->type == DUMMYBOT )
+ else if ( myStats->type == DUMMYBOT || myStats->type == HOLOGRAM )
{
return;
}
@@ -9345,10 +10428,25 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
int meleeDist = STRIKERANGE;
+ int strikeRange = STRIKERANGE;
+ if ( myStats->getEffectActive(EFF_ROOTED) || (shouldRetreat(*myStats) && myStats->getEffectActive(EFF_COWARDICE)) )
+ {
+ meleeDist = TOUCHRANGE - 1; // so you can't melee range-cheese
+ }
if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE )
{
meleeDist = TOUCHRANGE - 1;
}
+ if ( myStats->weapon && myStats->weapon->type == TOOL_WHIP )
+ {
+ meleeDist = std::max(meleeDist, (int)(STRIKERANGE * 1.5));
+ strikeRange = (int)(STRIKERANGE * 1.5);
+ }
+ else if ( myStats->weapon && myStats->weapon->type == STEEL_FLAIL )
+ {
+ meleeDist = std::max(meleeDist, (int)(STRIKERANGE * 1.5));
+ strikeRange = (int)(STRIKERANGE * 1.5);
+ }
// check the range to the target, depending on ranged weapon or melee.
if ( (dist < meleeDist && !hasrangedweapon) || (hasrangedweapon && dist < getMonsterEffectiveDistanceOfRangedWeapon(myStats->weapon)) || lichRangeCheckOverride )
@@ -9360,8 +10458,11 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
{
if ( (myStats->weapon->type == SLING
|| myStats->weapon->type == SHORTBOW
+ || myStats->weapon->type == BONE_SHORTBOW
|| myStats->weapon->type == ARTIFACT_BOW
|| myStats->weapon->type == LONGBOW
+ || myStats->weapon->type == BRANCH_BOW
+ || myStats->weapon->type == BRANCH_BOW_INFECTED
|| myStats->weapon->type == COMPOUND_BOW) )
{
bow = 2;
@@ -9377,7 +10478,7 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
}
}
- else if ( myStats->weapon->type == CROSSBOW )
+ else if ( myStats->weapon->type == CROSSBOW || myStats->weapon->type == BLACKIRON_CROSSBOW )
{
if ( myStats->shield && itemTypeIsQuiver(myStats->shield->type) )
{
@@ -9396,6 +10497,22 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
{
bow = 1.5;
}
+ else if ( myStats->type == REVENANT_SKULL || myStats->type == MONSTER_ADORCISED_WEAPON
+ || myStats->type == FLAME_ELEMENTAL || myStats->type == EARTH_ELEMENTAL )
+ {
+ bow = 1.5;
+ }
+ else if ( myStats->type == MOTH_SMALL )
+ {
+ if ( myStats->getAttribute("fire_sprite") != "" )
+ {
+ bow = 3.0;
+ }
+ else
+ {
+ bow = 0.5;
+ }
+ }
else if ( monsterIsImmobileTurret(this, myStats) )
{
bow = 2;
@@ -9419,6 +10536,18 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
}
}
+ else if ( myStats->weapon && myStats->weapon->type == STEEL_FLAIL )
+ {
+ if ( this->monsterAttack == MONSTER_POSE_FLAIL_SWING )
+ {
+ bow = 6;
+ }
+ else if ( this->monsterAttack == MONSTER_POSE_FLAIL_SWING_WINDUP )
+ {
+ bow = 1.5;
+ }
+ }
+
// check if ready to attack
if ( (this->monsterHitTime >= static_cast(HITRATE * monsterGlobalAttackTimeMultiplier * bow)
&& (myStats->type != LICH && myStats->type != LICH_ICE))
@@ -9464,7 +10593,7 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
else
{
- tracedist = STRIKERANGE;
+ tracedist = strikeRange;
}
// check again for the target in attack range. return the result into hit.entity.
@@ -9475,7 +10604,7 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
else
{
- lineTrace(this, this->x, this->y, newTangent, tracedist, 0, false);
+ lineTrace(this, this->x, this->y, newTangent, tracedist, LINETRACE_ATK_CHECK_FRIENDLYFIRE, false);
}
if ( hit.entity != nullptr && hit.entity == target )
{
@@ -9531,7 +10660,11 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
}
}
- if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE )
+ if ( myStats->type == MOTH_SMALL && pose == 0 )
+ {
+ // couldn't find a body to execute attack
+ }
+ else if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE )
{
if ( myStats->shield && myStats->shield->type == STEEL_SHIELD && local_rng.rand() % 3 == 0 && dist <= meleeDist )
{
@@ -9550,7 +10683,14 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist)
else if ( monsterDefend == MONSTER_DEFEND_HOLD )
{
// skip attack, continue defending. offset the hit time to allow for timing variation.
- monsterHitTime = HITRATE / 4;
+ if ( myStats->type == EARTH_ELEMENTAL )
+ {
+ monsterHitTime = HITRATE / 2;
+ }
+ else
+ {
+ monsterHitTime = HITRATE / 4;
+ }
}
else
{
@@ -9937,21 +11077,22 @@ int limbAnimateToLimit(Entity* limb, int axis, double rate, double setpoint, boo
}
else if ( axis == ANIMATE_WEAPON_YAW )
{
- while ( limb->fskill[5] < 0 )
+ real_t& limbSkill = limb->behavior == &actPlayer ? limb->fskill[2] : limb->monsterWeaponYaw;
+ while ( limbSkill < 0 )
{
- limb->fskill[5] += 2 * PI;
+ limbSkill += 2 * PI;
}
- while ( limb->fskill[5] >= 2 * PI )
+ while ( limbSkill >= 2 * PI )
{
- limb->fskill[5] -= 2 * PI;
+ limbSkill -= 2 * PI;
}
- if ( limbAngleWithinRange(limb->fskill[5], rate, setpoint) )
+ if ( limbAngleWithinRange(limbSkill, rate, setpoint) )
{
- limb->fskill[5] = setpoint;
+ limbSkill = setpoint;
return 1; //reached setpoint
}
- limb->fskill[5] += rate;
+ limbSkill += rate;
}
return 0;
@@ -10125,6 +11266,10 @@ bool forceFollower(Entity& leader, Entity& follower)
{
FollowerMenu[player].recentEntity = &follower;
}
+ if ( followerStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
+ {
+ followerStats->monsterForceAllegiance = Stat::MONSTER_FORCE_ALLEGIANCE_NONE;
+ }
}
if ( player >= 0 )
@@ -10238,7 +11383,7 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di
break;
case CRYSTALGOLEM:
specialRoll = local_rng.rand() % 20;
- enemiesNearby = numTargetsAroundEntity(this, STRIKERANGE, PI, MONSTER_TARGET_ENEMY);
+ enemiesNearby = numTargetsAroundEntity(this, TOUCHRANGE, PI, MONSTER_TARGET_ENEMY);
if ( enemiesNearby > 1 )
{
enemiesNearby = std::min(enemiesNearby, 4);
@@ -10351,7 +11496,76 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di
{
// special handled in slimeChooseWeapon()
monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY;
- break;
+ }
+ break;
+ case REVENANT_SKULL:
+ // magic
+ specialRoll = local_rng.rand() % 10;
+ if ( specialRoll == 0 )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SKULL_CAST;
+ }
+ break;
+ case FLAME_ELEMENTAL:
+ // magic
+ specialRoll = local_rng.rand() % 10;
+ if ( specialRoll == 0 )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SKULL_CAST;
+ }
+ break;
+ case MOTH_SMALL:
+ // magic
+ if ( monsterSpecialState == MOTH_CAST )
+ {
+ // special handled in mothChooseWeapon()
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MOTH_CAST;
+ }
+ break;
+ case DRYAD:
+ // magic
+ if ( (monsterSpecialState >= MONSTER_D_SPECIAL_CAST1
+ && monsterSpecialState <= MONSTER_D_SPECIAL_CAST3) )
+ {
+ // special handled in monsterDChooseWeapon()
+ if ( monsterSpecialState == MONSTER_D_SPECIAL_CAST2 )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_D_PUSH;
+ }
+ else
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_D;
+ }
+ }
+ break;
+ case MYCONID:
+ // special handled in monsterMChooseWeapon()
+ if ( monsterSpecialState == MONSTER_M_SPECIAL_THROW )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_M_THROW;
+ }
+ else if ( (monsterSpecialState >= MONSTER_M_SPECIAL_CAST1
+ && monsterSpecialState <= MONSTER_M_SPECIAL_CAST3) )
+ {
+ if ( monsterSpecialState == MONSTER_M_SPECIAL_CAST1 )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_M_CAST_SHORT;
+ }
+ else
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_M_CAST_LONG;
+ }
+ }
+ break;
+ case GREMLIN:
+ // special handled in monsterGChooseWeapon()
+ if ( monsterSpecialState == MONSTER_G_SPECIAL_THROW )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_G_THROW;
+ }
+ else if ( monsterSpecialState == MONSTER_G_SPECIAL_CAST1 )
+ {
+ monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MONSTER_G_CAST;
}
break;
case SPIDER:
@@ -10496,6 +11710,7 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di
this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SHADOW_TELEPORT;
break;
}
+ break;
case GOATMAN:
if ( monsterSpecialState == GOATMAN_POTION )
{
@@ -10596,6 +11811,33 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di
deinitSuccess = true;
}
break;
+ case REVENANT_SKULL:
+ case FLAME_ELEMENTAL:
+ if ( monsterSpecialState == SKULL_CAST || forceDeinit )
+ {
+ monsterSpecialState = 0;
+ shouldAttack = false;
+ deinitSuccess = true;
+ }
+ break;
+ case MOTH_SMALL:
+ if ( monsterSpecialState == MOTH_CAST || forceDeinit )
+ {
+ monsterSpecialState = 0;
+ shouldAttack = false;
+ deinitSuccess = true;
+ }
+ break;
+ case DRYAD:
+ if ( (monsterSpecialState >= MONSTER_D_SPECIAL_CAST1
+ && monsterSpecialState <= MONSTER_D_SPECIAL_CAST3)
+ || forceDeinit )
+ {
+ monsterSpecialState = 0;
+ shouldAttack = false;
+ deinitSuccess = true;
+ }
+ break;
case SPIDER:
if ( monsterSpecialState == SPIDER_CAST || forceDeinit )
{
@@ -10794,6 +12036,56 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di
deinitSuccess = true;
}
break;
+ case MYCONID:
+ if ( (monsterSpecialState >= MONSTER_M_SPECIAL_CAST1
+ && monsterSpecialState <= MONSTER_M_SPECIAL_CAST3) )
+ {
+ monsterSpecialState = 0;
+ shouldAttack = false;
+ deinitSuccess = true;
+ }
+ else if ( monsterSpecialState == MONSTER_M_SPECIAL_THROW || forceDeinit )
+ {
+ node = itemNodeInInventory(myStats, -1, WEAPON); // find weapon to re-equip
+ if ( node != nullptr )
+ {
+ swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
+ }
+ else
+ {
+ monsterUnequipSlotFromCategory(myStats, &myStats->weapon, THROWN);
+ }
+ shouldAttack = false;
+ monsterSpecialState = 0;
+ serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
+ deinitSuccess = true;
+ }
+ break;
+ case GREMLIN:
+ if ( monsterSpecialState == MONSTER_G_SPECIAL_CAST1 )
+ {
+ monsterSpecialState = 0;
+ shouldAttack = false;
+ deinitSuccess = true;
+ }
+ else if ( monsterSpecialState == MONSTER_G_SPECIAL_THROW || forceDeinit )
+ {
+ node = itemNodeInInventory(myStats, -1, WEAPON); // find weapon to re-equip
+ if ( node != nullptr )
+ {
+ swapMonsterWeaponWithInventoryItem(this, myStats, node, false, true);
+ }
+ else
+ {
+ monsterUnequipSlotFromCategory(myStats, &myStats->weapon, THROWN);
+ monsterUnequipSlotFromCategory(myStats, &myStats->weapon, POTION);
+ }
+ shouldAttack = false;
+ monsterSpecialState = 0;
+ serverUpdateEntitySkill(this, 33); // for clients to keep track of animation
+ deinitSuccess = true;
+ }
+ break;
default:
break;
}
@@ -11138,73 +12430,91 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
{
return;
}
- if ( command == -1 || command == ALLY_CMD_CANCEL || monsterAllyIndex == -1 || monsterAllyIndex > MAXPLAYERS )
+ Stat* myStats = getStats();
+ if ( !myStats )
{
return;
}
- if ( !players[monsterAllyIndex] )
+
+ if ( command == -1 || command == ALLY_CMD_CANCEL )
{
return;
}
- if ( !players[monsterAllyIndex]->entity )
+
+ int playerLeader = monsterAllyIndex;
+ bool compelSpell = false;
+ if ( myStats->getEffectActive(EFF_COMMAND) >= 1 && myStats->getEffectActive(EFF_COMMAND) < MAXPLAYERS + 1 )
{
- return;
+ playerLeader = myStats->getEffectActive(EFF_COMMAND) - 1;
+ compelSpell = true;
}
- if ( monsterTarget == players[monsterAllyIndex]->entity->getUID() )
+
+ if ( playerLeader <= -1 || monsterAllyIndex >= MAXPLAYERS )
{
- // angry at owner.
return;
}
- Stat* myStats = getStats();
- if ( !myStats )
+ if ( !players[playerLeader] )
+ {
+ return;
+ }
+ if ( !players[playerLeader]->entity )
+ {
+ return;
+ }
+ if ( monsterTarget == players[playerLeader]->entity->getUID() )
{
+ // angry at owner.
return;
}
if ( !isMobile() )
{
// doesn't respond.
- if ( monsterAllySpecial == ALLY_SPECIAL_CMD_REST && myStats->EFFECTS[EFF_ASLEEP]
+ if ( monsterAllySpecial == ALLY_SPECIAL_CMD_REST && myStats->getEffectActive(EFF_ASLEEP)
&& (command == ALLY_CMD_MOVETO_CONFIRM || command == ALLY_CMD_ATTACK_CONFIRM
|| command == ALLY_CMD_MOVEASIDE) )
{
- myStats->EFFECTS[EFF_ASLEEP] = false; // wake up
+ myStats->clearEffect(EFF_ASLEEP); // wake up
myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 0;
- myStats->EFFECTS[EFF_HP_REGEN] = false; // stop regen
+ myStats->clearEffect(EFF_HP_REGEN); // stop regen
myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = 0;
monsterAllySpecial = ALLY_SPECIAL_CMD_NONE;
}
else
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF, *myStats, Language::get(514), Language::get(515), MSG_COMBAT);
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF, *myStats, Language::get(514), Language::get(515), MSG_COMBAT);
}
return;
}
- bool isTinkeringFollower = FollowerMenu[monsterAllyIndex].isTinkeringFollower(myStats->type);
+ bool isTinkeringFollower = FollowerMenu[playerLeader].isTinkeringFollower(myStats->type);
int tinkeringLVL = 0;
int skillLVL = 0;
- if ( stats[monsterAllyIndex] )
+ if ( stats[playerLeader] )
{
- tinkeringLVL = stats[monsterAllyIndex]->getModifiedProficiency(PRO_LOCKPICKING) + statGetPER(stats[monsterAllyIndex], players[monsterAllyIndex]->entity);
- skillLVL = stats[monsterAllyIndex]->getModifiedProficiency(PRO_LEADERSHIP) + statGetCHR(stats[monsterAllyIndex], players[monsterAllyIndex]->entity);
+ tinkeringLVL = stats[playerLeader]->getModifiedProficiency(PRO_LOCKPICKING) + statGetPER(stats[playerLeader], players[playerLeader]->entity);
+ skillLVL = stats[playerLeader]->getModifiedProficiency(PRO_LEADERSHIP) + statGetCHR(stats[playerLeader], players[playerLeader]->entity);
if ( isTinkeringFollower )
{
skillLVL = tinkeringLVL;
}
+ if ( compelSpell )
+ {
+ skillLVL = std::max(skillLVL, stats[playerLeader]->getModifiedProficiency(PRO_MYSTICISM) + statGetCHR(stats[playerLeader], players[playerLeader]->entity));
+ }
}
if ( myStats->type != GYROBOT )
{
- if ( FollowerMenu[monsterAllyIndex].monsterGyroBotOnlyCommand(command) )
+ if ( FollowerMenu[playerLeader].monsterGyroBotOnlyCommand(command) )
{
return;
}
}
else if ( myStats->type == GYROBOT )
{
- if ( FollowerMenu[monsterAllyIndex].monsterGyroBotDisallowedCommands(command) )
+ if ( FollowerMenu[playerLeader].monsterGyroBotDisallowedCommands(command) )
{
return;
}
@@ -11213,7 +12523,7 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
// do a final check if player can use this command.
/*if ( FollowerMenu.optionDisabledForCreature(skillLVL, myStats->type, command) != 0 )
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
*myStats, Language::get(3638), Language::get(3639), MSG_COMBAT);
return;
}*/
@@ -11221,33 +12531,49 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
switch ( command )
{
case ALLY_CMD_RETURN_SOUL:
- if ( monsterAllySummonRank != 0 )
+ if ( monsterAllySummonRank != 0 && myStats->type == SKELETON )
{
float manaToRefund = myStats->MAXMP * (myStats->HP / static_cast(myStats->MAXHP));
setMP(static_cast(manaToRefund));
setHP(0);
- if ( stats[monsterAllyIndex] && stats[monsterAllyIndex]->MP == 0 )
+ if ( stats[playerLeader] && stats[playerLeader]->MP == 0 )
{
- steamAchievementClient(monsterAllyIndex, "BARONY_ACH_EXTERNAL_BATTERY");
+ steamAchievementClient(playerLeader, "BARONY_ACH_EXTERNAL_BATTERY");
}
}
- break;
- case ALLY_CMD_ATTACK_CONFIRM:
- if ( uid != 0 && uid != getUID() )
+ else if ( monsterAllySummonRank != 0 && myStats->type == EARTH_ELEMENTAL )
+ {
+ setHP(0);
+ setObituary(Language::get(6803));
+ }
+ else if ( myStats->type == MONSTER_ADORCISED_WEAPON && myStats->getAttribute("adorcised_weapon") != "" )
+ {
+ setHP(0);
+ setObituary(Language::get(6619));
+ }
+ else if ( myStats->type == FLAME_ELEMENTAL )
+ {
+ setHP(0);
+ setObituary(Language::get(6673));
+ }
+ break;
+ case ALLY_CMD_ATTACK_CONFIRM:
+ if ( uid != 0 && uid != getUID() )
{
Entity* target = uidToEntity(uid);
if ( target )
{
if ( target->behavior == &actMonster || target->behavior == &actPlayer )
{
- if ( stats[monsterAllyIndex] ) // check owner's proficiency.
+ Uint32 oldTarget = this->monsterTarget;
+ if ( stats[playerLeader] ) // check owner's proficiency.
{
if ( skillLVL >= SKILL_LEVEL_MASTER || myStats->type != HUMAN )
{
// attack anything except if FF is off + friend.
if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
{
- if ( !checkFriend(target) )
+ if ( !(checkFriend(target) && players[playerLeader]->entity->friendlyFireProtection(target)) )
{
monsterAcquireAttackTarget(*target, MONSTER_STATE_ATTACK);
handleNPCInteractDialogue(*myStats, ALLY_EVENT_ATTACK);
@@ -11278,6 +12604,10 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
}
}
monsterAllyInteractTarget = 0;
+ //if ( oldTarget != this->monsterTarget )
+ {
+ players[playerLeader]->mechanics.targetsCompelled[getUID()][target->getUID()] = ::ticks;
+ }
}
else
{
@@ -11288,7 +12618,7 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
}
break;
case ALLY_CMD_MOVEASIDE:
- monsterMoveAside(this, players[monsterAllyIndex]->entity);
+ monsterMoveAside(this, players[playerLeader]->entity);
handleNPCInteractDialogue(*myStats, ALLY_EVENT_MOVEASIDE);
break;
case ALLY_CMD_DEFEND:
@@ -11405,11 +12735,11 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
{
if ( skillLVL < SKILL_LEVEL_BASIC )
{
- messagePlayerColor(monsterAllyIndex, MESSAGE_STATUS, 0xFFFFFFFF, Language::get(3680));
+ messagePlayerColor(playerLeader, MESSAGE_STATUS, 0xFFFFFFFF, Language::get(3680));
}
else
{
- messagePlayerColor(monsterAllyIndex, MESSAGE_STATUS, 0xFFFFFFFF, Language::get(3679));
+ messagePlayerColor(playerLeader, MESSAGE_STATUS, 0xFFFFFFFF, Language::get(3679));
}
monsterAllyPickupItems = ALLY_GYRO_DETECT_NONE;
}
@@ -11464,16 +12794,16 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
if ( !droppedSomething )
{
- messagePlayer(monsterAllyIndex, MESSAGE_HINT, Language::get(3868));
+ messagePlayer(playerLeader, MESSAGE_HINT, Language::get(3868));
}
}
- else if ( stats[monsterAllyIndex] )
+ else if ( stats[playerLeader] )
{
Entity* dropped = nullptr;
bool confirmDropped = false;
bool dropWeaponOnly = false;
bool unableToDrop = false;
- Uint32 owner = players[monsterAllyIndex]->entity->getUID();
+ Uint32 owner = players[playerLeader]->entity->getUID();
if ( skillLVL >= SKILL_LEVEL_MASTER )
{
if ( myStats->helmet )
@@ -11630,10 +12960,10 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_WEAPON);
}
}
- /*if ( unableToDrop )
+ if ( unableToDrop )
{
handleNPCInteractDialogue(*myStats, ALLY_EVENT_DROP_FAILED);
- }*/
+ }
}
break;
case ALLY_CMD_MOVETO_CONFIRM:
@@ -11670,10 +13000,10 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
}
case ALLY_CMD_GYRO_RETURN:
{
- if ( players[monsterAllyIndex]->entity )
+ if ( players[playerLeader]->entity )
{
- destX = static_cast(players[monsterAllyIndex]->entity->x) >> 4;
- destY = static_cast(players[monsterAllyIndex]->entity->y) >> 4;
+ destX = static_cast(players[playerLeader]->entity->x) >> 4;
+ destY = static_cast(players[playerLeader]->entity->y) >> 4;
bool noground = false;
const int monsterX = static_cast(x) >> 4;
@@ -11687,7 +13017,7 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
}
}
- if ( entityDist(this, players[monsterAllyIndex]->entity) < STRIKERANGE
+ if ( entityDist(this, players[playerLeader]->entity) < STRIKERANGE
&& !noground )
{
monsterSpecialState = GYRO_RETURN_LANDING;
@@ -11715,7 +13045,7 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
else
{
//messagePlayer(0, "no path to destination");
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFF, *myStats, Language::get(4317), Language::get(4317), MSG_GENERIC);
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFF, *myStats, Language::get(4317), Language::get(4317), MSG_GENERIC);
}
}
break;
@@ -11724,23 +13054,30 @@ void Entity::monsterAllySendCommand(int command, int destX, int destY, Uint32 ui
{
if ( monsterAllySpecialCooldown == 0 )
{
- int duration = TICKS_PER_SECOND * (60);
- if ( myStats->HP < myStats->MAXHP && setEffect(EFF_ASLEEP, true, duration, false) ) // 60 seconds of sleep.
+ if ( myStats->getEffectActive(EFF_COMMAND) )
{
- setEffect(EFF_HP_REGEN, true, duration, false);
- monsterAllySpecial = ALLY_SPECIAL_CMD_REST;
- monsterAllySpecialCooldown = -1; // locked out until next floor.
- serverUpdateEntitySkill(this, 49);
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFF, *myStats, Language::get(398), Language::get(397), MSG_COMBAT);
- if ( players[monsterAllyIndex] && players[monsterAllyIndex]->entity
- && myStats->HP < myStats->MAXHP && local_rng.rand() % 3 == 0 )
- {
- players[monsterAllyIndex]->entity->increaseSkill(PRO_LEADERSHIP);
- }
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF, *myStats, Language::get(514), Language::get(515), MSG_COMBAT);
}
else
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFF, *myStats, Language::get(3880), Language::get(3880), MSG_GENERIC);
+ int duration = TICKS_PER_SECOND * (60);
+ if ( myStats->HP < myStats->MAXHP && setEffect(EFF_ASLEEP, true, duration, false) ) // 60 seconds of sleep.
+ {
+ setEffect(EFF_HP_REGEN, true, duration, false);
+ monsterAllySpecial = ALLY_SPECIAL_CMD_REST;
+ monsterAllySpecialCooldown = -1; // locked out until next floor.
+ serverUpdateEntitySkill(this, 49);
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFF, *myStats, Language::get(398), Language::get(397), MSG_COMBAT);
+ if ( players[playerLeader] && players[playerLeader]->entity
+ && myStats->HP < myStats->MAXHP && local_rng.rand() % 3 == 0 )
+ {
+ players[playerLeader]->entity->increaseSkill(PRO_LEADERSHIP);
+ }
+ }
+ else
+ {
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFF, *myStats, Language::get(3880), Language::get(3880), MSG_GENERIC);
+ }
}
}
break;
@@ -11849,7 +13186,7 @@ void Entity::clearMonsterInteract()
interactedByMonster = 0;
}
-bool Entity::monsterSetPathToLocation(int destX, int destY, int adjacentTilesToCheck, int pathingType, bool tryRandomSpot)
+bool Entity::monsterSetPathToLocation(int destX, int destY, int adjacentTilesToCheck, int pathingType, bool tryRandomSpot, bool shortByShortest)
{
int u, v;
bool foundplace = false;
@@ -11858,8 +13195,15 @@ bool Entity::monsterSetPathToLocation(int destX, int destY, int adjacentTilesToC
if ( static_cast(x / 16) == destX && static_cast(y / 16) == destY )
{
- monsterAllyFormations.updateOnPathSucceed(getUID(), this);
- return true; // we're trying to move to the spot we're already at!
+ if ( getStats() && getStats()->type == DUCK_SMALL && tryRandomSpot )
+ {
+ // allow small movements
+ }
+ else
+ {
+ monsterAllyFormations.updateOnPathSucceed(getUID(), this);
+ return true; // we're trying to move to the spot we're already at!
+ }
}
else if ( !checkObstacle((destX << 4) + 8, (destY << 4) + 8, this, nullptr) )
{
@@ -11893,9 +13237,31 @@ bool Entity::monsterSetPathToLocation(int destX, int destY, int adjacentTilesToC
if ( !possibleDestinations.empty() )
{
// sort by distance from monster, first result is shortest path.
- std::sort(possibleDestinations.begin(), possibleDestinations.end());
- pathToX = possibleDestinations.at(0).second.first;
- pathToY = possibleDestinations.at(0).second.second;
+ if ( shortByShortest )
+ {
+ std::sort(possibleDestinations.begin(), possibleDestinations.end());
+ pathToX = possibleDestinations.at(0).second.first;
+ pathToY = possibleDestinations.at(0).second.second;
+ }
+ else
+ {
+ int pick = local_rng.rand() % possibleDestinations.size();
+ pathToX = possibleDestinations.at(pick).second.first;
+ pathToY = possibleDestinations.at(pick).second.second;
+ if ( getStats() && getStats()->type == DUCK_SMALL )
+ {
+ if ( pathToX == destX && pathToY == destY )
+ {
+ // re-roll
+ if ( local_rng.rand() % 4 > 0 )
+ {
+ pick = local_rng.rand() % possibleDestinations.size();
+ pathToX = possibleDestinations.at(pick).second.first;
+ pathToY = possibleDestinations.at(pick).second.second;
+ }
+ }
+ }
+ }
foundplace = true;
}
@@ -11998,11 +13364,20 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
{
return;
}
- if ( monsterAllyIndex < 0 || monsterAllyIndex >= MAXPLAYERS )
+
+ int playerLeader = monsterAllyIndex;
+ bool compelSpell = false;
+ if ( myStats.getEffectActive(EFF_COMMAND) >= 1 && myStats.getEffectActive(EFF_COMMAND) < MAXPLAYERS + 1 )
+ {
+ playerLeader = myStats.getEffectActive(EFF_COMMAND) - 1;
+ compelSpell = true;
+ }
+
+ if ( playerLeader < 0 || playerLeader >= MAXPLAYERS )
{
return;
}
- if ( !stats[monsterAllyIndex] )
+ if ( !stats[playerLeader] )
{
return;
}
@@ -12026,9 +13401,9 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
message = Language::get(3077 + local_rng.rand() % 2);
break;
case ALLY_EVENT_INTERACT_ITEM_CURSED:
- if ( FollowerMenu[monsterAllyIndex].entityToInteractWith && FollowerMenu[monsterAllyIndex].entityToInteractWith->behavior == &actItem )
+ if ( FollowerMenu[playerLeader].entityToInteractWith && FollowerMenu[playerLeader].entityToInteractWith->behavior == &actItem )
{
- Item* item = newItemFromEntity(FollowerMenu[monsterAllyIndex].entityToInteractWith);
+ Item* item = newItemFromEntity(FollowerMenu[playerLeader].entityToInteractWith);
if ( item )
{
char fullmsg[256];
@@ -12172,13 +13547,13 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
for ( int c = 0; c < MAXPLAYERS; ++c )
{
players[c]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(),
- Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_ATTACK, message.c_str(), stats[monsterAllyIndex]->name);
+ Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_ATTACK, message.c_str(), stats[playerLeader]->name);
}
}
else
{
- players[monsterAllyIndex]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(),
- Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_FOLLOWER_CMD, message.c_str(), stats[monsterAllyIndex]->name);
+ players[playerLeader]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(),
+ Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_FOLLOWER_CMD, message.c_str(), stats[playerLeader]->name);
}
}
}
@@ -12192,9 +13567,9 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
case ALLY_EVENT_MOVEASIDE:
if ( local_rng.getU8() % 8 == 0 )
{
- players[monsterAllyIndex]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(),
+ players[playerLeader]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(),
Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_FOLLOWER_CMD, Language::get(getMonsterInteractGreeting(myStats)), stats[monsterAllyIndex]->name);
- //messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ //messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
// myStats, Language::get(3129), Language::get(3130), MSG_COMBAT);
}
break;
@@ -12207,19 +13582,19 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
case ALLY_EVENT_MOVETO_FAIL:
if ( local_rng.rand() % 2 == 0 )
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, Language::get(3131), Language::get(3132), MSG_COMBAT);
}
else
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, Language::get(3133), Language::get(3134), MSG_COMBAT);
}
break;
case ALLY_EVENT_INTERACT_ITEM_CURSED:
- if ( FollowerMenu[monsterAllyIndex].entityToInteractWith && FollowerMenu[monsterAllyIndex].entityToInteractWith->behavior == &actItem )
+ if ( FollowerMenu[playerLeader].entityToInteractWith && FollowerMenu[playerLeader].entityToInteractWith->behavior == &actItem )
{
- Item* item = newItemFromEntity(FollowerMenu[monsterAllyIndex].entityToInteractWith);
+ Item* item = newItemFromEntity(FollowerMenu[playerLeader].entityToInteractWith);
if ( item )
{
char fullmsg[256] = "";
@@ -12267,7 +13642,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
char namedStr[128];
strcpy(namedStr, Language::get(3120));
strcat(namedStr, fullmsg);
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, genericStr, namedStr, MSG_COMBAT);
}
}
@@ -12280,7 +13655,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
case ALLY_EVENT_INTERACT_ITEM_NOUSE:
case ALLY_EVENT_INTERACT_ITEM_FOOD_FULL:
{
- Item* item = newItemFromEntity(FollowerMenu[monsterAllyIndex].entityToInteractWith);
+ Item* item = newItemFromEntity(FollowerMenu[playerLeader].entityToInteractWith);
if ( item )
{
char itemString[64] = "";
@@ -12289,7 +13664,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
strcat(genericStr, itemString);
strcpy(namedStr, Language::get(3122));
strcat(namedStr, itemString);
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, genericStr, namedStr, MSG_COMBAT);
free(item);
}
@@ -12299,7 +13674,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
case ALLY_EVENT_INTERACT_ITEM_FOOD_GOOD:
case ALLY_EVENT_INTERACT_ITEM_FOOD_ROTTEN:
{
- Item* item = newItemFromEntity(FollowerMenu[monsterAllyIndex].entityToInteractWith);
+ Item* item = newItemFromEntity(FollowerMenu[playerLeader].entityToInteractWith);
if ( item )
{
char itemString[64] = "";
@@ -12308,7 +13683,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
strcat(genericStr, itemString);
strcpy(namedStr, Language::get(3122));
strcat(namedStr, itemString);
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, genericStr, namedStr, MSG_COMBAT);
free(item);
}
@@ -12326,7 +13701,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
strcat(genericStr, Language::get(3125));
strcpy(namedStr, Language::get(3122));
strcat(namedStr, Language::get(3125));
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, genericStr, namedStr, MSG_COMBAT);
break;
case ALLY_EVENT_DROP_EQUIP:
@@ -12334,7 +13709,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
strcat(genericStr, Language::get(3126));
strcpy(namedStr, Language::get(3122));
strcat(namedStr, Language::get(3126));
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, genericStr, namedStr, MSG_COMBAT);
break;
case ALLY_EVENT_DROP_ALL:
@@ -12342,30 +13717,38 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event)
strcat(genericStr, Language::get(3127));
strcpy(namedStr, Language::get(3122));
strcat(namedStr, Language::get(3127));
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
+ myStats, genericStr, namedStr, MSG_COMBAT);
+ break;
+ case ALLY_EVENT_DROP_FAILED:
+ strcpy(genericStr, Language::get(3121));
+ strcat(genericStr, Language::get(6970));
+ strcpy(namedStr, Language::get(3122));
+ strcat(namedStr, Language::get(6970));
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, genericStr, namedStr, MSG_COMBAT);
break;
case ALLY_EVENT_WAIT:
if ( myStats.type == SENTRYBOT || myStats.type == SPELLBOT )
{
- messagePlayerColor(monsterAllyIndex, MESSAGE_STATUS, 0xFFFFFFFF,
+ messagePlayerColor(playerLeader, MESSAGE_STATUS, 0xFFFFFFFF,
Language::get(3676), monstertypename[myStats.type]);
}
else
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, Language::get(3105), Language::get(3106), MSG_COMBAT);
}
break;
case ALLY_EVENT_FOLLOW:
if ( myStats.type == SENTRYBOT || myStats.type == SPELLBOT )
{
- messagePlayerColor(monsterAllyIndex, MESSAGE_STATUS, 0xFFFFFFFF,
+ messagePlayerColor(playerLeader, MESSAGE_STATUS, 0xFFFFFFFF,
Language::get(3677), monstertypename[myStats.type]);
}
else
{
- messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF,
+ messagePlayerMonsterEvent(playerLeader, 0xFFFFFFFF,
myStats, Language::get(529), Language::get(3128), MSG_COMBAT);
}
break;
@@ -12388,6 +13771,14 @@ int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat&
return MONSTER_DEFEND_NONE;
}
+ if ( myStats.type == EARTH_ELEMENTAL )
+ {
+ if ( local_rng.rand() % 20 < 8 )
+ {
+ return MONSTER_DEFEND_HOLD;
+ }
+ }
+
if ( !myStats.shield )
{
return MONSTER_DEFEND_NONE;
@@ -12415,7 +13806,7 @@ int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat&
bool isPlayerAlly = (monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS);
- if ( !(isPlayerAlly || myStats.type == HUMAN || myStats.type == BUGBEAR) )
+ if ( !(isPlayerAlly || myStats.type == HUMAN || myStats.type == BUGBEAR || myStats.type == GREMLIN || myStats.type == GOATMAN) )
{
return MONSTER_DEFEND_NONE;
}
@@ -12619,6 +14010,15 @@ bool Entity::monsterConsumeFoodEntity(Entity* food, Stat* myStats)
switch ( item->type )
{
case FOOD_APPLE:
+ case FOOD_NUT:
+ case FOOD_SHROOM:
+ case FOOD_RATION:
+ case FOOD_RATION_SPICY:
+ case FOOD_RATION_SOUR:
+ case FOOD_RATION_BITTER:
+ case FOOD_RATION_HEARTY:
+ case FOOD_RATION_HERBAL:
+ case FOOD_RATION_SWEET:
heal = 5 + item->beatitude;
buffDuration = std::min(buffDuration, 2 * TICKS_PER_SECOND);
myStats->HUNGER += 200;
@@ -12682,7 +14082,7 @@ bool Entity::monsterConsumeFoodEntity(Entity* food, Stat* myStats)
if ( buffDuration > 0 )
{
- myStats->EFFECTS[EFF_HP_REGEN] = true;
+ myStats->setEffectActive(EFF_HP_REGEN, 1);
myStats->EFFECTS_TIMERS[EFF_HP_REGEN] = buffDuration;
}
@@ -12711,7 +14111,7 @@ bool Entity::monsterConsumeFoodEntity(Entity* food, Stat* myStats)
return foodEntityConsumed;
}
-Entity* Entity::monsterAllyGetPlayerLeader()
+Entity* Entity::monsterAllyGetPlayerLeader() const
{
if ( behavior != &actMonster )
{
@@ -12847,6 +14247,15 @@ bool Entity::monsterAllyEquipmentInClass(const Item& item) const
case IRON_BREASTPIECE:
case IRON_HELM:
case IRON_SHIELD:
+ case BONE_BREASTPIECE:
+ case BLACKIRON_BREASTPIECE:
+ case SILVER_BREASTPIECE:
+ case SCUTUM:
+ case BLACKIRON_SHIELD:
+ case SILVER_SHIELD:
+ case BONE_HELM:
+ case BLACKIRON_HELM:
+ case SILVER_HELM:
return false;
break;
default:
@@ -12949,8 +14358,8 @@ void Entity::monsterHandleKnockbackVelocity(real_t monsterFacingTangent, real_t
{
// this function makes the monster accelerate to running forwards or 0 movement speed after being knocked back.
// vel_x, vel_y are set on knockback impact and this slowly accumulates speed from the knocked back movement by a factor of monsterKnockbackVelocity.
- real_t maxVelX = cos(monsterFacingTangent) * .045 * (monsterGetDexterityForMovement() + 10) * weightratio;
- real_t maxVelY = sin(monsterFacingTangent) * .045 * (monsterGetDexterityForMovement() + 10) * weightratio;
+ real_t maxVelX = cos(monsterFacingTangent) * .045 * (std::max(0, monsterGetDexterityForMovement()) + 10) * weightratio;
+ real_t maxVelY = sin(monsterFacingTangent) * .045 * (std::max(0, monsterGetDexterityForMovement()) + 10) * weightratio;
bool mobile = ((monsterState == MONSTER_STATE_WAIT) || isMobile()); // if immobile, the intended max speed is 0 (stopped).
if ( maxVelX > 0 )
@@ -12969,7 +14378,14 @@ void Entity::monsterHandleKnockbackVelocity(real_t monsterFacingTangent, real_t
{
this->vel_y = std::max(this->vel_y + (this->monsterKnockbackVelocity * maxVelY), mobile ? maxVelY : 0.0);
}
- this->monsterKnockbackVelocity *= 1.1;
+ if ( getStats() && getStats()->getEffectActive(EFF_MAGIC_GREASE) )
+ {
+ //this->monsterKnockbackVelocity *= 1.005;
+ }
+ else
+ {
+ this->monsterKnockbackVelocity *= 1.1;
+ }
}
int Entity::monsterGetDexterityForMovement()
@@ -12982,7 +14398,7 @@ int Entity::monsterGetDexterityForMovement()
Stat* myStats = getStats();
if ( myStats )
{
- if ( myStats->EFFECTS[EFF_DASH] )
+ if ( myStats->getEffectActive(EFF_DASH) )
{
myDex += 30;
}
@@ -13239,9 +14655,26 @@ void Entity::monsterGenerateQuiverItem(Stat* myStats, bool lesserMonster)
int Entity::getMonsterEffectiveDistanceOfRangedWeapon(Item* weapon)
{
+ int distance = 160;
+ if ( Stat* myStats = getStats() )
+ {
+ if ( myStats->type == DRYAD )
+ {
+ if ( myStats->getAttribute("monster_d_type") == "watcher" )
+ {
+ distance = 64;
+ if ( monsterStrafeDirection > 0 && monsterSpecialState == 0 )
+ {
+ distance = STRIKERANGE;
+ }
+ return distance;
+ }
+ }
+ }
+
if ( !weapon )
{
- return 160;
+ return distance;
}
if ( getMonsterTypeFromSprite() == SENTRYBOT )
@@ -13253,15 +14686,17 @@ int Entity::getMonsterEffectiveDistanceOfRangedWeapon(Item* weapon)
return sightranges[SPELLBOT];
}
- int distance = 160;
switch ( weapon->type )
{
case SLING:
case CROSSBOW:
case HEAVY_CROSSBOW:
+ case BLACKIRON_CROSSBOW:
distance = 100;
break;
case LONGBOW:
+ case BRANCH_BOW:
+ case BRANCH_BOW_INFECTED:
distance = 200;
break;
default:
@@ -13321,6 +14756,43 @@ bool Entity::isUntargetableBat(real_t* outDist) const
return false;
}
+bool Entity::monsterIsTargetable(bool targetInertMimics) const
+{
+ if ( flags[PASSABLE] )
+ {
+ return false;
+ }
+ if ( behavior == &actPlayer )
+ {
+ return true;
+ }
+ else if ( behavior == &actMonster )
+ {
+ Monster type = getMonsterTypeFromSprite();
+ if ( type == GYROBOT )
+ {
+ return false;
+ }
+ else if ( type == BAT_SMALL && isUntargetableBat() )
+ {
+ return false;
+ }
+ else if ( type == MOTH_SMALL && getStats() && getStats()->getAttribute("fire_sprite") != "" )
+ {
+ return false;
+ }
+ else if ( type == DUCK_SMALL )
+ {
+ return false;
+ }
+ else if ( type == MIMIC && !targetInertMimics && isInertMimic() )
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
void batResetIdle(Entity* my)
{
if ( !my ) { return; }
@@ -13439,3 +14911,512 @@ void mimicResetIdle(Entity* my)
my->monsterLookDir = (PI / 2) * (local_rng.rand() % 4);
}
}
+
+static ConsoleVariable cvar_monster_debug_models("/monster_debug_models", false);
+bool monsterDebugModels(Entity* my, real_t* dist)
+{
+#ifdef NDEBUG
+ return false;
+#endif
+ if ( !*cvar_monster_debug_models )
+ {
+ return false;
+ }
+ static Uint32 thisTick = 0;
+ if ( thisTick == ticks )
+ {
+ return false;
+ }
+ thisTick = ticks;
+ if ( !my )
+ {
+ return false;
+ }
+ Stat* myStats = my->getStats();
+ if ( !myStats )
+ {
+ return false;
+ }
+#ifndef NDEBUG
+ static bool forceWalk = false;
+ if ( keystatus[SDLK_KP_5] )
+ {
+ keystatus[SDLK_KP_5] = 0;
+ forceWalk = !forceWalk;
+ }
+ if ( keystatus[SDLK_KP_6] )
+ {
+ keystatus[SDLK_KP_6] = 0;
+ myStats->setEffectValueUnsafe(EFF_STUNNED, myStats->getEffectActive(EFF_STUNNED) ? 0 : 1);
+ }
+ if ( keystatus[SDLK_KP_PLUS] )
+ {
+ my->fskill[0] += 0.05;
+ }
+ if ( keystatus[SDLK_KP_MINUS] )
+ {
+ my->fskill[0] -= 0.05;
+ }
+ if ( forceWalk )
+ {
+ if ( dist )
+ {
+ *dist = 0.15;
+ }
+ }
+
+ if ( keystatus[SDLK_9] )
+ {
+ keystatus[SDLK_9] = 0;
+ if ( myStats->helmet )
+ {
+ myStats->helmet->appearance++;
+ }
+ }
+ if ( keystatus[SDLK_0] )
+ {
+ keystatus[SDLK_0] = 0;
+ if ( myStats->mask )
+ {
+ myStats->mask->appearance++;
+ }
+ }
+
+ if ( keystatus[SDLK_KP_7] )
+ {
+ keystatus[SDLK_KP_7] = 0;
+ my->attack(my->getAttackPose(), 0, nullptr);
+ }
+
+ if ( keystatus[SDLK_6] )
+ {
+ keystatus[SDLK_6] = 0;
+ if ( myStats->weapon )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->weapon->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->weapon->type = (ItemType)type;
+ if ( items[myStats->weapon->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_WEAPON )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->weapon->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ if ( myStats->weapon->node )
+ {
+ list_RemoveNode(myStats->weapon->node);
+ }
+ else
+ {
+ free(myStats->weapon);
+ }
+ myStats->weapon = nullptr;
+ break;
+ }
+ myStats->weapon->type = (ItemType)type;
+ if ( items[myStats->weapon->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_WEAPON )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->weapon = newItem(QUARTERSTAFF, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+ if ( keystatus[SDLK_5] )
+ {
+ keystatus[SDLK_5] = 0;
+ if ( myStats->shield )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->shield->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->shield->type = (ItemType)type;
+ if ( items[myStats->shield->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_SHIELD )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->shield->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ if ( myStats->shield->node )
+ {
+ list_RemoveNode(myStats->shield->node);
+ }
+ else
+ {
+ free(myStats->shield);
+ }
+ myStats->shield = nullptr;
+ break;
+ }
+ myStats->shield->type = (ItemType)type;
+ if ( items[myStats->shield->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_SHIELD )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->shield = newItem(WOODEN_SHIELD, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+ if ( keystatus[SDLK_7] )
+ {
+ keystatus[SDLK_7] = 0;
+ if ( myStats->shoes )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->shoes->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->shoes->type = (ItemType)type;
+ if ( items[myStats->shoes->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BOOTS )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->shoes->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ if ( myStats->shoes->node )
+ {
+ list_RemoveNode(myStats->shoes->node);
+ }
+ else
+ {
+ free(myStats->shoes);
+ }
+ myStats->shoes = nullptr;
+ break;
+ }
+ myStats->shoes->type = (ItemType)type;
+ if ( items[myStats->shoes->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BOOTS )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->shoes = newItem(LEATHER_BOOTS, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+ if ( keystatus[SDLK_8] )
+ {
+ keystatus[SDLK_8] = 0;
+ if ( myStats->gloves )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->gloves->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->gloves->type = (ItemType)type;
+ if ( items[myStats->gloves->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_GLOVES )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->gloves->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ if ( myStats->gloves->node )
+ {
+ list_RemoveNode(myStats->gloves->node);
+ }
+ else
+ {
+ free(myStats->gloves);
+ }
+ myStats->gloves = nullptr;
+ break;
+ }
+ myStats->gloves->type = (ItemType)type;
+ if ( items[myStats->gloves->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_GLOVES )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->gloves = newItem(GLOVES, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+ if ( keystatus[SDLK_2] )
+ {
+ keystatus[SDLK_2] = 0;
+ if ( myStats->helmet )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->helmet->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->helmet->type = (ItemType)type;
+ if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->helmet->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ type = 0;
+ }
+ myStats->helmet->type = (ItemType)type;
+ if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->helmet = newItem(LEATHER_HELM, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+ if ( keystatus[SDLK_4] )
+ {
+ keystatus[SDLK_4] = 0;
+ if ( myStats->breastplate )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->breastplate->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->breastplate->type = (ItemType)type;
+ if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->breastplate->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ if ( myStats->breastplate->node )
+ {
+ list_RemoveNode(myStats->breastplate->node);
+ }
+ else
+ {
+ free(myStats->breastplate);
+ }
+ myStats->breastplate = nullptr;
+ break;
+ }
+ myStats->breastplate->type = (ItemType)type;
+ if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->breastplate = newItem(LEATHER_BREASTPIECE, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+ if ( keystatus[SDLK_1] )
+ {
+ keystatus[SDLK_1] = 0;
+ if ( myStats->mask )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->mask->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->mask->type = (ItemType)type;
+ if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->mask->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ type = 0;
+ }
+ myStats->mask->type = (ItemType)type;
+ if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->mask = newItem(GEM_GLASS, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+
+ if ( keystatus[SDLK_MINUS] )
+ {
+ keystatus[SDLK_MINUS] = 0;
+ if ( myStats->cloak )
+ {
+ if ( keystatus[SDLK_LSHIFT] )
+ {
+ while ( true )
+ {
+ int type = myStats->cloak->type;
+ type--;
+ if ( type < 0 )
+ {
+ type = NUMITEMS - 1;
+ }
+ myStats->cloak->type = (ItemType)type;
+ if ( items[myStats->cloak->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_CLOAK )
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ while ( true )
+ {
+ int type = myStats->cloak->type;
+ type++;
+ if ( type >= NUMITEMS )
+ {
+ type = 0;
+ }
+ myStats->cloak->type = (ItemType)type;
+ if ( items[myStats->cloak->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_CLOAK )
+ {
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ myStats->cloak = newItem(CLOAK, EXCELLENT, 0, 1, 0, true, nullptr);
+ }
+ }
+
+#endif
+ return true;
+}
+
+bool Entity::monsterCanTradeWith(int player) const
+{
+ if ( behavior == &actMonster )
+ {
+ Monster type = NOTHING;
+ if ( Stat* myStats = getStats() )
+ {
+ type = myStats->type;
+ }
+ else
+ {
+ type = getMonsterTypeFromSprite();
+ }
+
+ /*if ( type == AUTOMATON || type == KOBOLD )
+ {
+ return true;
+ }*/
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/src/actpedestal.cpp b/src/actpedestal.cpp
index 2fde2db46..60573b915 100644
--- a/src/actpedestal.cpp
+++ b/src/actpedestal.cpp
@@ -230,14 +230,14 @@ void Entity::actPedestalBase()
switch ( pedestalOrbType )
{
case 1: // blue
- if ( stats[i] && !stats[i]->EFFECTS[EFF_SHRINE_BLUE_BUFF] )
+ if ( stats[i] && !stats[i]->getEffectActive(EFF_SHRINE_BLUE_BUFF) )
{
messagePlayer(i, MESSAGE_INTERACTION, Language::get(2910));
}
players[i]->entity->setEffect(EFF_SHRINE_BLUE_BUFF, true, 1000, false);
break;
case 2: // red
- if ( stats[i] && !stats[i]->EFFECTS[EFF_SHRINE_RED_BUFF] )
+ if ( stats[i] && !stats[i]->getEffectActive(EFF_SHRINE_RED_BUFF) )
{
messagePlayer(i, MESSAGE_INTERACTION, Language::get(2904));
}
@@ -246,7 +246,7 @@ void Entity::actPedestalBase()
case 3:
break;
case 4: // green
- if ( stats[i] && !stats[i]->EFFECTS[EFF_SHRINE_GREEN_BUFF] )
+ if ( stats[i] && !stats[i]->getEffectActive(EFF_SHRINE_GREEN_BUFF) )
{
messagePlayer(i, MESSAGE_INTERACTION, Language::get(2909));
}
diff --git a/src/actplayer.cpp b/src/actplayer.cpp
index 82b2bf4ea..8ed94c926 100644
--- a/src/actplayer.cpp
+++ b/src/actplayer.cpp
@@ -43,6 +43,9 @@ bool partymode = false;
static ConsoleVariable cvar_calloutStartZ("/callout_start_z", -2.5);
static ConsoleVariable cvar_calloutMoveTo("/callout_moveto_z", 0.1);
static ConsoleVariable cvar_calloutStartZLimit("/callout_start_z_limit", 7.5);
+static ConsoleVariable cvar_followerStartZ("/follower_start_z", -2.5);
+static ConsoleVariable cvar_followerMoveTo("/follower_moveto_z", 0.1);
+static ConsoleVariable cvar_followerStartZLimit("/follower_start_z_limit", 7.5);
/*-------------------------------------------------------------------------------
@@ -62,6 +65,8 @@ static ConsoleVariable cvar_calloutStartZLimit("/callout_start_z_limit",
#define GHOSTCAM_DEACTIVATED my->skill[7]
#define GHOSTCAM_SPAWN_ANIM my->skill[8]
#define GHOSTCAM_SPAWN_ANIM_COMPLETE my->skill[9]
+#define GHOSTCAM_COSMETIC_SPRITE my->skill[10]
+#define GHOSTCAM_SPIRIT_HIGH_PROFILE my->skill[11]
#define GHOSTCAM_BOB my->fskill[0]
#define GHOSTCAM_BOBMOVE my->fskill[1]
#define GHOSTCAM_DX my->fskill[3]
@@ -73,6 +78,7 @@ static ConsoleVariable cvar_calloutStartZLimit("/callout_start_z_limit",
#define GHOSTCAM_SQUISH_ANGLE my->fskill[9]
#define GHOSTCAM_WEAVE my->fskill[10]
#define GHOSTCAM_HOVER my->fskill[11]
+#define GHOSTCAM_THIRD_PERSON_CUSTOM my->fskill[12]
static ConsoleVariable cvar_player_can_move_in_gui("/player_can_move_in_gui", true);
bool playerAllowedMovement(const int playernum)
@@ -115,20 +121,34 @@ void Player::Ghost_t::handleGhostCameraBobbing(bool useRefreshRateDelta)
&& ((input.binary("Move Forward") || input.binary("Move Backward"))
|| (input.binary("Move Left") - input.binary("Move Right"))))
|| (inputs.hasController(playernum)
- && (inputs.getController(playernum)->getLeftXPercentForPlayerMovement()
- || inputs.getController(playernum)->getLeftYPercentForPlayerMovement()))) )
+ && (inputs.getController(playernum)->getLeftXPercentForPlayerMovement(playernum)
+ || inputs.getController(playernum)->getLeftYPercentForPlayerMovement(playernum)))) )
{
if ( !player.usingCommand()
&& player.bControlEnabled )
{
- if ( !GHOSTCAM_SNEAKING )
+ if ( player.ghost.isSpiritGhost() )
{
- GHOSTCAM_BOBMOVE += 0.0125 * *cvar_ghostBobSpeed;
+ if ( GHOSTCAM_SPIRIT_HIGH_PROFILE )
+ {
+ reset = true;
+ }
+ else
+ {
+ GHOSTCAM_BOBMOVE += 0.0125 * *cvar_ghostBobSpeed;
+ }
}
else
{
- //GHOSTCAM_BOBMOVE += 0.025;
- reset = true;
+ if ( !GHOSTCAM_SNEAKING )
+ {
+ GHOSTCAM_BOBMOVE += 0.0125 * *cvar_ghostBobSpeed;
+ }
+ else
+ {
+ //GHOSTCAM_BOBMOVE += 0.025;
+ reset = true;
+ }
}
}
else
@@ -180,7 +200,7 @@ void Player::Ghost_t::handleGhostMovement(const bool useRefreshRateDelta)
bool allowMovement = isControllable() && playerAllowedMovement(player.playernum);
static ConsoleVariable cvar_ghostSpeed("/ghost_speed", 1.5);
static ConsoleVariable cvar_ghostDrag("/ghost_drag", 0.95);
-
+ real_t drag = *cvar_ghostDrag;
if ( ((!player.usingCommand() && player.bControlEnabled && !gamePaused))
&& allowMovement )
{
@@ -199,12 +219,12 @@ void Player::Ghost_t::handleGhostMovement(const bool useRefreshRateDelta)
if ( inputs.hasController(player.playernum) /*&& !input.binary("Move Left") && !input.binary("Move Right")*/ )
{
- x_force = inputs.getController(player.playernum)->getLeftXPercentForPlayerMovement();
+ x_force = inputs.getController(player.playernum)->getLeftXPercentForPlayerMovement(player.playernum);
}
if ( inputs.hasController(player.playernum) /*&& !input.binary("Move Forward") && !input.binary("Move Backward")*/ )
{
- y_force = inputs.getController(player.playernum)->getLeftYPercentForPlayerMovement();
+ y_force = inputs.getController(player.playernum)->getLeftYPercentForPlayerMovement(player.playernum);
if ( y_force < 0 )
{
y_force *= backpedalMultiplier; //Move backwards more slowly.
@@ -212,16 +232,43 @@ void Player::Ghost_t::handleGhostMovement(const bool useRefreshRateDelta)
}
}
- real_t speedFactor = *cvar_ghostSpeed;// getSpeedFactor(weightratio, statGetDEX(stats[PLAYER_NUM], players[PLAYER_NUM]->entity));
+ real_t speedFactor = *cvar_ghostSpeed;
+
+ int speedMult = 1;
+ bool isSpirit = isSpiritGhost();
+ if ( !isSpirit )
+ {
+ speedMult += GHOSTCAM_SNEAKING;
+ }
+ else
+ {
+ if ( node_t* node = list_Node(&my->children, 2) )
+ {
+ if ( Entity* entity = (Entity*)node->element )
+ {
+ if ( Entity::getMonsterTypeFromSprite(entity->sprite) == DUCK_SMALL )
+ {
+ if ( my->monsterSpecialState == DUCK_INERT )
+ {
+ speedMult += 1;
+ drag = 0.9;
+ speedFactor = 3.0;
+ }
+ }
+ }
+ }
+ }
+
speedFactor *= refreshRateDelta;
- my->vel_x += y_force * cos(my->yaw) * .045 * speedFactor / (1 + GHOSTCAM_SNEAKING);
- my->vel_y += y_force * sin(my->yaw) * .045 * speedFactor / (1 + GHOSTCAM_SNEAKING);
- my->vel_x += x_force * cos(my->yaw + PI / 2) * .0225 * speedFactor / (1 + GHOSTCAM_SNEAKING);
- my->vel_y += x_force * sin(my->yaw + PI / 2) * .0225 * speedFactor / (1 + GHOSTCAM_SNEAKING);
+
+ my->vel_x += y_force * cos(my->yaw) * .045 * speedFactor / (speedMult);
+ my->vel_y += y_force * sin(my->yaw) * .045 * speedFactor / (speedMult);
+ my->vel_x += x_force * cos(my->yaw + PI / 2) * .0225 * speedFactor / (speedMult);
+ my->vel_y += x_force * sin(my->yaw + PI / 2) * .0225 * speedFactor / (speedMult);
}
- my->vel_x *= pow(*cvar_ghostDrag, refreshRateDelta);
- my->vel_y *= pow(*cvar_ghostDrag, refreshRateDelta);
+ my->vel_x *= pow(drag, refreshRateDelta);
+ my->vel_y *= pow(drag, refreshRateDelta);
/*for ( int i = 0; i < MAXPLAYERS; ++i )
{
@@ -371,13 +418,15 @@ bool Player::Ghost_t::allowedInteractEntity(Entity& entity)
{
if ( entity.behavior == &actItem
|| entity.behavior == &actDoor
+ || entity.behavior == &actIronDoor
|| entity.behavior == &actSwitch
|| entity.behavior == &actSwitchWithTimer
|| entity.behavior == &actPowerCrystal
|| entity.behavior == &actPowerCrystalBase
|| entity.behavior == &actTeleportShrine
|| entity.behavior == &::actDaedalusShrine
- || entity.behavior == &actTeleporter )
+ || entity.behavior == &actTeleporter
+ || entity.behavior == &actWallButton )
{
return true;
}
@@ -396,6 +445,15 @@ bool Player::Ghost_t::isActive()
return false;
}
+bool Player::Ghost_t::isSpiritGhost()
+{
+ if ( my )
+ {
+ return GHOSTCAM_COSMETIC_SPRITE != 0 && players[GHOSTCAM_PLAYERNUM]->entity && stats[GHOSTCAM_PLAYERNUM]->getEffectActive(EFF_PROJECT_SPIRIT);
+ }
+ return false;
+}
+
void Player::Ghost_t::handleAttack()
{
if ( !isActive() )
@@ -442,10 +500,13 @@ void Player::Ghost_t::handleAttack()
--errorFlashTeleportTicks;
}
+ bool spiritGhost = isSpiritGhost();
+
Input& input = Input::inputs[player.playernum];
bool attack = false;
bool useSpell = false;
bool defending = false;
+ bool highProfile = spiritGhost ? GHOSTCAM_SPIRIT_HIGH_PROFILE : false;
if ( !player.usingCommand()
&& player.bControlEnabled
&& !gamePaused
@@ -488,19 +549,35 @@ void Player::Ghost_t::handleAttack()
}
}
}
- if ( input.binary("Defend") )
+
+ if ( spiritGhost )
+ {
+ if ( player.shootmode && !CalloutMenu[player.playernum].calloutMenuIsOpen() && !FollowerMenu[player.playernum].followerMenuIsOpen() )
+ {
+ if ( input.consumeBinaryToggle("Defend") )
+ {
+ highProfile = !highProfile;
+ playSoundEntityLocal(my, highProfile ? 794 : 795, 64);
+ }
+ }
+ }
+ else
{
- defending = true;
+ if ( input.binary("Defend") )
+ {
+ defending = true;
+ }
}
}
- if ( GHOSTCAM_SNEAKING != defending || ticks % 120 == 0 )
+ if ( GHOSTCAM_SNEAKING != defending || GHOSTCAM_SPIRIT_HIGH_PROFILE != highProfile || ticks % 120 == 0 )
{
if ( multiplayer == CLIENT )
{
strcpy((char*)net_packet->data, "GHOD");
net_packet->data[4] = player.playernum;
- net_packet->data[5] = defending;
+ net_packet->data[5] = defending ? (1 << 0) : 0;
+ net_packet->data[5] |= highProfile ? (1 << 1) : 0;
net_packet->address.host = net_server.host;
net_packet->address.port = net_server.port;
net_packet->len = 6;
@@ -510,7 +587,8 @@ void Player::Ghost_t::handleAttack()
{
strcpy((char*)net_packet->data, "GHOD");
net_packet->data[4] = player.playernum;
- net_packet->data[5] = defending;
+ net_packet->data[5] = defending ? (1 << 0) : 0;
+ net_packet->data[5] |= highProfile ? (1 << 1) : 0;
net_packet->len = 6;
for ( int c = 1; c < MAXPLAYERS; ++c )
{
@@ -526,6 +604,7 @@ void Player::Ghost_t::handleAttack()
}
}
GHOSTCAM_SNEAKING = defending;
+ GHOSTCAM_SPIRIT_HIGH_PROFILE = highProfile;
const int castLoopDuration = 4 * TICKS_PER_SECOND / 10;
bool finishSpell = false;
@@ -543,6 +622,13 @@ void Player::Ghost_t::handleAttack()
finishSpell = true;
}
}
+ else if ( castingSpellAnimation == GHOST_SPELL_QUACK )
+ {
+ if ( castingHeldDuration >= 1 )
+ {
+ finishSpell = true;
+ }
+ }
if ( finishSpell )
{
@@ -556,12 +642,17 @@ void Player::Ghost_t::handleAttack()
castSpell(uid, &spell_ghost_bolt, false, true);
castingSpellAnimation = GHOST_SPELL_POST_CASTING;
}
+ else if ( castingSpellAnimation == GHOST_SPELL_QUACK )
+ {
+ castSpell(uid, getSpellFromID(SPELL_PROJECT_SPIRIT), false, true);
+ castingSpellAnimation = GHOST_SPELL_POST_CASTING;
+ }
castingHeldDuration = 0;
for ( int i = 0; i < 5; ++i )
{
- Entity* entity = spawnGib(my);
+ Entity* entity = spawnGib(my, 16);
entity->flags[INVISIBLE] = false;
entity->flags[SPRITE] = true;
entity->flags[NOUPDATE] = true;
@@ -604,10 +695,20 @@ void Player::Ghost_t::handleAttack()
{
if ( attack )
{
- castingSpellAnimation = GHOST_SPELL_BOLT;
- cooldownChill = cooldownChillDelay;
- errorFlashChillTicks = 0;
- playSoundEntity(my, 170, 128);
+ if ( spiritGhost )
+ {
+ castingSpellAnimation = GHOST_SPELL_QUACK;
+ cooldownChill = cooldownChillDelay;
+ errorFlashChillTicks = 0;
+ //playSoundEntity(my, 170, 128);
+ }
+ else
+ {
+ castingSpellAnimation = GHOST_SPELL_BOLT;
+ cooldownChill = cooldownChillDelay;
+ errorFlashChillTicks = 0;
+ playSoundEntity(my, 170, 128);
+ }
}
else if ( useSpell )
{
@@ -617,7 +718,8 @@ void Player::Ghost_t::handleAttack()
playSoundEntity(my, 170, 128);
}
}
- else if ( castingSpellAnimation == GHOST_SPELL_TELEPORT || castingSpellAnimation == GHOST_SPELL_BOLT )
+ else if ( castingSpellAnimation == GHOST_SPELL_TELEPORT || castingSpellAnimation == GHOST_SPELL_BOLT
+ || castingSpellAnimation == GHOST_SPELL_QUACK )
{
++castingHeldDuration;
@@ -665,7 +767,7 @@ void Player::Ghost_t::handleAttack()
if ( ticks % 5 == 0 )
{
- Entity* entity = spawnGib(my);
+ Entity* entity = spawnGib(my, 16);
entity->flags[INVISIBLE] = false;
entity->flags[SPRITE] = true;
entity->flags[NOUPDATE] = true;
@@ -694,6 +796,13 @@ void Player::Ghost_t::handleAttack()
entity->skill[11] = player.playernum;
}
}
+ else if ( castingSpellAnimation == GHOST_SPELL_QUACK )
+ {
+ if ( castingHeldDuration == 1 )
+ {
+ createEnsembleHUDParticleCircling(my);
+ }
+ }
else if ( castingSpellAnimation == GHOST_SPELL_BOLT )
{
float x = 6;
@@ -763,11 +872,14 @@ void Player::Ghost_t::handleActions()
Input& input = Input::inputs[player.playernum];
CalloutRadialMenu& calloutMenu = CalloutMenu[player.playernum];
+ FollowerRadialMenu& followerMenu = FollowerMenu[player.playernum];
auto& b = (multiplayer != SINGLE && player.playernum != 0) ? Input::inputs[0].getBindings() : input.getBindings();
clickDescription(player.playernum, NULL); // inspecting objects
- if ( !calloutMenu.calloutMenuIsOpen() )
+ bool enableFollowerMenu = my && isSpiritGhost();
+
+ if ( !followerMenu.followerMenuIsOpen() && !calloutMenu.calloutMenuIsOpen() )
{
bool clickedOnGUI = false;
@@ -838,6 +950,124 @@ void Player::Ghost_t::handleActions()
selectedEntity[player.playernum] = nullptr;
}
}
+ else if ( enableFollowerMenu && followerMenu.followerMenuIsOpen() )
+ {
+ selectedEntity[player.playernum] = NULL;
+
+ if ( !players[player.playernum]->usingCommand() && players[player.playernum]->bControlEnabled && !gamePaused && input.binaryToggle("Use") )
+ {
+ if ( !followerMenu.menuToggleClick && followerMenu.selectMoveTo )
+ {
+ if ( followerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT )
+ {
+ // we're selecting a point for the ally to move to.
+ input.consumeBinaryToggle("Use");
+ //input.consumeBindingsSharedWithBinding("Use");
+
+ // we're selecting a point for the ally to move to.
+ if ( my )
+ {
+ real_t startx = cameras[player.playernum].x * 16.0;
+ real_t starty = cameras[player.playernum].y * 16.0;
+ real_t startz = cameras[player.playernum].z + (4.5 - cameras[player.playernum].z) / 2.0 + *cvar_followerStartZ;
+ real_t pitch = cameras[player.playernum].vang;
+ if ( pitch < 0 || pitch > PI )
+ {
+ pitch = 0;
+ }
+
+ // draw line from the players height and direction until we hit the ground.
+ real_t previousx = startx;
+ real_t previousy = starty;
+ int index = 0;
+ const real_t yaw = cameras[player.playernum].ang;
+ for ( ; startz < *cvar_followerStartZLimit; startz += abs((*cvar_followerMoveTo) * tan(pitch)) )
+ {
+ startx += 0.1 * cos(yaw);
+ starty += 0.1 * sin(yaw);
+ const int index_x = static_cast(startx) >> 4;
+ const int index_y = static_cast